ziguzagu.org

Hatena::API::Auth やってみる

このあいだやった Flickr::API 認証の Hatena::API::Auth 版をやってみた。またしてもだらだらコード張り付けの刑。

logout と verify は結局 Flickrと同じなので、ルートクラスに移動。

package MyAuth;

use strict;
use warnings;
use base qw(CGI::Application);

use CGI::Application::Plugin::TT;
use CGI::Application::Plugin::Stash;
use CGI::Application::Plugin::Session;
use CGI::Application::Plugin::DebugScreen;

sub cgiapp_init {
    my ($self) = @_;

    $self->tt_config(
        TEMPLATE_OPTIONS => {
            COMPILE_DIR => '/tmp/ttcache',
            PRE_CHOMP  => 1,
            INCLUDE_PATH => '/home/www/lab/auth/tmpl',
        },
    );

    $self->session_config(
        CGI_SESSION_OPTIONS => [
            "driver:file",
            $self->query,
            { Directory => '/tmp/cgisess' },
        ],
        COOKIE_PARAMS => {
            -domain  => 'lab.norainu.net',
            -expires => '+1h',
            -path    => $self->query->url(-absolute => 1),
        },
        SEND_COOKIE => 1,
    );

    $self->mode_param(
        path_info => 1,
    );
}

sub verify {
    my $self = shift;

    my $uinfo = $self->_process_verify;
    $uinfo->{logged_in} = $self->stash->{apiname};
    $self->session->param(%$uinfo);

    $self->redirect($self->query->url);
}

sub logout {
    my $self = shift;

    $self->session->delete;
    $self->redirect($self->query->url);
}

1;

Hatena::API::Auth で認証するクラス。

package MyAuth::Hatena;

use strict;
use warnings;
use base qw(MyAuth);

use CGI::Application::Plugin::Redirect;
use Hatena::API::Auth;

sub setup {
    my $self = shift;

    $self->start_mode('default');
    $self->run_modes(
        default => 'default',
        verify  => 'verify',
        logout  => 'logout',
    );

    my $api = Hatena::API::Auth->new({
        api_key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        secret  => 'wwwwwwwwwwwwwwww',
    });
    $self->stash->{api} = $api;

    $self->stash->{apiname}   = 'Hatena::API::Auth';
    $self->stash->{login_uri} = $api->uri_to_login;
}

sub default {
    my $self = shift;

    if ($self->session->param('logged_in') eq $self->stash->{apiname}) {
        $self->stash->{icon} = $self->session->param('image_url');
    }
    $self->tt_process('index.tt');
}

sub _process_verify {
    my $self = shift;

    my $q    = $self->query;
    my $api  = $self->stash->{api};

    my $cert = $q->param('cert');
    my $user = $api->login($cert) or die $api->errstr;
    return {
        name          => $user->name,
        image_url     => $user->image_url,
        thumbnail_url => $user->thumbnail_url,
    };
}

1;

Flickr は、認証のキーになる token を、コールバックされたときに自分でとりに行く必要があるけど、はてなの場合はコールバック時に cert パラメーターとして渡されるので、それを使ってログインしに行く。実際には JSON なげてうんたらかんたらやる必要があるけど、その辺はモジュールがやってくれるので簡単。

あと、[http://auth.hatena.ne.jp/help/api:title=image_url はプロフィール画像のURL、ってヘルプに書いてあった]ので、アップしてみたけど人型のアイコン画像が出てしまった。うーん、しばらくしたら出るようになんのかしら。。。

ついでなので、Flickr版も少し変更。Buddyicon 出してみた。

package MyAuth::Flickr;

use strict;
use warnings;
use base qw(MyAuth);

use CGI::Application::Plugin::Redirect;

use Flickr::API;
use XML::Parser::Lite::Tree::XPath;

sub setup {
    my $self = shift;

    $self->start_mode('default');
    $self->run_modes(
        default => 'default',
        verify  => 'verify',
        logout  => 'logout',
    );

    my $api = Flickr::API->new({
        key    => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        secret => '****************',
    });
    $self->stash->{api} = $api;

    $self->stash->{apiname}   = 'Flickr::API';
    $self->stash->{login_uri} = $api->request_auth_url('read');
}

sub default {
    my $self = shift;

    if ($self->session->param('logged_in') eq $self->stash->{apiname}) {
        $self->stash->{icon}
            = sprintf "http://static.flickr.com/%d/buddyicons/%s.jpg",
                $self->session->param('iconserver'),
                $self->session->param('nsid');
    }

    $self->tt_process('index.tt');
}

sub _process_verify {
    my $self = shift;

    my $api = $self->stash->{api};
    my $q   = $self->query;

    my ($nsid, $token);
    {
        my $frob = $q->param('frob');
        my $res  = $api->execute_method(
            'flickr.auth.getToken',
            { frob => $frob },
        );
        $res->{success} or die $res->{error_message};
        my $xpath = XML::Parser::Lite::Tree::XPath->new($res->{tree}) or die;

        my ($node) = $xpath->select_nodes('/auth/user');
        $nsid = $node->{attributes}->{nsid};

        ($node) = $xpath->select_nodes('/auth/token');
        $token = $node->{children}[0]->{content};
    }

    my ($xpath);
    {
        my $res = $api->execute_method(
            'flickr.people.getInfo',
            { user_id => $nsid, auth_token => $token },
        );
        $res->{success} or die $res->{error_message};
        $xpath = XML::Parser::Lite::Tree::XPath->new($res->{tree}) or die;
    }

    my $uinfo = { nsid => $nsid };
    for my $field (qw(username realname mbox_sha1sum profileurl photosurl)) {
        my ($node) = $xpath->select_nodes("/person/$field");
        $uinfo->{$field} = $node->{children}[0]->{content};
    }

    my ($node) = $xpath->select_nodes("/person");
    $uinfo->{iconserver} = $node->{attributes}->{iconserver};

    return $uinfo;
}

1;

XPathでごにょごにょというのがどうしても長いです。まぁ、でも Flickr::API は認証だけのものじゃないわけで。

最後に、唯一よびだしてるテンプレ、index.tt。

[% INCLUDE header.tt %]

[% IF c.session.param('logged_in') -%]
[% IF c.stash.icon -%]
<div><img alt="profile icon" src="[% c.stash.icon | uri | html %]" /></div>
[% END %]
<ul>
  [% FOR pair IN c.session.dataref.pairs %]
  <li>[% pair.key %]: [% pair.value %]</li>
  [% END %]
</ul>
<p><a href="[% c.query.url | uri | html %]/logout">Logout</a></p>
[% ELSE %]
<p><a href="[% c.stash.login_uri | uri | html %]">Login</a></p>
[% END %]

[% INCLUDE footer.tt %]

後は、TypeKey か。OpenID はいまだに仕組みを理解していないのでこの機会に理解するもよし。

WEB+DB PRESS Vol.34