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.

class CGI::Auth

CGI::AuthはCGIスクリプトでのユーザ認証を支援するためのクラスである。

CGI::Authでは次に示すような二つのデータベースと 二つのセッション(CGI::Sessionまたは その子クラスによるもの)を内部的に使用する。

ユーザDBに登録する情報およびセッションに登録する情報において、 そのキーを"_"で始めることはできないことに注意。 "_"で始まるキーはCGI::Authの内部で使うために予約されている。

クラスメソッド

CGI::Auth::new(request, userdb, unamedb = nil, bs_opt = {}, rs_opt = {})

CGI::Authインスタンスを作る。

requestは対象とするCGIクラスのインスタンスであり、 userdbunamedbは、それぞれユーザDBとユーザ名DBである。

bs_optrs_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#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にリダイレクトする。
end
CGI::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_logintrueにして呼び出すと、 特定のユーザ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
end
CGI::Auth#user_transaction(uid)

ユーザIDを指定して対応するユーザのユーザ情報にアクセスすることができる。 処理内容はブロック引数で渡す。

制限事項

ブラウザセッションから導かれるユーザIDと違ったユーザIDを指定して 認証を受けることができない。

CGI::AuthBasic

CGI::AuthBasicはCGI::Authを使ってHTTPのBasic認証を行うためのクラスである。 CGI::AuthBasicではCGI::Auth#authenticateを提供する。

Basic認証の際に使う認証名(realm)は CGI::AuthBasic#loginoptsで指定する。 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形式で登録されていなければならない。