partuzaのガジェット追加のシナリオ

Partuza のソースを追うに当たって、2-legged oauthは納得したので後は実際のユーザーストーリーの流れに即してコードを追うことに。

ということで、手元にインストールして試してみたメモをベタバリ(インストールログじゃないです)、したものを徐々に編集しつつ。

Story 0. End User がガジェットを追加する。

エンドユーザーはGadget定義であるXMLファイルのURIを直接指定して自分のページにガジェットを追加します。iGoogle などだとURIを直接書くのではなくクリックして選べるようになってますが、要するにどのXMLファイルを使うか、ってことなので、partuzaだと、URIを直接指定できるようです。

とりあえず、試しにCouchDBでホストとしている以下のXML定義を読ませます。

http://webjourney.local/webjourney-default/_design/webjourney/gadgets/sticky.xml

このガジェット、HTML/Markdownで編集可能なメモガジェット用に使うつもりでいるのですが、、それはおいといて、次のような実装になっています。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Sticky">
    <Require feature="opensocial-0.9" />
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
    <div id="content"></div>
    <script type="text/javascript">
     var req = opensocial.newDataRequest();
     req.add(req.newFetchPersonRequest('VIEWER'), 'viewer');
     req.send(function(response) {
     document.getElementById("content").innerHTML =
       "Fetched Data: <br/>" +
       JSON.stringify(response.get('viewer').getData());
     });
    </script>
    ]]>
  </Content>
</Module>

newDataRequest() で 実際にviewer(End User)のデータをとってこよう、というわけです。

Story 1. Container(Partuza) がガジェットをDBに登録する。

End User がガジェットを追加すると、HTTP GET(ダサイ) で /profile/addapp?appUrl={追加するGagetのURI} がリクエストされ、Container側に制御が映ります。

Containerはまず、partuza/Application/Controllers/profile/profile.php から addapp($params) メソッドを呼び出します。ちなみに、$_SESSION["_id"] にはログインユーザーのIDが格納されているようです。

addapp から、partuza/Application/Models/applications.php の get_application($appUrl) に制御が映ります。application 情報を取得する、ということですが、このメソッド呼び出し、実はPartuza固有のキャッシュ機構が働いていて、実際に呼び出されるのは load_get_application($appUrl) のようです。ここで、実際に、$appUrl にアクセスしてXMLを取得し、その辺の情報をデータベースに格納します。appUrlの単位に登録されているようです。get_applicationは最終的に アプリケーションのIDを含むHashを返します。

続いて profile.php のaddapp($params) に制御が戻り、ここでは、上記のHashからIDを取得し、登録者のid ($_SESSION["id"]) とアプリケーションのID のひも付けをデータベースに保存するようです。

この作業が終わるとHomeビューやProfileビューにガジェットが表示されるようになるようです。


Story 2. ContainerがガジェットのIframeを用意する

HomeビューやProfileビューにガジェットが表示されるわけですが、実体はiframeで、Gadget Rendering Server(Shindig)のほうにリクエストを発行するiframeをセットします。

Homeビュー(http://partuza/home)にアクセスしたときは、partuza/Application/Controllers/home/home.php の index($params) メソッドを呼び出します。partuza/Application/Models/applications.php の get_person_applications($_SESSION["id"]) を呼び出してStory 1で登録済みのアプリケーション一覧を取得し partuza/Application/Views/profile/home.php を使ってレンダリングします。

個々のgadgetのテンプレートは Views/gadget/gadget.php にあるようです。詳細はおい説いて、ひとまず、次のようなiframeを生成することに注目します。

<iframe src="http://shindig/gadgets/ifr?synd=default&container=default&viewer=1&owner=1&aid=2&mid=3&country=US&lang=en&view=home&parent=http%3A%2F%2Fpartuza&st=TWF1QTRyWXhBY1Awd0pmazFWQWxaM0UxSUdvOFlHMmNXWFdZemhLJTJGcnlIOWp1VzFXMndQM0V3MTdwYm8wREolMkZBRmp1MmFkelVFUzRMZDNVSDZvdnFFM21seVpJN0xQaU9Na3ZOdmhRUjV3RVJSUjJMRlglMkZOcThSRUlmZ1c4ZlVEd0Q0SW1Eb20xJTJCcDhBSlRXUEtiWDJLdzJZbkdEOGVXaHV6VkdOM1VHSHg1UWJjZUxQeGh4cGVjdG9xSnM3SlYyTFZlUjFaVmduUk5Mc29MSkphYzVNUlJiR3B1aWszNWJWR3ltUEFaRm1FdldXYkU1T25SNE5HNXRmVTBycU9HY3NmZmZnJTNEJTNE&v=17de9532397c76282440e36585c36d73&url=http%3A%2F%2Fwebjourney.local%2Fwebjourney-default%2F_design%2Fwebjourney%2Fgadgets%2Fsticky.xml#rpctoken=1476043344
">
</iframe>

ちょっと長いですが、いろいろパラメーターがくっついています。このパラメーターはShindigの実装に依存するようで、OpenSocialの規格にはContainerからGadget Rendering Server のリクエストパラメーターのスペックなんてなさそうなんですが、どうなんでしょう(ちゃんと確認していない)。

ざっと表にすると以下のようになります。

key value
aid 2
container default
country US
lang en
mid 3
owner 1
parent http://partuza
st TWF1QTRyWXhBY1Awd0pmazFWQWxaM0UxSUdvOFlHMmNXWFdZemhLJTJGcnlIOWp1VzFXMndQM0V3MTdwYm8wREolMkZBRmp1MmFkelVFUzRMZDNVSDZvdnFFM21seVpJN0xQaU9Na3ZOdmhRUjV3RVJSUjJMRlglMkZOcThSRUlmZ1c4ZlVEd0Q0SW1Eb20xJTJCcDhBSlRXUEtiWDJLdzJZbkdEOGVXaHV6VkdOM1VHSHg1UWJjZUxQeGh4cGVjdG9xSnM3SlYyTFZlUjFaVmduUk5Mc29MSkphYzVNUlJiR3B1aWszNWJWR3ltUEFaRm1FdldXYkU1T25SNE5HNXRmVTBycU9HY3NmZmZnJTNEJTNE
synd default
url http://webjourney.local/webjourney-default/_design/webjourney/gadgets/sticky.xml
v 17de9532397c76282440e36585c36d73
view home
viewer 1

ここで重要となるのがstで渡されているSecurity Tokenと呼ばれるものです。

Shindigでは、GadgetとShindig間の通信の妥当性検証を行うために、stパラメータ値が使わ
れます。これは、

"OwnerID:ViewerID:AppID:Domain:AppUrl:ModuleID"

という文字列で構成されるのですが、このstパラメータ値は暗号化された文字列が送受信される
ことになります。

http://groups.google.co.jp/group/opensocial-japan/browse_thread/thread/5db84f55b970630a?pli=1

だそうです。何が妥当で何が妥当でないのかはともかくとして*1、このst値は、GadgetからShindigに対して発行されるデータリクエストに使われるようです。これまでOAuthが云々、と調べてきたのですが、なんという、Container -> Shindig(Renderer) -> Shindig(REST/JSONRPC API)感では、OAuthじゃなくて、このst値でアクセス制御をやっていたようです(涙目)。

つまり、3週間ほど前に書いた、

src/social/servlet/ApiServlet.php から。

(略)

    } // else, not a valid oauth request, so don't bother
http://d.hatena.ne.jp/yssk22/20090801#1249143606

で、この not a valid oauth request のほうで処理しているようなんですよ、これが。


ちなみに、Partuza のほうで、st値を生成していますが、

  $securityToken = BasicSecurityToken::createFromValues(
    isset($vars['person']['id']) ? $vars['person']['id'] : SecurityToken::$ANONYMOUS, // owner
    isset($_SESSION['id']) ? $_SESSION['id'] : SecurityToken::$ANONYMOUS,             // viewer
    $gadget['id'],                    // app id
    PartuzaConfig::get('container'),  // domain key, shindig will check for php/config/<domain>.php for container specific configuration
    urlencode($gadget['url']),        // app url
    $gadget['mod_id']                 // mod id
  );

具合で、st値を作成しています(BasicSecurityTokenの実装ではSHA1による暗号化(+base64encode)が行われていました)。

このst値は、実際には timestamp なども含まれていて、RESTService側でExpireチェックできるような仕組みになっていました。
つまり厳密には Gadget と Shindig 間ではなく Gadget Client(Partuza) と REST API (Partuza) 間の妥当性検証のために使われているように見えますが、どうなんだろう。Shindig が具体的に st の検証をしているようなコードは見あたらなかった、というか暗号化しなくてもいいし、してもshindig単体では復号できないし。

ということで、OAuthはひとまず後回しにして、このstの取り扱いの実装をすればいいんじゃん、ということに3週間ほどして気がついたorz

*1:おそらく、正当なContainerで生成されてたtokenであることを検証するのかと