Authenイベントハンドラ

認証に関するイベントはAuthenイベントカテゴリとなり、Authenイベントハンドラで実装します。

プロトコルガイドのログインからログアウトまでのケーススタディ [ SASL認証 ] を参照するとよいでしょう。

このイベントハンドラで実装されるのは認証に関するものだけですが、さらにその中でも大きく分類することができ、三種類のハンドラがあります。

  • SASL認証用
  • HTTP認証用
  • 不正利用対策

on_too_many_auth_attempt

コンフィグで指定された数値以上の回数、ストリームが認証を連続して失敗した場合にこのイベントが呼ばれます。不正アクセスの検知などに利用するとよいでしょう。

コンフィグ値の設定についてはコンフィギュレーションガイド [ SASL認証セクション ]を参照するとよいでしょう。

引数としてOcean::HandlerArgs::TooManyAuthAttemptを受け取ります。このオブジェクトは次のアクセサを持ちます。

アクセサ名 概要
host指定された数値を超えて、認証を試してきたストリームのIPアドレスです
port指定された数値を超えて、認証を試してきたストリームのポートナンバーです

on_sasl_auth_request

このイベントはSASL認証時に呼ばれます。

ただし、一回のリクエストで解決するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはPLAINX-OAUTH2です。

スタンドアローンプロジェクトでの実装例は次のようになっています。

SYNOPSIS
sub on_sasl_auth_request {
    my ($self, $ctx, $args) = @_;

    my $stream_id = $args->stream_id;

    if ($args->mechanism ne 'PLAIN') {
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }

    $self->log_info("PLAIN sasl authentication");
    $self->log_info("Received %s", $args->text||'');

    my ($authcid, $password) = 
        parse_sasl_plain_b64($args->text||'');

    $self->log_info("Username - %s", $authcid);

    unless ($authcid && $password) {
        $self->log_info("both username and password needed");
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }

    my $user = $ctx->get('db')->find_user_by_username($authcid);
    unless ($user) {
        $self->log_info("User %s doesn't exist", $authcid);
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }

    if ($user->password eq $password) {
        $self->log_info("Password matched");
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletion->new;
        $builder->stream_id($stream_id);
        $builder->user_id($user->user_id);
        $builder->username($user->username);
        $builder->session_id( sha1_hex( gen_random(32) ) );
        $ctx->deliver($builder->build());
    } else {
        $self->log_info("Password not matched");
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
    }
}

引数としてOcean::HandlerArgs::SASLAuthRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。

アクセサ名 概要
stream_id認証を要求してきたストリームのID
mechanism選択されたSASLメカニズム
text選択されたSASLメカニズムに対応するテキスト

Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure

セッター名 概要
stream_id認証を要求してきたストリームのID

Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletion

セッター名 概要
stream_id認証を要求してきたストリームのID
user_id認証を完了したユーザーのID
username認証を完了したユーザーのusername
session_id後でJIDのリソースして利用されるセッションID

on_sasl_password_request

このイベントはSASL認証時に呼ばれます。

ただし、ダイジェスト認証を利用するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはCRAM-MD5DIGEST-MD5です。

一度で認証が完了する、上記のon_sasl_auth_requestとは違い、ダイジェスト認証では、二度のイベントハンドラの呼び出しが発生します。まずはこのon_sasl_password_requestが呼ばれるので、該当するパスワードを渡します。その結果認証が成功した場合のみ、次に説明するon_sasl_success_notificationが呼び出されます。

ログインからログアウトまでのケーススタディ[ SASL認証 ]で説明した通り、生パスワードの保存が必要になるので、正式なサービスを運用する場合、ダイジェスト認証のメカニズムの採用は推奨しません。

SYNOPSIS
sub on_sasl_password_request {
    my ($self, $ctx, $args) = @_;

    my $stream_id = $args->stream_id;
    my $username  = $args->username;

    my $user = $ctx->get('db')->find_user_by_username($username);
    unless ($user) {
        $self->log_info("User %s doesn't exist", $username);
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }
    my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLPassword->new;
    $builder->stream_id($stream_id);
    $builder->password($user->password);
    $ctx->deliver($builder->build());
}

引数としてOcean::HandlerArgs::SASLPasswordRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。

アクセサ名 概要
stream_id認証を要求してきたストリームのID
usernameパスワードを要求したいユーザーのusername

Ocean::Stanza::DeliveryRequestBuilder::SASLPassword

セッター名 概要
stream_id認証を要求してきたストリームのID
password指定されたusernameに対応するユーザーのパスワード

on_sasl_success_notification

このイベントはSASL認証時に呼ばれます。

ただし、ダイジェスト認証を利用するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはCRAM-MD5DIGEST-MD5です。

上記のon_sasl_password_requestが呼ばれた後、そこから取得したパスワードを利用してリクエストの検証がうまくいった場合、このイベントハンドラが呼び出されます。

スタンドアローンプロジェクトの実装例は次の例のようになっています。

SYNOPSIS
sub on_sasl_success_notification {
    my ($self, $ctx, $args) = @_;

    my $stream_id = $args->stream_id;
    my $username  = $args->username;

    my $user = $ctx->get('db')->find_user_by_username($username);
    unless ($user) {
        $self->log_info("User %s doesn't exist", $username);
        my $builder = 
            Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }

    my $builder = 
        Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletion->new;
    $builder->stream_id($stream_id);
    $builder->user_id($user->user_id);
    $builder->username($user->username);
    $builder->session_id( sha1_hex( gen_random(32) ) );
    $ctx->deliver($builder->build());
}

usernameの確認だけ行い、問題があればOcean::Stanza::DeliveryRequestBuilder::SASLAuthFailureで配送リクエストを生成してコンテキストに渡します。問題がなかったならば適切にsession_idを生成し、Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletionで配送リクエストを生成してコンテキストに渡します。

引数としてOcean::HandlerArgs::SASLSuccessNotificationを受け取ります。このオブジェクトは次のアクセサを持ちます。

アクセサ名 概要
stream_id認証を要求してきたストリームのID
username認証に成功したユーザーのusername

on_http_auth_request

このイベントはHTTP Binding時のみ呼ばれます。サーバーの起動タイプをxmppにしている場合は呼ばれることはありませんので、その場合は実装する必要はありません。

コンフィギュレーションガイド [ サーバーセクション ]type設定項目を参照するとよいでしょう。

SYNOPSIS
sub on_http_auth_request {
    my ($self, $ctx, $args) = @_;

    my $stream_id = $args->stream_id;
    my $cookie    = $args->cookie;

    if (!$cookie) {
        $self->log_debug("on_http_auth cookie not found");
        my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }
    my $cookie_value = $cookie->{foo} || '';
    $self->log_debug("on_http_auth found cookie %s", $cookie_value);
    my $user = $ctx->get('db')->find_user_by_cookie($cookie_value);
    if (!$user) {
        $self->log_debug("on_http_auth user not found");
        my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthFailure->new;
        $builder->stream_id($stream_id);
        $ctx->deliver($builder->build());
        return;
    }

    $self->log_debug("on_http_auth found user %s for cookie", $user->username);

    # XXX NOT GOOD FOR SECURITY
    my $session_id = sha1_hex($cookie_value);

    my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthCompletion->new;

    $builder->stream_id($stream_id);
    $builder->session_id($session_id);
    $builder->user_id($user->user_id);
    $builder->username($user->username);
    $builder->add_cookie(foo => $cookie_value);
    $builder->add_cookie(bar => { value => 'fugafuga', domain => 'xmpp.example.org', path => '/foo' });
    $ctx->deliver($builder->build());
}

引数としてOcean::HandlerArgs::HTTPAuthRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。

アクセサ名 概要
stream_id-
cookie-

Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthFailure

セッター名 概要
stream_id認証を要求してきたストリームのID

Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthCompletion

セッター名 概要
stream_id認証を要求してきたストリームのID
session_id後でJIDのリソースして利用されるセッションID。HTTP Binding時には、SessionオブジェクトのIDとしても利用される
user_id認証を完了したユーザーのuser_id
username認証を完了したユーザーのusername