partuzaでAdd applicationsからガジェットxmlのURLを登録しても、ガジェットが表示されない!

これは、oauthではなく、shindigとpartuzaの間でやりとりしている、stっていうセキュリティトークンのためのPOSTパラメータをどうにかしてやれば解決する問題。

それでは、partuzaのガジェット登録、表示までのコードをshindigを含めて追ってみます。

Add applicationsは、partuzaの、Application/Models/applications/applications.phpの、load_get_application()というメソッドを実行していて…

まず、DBにガジェットがまだ登録されてないから、この戻り値は空。

$res = $db->query("select * from applications where url = '$url' and modified > $time");

このレスポンスも得られてないため、

$response = $this->fetch_gadget_metadata($app_url);

このエラーに来ている。

$error = 'An error occured while retrieving the gadget information';

で、上記のレスポンスを得る、fetch_gadget_metadata()というメソッドの中は、このようにshindigにガジェットxmlのURLを含むPOSTデータを送ってる。

  private function fetch_gadget_metadata($app_url) {
// debug
//echo 'fetch_gadget_metadata : $app_url -> '.$app_url."
"; //echo 'fetch_gadget_metadata : gadget_server metadata URL -> '.PartuzaConfig::get('gadget_server') . '/gadgets/metadata'; echo "
"; $request = json_encode(array( 'context' => array('country' => 'US', 'language' => 'en', 'view' => 'default', 'container' => 'partuza'), 'gadgets' => array(array('url' => $app_url, 'moduleId' => '1')))); // debug //echo 'fetch_gadget_metadata : urlencode $request -> '.urlencode($request)."
"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, PartuzaConfig::get('gadget_server') . '/gadgets/metadata'); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_MAXREDIRS, 10); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4); curl_setopt($ch, CURLOPT_TIMEOUT, 20); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, 'request=' . urlencode($request)); $content = @curl_exec($ch); // debug //echo 'fetch_gadget_metadata : $content -> '.$content."
"; return json_decode($content); }

各所にデバッグ出力してみている。

shindigの/gadgets/metadataというURLにアクセスしているようなので、shindigのindex.phpを覗いてみる。

ここで、servlet_mapという値をconfig/container.phpからとっているので、

$configServletMap = Config::get('servlet_map');

config/container.phpは、

'servlet_map' => array(
   '/container' => 'ContentFilesServlet',
   '/samplecontainer' => 'ContentFilesServlet',
   '/gadgets/resources' => 'ResourcesFilesServlet',
   '/gadgets/js' => 'JsServlet',
   '/gadgets/proxy' => 'ProxyServlet',
   '/gadgets/makeRequest' => 'MakeRequestServlet',
   '/gadgets/ifr' => 'GadgetRenderingServlet',
   '/gadgets/metadata' => 'MetadataServlet',
   '/gadgets/oauthcallback' => 'OAuthCallbackServlet',
   '/gadgets/api/rpc' => 'JsonRpcServlet',
   '/gadgets/api/rest' => 'DataServiceServlet',
   '/social/rest' => 'DataServiceServlet',
   '/social/rpc' => 'CompatibilityJsonRpcServlet',
   '/rpc' => 'JsonRpcServlet',
   '/public.crt' => 'CertServlet',
   '/public.cer' => 'CertServlet',
 ),

MetadataServletというクラスを調べればよい。

src/gadgets/servlet/MetadataServlet.phpのdoPost()というメソッドの中で、

        $handler = new MetadataHandler();
        $response = $handler->process($request);
        echo json_encode(array('gadgets' => $response));

このMetadataHandler()の中が気になる。

src/gadgets/MetadataHandler.phpの中を覗くと、process()メソッドの中で、

$token = $this->getSecurityToken();

をやっていて、

すぐ下にある、getSecurityToken()メソッドの中で、色々とデバッグ出力してみると、

  private function getSecurityToken() {
    $token = isset($_POST['st']) ? $_POST['st'] : (isset($_GET['st']) ? $_GET['st'] : '');
// debug
//var_dump($token); exit;
    if (empty($token)) {
      if (Config::get('allow_anonymous_token')) {
        // no security token, continue anonymously, remeber to check
        // for private profiles etc in your code so their not publicly
        // accessable to anoymous users! Anonymous == owner = viewer = appId = modId = 0
        // create token with 0 values, no gadget url, no domain and 0 duration
        $gadgetSigner = Config::get('security_token');
// debug
//var_dump($gadgetSigner); exit;
//var_dump(SecurityToken::$ANONYMOUS); exit;
// -> src/common/SecurityToken.phpで、SecurityToken::$ANONYMOUSに-1を格納
        return new $gadgetSigner(null, 0, SecurityToken::$ANONYMOUS, SecurityToken::$ANONYMOUS, 0, '', '', 0, Config::get('container_id'));
      } else {
        return null;
      }
    }
    $gadgetSigner = Config::get('security_token_signer');
// debug
var_dump($gadgetSigner); exit;
    $gadgetSigner = new $gadgetSigner();
    return $gadgetSigner->createToken($token);
  }

ここで、$_POST['st']とあって、このstというパラメータがpartuza側から渡されていないという問題がある。

partuzaのApplication/Models/applications/applications.phpの、この行を

curl_setopt($ch, CURLOPT_POSTFIELDS, 'request=' . urlencode($request));

以下のようにstパラメータをPOSTするように書き換えておく。

curl_setopt($ch, CURLOPT_POSTFIELDS, 'request=' . urlencode($request) . 'st=o:v:a:d:u:m:c');

で、

src/gadgets/MetadataHandler.phpにコード読みを戻して、

どうやら、$gadgetSignerにBasicSecurityTokenDecoderが入ってくるので、その名前のクラスを調べてみる。

src/common/sample/BasicSecurityTokenDecoder.phpを覗くと、見事にst=o:v:a:d:u:m:cという記述があったため、上記のように行を追加したのだった。
ただし、コレだけでは上手くいかないので、これ以上は各自工夫してほしい。
あと少しデバッグすればゴールなんで!

  public function createToken($stringToken) {
    if (empty($stringToken) && ! empty($_GET['authz'])) {
// debug
//echo "check1 INVALID_GADGET_TOKEN";
      throw new GadgetException('INVALID_GADGET_TOKEN');
    }
    try {
      //TODO remove this once we have a better way to generate a fake token
      // in the example files
      if (Config::get('allow_plaintext_token') && count(explode(':', $stringToken)) >= 7) {
        //Parses the security token in the form st=o:v:a:d:u:m:c
            $tokens = $this->parseToken($stringToken);

        return new BasicSecurityToken(null, null, urldecode($tokens[$this->OWNER_INDEX]), urldecode($tokens[$this->VIEWER_INDEX]), urldecode($tokens[$this->APP_ID_INDEX]), urldecode($tokens[$this->DOMAIN_INDEX]), urldecode($tokens[$this->APP_URL_INDEX]), urldecode($tokens[$this->MODULE_ID_INDEX]), urldecode($tokens[$this->CONTAINER_INDEX]));
      } else {
        return BasicSecurityToken::createFromToken($stringToken, Config::get('token_max_age'));
      }
    } catch (Exception $e) {
// debug
//echo "check2 INVALID_GADGET_TOKEN";
      throw new GadgetException('INVALID_GADGET_TOKEN');
    }
  }


無事、ガジェットの追加ができるようになると…

こんな感じで、partuzaにガジェットを追加していけます。

これで、自分のサーバでopensocialコンテナを動かしているということになり、ソーシャルアプリを開発する際に、opensocialコンテナの中までデバッグしていくような開発が可能になりました。

世界を大いに盛り上げるためのジョン・スミスをよろしく!