2008年11月11日 星期二

Merb-Auth (4) 討厭的 Google OpenID (其實 Yahoo 更討厭)

為什麼說他們討厭呢?其實是因為他們不支援簡單好用的 SReg ,所以即使是登入成功,也拿不到使用者的個人資訊。還好 Google 還支援 AX (不過現在也只能拿到 email),Yahoo 的話,就只好乖乖的去用 BBAuth 了。

這篇文章就來擴充一下 default_openid 的功能,讓它也可以支援 google。

首先我們在 login form 上加上 "用google" 登入的按鈕:
<div>
<form action="<%= slice_url(:merb_auth_slice_password, :perform_login) %>" method="POST" accept-charset="utf-8"> <input type="hidden" name="_method" value="PUT" />
<div class="formRow">
<input type="submit" name="google_openid_url" value="用 GOOGLE 帳號登入" id="google_openid_url">
</div> <!-- close: formRow -->
</form>
</div>
再來編輯 merb/merb-auth/strategies.rb ,多加上一個 GoogleOpenID strategy:
require 'openid/extensions/ax'
class GoogleOpenID < Merb::Authentication::Strategies::Basic::OpenID
def run!
if params[:google_openid_url]
identity_url = 'https://www.google.com/accounts/o8/id'
begin
openid_request = consumer.begin(identity_url)
openid_ax = ::OpenID::AX::FetchRequest.new
ax_info = ::OpenID::AX::AttrInfo.new('http://axschema.org/contact/email','email',true)
openid_ax.add(ax_info)
openid_request.add_extension(openid_ax)
redirect!(openid_request.redirect_url("#{request.protocol}://#{request.host}", openid_callback_url))
rescue ::OpenID::OpenIDError => e
request.session.authentication.errors.clear!
request.session.authentication.errors.add(:openid, 'The OpenID verification failed')
nil
end
end
end
end
GoogleOpenID 只是繼承了 OpenID,然後將 run! 的部份改寫成,當發現 params 中有 google_openid_url 時,就發出一個 openid 的 request 給 google 的 server,原本 SReg 去要 nickname/email 的部份則改成用 AX 來要 email。這個 Strategy 是一定不會回傳登入物件的,因為 google 導回來我們的程式時,相關的參數會被 OpenID 那個 Strategy,所以我們也要將他稍微的改寫:
  def on_success!(response, sreg_response)
if user = find_user_by_identity_url(response.identity_url)
user
else
user = user_class.new
unless user.login = sreg_response.data['nickname']
ax_response = ::OpenID::AX::FetchResponse.from_success_response(response)
user.login = ax_response.data["http://axschema.org/contact/email"][0] if ax_response
user.login.sub!(/@.*/,'') if user.login
end

user.identity_url = response.identity_url
user.password = user.password_confirmation = Digest::SHA1.hexdigest("#{response.identity_url}#{rand(1000)}")
user.save ? user : nil
end
end
我們只改寫 on_success! 的部份。一開始先判斷是否已經用 SReg 取得了 nickname 和 email 了,如果拿不到的話,再用 AX 試試看能不能拿到 email,再將 email 的 @ 之前的部份暫且當做是 nickname,其它的部份就和原來的 OpenID 相同了。

其實要這樣寫應該是原本的 default_openid 不夠彈性。應該是在發出 OpenID request 給 OpenID Provider 之前提供一個 callback 讓使用者可以設定不同的需求才對。

1 則留言:

contagion 提到...

啊....我真是個眼殘!!原來人家明明有提供 customize_openid_request! 這個 callback!!
還說人家不彈性呢...