mod_auth_opensocial をリリースしました

mod_auth_opensocial 0.1.0 をリリースしました。

mod_auth_opensocial は、OpenSocial アプリからサーバに届いた署名付きリクエストの検証(Verification)を行うための Apache 2.0, 2.2 用のモジュールです。

署名付きリクエストとは、OpenSocial の makeRequest 関数 (AUTHORIZATIONパラメータにSIGNEDを指定) により生成され、コンテナにより署名されたリクエストです。

mod_auth_opensocial が署名の検証に成功すると、リクエストは通常通り(HTMLの取得や、PHP、Servlet Containerへのアクセスなど)に処理されます。 検証に失敗すると、HTTPエラーをリクエストの送信者へ返します。
バックエンドが受け取るリクエストは全て署名の検証に成功したものなので、バックエンドはリクエストを再度検証する必要が無くなります。
リクエストに署名が付加されているかどうかすらバックエンドは気にする必要がありません。

尚、本プロジェクトは Google Code で管理しています。
http://code.google.com/p/mod-auth-opensocial/

■ インストール

mod_auth_opensocial は openssl の機能を使用していますので、Apache に mod_ssl が組み込まれている必要があります。

Apache のインストール

※ configure コマンドへのオプションの –prefix と –enable-mods-shared の値は適宜変更してください。
またシステムによっては、 –with-ssl オプションで openssl へのパスを指定する必要があるかもしれません。 (例: –with-ssl=/usr/local/openssl )

#./configure 
--prefix=/usr/local/apache \ --enable-mods-shared=most \ --enable-so \ --with-included-apr \ --enable-ssl # make # make install

mod_auth_opensocial のインストール

# tar xvzf mod_auth_opensocial-0.1.0tar.gz
# cd mod-auth-opensocial-0.1.0
# vi Makefile APACHE_BASE=PATH_TO_APACHE の行の PATH_TO_APACHE を Apache を↑でインストールしたディレクトリに変更してください。 APACHE_BASE=/usr/local/apache # make # make install

httpd.conf の変更

httpd.conf 内の LoadModule ディレクティブ が記述されている箇所に↓の行を追加してください。

LoadModule auth_opensocial_module modules/mod_auth_opensocial.so

Apache をリスタートすると mod_auth_opensocial がロードされます。
ただし、この段階では設定を行っていないので、まだ何も起こりません。

■ mod_auth_opensocial の設定

Apache が http://www.example.com という URL で稼働しているとします。
http://www.example.com/secure/ の以下全てのディレクトリ(パス) へのアクセスを検証するには、以下のように設定します。

<Location "/secure/">
   AuthType OpenSocial
   Require valid-user

   AuthOpenSocialEnabled On

   AuthOpenSocialAcceptSignatureMethods hmac-sha1 rsa-sha1

   AuthOpenSocialConsumerKey 4345983491233
   AuthOpenSocialConsumerSecret wa/nx35kdmerLwQZp8AfmoX

   AuthOpenSocialRSAPublicKeyFile ./conf/pub.1199819524.-1556113204990931254.cer
</Location>

※ consumer secret の値を記述しているので、httpd.conf のパーミッションには注意を払ってください。

AuthType Require ディレクティブは、必ず必要なので忘れずに記述してください。

それでは mod_auth_opensocial 個別のディレクティブを見ていきましょう。

AuthOpenSocialEnabled :

引数の数 : 1
説明 : このモジュールの有効、無効を指定します。 指定の方法は、有効の場合には On を、無効にする場合には Off を指定します。

AuthOpenSocialAcceptSignatureMethods :
引数の数 : 1
説明 : 受け付ける署名方式です。 hmac-sha1, rsa-sha1 のどちらか、もしくは両方(スペース区切り)を指定します。

AuthOpenSocialConsumerKey :

引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Key です。 AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。

AuthOpenSocialConsumerSecret :

引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Secret です。AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。

AuthOpenSocialRSAPublicKeyFile :

引数の数 : 1
説明 : OpenSocial サービスから提供される RSA PublicKey を保存したファイルへのパスを指定します。 AuthOpenSocialAcceptSignatureMethods ディレクティブに rsa-sha1 を指定した場合に必須となります。

上の設定例では用いていませんが、以下のディレクティブもオプションで指定可能です。

AuthOpenSocialRequiredKey :

引数の数 : 1
説明 : 必須HTTPパラメータ (query string 又は post data) の key を指定します。リクエストのパラメータにこの値が存在しなければ、HTTPエラーをリクエスト送信者へ返します。

AuthOpenSocialRequiredKeyAndValue :

引数の数 :2
説明 : 必須HTTPパラメータ (query string 又は post data) の key と値のペアをスペース区切りで指定します。リクエストのパラメータに、このディレクティブで指定したkeyと値のペアが含まれていなければ、HTTPエラーをリクエスト送信者へ返します。

今回リリースした mod_auth_opensocial の バージョン 0.1 では、ある一つの URL に対して1つの consumer key や rsa publickey しか設定できないため、複数のOpenSocial サービスからの署名リクエストを1つのLocationディレクティブでさばくことができません。

例えば、http://www.example/myapp/ に対して mixi アプリとOrkut の両方からの署名リクエストの検証は行えないということです。 Locationタグには、1つだけOpenSocial サービスの設定を行ってください。
この制限や、他にも課題が多数ありますが、今後のバージョンで対応していきます。

このモジュールの使いどころやOpenSocialについて、このブログに順次アップしていきます。
この機会に是非 RSSの購読をお願いいたします。

速攻で作る OpenSocialアプリ ( RSA-SHA1 )

前回のブログにて OpenSocial Client Library Ruby版に付属している Gifts アプリを動作させるまで説明しました。

makeRequestの署名方式に RSA-SHA1 を用いる

OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式には HMAC-SHA1 を用いていましたが、今回はより一般的な RSA-SHA1 を用いた方法を説明します。

HMAC-SHA1では consumer key と consumer secret の2つが必要でしたが、RSA-SHA1ではそれらは必要なく、公開鍵だけが必要となります。

■ OpenSocialコンテナ(Orkut) -> 外部サーバ のリクエストの署名方式を HMAC-SHA1 から RSA-SHA1 へ変更

GIFT_SAMPLE/public/gifts.xml の 14行目辺りのparams["OAUTH_SERVICE_NAME"] = “HMAC”;
をコメントアウトしてください。
この変更により OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式がデフォルトの RSA-SHA1 になります。

■ Rails側で RSA-SHA1 にて署名されたリクエストを受け取れるようにする

○ 公開鍵の取得

OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストのパラメータに
xoauth_signature_publickey=pub.1199819524.-1556113204990931254.cer
というkeyとvalueが付与されてきますが、この pub.1199819524.-1556113204990931254.cer ファイルが公開鍵となります。

で、そのファイルはどこにあるのかというと http://sandbox.orkut.com/46/o/pub.1199819524.-1556113204990931254.cer にあります。

xoauth_signature_publickeyの値が変更されたらどうするんだ?という疑問が浮かぶかもしれませんが、Orkutの場合は、xoauth_signature_publickeyの値が変わる時にはGoogleが前もって告知してくれますので、現状はこの公開鍵のファイルの内容をソースコードにハードコーディングしておいても大丈夫だと思います。
※逆に、自動で公開鍵ファイルを取得するような実装は危険です。そのファイルが正規のものかどうか判断のしようがありませんので。

○ gifts コントローラの修正

opensocial client library ( gem opensocial-0.0.2 )ですが、何故か署名方式に HMAC-SHA1 しか使用できないようになっています・・・。
RSA-SHA1の署名方式が使用できるようにします。

gifts コントローラ内で require ‘oauth/signature/rsa/sha1′ を行います。

また check_signature メソッド内にてコールされている validate メソッド(OpenSocial::Auth#validate(…)) では opensocial client が内部で用いている oauth ライブラリの Consumer クラスのコンストラクタに options を渡せない(署名方式を指定できない)ので、その validate メソッドを用いず、新たに validate メソッドを作成します。

上記全てを反映した gifts コントローラは下のようになります。

class GiftsController < ApplicationController
  include OpenSocial::Auth

  require 'oauth/signature/rsa/sha1'


# 公開鍵のハードコーディング
CERT = <<"EOS"
-----BEGIN CERTIFICATE-----
MIIDHDCCAoWgAwIBAgIJAMbTCksqLiWeMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIG
A1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlh
bjAeFw0wODAxMDgxOTE1MjdaFw0wOTAxMDcxOTE1MjdaMGgxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChML
R29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlhbjCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAseBXZ4NDhm24nX3sJRiZJhvy9eDZX12G
j4HWAMmhAcnm2iBgYpAigwhVHtOs+ZIUIdzQHvHeNd0ydc1Jg8e+C+Mlzo38OvaG
D3qwvzJ0LNn7L80c0XVrvEALdD9zrO+0XSZpTK9PJrl2W59lZlJFUk3pV+jFR8NY
eB/fto7AVtECAwEAAaOBzTCByjAdBgNVHQ4EFgQUv7TZGZaI+FifzjpTVjtPHSvb
XqUwgZoGA1UdIwSBkjCBj4AUv7TZGZaI+FifzjpTVjtPHSvbXqWhbKRqMGgxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEU
MBIGA1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVs
cnlhboIJAMbTCksqLiWeMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
CETnhlEnCJVDXoEtSSwUBLP/147sqiu9a4TNqchTHJObwTwDPUMaU6XIs2OTMmFu
GeIYpkHXzTa9Q6IKlc7Bt2xkSeY3siRWCxvZekMxPvv7YTcnaVlZzHrVfAzqNsTG
P3J//C0j+8JWg6G+zuo5k7pNRKDY76GxxHPYamdLfwk=
-----END CERTIFICATE-----
EOS


  # Declares the keys for your app on different containers. The index is the
  # incoming consumer key sent in the signed makeRequest from the gadget.
  # These are sample gadget credentials and should be replaced with the HMAC
  # keys granted to your own gadget.
  KEYS = {
    'XXXXXXXXXX' => {
      :secret => 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
      :outgoing_key => 'orkut.com:XXXXXXXXXX',
      :container => OpenSocial::Connection::ORKUT
    }
  }

  # Declares where your application lives. Points to the page where users are
  # redirected after loading the iframe.
  SERVER = 'http://www.example.com/'

  # Controls the auto-validation of the signed makeRequest.
  before_filter :check_signature, :only => [:iframe]

  # Renders pages with boilerplate HTML/CSS/JS.
  layout 'default'

  # Implicitly checks the signature of the incoming request and saves the
  # owner id, viewer id, and consumer key in a temporary session for later use.
  # The session id is returned as an iframe snippet that the gadget can use to
  # render the app.
  def iframe
    session[:id] = params[:opensocial_owner_id]
    session[:viewer] = params[:opensocial_viewer_id]
    session[:consumer_key] = params[:oauth_consumer_key]

    render :text => "<iframe width='98%' height='600px' frameborder='0' src='#{SERVER}?sessid=#{session.model.session_id}' />"
  end

  # Loads the temporary session referenced by sessid, copies the values into
  # a persistent session that will be used to actually drive the app, and
  # deletes the temporary session (to prevent replay). Then, initiates a
  # connection to the given container and renders the appropriate content.
  def index

    proxied_session = session.model.class.find(:first,
                        :conditions => ['session_id = ?', params[:sessid]])

    if params[:sessid] && proxied_session
      session[:id] = proxied_session.data[:id]
      session[:viewer] = proxied_session.data[:viewer]
      session[:consumer_key] = proxied_session.data[:consumer_key]
      proxied_session.destroy
    end

    c = OpenSocial::Connection.new(:container => KEYS[session[:consumer_key]][:container],
                                   :consumer_key => KEYS[session[:consumer_key]][:outgoing_key],
                                   :consumer_secret => KEYS[session[:consumer_key]][:secret],
                                   :xoauth_requestor_id => session[:id])

    if session[:id] == session[:viewer]
      render_owner(c)
    elsif session[:viewer] && session[:id] != session[:viewer]
      render_viewer_with_app(c)
    else
      render_viewer_without_app(c)
    end
  end

  # Sends a gift on behalf of the viewer. If the viewer is the owner, the gift
  # is sent to the selected friend (but doesn't check to confirm that the owner
  # is friends with the specified ID). If the viewer is not the owner, the gift
  # is sent to the owner. Then the app redirects to the index.
  def give
    gift = Gift.new(params[:gift])
    if session[:id] != session[:viewer]
      gift.sent_by = session[:viewer]
      gift.received_by = session[:id]
    else
      gift.sent_by = session[:id]
    end

    if gift.save
      redirect_to :action => :index
    else
      flash[:error] = 'Error giving gift.'
      redirect_to :action => :index
    end
  end

  private

  # If the viewer is also the owner, this will render the list of gifts the
  # owner has sent or received. The owner can elect to send a gift to a friend.
  def render_owner(c)
    @id = session[:id]
    @gifts = Gift.find(:all,
                       :conditions => ['sent_by = ? OR received_by = ?', @id, @id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @people = fetch_gift_givers_and_friends(@id, @gifts, c)
    @owner = @people.delete(@id)

    render :action => "owner"
  end

  # If the viewer has the app installed, this will render the list of gifts
  # exchanged between the owner and viewer. The viewer can elect to send a gift
  # to the owner.
  def render_viewer_with_app(c)
    @id = session[:viewer]
    @owner_id = session[:id]

    @gifts = Gift.find(:all,
                       :conditions => ['(sent_by = ? AND received_by = ?) OR ' +
                                       '(sent_by = ? AND received_by = ?)',
                                       @owner_id, @id, @id, @owner_id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @viewer = OpenSocial::FetchPersonRequest.new(c, @id).send
    @owner = OpenSocial::FetchPersonRequest.new(c).send

    render :action => "viewer_with_app"
  end

  # If the viewer doesn't have the app installed, this will render the list of
  # gifts the owner has sent or received (without notes). No gifts may be sent.
  def render_viewer_without_app(c)
    @id = session[:id]
    @gifts = Gift.find(:all,
                       :conditions => ['sent_by = ? OR received_by = ?', @id, @id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @people = fetch_gift_givers_and_friends(@id, @gifts, c)

    render :action => "viewer_without_app"
  end

  # Requests social data for each of the people that have sent or received a
  # gift from the owner (oid). The method first generates a unique list of
  # user ids from the list of gifts, and sends a REST request for their data.
  # Users without the app installed will trigger an exception which is caught.
  # Finally, the owner's friends are fetched (which fills in the missing spaces
  # where insufficient permissions are granted to collect the user data
  # directly) and merged with the existing data.
  # This function could be sped up considerably by using a single RPC request
  # if the container supports it.
  def fetch_gift_givers_and_friends(oid, gifts, c)
    people = {}
    ids = gifts.collect {|g| [g.sent_by, g.received_by]}.flatten.uniq
    ids.each do |id|
      begin
        r = OpenSocial::FetchPersonRequest.new(c, id)
        people[id] = r.send
      rescue OpenSocial::AuthException
      end
    end
    friends = OpenSocial::FetchPeopleRequest.new(c, oid).send
    return people.merge(friends)
  end


  # Looks up the consumer secret paired with the given consumer key and
  # hands them off to the client library authentication.
  def check_signature

    is_valid_request = false

    if params[:oauth_signature_method] == 'HMAC-SHA1'
      # 署名方式が HMAC-SHA1 
      key = params[:oauth_consumer_key]
      is_valid_request = validate(key, KEYS[key][:secret], {:signature_method => 'HMAC-SHA1'})
    elsif params[:oauth_signature_method] == 'RSA-SHA1'
      # 署名方式が RSA-SHA1
      # RSA-SHA1 では 公開鍵のみ必要なので validate メソッドの 第1引数は nil. 公開鍵は第2引数に指定する )
      is_valid_request = validate(nil, CERT, {:signature_method => 'RSA-SHA1'})
    end

    unless is_valid_request
      render :text => '401 Unauthorized', :status => :unauthorized
    end

  end


  # opensocial client の validate メソッドを用いずに新たに作成.
  def validate(key = CONSUMER_KEY, secret = CONSUMER_SECRET, options={})

    consumer = OAuth::Consumer.new(key, secret, options)
    begin
      signature = OAuth::Signature.build(request) do
        [nil, consumer.secret]
      end
      pass = signature.verify
    rescue OAuth::Signature::UnknownSignatureMethod => e
      logger.error 'An unknown signature method was supplied: ' + e.to_s
    end
    return pass
  end


end

以上で、署名方式 RSA-SHA1 を用いたリクエストが受け取れるようになります。

が!

これだけではアプリケーションとしては動作しません。

何故かというと、index メソッド内にてユーザ情報を取得するために OpenSocialコンテナ(Orkut)へリクエストを送信するための Connection クラスのオブジェクトを作成しているのですが、そこで HMAC-SHA1 のデータが必要となるからです。

外部サーバ -> OpenSocialコンテナ(Orkut) へのリクエストには HMAC-SHA1 が必須となっていますので、結局前回のブログと同様に consumer key, consumer secret が必要になります。

それぞれを取得したら、下記のようにKEY定数を変更してます。

KEYS = {
    'orkut.com' => {
      :secret => '<CONSUMER SECRET>',
      :outgoing_key => 'orkut.com:<CONSUMER KEY>',
      :container => OpenSocial::Connection::ORKUT
    }

これにてアプリケーションとして動作します。

外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストが必要ないのであれば RSA-SHA1 を用いるのが良いと思いますが、外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストも必要だとすると 両方向のリクエストの署名方式に HMAC-SHA1 を用いるのが楽でいいかもしれません。

よういちろうさんが執筆された 「OpenSocial入門 ソーシャルアプリケーションの実践開発」が 2009年1月24日に発売されるとのことですので楽しみにしている今日この頃ですOpenSocialを用いたアプリ開発をされる方には必須となるのではないでしょうか。

速攻で作る OpenSocialアプリ

新年明けましておめでとうございます。


mixiアプリも採用の Open Social



日本最大のSNSである mixiが mixiアプリ にOpen Socialを採用したことにより、今年2009年は他社のサービスでもOpenSocialを採用するところが一気に増えてくるではないでしょうか。

そこで弊社Banana Systemsが2009年最初にお届けする記事は「速攻で作るOpenSocialアプリ」です。

OpenSocialの詳細につきましては、オフィシャル・ページ にて説明されていますのでここでは割愛します。

まず前提として、

Orkut用のソーシャル・アプリ

  今現在 mixiアプリはまだβ版であり一般には公開されていませんので、

  GoogleのSNSであるOrkutにてソーシャル・アプリを開発します。

自社サーバからデータの取得、保存

  HTML, Javascriptのみを用いたシンプルなソーシャル・アプリではなく、

  実際のサービスを考えた時には外部(自社)サーバからコンテンツ、

  データの取得、また逆に保存を行いますので、↓のような形式とします。

  外部サーバ <-> Orkut(OpenSocialコンテナ) <-> ソーシャル・アプリ



OpenSocial Client Libraryの使用

  2008年12月17日に OpenSocial Blog にてPHP, Java, Ruby, Python用のOpenSocial Client Library が発表されました。

  このライブラリはサーバ間(外部サーバ <-> OpenSocialコンテナ)通信の複雑なやり取りを隠ぺいしてくれます。

  Ruby用のものには Railsアプリのサンプルが入っていますので、今回はそのRailsアプリを動作させるまでを説明します。


OpenSocialアプリの構築



それでは始めていきましょう!



Getting started with the Ruby Client Library に大まかな説明がされているので、それに沿って進めていきますが、Orkut(OpenSocialコンテナ)から外部サーバへ通信する際に必要となるSigned 通信の設定方法が記述されていなかったり、migration
ファイルに誤りがありそのままでは動作しませんので、補足しながら説明していきます。



1. 必要なライブラリのインストール



 # gem install oauth json mocha rails



2. opensocial ライブラリのダウンロードとインストール



 ダウンロード ページ からopensocial-X.X.X.gem をダウンロードします。



 ダウンロードしたgemをインストールします。



 # gem install opensocial-X.X.X.gem



3. サンプルの Railsアプリのダウンロード



 ダウンロード ページ から gifts_sample_X.X.zip をダウンロードします。



 ダウンロードしたzipファイルを展開すると gifts_sampleディレクトリ(Railsアプリのトップ)が作成されますので、Railsアプリを設置する適切なディレクトリに移動してください。



4. DB設定



 GIFT_SAMPLE/config/database.ymlの設定を適切なものに変更し、DBにデータベースを作成してください。

 ※GIFT_SAMPLE は 当Railsアプリのトップディレクトリとします。



今回はMySQLを使用しました。

※database, username, password はサンプルですので適宜変更してください。


development:
  adapter: mysql
  encoding: utf8
  database: gifts_development
  pool: 5
  username: banana
  password: banana
  host: localhost

test:
  adapter: mysql
  encoding: utf8
  database: gifts_test
  pool: 5
  username: banana
  password: banana
  host: localhost

production:
  adapter: mysql
  encoding: utf8
  database: gifts_production
  pool: 5
  username: banana
  password: banana
  host: localhost

MySQL コンソールにて ( GRANT文は適宜変更してください )

> GRANT ALL PRIVILEGES ON *.* TO banana@localhost IDENTIFIED BY 'banana' WITH GRANT OPTION;
> flush privileges;
> create gifts_development DEFAULT CHARSET=utf8;
> create gifts_test DEFAULT CHARSET=utf8;
> create gifts_production DEFAULT CHARSET=utf8;

5. migrationファイルの修正



 GIFT_SAMPLE/config/20081201192139_create_gifts.rb に note カラムを追加します。

class CreateGifts < ActiveRecord::Migration
  def self.up
    create_table :gifts do |t|

      t.integer :gift_name_id
      t.string :sent_by
      t.string :received_by
      t.boolean :viewed
      
      # note カラムの追加
      t.string :note
      
      t.timestamps
    end
  end

  def self.down
    drop_table :gifts
  end
end

※通常はmigrationファイルを直接編集せずに ruby script/generate migration AddNoteToGift note:string コマンドにて新しい migrationファイルを作成しますが、DBにまだテーブルを作成していないので今回は migration ファイルを直接編集しました。↓の migration ファイルも同様に直接編集しています。


 GIFT_SAMPLE/config/20081201203248_create_gift_names.rb に ギフトを追加します。

 ※ Gift アプリはギフトを友達に贈ったりするものなのですが、ギフトはDBに保存しておかないといけません。

class CreateGiftNames < ActiveRecord::Migration
  def self.up
    create_table :gift_names do |t|
      t.string :name
      t.timestamps
    end
    
    # ギフトの追加
    
    GiftName.create(:id => 1, :name => 'book')
    GiftName.create(:id => 2, :name => 'coffee')
    GiftName.create(:id => 3, :name => 'candy')
    GiftName.create(:id => 4, :name => 'icecream')
    
    
  end

  def self.down
    drop_table :gift_names
  end
end

6. DBにテーブル作成



 Gift sample アプリのトップディレクトリにて

 # rake migrate



7. サーバの起動



 Gift sample アプリのトップディレクトリにて

 # ruby script/server



 上記コマンドにより、本アプリは http://www.example.com/にて動作しているとし以降の説明を進めます。


OrkutにてGiftsアプリのインストール



1. Orkutにアカウントのない方は、ここから アカウントを作成してください。



2. デベロッパー サンドボックスへの登録



 Orkutにてサンプルのアプリをインストールするには、サンドボックスに登録する必要があります。



 Orkutにログイン後、ここから 登録します。



 ※以前は登録が承認されるまでに数日要しましたが、現在はすぐに完了します。



3. Giftsアプリのインストール



 サイドメニューのアプリの編集をクリックします。



orkut_sidemenu_1













 URLに本Giftsアプリの下記URLを入力し、「アプリケーションを追加」ボタンをクリックします。



 http://www.example.com/gifts.xml



orkut_add_sample_app_1












 アプリケーション追加をクリックします。


orkut_add_sample_app_2





















 これでGiftsアプリのサンドボックスへのインストールは完了です。


外部サーバにてOrkut(OpenSocialコンテナ)からの署名付きのリクエストを受け付ける



 Gifts アプリでは、Orkutと外部サーバ間にてデータの改ざんが行われないように、Orkutから外部サーバへの通信には署名付けられるように実装されています。

 (GIFT_SAMPLE/public/gifts.xml の 13行目辺り params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED; )

 実サービスにおいても、署名付きリクエストは必須となるでしょう。


追記: 2009-01-10 05:30 (コメント参照) ———
また、署名方式には HMAC-SHA1 を用いるように指定されています。
(GIFT_SAMPLE/public/gifts.xml の 14行目辺り params["OAUTH_SERVICE_NAME"] = “HMAC”; )

追記ここまで ————

この署名方式では Consumer Key と Consumer Secret Key が必要となります。

1. Consumer Key と Consumer Secret Key の取得



 Orkutでは、Consumer Key と Consumer Secret Keyを発行する際に、本アプリの所有者があなたか確認するためにトークンを発行します。



 下記ページにGiftsアプリのURL( http://www.example.com/gifts.xml )を入力し、「submit」ボタンをクリックします。

 https://www.google.com/gadgets/directory/verify



 すると、 <!– ALm6fM2iKDPf9qY5 … gMjzbD1cwDBifBX5w== –> といったトークンが発行されます。

 ※ブラウザは閉じずにそのままにしておいてください。



 GIFT_SAMPLE/public/gifts.xml の Content タグ内(CDATA開始タグとscriptタグの間)に発行されたトークンを挿入し、保存します。

 

<?xml version="1.0" encoding="UTF-8"?>
<Module>
  <ModulePrefs title="Gifts">
	  <Require feature="opensocial-0.8" />
	  <Require feature="dynamic-height"/>
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
    <!-- ALm6fM2iKDPf9qY5 ... gMjzbD1cwDBifBX5w== -->
	    <script>
  	    // Sends a signed makeRequest to a specified remote server to load the iframe
  	    function sendSignedRequest(server) {

 さきほどのページにて「Verify」ボタンをクリックします。

 Giftsアプリの所有者があなたであることが確認されると、下記のように Consumer Key と Consumer Secret Key が発行されます。



OAuth Consumer Key: 294828716828

OAuth Consumer Secret: xw/zDlw3y4p4yRFZo2mCQCID



2. Controller の修正



 Gifts アプリの gifts controller にて、署名付きリクエストを受け付けられるようにします。



 GIFT_SAMPLE/app/controllers/gifts_controller.rb を開き、KEYS定数に先ほどのConsumer KeyとConsumer Secretを追加します。

  # Declares the keys for your app on different containers. The index is the
  # incoming consumer key sent in the signed makeRequest from the gadget.
  # These are sample gadget credentials and should be replaced with the HMAC
  # keys granted to your own gadget.
  KEYS = {
    # ↓追加
    
    '294828716828' => {
      :secret => 'xw/zDlw3y4p4yRFZo2mCQCID',
      :outgoing_key => 'orkut.com:294828716828',
      :container => OpenSocial::Connection::ORKUT
    },
    
    # ↓削除してしまってかまいません
    '649048920060' => {
      :secret => '2rKmqUigMbFqK783szqzzOky',
      :outgoing_key => 'orkut.com:649048920060',
      :container => OpenSocial::Connection::ORKUT
    },
    'http://opensocial-resources.googlecode.com/svn/samples/rest_rpc/sample.xml' => {
      :secret => '6a838d107daf4d09b7d446422f5e7a81',
      :outgoing_key => 'http://opensocial-resources.googlecode.com/svn/samples/rest_rpc/sample.xml',
      :container => OpenSocial::Connection::MYSPACE
    }
  }

 KESY定数はHashになっています。HashのKeyに先ほどの Consumer Key を指定します。そのkeyのvalueもHashになっていますので、secretには先ほどのConsumer Secretを指定します。また、outgoing_keyには orkut.com: に続けて再度 Consumer Key を指定します。



 SERVER 定数も変更します。

 

# Declares where your application lives. Points to the page where users are
  # redirected after loading the iframe.
  SERVER = 'http://www.example.com/'

 これにて署名付きリクエストが受け付けられるようになります。



 Giftsアプリが署名付きリクエストを外部サーバに送信する(実際には、リクエストを送信するのはOpenSocialコンテナ(今回はOrkutのサーバ))のはユーザが本アプリにアクセスした初めの一度だけです。

 これは Giftsアプリがコンテンツをiframeにて表示するからです。



 よって、iframe内にて表示するコンテンツのURLを指定する必要があります。



 GIFT_SAMPLE/public/gifts.xml を開いて、30行目辺りの sendSignedRequest メソッドへの引数を下記のように gifts controller の iframe アクションへのURLに変更します。

 

    sendSignedRequest('http://www.example.com/gifts/iframe');

 最後にサーバを再起動して終了です。



 今回は OpenSocialコンテナ(Orkut) -> 外部サーバへの一方的な通信でしたが、OAuthを用いて外部サーバ -> OpenSocialコンテナへの通信も可能となります。

 mixi では mixi connect がそれに当ります。



 今回の記事のタイトルが「速攻で作る OpenSocialアプリ」でありながら全くもって速攻では終わりませんでしたが、実サービスにて必須となるであろう署名付きリクエストの受付方法を示すことができましたのでご参考になれば幸いです。


 
 本年もBananan Systemsをどうぞ宜しくお願い申し上げます。