[ruby]GoogleAuthSubを使う

GoogleカレンダーやGoogleDocの情報は、ユーザーの同意の下に他のアプリケーションからも読み書きができるようにAPIが定義されています。

で、そういう時に、「ここにユーザー名とパスワードを入れてね」という危険なやり方じゃなくて、ちゃんとユーザーの同意をとりつつ、アプリケーションにはパスワードを渡さなくていいようにするための認証APIが、GoogleAuthSubです。

実際に使っている例としては、携帯電話でGoogleカレンダーを読み書きできるGoogle Calendar Mobile Gatewayが有名です。

で、そのGoogleAuthSubをRails(というかRuby)で実現するためのgoogle_auth_sub.rbというのを作ってみました。

手元では動いていますが、いろいろ自信がないところがあるので、動いたとか動かなかったとか、このコードはまずいだろ、とか、いろいろフィードバックいただけると嬉しいです。

google_auth_sub.rb

使い方

 GoogleAuthSubについての情報は、この辺にまとまっています。



流れを見るのであれば最後のサイトを見るのがわかりやすいと思います。

ここでは、google_auth_sub.rbを使うことを前提に説明してみます。

まず、Googleの認証ページ用のURLを生成して、ユーザーさんに踏んでいただきます。

google_auth_sub.rbでは、こんな感じで生成します。

@uri = GoogleAuthSub.getURLForAuthSubRequest(

“https://www.google.com/calendar/feeds/”,

“http://www.example.com/responce”

)

最初の引数はscope、Googleのどのサービスにアクセスしたいかを示すURLです。

二つ目は、next、ユーザーさんがGoogleのサイトでアクセスを許可した後戻ってくるページのURLです。三番目以下を使いたい人は、Googleのドキュメント見てください:-)

ともあれ、こうやって生成したURLをWEBサイトに表示して、ユーザーさんに踏んでいただきます。

そうするとユーザーさんはGoogleの認証ページに飛んで、

サイト xxxx は次のサービスで使用するため Google アカウントへのアクセスをリクエストしています。
というようなメッセージで、アプリケーションがGoogleカレンダーにアクセスすることへの確認を求められます。

ユーザーさんが「アクセスを許可」を押すと、ユーザーさんは先ほどnextで指定したURLに帰ってきます。この際、URIにToken=XXXX という形でトークンがついてきます。

これは一時トークンといって、一回しか使えませんが、Googleにお願いすると、当分使えるセッショントークンと交換してもらうことができます。

single_use_token = params[:token]

gas = GoogleAuthSub.new()

token = gas.getSessionToken(single_use_token)



セッショントークンがとれたら、あとはGoogleの各種APIにアクセスすることで、情報を取り出すことができます。このさい、セッショントークンをヘッダに入れてアクセスしないといけませんが

gas = GoogleAuthSub.new()

res = gas.googleHttpGet(‘https://www.google.com/calendar/feeds/default/private/full’,token)

とすることで、その辺をラップして情報をとってくることができるようになります。

とってきた情報はGCal namespace element referenceというフォーマットで格納されていますが、ここから先はSubAuthじゃなくて各アプリケーションの処理なので、google_auth_sub.rbが面倒見るのはここまでです。

そのほか

GoogleAuthSub.new()の最初の引数で、CA 証明書ファイルのパスを指定します。nilで省略した場合には、HTTPSのサーバ証明書をチェックしなくなるので、セキュリティ上まずいような気がしますが、ライブラリとしては一応動作します。

CA 証明書ファイルを渡すと、VERIFY_PEERモードで証明書を検証します。でも、これで安全なのかどうなのかあまり分かっていないので、このあたりつっこみいただけるととても嬉しいです。

GoogleAuthSub.new()の二番目の引数にloggerを渡すと、Googleとのやりとりを見ることができます。

getAuthSubTokenInfo()をつかうと、トークンが有効かどうかを確認することができます。セッショントークンであっても、ユーザーが有効性を取り消すことができるので、時々チェックした方がいいのかも。

ちなみに一時トークンをこの関数に渡して有効性をチェックすることもできますが、チェックしたことでトークンを使い終わってしまうので、あんまり役にも立ちません:-)

[ruby]Net::HTTPで無限に302 Movedを繰り返すトラブルにあった

まとめ

 res= http.get( url.path )

って書いていると、30x系のリダイレクトでパラメータ付きのURLを指示された時にはまります。url.request_uriを使いましょう。


内容

Google Calendar APIなんかを使ってWEBサービスからデータをとってくる時、net/httpを使うことはよくあるかと思います。

とってきたデータが30x系のリダイレクトであった場合まで考慮に入れたとすると、こんなコードになりますよね?

 max_retry_count = 5

 max_retry_count.times {|retry_count|

  http = Net::HTTP.new(url.host, url.port)

  http.use_ssl = true if (443==url.port)

  http.ca_file = ‘/var/hogehoge/www.google.com.cer’

  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  http.verify_depth = 5

  res= http.get( url.path )

  

  case res

  when Net::HTTPSuccess

   break

  when Net::HTTPRedirection

   url = URI.parse(res[‘Location’])

   next

  else

   break

  end

 }

 resを使ってあれこれ。

 ところが、これでGoogleカレンダーのデータをとってこようとしたらひどい目に遭います。とりあえずとってくると…

Moved Temporarily

The document has moved https://www.google.com/xxxx?gsessionid=m6Kxxxx

想定の範囲内の302 Moved Temporarilyなので、プログラムはいわれたとおりにリトライして、データをとってきます。

Moved Temporarily

The document has moved https://www.google.com/xxxx?gsessionid=1rJxxxx

え?と思いつつ、プログラムはいわれたとおりにリトライして、データをとってきます。

Moved Temporarily

The document has moved https://www.google.com/xxxx?gsessionid=Cmvxxxx

….たらい回し状態です。いっこうに目的のデータに行き着きません。

やり方が間違っているのかなぁ、それともドキュメントに読み落としがあるかなぁ、とさんざん悩んだ末、やっと原因に気がつきました。

rubyのURIライブラリは、URIを渡すと部品に分解してくれます。host/port/path/query….

そこで、http.get( url.path )を使うと? queryとして指示されている「?gsessionid=Cmvxxxx」が吹っ飛んじゃいますよね。

Googleさんから見ると、

・”xxxx”にクライアントが来たので、”xxxx?gsessionid=….”にリダイレクトを指示しました。

・なぜかクライアントはgsessionidを外して、再び”xxxx”にアクセスしてきました

・仕方がない(というか、別クライアントに見えるので)、”xxxx?gsessionid=….”にリダイレクトを指示しました。

・なぜかクライアントはgsessionidを外して”xxxx”にアクセスしてきたので….

ということになっていたわけです。

….だって みんな“http.get( url.path )” って書くじゃーん!

 res= http.get( url.request_uri )

って書くのがいいみたいです。