cgi/auth.rb$Id: auth.rb,v 1.17 2001/05/07 08:27:55 akira Exp $ Copyright (c) 2001 akira yamada <akira@ruby-lang.org> You can redistribute it and/or modify it under the same term as Ruby.
CGI::AuthはCGIスクリプトでのユーザ認証を支援するためのクラスである。
CGI::Authでは次に示すような二つのデータベースと 二つのセッション(CGI::Sessionまたは その子クラスによるもの)を内部的に使用する。
データベース
ユーザIDをキーとするデータベース。
各ユーザについての登録情報が入るのはこのデータベースである。
次のユー名DBを使ってユーザ名とユーザIDの対応管理する場合には、
最低限ユーザ名(ユーザ名に対応するキーは"_uname")を
含んでいなくてはならない。
ユーザIDは1文字以上の任意の文字列である。
空文字列("")は無名ユーザのためのユーザIDとして予約されている。
ユーザ名とユーザIDの対応表。キーはユーザ名である。 このデータベースは必ずしも使わなくてもよい(後述)。
いずれにもPStoreインタフェースを持つデータベースを使用する。
それぞれのデータベースにおいて使用するルート名は
ユーザDBが"user"、ユーザ名DBが"uname"である。
したがって少なくともPStoreインスタンスを使うのであれば
両者の実体が同じであっても問題ない。
セッション
各ブラウザ用のセッション。 ブラウザに一意に割り当てられ、 登録後はこのセッションをベースにやり取りを行う (CGI::Authのユーザが個々のブラウザセッションに 直接アクセスすることは基本的に認められていない)。
ユーザ名DBを使うかどうかで動作が異ってくることに注意。
ユーザ名DBを使う場合
ブラウザに対してユニークなセッションを作り、 セッションとユーザ情報との対応をユーザ名DBで取る。 一人のユーザが複数のブラウザを使い分けているようなケースでも それぞれのブラウザを判別できるようになる。
ユーザ名DBを使わない場合
ブラウザに対してユニークなセッションを作るが、 一人のユーザが複数のブラウザを使っているケースにおいて、 それらのアクセスを一人のユーザからのものであると 判別することができない。
いずれの場合でもブラウザセッションは 対応するユーザID(ユーザ名ではない)と セッションの有効期限だけを保持する。
ユーザ登録またはユーザへのブラウザの追加の際に 一時的に使うセッション。登録完了後には削除される。
ユーザ名DBを用いてユーザおよびブラウザを管理する場合には
このセッションに"_uname"というキーで
ユーザ名(通常はユーザに入力をもとめる)を登録する必要がある。
この点についてはCGI::Auth#registrationの項も参照のこと。
なおユーザ名には任意の文字列を使用できるが、
文字コードの問題などでなやみたくなければ
あらかじめ英数字だけに制限しておいた方がよいだろう。
ユーザDBに登録する情報およびセッションに登録する情報において、
そのキーを"_"で始めることはできないことに注意。
"_"で始まるキーはCGI::Authの内部で使うために予約されている。
CGI::Auth::new(request, userdb, unamedb = nil, bs_opt = {}, rs_opt = {})CGI::Authインスタンスを作る。
requestは対象とするCGIクラスのインスタンスであり、
userdbとunamedbは、それぞれユーザDBとユーザ名DBである。
bs_optとrs_optはブラウザセッションと
登録セッションのためのオプションで、
CGI::Session::newの引き数として渡される。
メンテナンスのことを考えると、
少なくともお互いに"prefix"を違えておく方がよいだろう。
CGI::Auth::create_new_idユーザ用、ブラウザ用の新しいIDを生成する。 IDとしては文字列を使用する。
CGI::Auth#login(*opts)認証(ログイン)を試みる。
このメソッドでは次のことを行うことができる。
まったく新規のブラウザからのアクセスがあると
CGI::Auth::NewBrowser例外が起こる。
この例外を捕えて、後述の
CGI::Auth#enter_registration_sessionメソッドを
呼んでやると登録セッションを新たに生成することができる。
例1:
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
x.login
rescue CGI::Auth::NewBrowser
x.enter_registration_session
c.out do
# ログインできない旨を表示しつつ
# 登録処理用のページを出力する。
end
end
その他起り得る例外は次の通り。
CGI::Auth::SessionExpired
期限切れのセッションIDへのアクセスがあった。
CGI::Auth::UnknownBrowserID
存在しないセッションIDを指定してのアクセスがあった。 この例外が連続して発生する場合、総当たり的に セッションをぬすもうとしている者がいる可能性がある。
CGI::Auth#enter_registration_session(opt = nil, &block)ユーザまたはブラウザを登録するための 一時的なセッション(登録セッション)を生成する。
optおよびblockを与えると
それをCGI#outを使ってブラウザに返すことができる。
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
x.login
rescue CGI::Auth::NewBroser
x.enter_registration_session do
# ログインできない旨を表示しつつ
# 登録処理用のページを出力する。
end
end
また、この機能は、登録セッションのセッションIDを
QUERY_STRINGに入れた形でリダイレクトさせたい場合などにも
使うことができる。
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
x.login
rescue CGI::Auth::NewBroser
x.enter_registration_session({'status' => 'REDIRECT'})
# QUERY_STRINGに登録セッションのセッションIDを仕込んで
# 同じURIにリダイレクトする。
endCGI::Auth#enter_browser_session(anon_login = false, opt = nil, &block)CGI::Auth#enter_registration_sessionの代わりに使うと、
登録セッションに入ることなく、
いきなりブラウザセッションを生成することができる。
当然だが、この時点でユーザ情報を登録することはできない。
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
x.login
rescue CGI::Auth::NewBroser
x.enter_browser_session(true, {'status' => 'REDIRECT'})
end
anon_loginをtrueにして呼び出すと、
特定のユーザIDは割り当てられず無名ユーザとして扱われる。
たとえば、一旦無名ユーザとして登録し、
後日そうしたい人にだけユーザ登録をしてもらうというようなケースでは、
適当なタイミングで
CGI::Auth#enter_registration_session
を呼び出すという方法をとることができる。
enter.cgi:
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
x.login
rescue CGI::Auth::NewBroser
x.enter_browser_session(true)
end
regist.cgi:
c = CGI.new(...)
x = CGI::Auth.new(c, ...)
begin
uid = x.login
if uid == CGI::Auth::ANONYMOUS_USER_ID
x.enter_registration_session
end
rescue CGI::Auth::NewBroser
x.enter_registration_session
end
この例では、単にenter.cgiにアクセスすると、
事前にユーザ登録が行われていない限り生成された
ブラウザセッションは無名ユーザとして扱われる。
しかし、なんらかのタイミングでユーザをregist.cgiに
誘導することによって、
改めてユーザ登録をしてもらうことができるわけである。
CGI::Auth#transaction与えられたブロックを実行する。
その際、ブロック引数を介して現在認証されているユーザの
ユーザ情報にアクセスできる。
CGI::Auth#loginをする前に呼び出すと
CGI::Auth::NotYetAuthenticate例外が起こる。
c = CGI.new(...) x = CGI::Auth.new(c, ...) x.login x.transaction do |ui| # uiでユーザ情報にアクセスできる。 end
CGI::Auth#hiddenセッション維持のためにFORMに入れるべき
HIDDENフィールドを生成する。
CGI::Auth#add_user(uid, data = {})ハッシュで与えられたデータを元に新しいユーザを登録する。 ユーザ名DBが使える場合にはユーザ名DBも更新する。
主にユーザ管理用。セッション中においては
CGI::Auth#transactionの使用を勧める。
CGI::Auth#mod_user(uid, data)指定されたユーザIDのユーザ情報を与えられたハッシュの値で置き換える。
主にユーザ管理用。セッション中においては
CGI::Auth#transactionの使用を勧める。
CGI::Auth#del_user(uid)ユーザIDを指定してそのユーザ情報を削除する。 ユーザDBが使える場合にはユーザDBからも情報を削除する。
主にユーザ管理用。セッション中においては
CGI::Auth#transactionの使用を勧める。
CGI::Auth#show_user(uid)ユーザIDを指定してユーザ情報を取り出す。
主にユーザ管理用。セッション中においては
CGI::Auth#transactionの使用を勧める。
CGI::Auth#user_exist?(uname)指定されたユーザ名のユーザがいるかどうかを確認する。
存在していれば対応するユーザのユーザIDを返し、
存在しなければfalseを返す。
CGI::Auth#in_registration_session?現在登録セッション中であるかどうかを返す。
CGI::Auth#in_authenticate_session?現在認証セッション中であるかどうかを返す。
CGI::Auth#registration(session, opts)ユーザ定義の登録処理で上書きするべきメソッド。
この中では、ユーザの登録報情として必要なものを
すべて入力してもらい登録セッションsessionに
記録するという処理を行う。
この時、現在のCGIインスタンスには
@requestによってアクセスできる。
また、optsによってCGI::Auth#loginに
渡された引数を参照できる。
すべての入力が終ったらtrueを返し、
まだ続きがあるのであれば false を返すようにすること。
なお、一ユーザが複数のブラウザを使うことがある場合でも
それらをすべて同じユーザとして扱いたい場合には
ユーザを一意に認識できる名前(ユーザ名)を
session["_uname"]として登録しておくこと。
この機能が不要ならsession["_uname"]には何も登録せずにおくこと
(その場合、あるユーザがどのブラウザを使っているのかは判別できない)。
ユーザ名を登録している場合で、
この処理の中で登録されたユーザ名がすでに存在しているときには
そのユーザが新しい別のブラウザを使っているケースである
(つまりユーザに対してブラウザを追加する処理をしている)として扱う。
このような場合、通常はユーザ名の他にパスワードが入力されていることが
必要であると考えられる。というのは、今行われているアクセスが
たしかにそのユーザからのものであることを確認するためであり、
そのため、このメソッドをぬけた後のタイミングで
自動的にCGI::Auth#authenticateが呼ばれるようになっている。
なお、新規ユーザの登録をしようとしているときには
新しいユーザのユーザ名がすでに登録されているものと
重複しないようにあらかじめチェックしなくてはならない。
もしも重複していた場合にはCGI::Auth::UserAlreadyExist例外を
起こすようにしておくべきであり、
ユーザにブラウザの登録をしているときには
指定されたユーザ名がすでに登録されていることを
確認しておくべきである。
CGI::Auth#authenticate(uid, opts)前述のCGI::Auth#loginの中で、あるいは登録セッションにおいて
ユーザに対するブラウザの追加登録が行われるときに呼び出される。
このメソッドはユーザによって再定義されなければならない。
uidにはブラウザセッションから特定されたユーザIDが、
optsにはCGI::Auth#loginに渡された引数が、
それぞれ渡される。
また、現在のCGIインスタンスは@requestで参照できる。
このメソッドで行うべきなのはユーザ定義の認証処理である。 CGI::Authで受け持つのはブラウザの特定と、 それによりユーザの特定までである。 したがって、たとえばブラウザセッションの乗っ取りなどには 十分に対処することができない。
認証の本質的な部分についてはこのメソッドにおいて
ユーザが定義すべきものであるとする。
認証に失敗した場合には
CGI::Auth::AuthFailedか、その子として定義される
例外を起こすべきであり、認証が成功した場合には
認証できたユーザのユーザIDを返さなくてはならない。
class CGI
class Auth
def authenticate(uid, opts)
md5 = MD5.new(@request['passwd']).hexdigest
user_transaction(uid) do |ui|
if @request['_uname'] != ui['_uname'] || md5 != ui['passwd']
raise AuthFailed, "bad user name and password"
end
end
uid
end
private :authenticate
end
end
ここで返したユーザIDと、ブラウザセッションから
導かれるユーザID(すなわち引数のuidで参照できるID)が
一致しない場合には、セッションとユーザの不一致が
起きているものとしてCGI::Auth::AuthFailed例外を起こす。
ブラウザが特定できればよく、特に認証を行う必要がなければ 単になにもしないメソッドとして再定義するとよい。
class CGI
class Auth
def authenticate(uid, opts)
uid
end
private :authenticate
end
end
また、最初のユーザ登録のときとブラウザの追加のときだけ認証を行い、
普段は認証をしなくてよいのであれば前述の
CGI::Auth#in_registration_session?を使って
次のようにするとよい。
class CGI
class Auth
def authenticate(uid, opts)
if in_authenticate_session?
return uid
end
# ここに認証処理を書く
end
end
endCGI::Auth#user_transaction(uid)ユーザIDを指定して対応するユーザのユーザ情報にアクセスすることができる。 処理内容はブロック引数で渡す。
ブラウザセッションから導かれるユーザIDと違ったユーザIDを指定して 認証を受けることができない。
CGI::AuthBasicはCGI::Authを使ってHTTPのBasic認証を行うためのクラスである。
CGI::AuthBasicではCGI::Auth#authenticateを提供する。
Basic認証の際に使う認証名(realm)は
CGI::AuthBasic#loginのoptsで指定する。
optsの中で"force_auth" => trueを指定しておくと、
ブラウザセッションに入れたとしても
ユーザ名・パスワードでの認証をしなくてはならない。
c = CGI.new(...)
x = CGI::AuthBasic(c, ...)
begin
x.login({'realm' => 'RubyWorld'})
rescue CGI::Auth::AuthFailed
c.out({'status' => 'FORBIDDEN'}) do
# 認証に失敗した場合には403 Forbiddenレスポンスを返す。
# その上で登録したければこのようにしないさいという
# 旨のメッセージを返す。
end
end
なお、ユーザ名DBが使える状態であり、
かつパスワードがユーザDB中に"_passwd"というキーで
MD5#hexdigest形式で登録されていなければならない。