Catalyst/DBICでDigest認証する
自分の為のメモですよ。
準備
まずはCatalystプロジェクトを作ります
$ mkdir -p /path/to/dir $ cd /path/to/dir $ catalyst.pl AuthSample
ユーザー用のDBICスキーマを定義します。
$ module-starter --module AuthSample::Schema $ cd AuthSample-Schema
でこのディレクトリにて、
CREATE TABLE user ( user_seq INTEGER PRIMARY KEY, user_id TEXT UNIQUE, password TEXT, created_on DATETIME, updated_on DATETIME );
こんなスキーマを定義して、schema.sqlとして保存して、
$ sqlite3 -init schema.sql authsample.db
として初期化する。
次にスキーマ生成の為の簡易スクリプトをtypesterさんの奴をベースに ./script/schema.pl として作る。
#!/usr/bin/perl use strict; use warnings; use FindBin; use File::Spec; use lib ( File::Spec->catfile( $FindBin::Bin, qw/.. lib/ ), File::Spec->catfile( $FindBin::Bin, qw/.. schema/ ) ); use DBIx::Class::Schema::Loader qw(make_schema_at); die('Required arguments dsn dbuser dbpass') unless (@ARGV); my $schema_class = 'AuthSample::Schema'; unlink( glob( File::Spec->catdir( $FindBin::Bin, '..', 'lib', split( /::/, $schema_class ) ) . '/*.pm' ) ); make_schema_at( $schema_class, { components => [ qw/ResultSetManager UTF8Columns InflateColumn::DateTime TimeStamp DigestColumns/ ], dump_directory => File::Spec->catfile( $FindBin::Bin, qw/.. lib/ ), debug => 1, really_erase_my_files => 0, }, \@ARGV, );
こんな感じ。改良点は、
- 各テーブルクラスは自分で自ら消す
- really_erase_my_files を 0 にしてるので Schema::Loader 自身は何も消さない
- つまり Schema クラスは残ったままで、自由に編集出来る
みたいな点。詳しくはCatalystConで話す。
実行権限を与えて、さらに事前にmodule-starterでSchemaクラスが出来ちゃってるからそれを消してからschema.plを実行する。
$ chmod +x ./script/schema.pl $ rm -f ./lib/AuthSample/Schema.pm $ ./script/schema.pl dbi:SQLite:dbname=authsample.db
これでひな形は完成。
DBIC関連を今回は手動でちょっと弄る
*** lib/AuthSample/Schema/User.pm.orig 2008-04-15 11:49:27.000000000 +0900 --- lib/AuthSample/Schema/User.pm 2008-04-15 11:54:33.000000000 +0900 *************** *** 5,10 **** --- 5,13 ---- use base 'DBIx::Class'; + __PACKAGE__->mk_classdata('digest_user_name_column'); + __PACKAGE__->mk_classdata('digest_realm' => ''); + __PACKAGE__->load_components( "ResultSetManager", "UTF8Columns", *************** *** 20,34 **** "user_id", { data_type => "TEXT", is_nullable => 0, size => undef }, "password", ! { data_type => "TEXT", is_nullable => 0, size => undef }, "created_on", ! { data_type => "DATETIME", is_nullable => 0, size => undef }, "updated_on", ! { data_type => "DATETIME", is_nullable => 0, size => undef }, ); __PACKAGE__->set_primary_key("user_seq"); __PACKAGE__->add_unique_constraint("user_id_unique", ["user_id"]); # Created by DBIx::Class::Schema::Loader v0.04004 @ 2008-04-15 11:39:32 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7O29VfwnVCsyZL2avThORA --- 23,61 ---- "user_id", { data_type => "TEXT", is_nullable => 0, size => undef }, "password", ! { data_type => "TEXT", is_nullable => 0, size => undef, digest_check_method => 'check_password' }, "created_on", ! { data_type => "DATETIME", is_nullable => 0, size => undef, set_on_create => 1, }, "updated_on", ! { data_type => "DATETIME", is_nullable => 0, size => undef, set_on_create => 1, set_on_update => 1 }, ); __PACKAGE__->set_primary_key("user_seq"); __PACKAGE__->add_unique_constraint("user_id_unique", ["user_id"]); + __PACKAGE__->digestcolumns( + columns => [qw/password/], + algorithm => 'MD5', + encoding => 'hex', + auto => 1, + dirty => 1, + ); + + __PACKAGE__->digest_user_name_column('user_id'); + __PACKAGE__->digest_realm('Are you TKSK?'); + + sub _get_digest_string { + my ($self, $value) = @_; + + $self->digest_maker->reset; + + return $self->next::method( + join(':', + $self->get_column($self->digest_user_name_column) || '', + $self->digest_realm, + $value || '' + ) + ); + } # Created by DBIx::Class::Schema::Loader v0.04004 @ 2008-04-15 11:39:32 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7O29VfwnVCsyZL2avThORA
主な変更点は、
- DigestColumnsを使ってDigest認証用のハッシュ値を格納するようにした
- TimeStampが勝手に挿入されるようにした
って感じ。
と言う訳で試してみる。
#!/usr/bin/perl use strict; use warnings; use lib qw(lib); use AuthSample::Schema; my $schema = AuthSample::Schema->connect('dbi:SQLite:dbname=authsample.db'); $schema->resultset('User')->create({ user_id => 'zigorou', password => 'hogehoge' }); print $schema->find({ user_id => 'zigorou' })->password;
とすると、
c11485a6cebc09f30a61df78a33961df
などと出るので、どうやら問題なく動作している模様。念のためSQLiteコンソールでも確かめる。
sqlite> SELECT * FROM user; 1|zigorou|c11485a6cebc09f30a61df78a33961df|2008-04-15 02:59:12|2008-04-15 02:59:12
これでDBICはとりあえず出来た。
Catalyst側の実装とか設定とか
まずは最初にauthsample_server.plにモジュールのパスを追加しておきます。
use lib ( "$FindBin::Bin/../lib", glob("$FindBin::Bin/../../*/lib") );
こういう感じ。
今度はCatalyst側に移動して、./lib/AuthSample.pmの use Catalyst してる部分を次のように。
use Catalyst qw/ -Debug ConfigLoader Static::Simple Cache Authentication Authentication::Store::DBIC Authentication::Credential::HTTP /;
次にCatalyst::Model::AdaptorでDBIC::Schemaのadaptorを作ります。
$ ./script/authsample_create.pl model DBIC::Schema Adaptor AuthSample::Schema
出来上がったadaptorにprepare_arguments, mangle_argumentsを追加します。Catalyst::Utilsをuseしておく必要があります。
ついでにconfigのconstructorもnewからconnectに変更する。
__PACKAGE__->config( class => 'AuthSample::Schema', constructor => 'connect', ); sub prepare_arguments { my ($self, $app) = @_; return $app->config->{Catalyst::Utils::class2classsuffix(__PACKAGE__)}; } sub mangle_arguments { my ($self, $args) = @_; return $args ? @$args : (); }
さらにAuthentication::Store::DBIC用にUserクラスを作ります。
$ ./script/authsample_create.pl model DBIC::Schema::User
生成されたModel::DBIC::Schema::UserにACCEPT_CONTEXT()を次のように追加する。
sub ACCEPT_CONTEXT { my ($self, $c, @args) = @_; return $c->model('DBIC::Schema')->resultset('User'); }
さらにyamlを次のように設定する。
------ Model::DBIC::Schema: - dbi:SQLite:dbname=/Users/zigorou/tmp/digestauth/AuthSample-Schema/authsample.db authentication: dbic: password_field: password password_hash_type: MD5 password_type: clear user_class: DBIC::Schema::User user_field: user_id http: algorithm: MD5 type: digest cache: backend: class: Cache::Memory default_expire: 600 sec namespace: test name: AuthSample
これで準備完了です。
$ ./script/authsample_server.pl -d -r
とかで動くはず。
認証ページを作る
面倒なのでController/Root.pmに仕込みます。
sub default : Private { my ( $self, $c ) = @_; $c->authorization_required( realm => 'Are you TKSK?' ); # Hello World $c->response->body( $c->welcome_message ); }
これで http://localhost:3000/ にアクセスすれば認証ページが出るはずです。
おしまい。