-
001354
Ruby on Rails で OpenID - 麦酒堂 の続きです。
さきほどは はてな の OpenID を利用しました。 今回は mixi OpenID を利用してみます。
認証を行うだけなら、はてな も mixi も大差ありません。 外部アプリケーションのログイン画面にて "mixi.jp" と入力し、遷移先の mixi にログインするだけです。 mixi の場合はこれに加えて拡張属性 SREG に対応しています。 この仕組みを利用して、外部アプリケーションから mixi ニックネームの取得が可能です。
ニックネームを取得するには、先ほど編集した authn_controller.rb に対して、次の変更を加えます。
まず、SREG を扱うためのライブラリーを読み込みます。
require "openid/extensions/sreg"
リクエストに SREG 要求を追加します。
sreg_request = OpenID::SReg::Request.new sreg_request.request_fields(['nickname'], true) request.add_extension(sreg_request)レスポンスから SREG の拡張属性を読み出します。
sreg_response = OpenID::SReg::Response::from_success_response(response) flash[:notice] = "Logged in as #{sreg_response['nickname']}"以上の変更で、mixi からニックネームを取得してログイン画面に表示されるようになります。
[ permalink ] [ 0 comment(s) ] [ 0 trackback(s) ] -
001353
Ruby on Rails を使い OpenID で認証を行うアプリケーションを作ってみました。 「認証を行う」というのもあいまいな表現ですね。 正確には RP です。 OpenID の諸々はこちら。
Web アプリケーションを OpenID に対応させるための諸々はこちらが参考になります。
今回は既存アプリケーションへの機能追加ではなく、新規に OpenID 対応アプリケーションを作成します。
前提として、以下のパッケージがそろっていることとします。
- Ruby 1.8.7
- RubyGems 1.2.0
- Ruby on Rails 2.1.1
最初にライブラリーをインストールします。
- ruby-openid 2.1.2
- openid_login_generator 0.1
ruby-opendid は Ruby 用の OpenID ライブラリーです。 OpenID 2.0 に対応し、RP だけではなく OP も構築可能です。 これは特定のフレームワークに依存したものではなく、もちろん Ruby on Rails 専用というわけではありません。
Ruby on Rails で ruby-openid を使った認証メカニズムを簡単に構築するためのジェネレーターが openid_login_generator です。 ただ、2008-09-15 時点では多少古くなっているようで、Ruby on Rails 2.x や ruby-openid 2.1.2 に完全には対応していません。 後ほど手で修正を加えます。
この二つのライブラリーを gem でインストールします。
sudo gem install ruby-openid --include-dependencies sudo gem install openid_login_generator --include-dependencies
以上で下準備完了。 ここからアプリケーションの作成に入ります。
まずはお決まりの
rails openid_app cd openid_app
アプリケーション名はお好みでどうぞ。 続いて openid_login_generator で認証メカニズムを生成したいのですが、その前にちょっと修正が必要です。 Ruby on Rails では 2.x でビューのテンプレートの拡張子が変更されました。 しかし openid_login_generator は古い拡張子を指しているのでそのままでは動きません。 生成前にこれを修正しておきます。 修正対象は openid_login_generator.rb です。 ぼくの環境では /usr/local/lib/ruby/gems/1.8/gems/openid_login_generator-0.1 にありました。
*** openid_login_generator.rb.org 2008-09-15 00:41:13.000000000 +0900 --- openid_login_generator.rb 2008-09-15 00:41:28.000000000 +0900 *************** *** 14,20 **** m.template "users.yml", "test/fixtures/users.yml" # Layout and stylesheet. ! m.template "scaffold:layout.rhtml", "app/views/layouts/scaffold.rhtml" m.template "scaffold:style.css", "public/stylesheets/scaffold.css" # Views. --- 14,20 ---- m.template "users.yml", "test/fixtures/users.yml" # Layout and stylesheet. ! m.template "scaffold:layout.html.erb", "app/views/layouts/scaffold.html.erb" m.template "scaffold:style.css", "public/stylesheets/scaffold.css" # Views.この修正が済んだら晴れて生成です。
./script/generate openid_login authn
第二引数はコントローラー名です。 お好みでどうぞ。
ここで生成されたファイル群はそのままでは動かず、次の修正が必要になります。
まずはビューです。
*** app/views/authn/login.rhtml.org 2008-09-15 00:45:46.000000000 +0900 --- app/views/authn/login.rhtml 2008-09-15 00:45:59.000000000 +0900 *************** *** 1,4 **** ! <%= start_form_tag :action=> "login" %> <div title="Account login" id="loginform" class="form"> <h3>Please login</h3> --- 1,4 ---- ! <% form_tag :action=> "login" do %> <div title="Account login" id="loginform" class="form"> <h3>Please login</h3> *************** *** 11,15 **** </div> ! <%= end_form_tag %> --- 11,15 ---- </div> ! <% end %>続いてコントローラーの修正。 これは ruby-openid に同梱されたサンプル "consumer_controller.rb" が参考になります。 主な修正点は次のとおりです。
- ライブラリーの読み込み (require) のお作法
- response.status が無くなったので例外捕捉に
- params, request, session がインスタンス変数からローカル変数に変更
- response.status は健在だがクラスを OpenID::Consumer::SUCCESS に変更
- ストアーのクラスを OpenID::Store::Filesystem に変更
実際の差分はこう。
*** app/controllers/authn_controller.rb.org 2008-09-15 00:43:51.000000000 +0900 --- app/controllers/authn_controller.rb 2008-09-15 06:09:31.000000000 +0900 *************** *** 2,13 **** require "cgi" # load the openid library ! begin ! require "rubygems" ! require_gem "ruby-openid", ">= 1.0.2" ! rescue LoadError ! require "openid" ! end class AuthnController < ApplicationController layout 'scaffold' --- 2,9 ---- require "cgi" # load the openid library ! require "openid" ! require "openid/store/filesystem" class AuthnController < ApplicationController layout 'scaffold' *************** *** 15,27 **** # process the login request, disover the openid server, and # then redirect. def login ! openid_url = @params[:openid_url] ! ! if @request.post? ! request = consumer.begin(openid_url) ! case request.status ! when OpenID::SUCCESS return_to = url_for(:action=> 'complete') trust_root = url_for(:controller=>'') --- 11,21 ---- # process the login request, disover the openid server, and # then redirect. def login ! openid_url = params[:openid_url] ! if request.post? ! begin ! request = consumer.begin(openid_url) return_to = url_for(:action=> 'complete') trust_root = url_for(:controller=>'') *************** *** 29,41 **** redirect_to(url) return ! when OpenID::FAILURE escaped_url = CGI::escape(openid_url) flash[:notice] = "Could not find OpenID server for #{escaped_url}" - - else - flash[:notice] = "An unknown error occured." - end end --- 23,31 ---- redirect_to(url) return ! rescue => e escaped_url = CGI::escape(openid_url) flash[:notice] = "Could not find OpenID server for #{escaped_url}" end end *************** *** 43,54 **** # handle the openid server response def complete ! response = consumer.complete(@params) case response.status ! when OpenID::SUCCESS ! @user = User.get(response.identity_url) # create user object if one does not exist if @user.nil? --- 33,46 ---- # handle the openid server response def complete ! current_url = url_for(:action => 'complete') ! parameters = params.reject{|k,v|request.path_parameters[k]} ! response = consumer.complete(parameters, current_url) case response.status ! when OpenID::Consumer::SUCCESS ! @user = User.find_by_openid_url(response.identity_url) # create user object if one does not exist if @user.nil? *************** *** 58,71 **** # storing both the openid_url and user id in the session for for quick # access to both bits of information. Change as needed. ! @session[:user_id] = @user.id flash[:notice] = "Logged in as #{CGI::escape(response.identity_url)}" redirect_to :action => "welcome" return ! when OpenID::FAILURE if response.identity_url flash[:notice] = "Verification of #{CGI::escape(response.identity_url)} failed." --- 50,63 ---- # storing both the openid_url and user id in the session for for quick # access to both bits of information. Change as needed. ! session[:user_id] = @user.id flash[:notice] = "Logged in as #{CGI::escape(response.identity_url)}" redirect_to :action => "welcome" return ! when OpenID::Consumer::FAILURE if response.identity_url flash[:notice] = "Verification of #{CGI::escape(response.identity_url)} failed." *************** *** 97,105 **** # create the OpenID store for storing associations and nonces, # putting it in your app's db directory store_dir = Pathname.new(RAILS_ROOT).join('db').join('openid-store') ! store = OpenID::FilesystemStore.new(store_dir) ! return OpenID::Consumer.new(@session, store) end # get the logged in user object --- 89,97 ---- # create the OpenID store for storing associations and nonces, # putting it in your app's db directory store_dir = Pathname.new(RAILS_ROOT).join('db').join('openid-store') ! store = OpenID::Store::Filesystem.new(store_dir) ! return OpenID::Consumer.new(session, store) end # get the logged in user object最後にユーザー情報を扱うための model を作りましょう。 本来は一人のユーザーが複数の Identifier を持つことができるように、1:n の関係にしなければならないのですが、今回はシンプルに、ユーザー名とそれに紐付くひとつの Identifier とします。 model 直接ではなくて scaffold で生成しました。
./script/generate scaffold user name:string openid_url:string
スキーマを作成します。
rake db:migrate
以上で完了です。 動作を確認してみましょう。 まずはサーバーの起動。
./script/server
ログイン・ページにアクセスしましょう。
- http://example.com:3000/authn/login
認証ダイアログに OpenID の Identifier を入力します。 今回は、はてなの OpenID を使ってみます。
"Login" ボタンをクリックすると、はてなのログイン画面に遷移します。 はてなのユーザーIDとパスワードを使ってログインします。
外部サイトへのログイン許可を求められます。 URI を確認し、当該 ID でのログインを許可してもよければ "今回のみ許可"、"常に許可" をクリックします。
ログイン成功画面が表示されます。
既に はてな にログインしていた場合は はてな へのログイン画面は表示されず、既に許可したアプリケーションの場合は外部サイトへのログイン許可画面は表示されません。 もっともシンプルな場合は、(見かけ上) はてなのページは一度も表示されません。
ログインに成功して以降はアプリケーション側でセッションを維持します。
この仕組みの最大の利点は、アプリケーション側でユーザーのパスワードを保持する必要がない点です。 ユーザー側はあちこちのアプリケーションにパスワードを預けなくても良いので、管理の手間、漏洩のリスクを低減することができます。 アプリケーション側もパスワードを管理するコストを削減することができます (ユーザー管理テーブルは (:name, :openid_url) でしたよね)。
次は拡張属性 SREG を試してみます。
-
001352
Ruby で HTML スクレイピングを行うライブラリ、scRUBYt を試しました。
なかなか見つけられなかったんだけれど、ここにいろいろと情報がありました。
インストールは gem で簡単にできるんだけれど、いくつかの依存ライブラリが最新のものではなかったためにちょっと手間がかかりました。 バージョンを明示してインストールします。
$ sudo gem install ruby2ruby --version 1.1.6 $ sudo gem install ParseTree --version 1.7.1 $ sudo gem uninstall RubyInline --version 3.7.0 $ sudo gem install RubyInline --version 3.6.3 $ sudo gem install scrubyt --include-dependencies
Google を使った簡単なサンプルを。 キーワード "Java" で Google 検索をかけて、結果を XML で表示します。
#!/usr/bin/env ruby require "rubygems" require "scrubyt" keyword = "java" google_data = Scrubyt::Extractor.define do fetch "http://www.google.com/" fill_textfield "q", keyword submit link %Q{//div[@id="res"]//ol/li//a[@class="l"]} do name "./text()" url "./@href" end end puts google_data.to_xml非常に簡単です。 一部、HTML ページの構造を利用して XPath を記述していますが、これも必須ではありません。 キーワードで引っ掛ける、もっと楽な書き方もあります。 結果はこんな感じです。
<root> <link> <name>java.com: なたと Java</name> <url>http://www.java.com/ja/</url> </link> <link> <name>無料 Java ソフトウェアをダウンロード - Sun Microsystems</name> <url>http://www.java.com/ja/download/</url> </link> <link> <name>Java Developer Center</name> <url>http://www.oracle.com/technology/global/jp/tech/java/index.html</url> </link> <link> <name>Java - Wikipedia</name> <url>http://ja.wikipedia.org/wiki/Java</url> </link> <link> <name>Javaテクノロジ - サン・マイクロシステムズ</name> <url>http://jp.sun.com/java/</url> </link> <link> <name>Javaとは - 意味・解説 : IT用語辞典</name> <url>http://e-words.jp/w/Java.html</url> </link> <link> <name>Javaの道(Java入門・リファレンス)</name> <url>http://www.javaroad.jp/</url> </link> <link> <name>JAVA 動物実験の廃止を求める会</name> <url>http://www.java-animal.org/</url> </link> <link> <name>Javaとは - はてなキーワード</name> <url>http://d.hatena.ne.jp/keyword/Java</url> </link> <link> <name>Java Solution - @IT</name> <url>http://www.atmarkit.co.jp/fjava/</url> </link> </root>to_xml ではなく to_hash を使えば Ruby 内でスクレイピング結果をほげほげすることもできます。
-
001351
いまさら感はありますが、Ruby 1.8.7 にバージョンを上げました。
2008-09-14 現在の最新は 1.8.7-p72 なので、これを手に入れて解凍。
$ wget http://core.ring.gr.jp/archives/lang/ruby/ruby-1.8.7-p72.tar.gz $ tar zxf ruby-1.8.7-p72.tar.gz
Ruby のインストール・ファイル群の名前にはマイナー・バージョンまでしか含まれないので、1.8.6 から 1.8.7 のバージョンアップはそのまま上書きでいけるはず。 よって、このシンプルな手順で。
$ cd ./ruby-1.8.7-p72 $ ./configure $ make $ sudo make install
ついでに gem も上げておきましょう。
$ wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz $ tar zxf rubygems-1.2.0.tgz $ cd rubygems-1.2.0 $ sudo ruby setup.rb $ sudo gem pristine --all
gem から入れたパッケージ群も
$ sudo gem update
-
001252
昨年末、12/07 に Ruby on Rails 2.0 がリリースされました。 種々の変更点がありますが、特に気になったのは scaffold の刷新。 Ruby on Rails 1.2 では scaffold_resource とされていたものが、新たに標準の scaffold に格上げとなりました。 Ruby on Rails は RESTful なフレームワークとしてさらに歩を進めた形ですな。
この scaffold を利用すると、実に簡単に RESTful な API を備えたリソースを作成できます。 Ruby on Rails 2.0.2 からデフォルトの RDBMS となった SQLite3 を使って、この機能を試してみました。
Ruby を SQLite3 に対応させる前述の通り、Ruby on Rails 2.0.2 からはデフォルトの RDBMS が SQLite3 となりました。 これまでの標準も比較的敷居は低い MySQL でしたが、今回さらにそれが下がることになります。 実証コードやプロトの開発には SQLite3 でもある程度事足りますしね。 もちろんオプション指定で MySQL も引き続き利用可能です。
さて、Ruby から SQLite3 を使うための設定を行います。 まずは SQLite3 のパッケージのインストール。 もともとインストール済みの場合はこの手順は不要です。
$ sudo yum install sqlite sqlite-devel
続いて Ruby から SQLite3 を叩くために、gem にて sqlite3-ruby をインストール。
$ sudo gem install sqlite3-ruby
アプリケーションの作成実験用に適当な名前でアプリケーションを生成します。
$ rails example00
続いて scaffold を利用してリソースを生成します。 引数にはリソースが持つ属性とその型を指定します。 このあたりの書き方は Ruby on Rails 1.2 の scaffold_resource からは変更されているようです。 ここでは、リソース User が属性 (name:stirng, password:string, email:string, is_admin:boolean) を持つとします。
$ cd example00 $ ./script/generate scaffold user name:string password:string \ > email:string is_admin:boolean
これでリソース User の定義、それに対する CRUD を提供する RESTful な API、その API への Web インタフェースが生成されました。 ただし、今のこところは HTML4 の制約を超えることはできないので、Uniform Interface を理想的な形で実現することはできません。 代替手段として、生成される Web インタフェースでは "_method" というパラメータに HTTP method 名を埋め込んで POST する手法(overloaded POST) を採っています。 もちろん、直接 HTTP を話せるクライアントを用意すれば、PUT, DELETE などでリソースを操作することが可能です。
scaffold の生成によって、データベースのスキーマ定義も完了しています。 db/migrate/001_create_users.rb を覗くと、引数に渡した属性が書かれていることが確認できるはずです。 というわけで、データベースを作成します。
$ rake db:migrate
サーバーの起動サーバーを起動して動作を確認してみましょう。
$ ./script/server
特に設定を変更していなければ TCP 3000番を聴くはずです。 Web ブラウザでたたいてみましょう。
- http://example.com:3000/users

まだユーザーは作成されていません。 "New user" のリンクをクリックします。

ユーザーの作成画面です。 この画面には対応する RESTful な API はありません。 リソース作成用のフォームを表示するために、Web インタフェースに用意された画面です。 このフォームを埋めて "Creat" ボタンを押下すると、/users に POST されてユーザーが作成されます。


つまり /users は User のファクトリ・リソースです。 画面では /users/1 が表示されていますが、これは /users が 302 Found を返し、Location ヘッダフィールドで /users/1 に飛ばしているためです。 ここで 302 Found はちょっとおかしいですね。 201 Created のほうが意味がしっくりきます。
続いて "Edit" のリンクをクリックしてみます。

これも /users/new と同様に、フォームを表示するためのページで RESTful な API とは一対一対応してはいません。 ここでこのページのソースに注目。 こんな箇所があるはずです。
<input name="_method" type="hidden" value="put" />これが、HTML4 の制約を回避するための overloaded POST を表した箇所です。 Ruby on Rails は "_method" に指定された値を HTTP メソッドとして扱い、呼び出すアクションを決定します。 この場合は既存リソースへの PUT なので :action => update が呼ばれます。 繰り返しになりますが、直接 HTTP PUT メソッドをたたくようなクライアント・ツールを使用した場合には _method=put と同等の処理が行われるので overloaded POST は不要です。 値を一部書き換えて "Update" しましょう。


最後は DELETE です。 一覧画面で "Destroy" リンクをクリックします。


JavaScript で処理されてしまうので分かり辛いかもしれませんが、この場合も _method=delete が POST されています。

当該リソースは削除されました。 くどいかもしれませんが、/users/1 に直接 DELETE メソッドを送り込むことでユーザーを削除することも可能です。 むしろ、そちらが本来の姿です。
といった具合に、実に簡単に RESTful な API を備えたリソースを作成することができました。 データ・ストアに SQLite3 を使うことで、さらにお手軽に実証レベルのアプリケーションを作成することができそうです。 可搬性も向上しますしね。 ひとまずは「素」な状態のリソースを作ることができたので、以降これをベースに手を加えていってみたいと思います。