RubyらしいAPIでLDAPのエントリを操作できるライブラリActiveLdapの新しいバージョンがリリースされました。以下のようにgemでアップデートできます。
% sudo gem install activeldap
ActiveLdapについてはこのあたりを見てください。
今回のリリースではRuby on Railsの最新安定版2.3.8に対応しました。ActiveLdapは国際化対応のためにRuby-GetText-Packageを使っています。そのため、ActiveLdapをRailsで使う場合にlocale_railsと一緒に使っている場合も多いでしょう。しかし、locale_railsの最新版はRails 2.3.8に対応していないので、locale_railsを利用している場合はアップデートするかどうかよく検討してください。(locale_railsのリポジトリ上では2.3.8に対応しているので、locale_railsのリリース版ではなくて未リリースのものを利用するのも対応策の1つです。)
LDAPといえば、日本Ruby会議2010では「Rubyで扱うLDAPのススメ」という企画があります。[ANN]RubyKaigi2010 企画 "Ruby で扱う LDAP のススメ" にご協力頂ける方を募集しています - tashenの日記ということなので、ぜひ、ご協力をお願いします。
とあるRails 3を使っているたいやき用のCMSでDeviseを使ってOpenID認証をするようにしたので、そのやり方を紹介します。RubyはRuby 1.9.2 RC2も出ていますが、今回はRuby 1.9.1を使います。
DeviseはRackベースの認証システムです。バックエンドにWardenを利用しているため、Basic認証やOpenID、OAuthなど認証方法を切り替えることができます。
ただ、以下の説明を読んでみてもらってもわかる通り、動き出すまでにそこそこの作業が必要になります。機能は豊富なので、動き出したらカスタマイズしてアプリケーションの要求に合わせていくことができるでしょう。日本語での情報もあまりありませんが、探せばいくつかはあるので、試してみてはいかがでしょうか。
とはいえ、今回はDeviseのデフォルトの認証方法ではなく、OpenIDでのみ認証することにします。また、未登録のユーザがログインしようとしたときは自動的に新規ユーザを作成することにします。このようにも使えるという例ということで読むとよいかもしれません。
まず、Rails 3.0 beta4をインストールします。
% sudo gem1.9.1 install rails --pre
サンプル用のアプリケーションを作ります。
% ruby1.9.1 rails new taiyaki % cd taiyaki
次にDeviseをインストールします。
% sudo gem1.9.1 install devise --version=1.1.rc2
インストールしたDeviseを利用するため、以下のようにGemfileに追記します。
Gemfile:
1 |
gem 'devise', "1.1.rc2" |
アプリケーションにDeviseが動作するために必要なファイルをインストールします。config/initializers/devise.rbなど主に設定ファイルです。
% ruby1.9.1 script/rails generate devise:install
いくつかは手動で設定する必要があります。それぞれ以下の通りです。
Deviseはパスワードの再設定をする機能もあり、そのときはユーザにメールを送信します。そのような機能を使うときはActionMailerのURL生成オプションを設定する必要があります。例えば、開発時のホスト情報を設定する場合は以下のようになります。
config/environments/development.rb:
1 |
config.action_mailer.default_url_options = {:host => 'localhost:3000'} |
Deviseはリダイレクト先のURLを生成するときなどにデフォルトではroot_path
を使うので、rootパスへのマッピングを追加します。以下の例ではwelcome#index
を指定しているので、後でWelcomeControllerを作ります。
config/routes.rb:
1 |
root :to => "welcome#index" |
Deviseはnoticeとalertのflashを設定するので、レイアウトに追加しておくとよいでしょう。例えば、以下のようにyield
の前に追加します。
app/views/layouts/application.html.erb:
<%# ... %> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> <%= yield %> <%# ... %>
これでインストールは完了したので、コントローラーやモデルを作成します。
まず、ユーザー用のモデルを作成します。
% ruby1.9.1 script/rails generate devise User
このとき生成されるスキーマは、以下のようにデータベース上にパスワードのダイジェストなどの情報を持ち、それを利用して認証することになります。
db/migrate/XXXX_devise_create_users.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class DeviseCreateUsers < ActiveRecord::Migration def self.up create_table(:users) do |t| t.database_authenticatable :null => false t.recoverable t.rememberable t.trackable # t.confirmable # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both # t.token_authenticatable t.timestamps end add_index :users, :email, :unique => true add_index :users, :reset_password_token, :unique => true # add_index :users, :confirmation_token, :unique => true # add_index :users, :unlock_token, :unique => true end def self.down drop_table :users end end |
しかし、今回は自分では認証情報を持たずにOpenIDで認証するので、データベース上に認証情報を持たないようにします。代わりにOpenID用のカラムを追加します。
db/migrate/XXXX_devise_create_users.rb(変更後):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class DeviseCreateUsers < ActiveRecord::Migration def self.up create_table(:users) do |t| t.string :email t.string :nickname t.string :identity_url t.string :fullname t.string :birth_date t.integer :gender t.string :postcode t.string :country t.string :language t.string :timezone t.rememberable t.trackable t.confirmable t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both t.token_authenticatable t.timestamps end add_index :users, :identity_url, :unique => true add_index :users, :email, :unique => true add_index :users, :confirmation_token, :unique => true add_index :users, :unlock_token, :unique => true end def self.down drop_table :users end end |
変更したらスキーマを反映させます。
% rake1.9.1 db:migrate
モデルのコードにもデータベースで認証するためのコードが入っています。今回は必要のないユーザ登録用の:registerable
オプションやパスワードの入力チェックなどをする:validatable
オプションなどは外します。password_required?
メソッドをオーバーライドしているのは、OpenIDで認証するためパスワードが必要がないからです。
app/models/user.rb:
1 2 3 4 5 6 7 8 9 10 11 |
class User < ActiveRecord::Base ... # devise :database_authenticatable, :registerable, # :recoverable, :rememberable, :trackable, :validatable devise :database_authenticatable, :rememberable, :trackable ... def password_required? false end end |
Deviseではログイン画面などデフォルトのビューも提供してくれますが、今回はOpenIDを使った認証にするためビューをカスタマイズします。ビューをカスタマイズする場合は、コントローラーごとカスタマイズする方法と、ビューだけカスタマイズする方法がありますが、今回はコントローラーごとカスタマイズする方法にします。
コントローラーをカスタマイズするにはconfig/routes.rbに追加されたdevise_for
で:controllers
オプションを指定します。以下のように指定するとUsers::SessionsController
コントローラーを使います。
config/routes.rb:
1 2 |
devise_for(:users, :controllers => {:sessions => "users/sessions"}) |
コントローラーを作成します。
% ruby1.9.1 script/rails generate controller Users::Sessions
コントローラーをカスタマイズする場合は、ApplicationController
ではなくDevise::SessionsController
を継承します。
app/controllers/users/sessions_controller.rb:
1 2 |
class Users::SessionsController < Devise::SessionsController end |
ログインフォームではOpenID用の識別子を入力してもらうようにします。
app/views/users/sessions/new.html.erb:
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> <p> <label for="openid_identifier" >OpenID URL:</label> <%= text_field_tag :openid_identifier %> </p> <p><%= f.label :remember_me %> <%= f.check_box :remember_me %></p> <p><%= f.submit "Login" %></p> <% end %>
あとは、トップページを準備すれば画面を確認することができます。
トップページ用のコントローラーを生成します。
% ruby1.9.1 script/rails generate controller welcome index % rm public/index.html
トップページではログインページに移動できるようにします。ログイン時はログイン中のユーザ情報を表示します。
app/views/welcome/index.html.erb:
<h1>Welcome#index</h1> <% if user_signed_in? %> <p>ようこそ<%= current_user.nickname %>さん</p> <%= link_to("ログアウト", destroy_user_session_path) %> <% else %> <%= link_to("ログイン", new_user_session_path) %> <% end %>
サーバを起動します。
% ruby1.9.1 script/rails server
http://localhost:3000/にアクセスすると以下のような画面になります。
ログインページに行くと以下のようなフォームになります。
それではOpenIDに対応します。DeviseからOpenIDを使うために、warden-openidを使います。
% sudo gem1.9.1 install warden-openid
Gemfileにも追記します。
Gemfile:
1 |
gem 'warden-openid'
|
WargenでOpenIDを使うようにします。
config/initializers/devise.rb:
1 2 3 4 5 6 |
Devise.setup do |config| ... config.warden do |manager| manager.default_strategies(:openid, :scope => :user) end end |
OpenIDの設定をします。warden-openidではOpenIDの認証が成功した時にコールバックが実行され、そこで認証情報に対応したアプリケーション用のユーザを返すことになります。今回は、ここで、ユーザが存在しない場合は自動的に新規ユーザを作成することにします。
config/initializers/openid.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Rails.application.config.middleware.insert(Warden::Manager, Rack::OpenID) Warden::OpenID.configure do |config| config.required_fields = User.required_open_id_fields config.optional_fields = User.optional_open_id_fields config.user_finder do |response| user = User.find_by_identity_url(response.identity_url) if user.nil? user = User.new user.extract_open_id_values(response) unless user.save message = "failed to create user: " message << "#{users.errors.full_messages.inspect}: " message << user.inspect Rails.logger.error(message) user = nil end end user end end |
OpenIDの情報とアプリケーションのユーザ情報をマッピングする処理はモデルで行います。
app/models/user.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
class User < ActiveRecord::Base REQUIRED_FIELDS = { :nickname => "nickname", } OPTIONAL_FIELDS = { :email => "email", :fullname => "fullname", :birth_date => "dob", :gender => "gender", :postcode => "postcode", :country => "country", :language => "language", :timezone => "timezone" } class << self def required_open_id_fields REQUIRED_FIELDS.values end def optional_open_id_fields OPTIONAL_FIELDS.values end end def password_required? false end def extract_open_id_values(response) profile_data = {} [OpenID::SReg::Response, OpenID::AX::FetchResponse].each do |response_class| data_response = response_class.from_success_response(response) profile_data.merge!(data_response.data) if data_response end [REQUIRED_FIELDS, OPTIONAL_FIELDS].each do |fields| fields.each do |model_key, profile_key| unless profile_data[profile_key].blank? self.send("#{model_key}=", profile_data[profile_key]) end end end self.identity_url = response.identity_url self.nickname ||= identity_url end end |
一応、ニックネーム情報は欲しいとリクエストしますが、もらえなくてもなんとなく動くようになっています。この状態でログインページにOpenID識別子を入力して、認証に成功するとアプリケーションにログインする事ができます。
ただし、現在リリースされているruby-openidはRuby 1.9のEncodingに対応していないため、認証中にASCII以外の文字列を含むページにアクセスすることになると失敗します。これを修正する方法はRuby 1.9.1 supportで報告済みですが、まだ取り込まれていません。
Rails 3.0 beta4でDeviseを使ってOpenID認証する方法を紹介しました。Rails 3で認証まわりはどうしようか、と考えていている人は試してみるとよいかもしれません。ただ、betaやrcのものを使っているので、これから使い方は変わっていく可能性が高いと考えられます。注意してください。
そういえば、トップページにある会社紹介資料PDFを更新しました。エンジニア紹介ページなどが更新されています。
数ヶ月前、すでにPythonを知っている人向けのPythonの本が出版されました。オリジナルは2008年に海外で出版されたもので、これはその翻訳です。
エキスパートPythonプログラミング
KADOKAWA/アスキー・メディアワークス
¥ 33,482
さらにステップアップしたいPython開発者向けの内容なので、構文の説明など初級者用の内容はなく*1、どうやってPythonを使うのがよいかを書いてあります。プログラミングだけではなく、開発全体を意識しているのが実務的といえます。パッケージを作り方を説明するところでも、単にEggを作るだけではなく、PyPIに登録するところまで説明しています。他にもよい名前について1章使ったり、テスト駆動開発に1章使ったりと、しっかりと大事な話題はおさえています。ドキュメントについての章があるのもPythonらしいですね。
ただし、幅広く扱っている分、少し物足りない部分もあります。例えば、テスト駆動開発の部分は標準添付のunittestよりもnoseの方により重みをおいて説明した方がより実務的でしょう。
一番有用なのは付録のPython 2とPython 3のUnicode文字列についてまとめたところかもしれません。この部分は日本語版用に翻訳者たちが書き下ろしたもので、マルチバイト文字列を扱う機会の多い日本のPython開発者には特に有用でしょう。Python 2とPython 3は互換性がないため、文字列の扱いを移行するときにこの付録が役に立つことでしょう。
また、オリジナルが出版されてから現在までにPython界隈の状況も変わってきていますが、それについて訳注で補足されているのもうれしいところでしょう。新しく書くコードでは最新の状況にあわせたコードにしたいものです。
エキスパートPythonプログラミングの内容を簡単に紹介しました。
エキスパートPythonプログラミングではまったく触れられていませんが、単体テストフレームワークはunittestやnoseよりもPikzieがオススメです。
*1 むしろ最初にインストール方法があるのが違和感。ただ、MinGWなどもインストールするように書いてあるので、初心者用ではない。