[flipclip][plagger] FlipClipのフィードでPlaggerでWSSE認証


FlipClipでもWebAPIってことで、まずはクリップ検索用のフィードを公開しました。

http://www.flipclip.net/developer/

(...って、もう公開から1ヶ月経ってる話題なんですがw)


せっかくだから、とりあえず簡単になんかやってみようってことで、そうなるとやっぱりPlagger?。
実はずいぶん前に、とりあえずインストールだけはしたものの全然さわってなかったんで、まあちょうどいい機会かなということで試してみました。


とりあえず普通にクリップの一覧とか取ってくるだけなら、Subscription::Configでurlだけ書いとけばOKで、なんも考える必要もなくてアラ簡単!なんだけど、それだけじゃネタにもなりませんね。
FlipClipのクリップ検索フィードでは、誰でも見られる「一般公開」クリップの他、自分だけが見られる「非公開」、「友だちのみに公開」、「(指定した)グループに公開」などのように、それぞれ閲覧を制限されたクリップも、認証機能を利用して取得することができます。
まあ、自分の非公開クリップのフィードが取れてもあまりうれしくない気もしますが、自分の友だちが、友だちのみに公開しているクリップが見れるってのはちょっといいかもね。ということでこっちでいってみよう。


でもってさっそく、まずはPlaggerでWSSE認証ってどうすんの?
とりあえずUserAgent::AuthenRequestってのがあるので、これ使えばOKかな?楽勝楽勝!、とか思ってたら、どうもうまくいかないっぽい。
いろいろソースを見てみると、結局これ(というかLWP::UserAgent)は、Webサーバから401が返ってきたら認証ヘッダを投げるっていう実装なわけで、まあもちろんそれが正しいんだけども、今回のフィードAPIはというと、認証ヘッダがなかったら一般ユーザーとして扱い、認証ヘッダがあったら認証する、っていう仕様なもんだから、そもそも401は返ってこないわけですね。


じゃあもうしかたないから、もう頼まれなくてもこちらから一方的に認証ヘッダを送りつけてやらないとってことになりますね。すでになんかそういうのができる方法もあるのかもしんないけど見つからなかったので、ちょろっとプラグインを書いてみました。


ということでUserAgent::AuthenRequest::Constraint。
とりあえず名前もコードもかなりテキトーです。
こんな感じで使います。

    - module: Subscription::Config
      config:
        feed:
          - url: http://flipclip.net/clips/hi-rocks/friends/?_accept=rss
    - module: UserAgent::AuthenRequest::Constraint
      config:
        username: hi-rocks
        password: hogehoge


ソースはこんな。(WSSE認証文字列を生成するとこは、フィードのドキュメントにある例と同様、LWP::Authen::Wsseからほぼそのまんま拝借。)

package Plagger::Plugin::UserAgent::AuthenRequest::Constraint;
use strict;
use warnings;
use base qw(Plagger::Plugin);
use Digest::SHA1;
use MIME::Base64;

sub register {
    my ( $self, $context ) = @_;
    $context->register_hook( $self, 'useragent.request' => ?&add_wsse_header,
    );
}

sub add_wsse_header {
    my ( $self, $context, $args ) = @_;

    my $username = $self->conf->{username};
    my $password = $self->conf->{password};

    my $wsse_value = create_auth_header( $username, $password );
    $context->log( info => "Adding X-WSSE header: $wsse_value" );

    $args->{ua}->default_header( 'X-WSSE' => $wsse_value );
}

sub create_auth_header {
    my ( $username, $password ) = @_;

    my $pass_digest = Digest::SHA1::sha1_hex($password);
    my $now         = now_w3cdtf();
    my $nonce       = make_nonce();
    my $nonce_enc   = MIME::Base64::encode_base64( $nonce, q{} );
    my $digest      = MIME::Base64::encode_base64(
        Digest::SHA1::sha1( $nonce . $now . $pass_digest ), q{} );

    return 'UsernameToken '
        . join( ', ',
        qq(Username="$username"), qq(PasswordDigest="$digest"),
        qq(Nonce="$nonce_enc"),   qq(Created="$now"),
        );
}

sub make_nonce {
    Digest::SHA1::sha1( time() . {} . rand() . $$ );
}

sub now_w3cdtf {
    my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime();
    return sprintf(
        "%04s-%02s-%02sT%02s:%02s:%02sZ",
        $year + 1900,
        ++$mon, $mday, $hour, $min, $sec
    );
}

1;


わざわざこんなんしなくても「そんなのこうすればできるよ!」とかあったら教えてください。
あ、WSSE認証やめてクッキー使う、とかはとりあえず今回はナシで。