3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

myThingsAdvent Calendar 2016

Day 9

Azure WebAppsでmyThings Developersのサンプルコードを動かしてみる。

Posted at

myThings Advent Calendar 2016 9日目です。(やばいもうすぐ10日になってしまう)

myThings DeveropersのハンズオンではBluemix上にサーバを構築しますが、用意するサーバはPHPが動けばいいので、Microsoft AzureのWebAppsをサーバにしてみました。簡単にできると思ったら以外に大変でした・・・・

##準備
・Microsoft Azure サブスクリプション
・Yahoo! Japan ID

##目標
こちらのチュートリアルStep3をAzure WebAppsで実行できるようにします。

##手順
###WebAppsの作成
・Azureポータルから新規>Web+モバイル>Web Appを選択します。
1.PNG

・アプリ名とリソースグループを入力して下の「作成」ボタンをクリックします。
2.PNG

・Web Appのデプロイが完了したら「拡張機能」>「拡張機能の追加」>「拡張機能の選択」の中から「Composer」を選択して「OK」をクリックします。つづいて「拡張機能の追加」の「OK」をクリックするとComposerが追加されます。
3.PNG

・Composerを追加した後は一度サイトを再起動します。

こちらのページの下の方にあるComposerの実行ファイル(composer.phar)をダウンロードしておきます。

・Yahoo! ID連携 SDK for PHPをインストールするためのcomposer.jsonを次の内容で作成します。

composer.json
{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/yahoojapan/yconnect-php-sdk"
        }
    ],
    "require": {
        "yahoojapan/yconnect-php-sdk": "dev-master"
    }
}

・先ほどダウンロードした「composer.phar」と「composer.json」をWebAppsにアップロードします。FTPクライアントを使うのが簡単でしょうか。アップロード先はサイトのルートディレクトリ(WWWROOT)になります。

・再びAzureポータルからWebAppsのコンソールを選択します。コンソールが起動したら「php composer.phar install」と入力して実行します。しばらくすると文字化けの状態ですがインストールが完了したメッセージが表示されます。
4.PNG

・myThingsDevelopersサイトで実装を選択して必要な内容をすべて入力します。
5.PNG

・サンプルコードを表示して次のファイルを一旦保存します。
  ・index.php
  ・callback.php
  ・geturl.php
  ・setting_callback.php
  ・customtrigger.php
  ・errorlist.php

・一部のファイルを変更します。

callback.php
<?php 
	require("vendor/autoload.php");
	use YConnect\Credential\ClientCredential;
	use YConnect\YConnectClient;

	// アプリケーションID, シークレット
	$client_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	$client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	// コールバックURL
	$redirect_uri  = "http://yoursite.azurewebsites.net/callback.php";

	$state="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	$nonce="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

	$cred = new ClientCredential( $client_id, $client_secret );
	$client = new YConnectClient( $cred );
	try {
		// Authorization Codeを取得
		$code_result = $client->getAuthorizationCode( $state );
		// Tokenエンドポイントにリクエスト
		$client->requestAccessToken( $redirect_uri, $code_result );
		// アクセストークン, リフレッシュトークンを取得
		$access_token  = $client->getAccessToken();
		$refresh_token = $client->getRefreshToken();
		// IDトークンを検証
		$verify_result = $client->verifyIdToken( $nonce );
		$id_token = $client->getIdToken();

		/*
		try {
			$verify_result = $client->verifyIdToken( $nonce );
			if ( $verify_result ) {
				$id_token = $client->getIdToken();
			} else {

			}
		} catch ( Exception $e ) {

		}
		*/

		// TODO : ここから下はリリースまでにトークンの保存方法の修正が必要です。↓↓↓↓↓↓
		// アクセストークン、リフレッシュトークンの保存
		save_token($access_token, $refresh_token, $id_token->user_id, $state);
		// TODO : ここから上はリリースまでにトークンの保存方法の修正が必要です。↑↑↑↑↑

	} catch ( TokenException $e ) {
		// 再度ログインして認可コードを発行してください
	}


	// TODO : ここから下はリリースまでにトークンの保存方法の修正が必要です。↓↓↓↓↓↓
	/*
	 * アクセストークン、リフレッシュの保存
	 * 
	 * アクセストークン($access_token)、リフレッシュトークン($refresh_token)は安全な場所に保管してください。
	 * ここでは仮実装として、mcrypt関数で暗号化してファイルに保存しています。
	 * 動作確認が完了したらファイルに保存したアクセストークン、リフレッシュトークンは必ず削除してください。
	 * アクセストークン、リフレッシュトークンを保存したファイルは/tmpに配置されます。
	 * 
	 * @param string $access_token アクセストークン
	 * @param string $refresh_token リフレッシュトークン
	 * @param string $key ユーザーを一意に判定できるキー
	 * @param string $state ランダムな文字列
	 * 
	 */
	function save_token($access_token, $refresh_token, $key, $state)
	{
		
		//echo 'AT: '.$access_token.' RT: '.$refresh_token.' key: '.$key.'ST: '.$state;

		
		
/*
		$td = mcrypt_module_open('des', '', 'cbc', '');
		$key = substr($key, 0, mcrypt_enc_get_key_size($td));
		$iv_size = mcrypt_enc_get_iv_size($td);
		$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
		echo "iv value: " .base64_encode( $iv)."\n";

		// アクセストークンの暗号化
		//$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);

		//$iv = substr($state, 0, $size);
		//$iv=mcrypt_create_iv($size, MCRYPT_RAND);
		

		exec("echo $iv > /tmp/access_token_iv_$key");
		$_SESSION['access_token_iv_'.$key]=$iv;
		
		$enc_token = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $access_token, MCRYPT_MODE_CBC, $iv);
		echo $enc_token;

		$base64_token = base64_encode( $enc_token );
		// 暗号化したアクセストークンをファイルに保存(本来は外部からアクセスできない環境で保管してください)
		exec("echo $base64_token > /tmp/access_token_$key");
		$_SESSION['access_token_'.$key]=$base64_token;

		// リフレッシュトークンの暗号化
		$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
		$iv = substr($state, 0, $size);
		exec("echo $iv > /tmp/refresh_token_iv_$key");
		$_SESSION['refresh_token_iv_'.$key]=$iv;
		

		$enc_token = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $refresh_token, MCRYPT_MODE_CBC, $iv)	;
		$base64_token = base64_encode( $enc_token );
		// 暗号化したリフレッシュトークンをファイルに保存(本来は外部からアクセスできない環境で保管してください)
		exec("echo $base64_token > /tmp/refresh_token_$key");
		$_SESSION['refresh_token_'.$key]=$base64_token;
		*/
		
		//echo "access_token : ".$access_token."\n";

		session_start();

		$enc_access_token=openssl_encrypt($access_token,'AES-128-CBC',$key);
		$_SESSION['access_token_'.$key]=$enc_access_token;

		$enc_refresh_token=openssl_encrypt($refresh_token,'AES-128-CBC',$key);
		$_SESSION['refresh_token_'.$key]=$enc_refresh_token;

		//echo "access : ".$enc_access_token;
		//echo "refresh : ".$enc_refresh_token;

	}

	// TODO : ここから上はリリースまでにトークンの保存方法の修正が必要です。↑↑↑↑↑
?>

<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div class="wrap">
<!-- 「Step.2 ユーザー設定画面のURL取得処理」を呼び出す -->
<form action="geturl.php" method="POST">
<input type="hidden" name="user_id" value="<?php echo $id_token->user_id ?>">
<input type="submit" value="ユーザー設定">
</form>
</div>
<style type="text/css">body{margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0}.wrap,body,html{width:100%;height:100%}.wrap{width:40pc;height:60px;position:absolute;top:50%;left:50%;margin-top:-30px;margin-left:-20pc}form{width:300px;float:left;margin:10px}input[type=submit]{width:100%;color:#76b729;border:solid 1px #76b729;text-align:center;display:block;font-size:14px;box-sizing:border-box;background-color:#fff;cursor:pointer;padding:0 4px;min-height:36px;line-height:36px;border-radius:6px;font-weight:700}input[type=submit]:hover{background-color:#eee;text-decoration:none}</style>
</body>
</html>
geturl.php
<?php
	require("vendor/autoload.php");
	use YConnect\Credential\ClientCredential;
	use YConnect\YConnectClient;
	
	// ユーザー設定画面URL取得APIエンドポイント
	$url = "https://mythings-developers.yahooapis.jp/v2/services/3be8d46d1715dd2af8111595a44743a0/mythings/3e3c5a118ee11fe5b9ee95e0258c6a25/url";

	// アプリケーションID
	$appid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	// シークレット
	$secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

	// アクセスしたユーザーのアクセストークン取得
	$user_id = $_POST["user_id"];

	$access_token = decrypt_token($user_id, 'access_token');
	//echo "access_token : ".$access_token;

	// ユーザー設定画面URL取得
	$ret = request_with_token($url , $access_token);
	$code = $ret["code"];

	// 401認証エラーが発生した場合はアクセストークンの有効期限が切れている
	if($code === 401){
		// アクセストークンの更新
		$access_token = refresh_access_token($appid, $secret, $user_id);

		// ユーザー設定画面URL取得
		$ret = request_with_token($url , $access_token);
	}

	$result = $ret["result"];
	$res=json_decode($result,true);
	//echo $res["url"];

	if(!empty($res["url"])){
		// ユーザー設定画面にリダイレクト
		header("Location: ".$res["url"]);
	}
	else{
		echo "ユーザー設定画面のURLを取得に失敗しました。<br>";
		echo "原因は以下の4つの可能性があります。<br>";
		echo "・テストユーザーに登録されていないユーザーでログインしている可能性があります。myThings Developersからテストユーザーを設定してください。<br>";
		echo "・IP登録したサーバー以外からアクセスしている可能性があります。サーバーのIPとmyThings Developersに登録しているIPを確認してください。(未設定の場合は関係ありません。)<br>";
		echo "・URLが間違っている可能性があります。サンプルコードの".'$url'."とmyThings Developersに表示されているAPIエンドポイントが一致している事を確認してください。<br>";
		echo "・アクセストークンが正しく取得出来ていない可能性があります。アクセストークンの保存(callback.php)、および、取得を確認してください。<br>";
	}

	/*
	 * APIエンドポイントへのリクエスト
	 * 
	 * トークンをヘッダーに設定してAPIエンドポイントへリクエストする
	 * 
	 * @param string $url APIエンドポイント
	 * @param string $access_token アクセストークン
	 * @param array $post POSTデータ
	 * 
	 * @return array $ret APIの戻り値とHTTPコード
	 */
	function request_with_token($url , $access_token, $post=array())
	{
		// HTTPヘッダーにアクセストークンを設定
		$http_header = array(
			"Content-Type: application/x-www-form-urlencoded; charset=utf-8",
			"Authorization: Bearer " . $access_token,
		);

		// curlのオプション設定
		$curl_setopt_array = array(
			CURLOPT_URL             => $url,
			CURLOPT_HTTPHEADER      => $http_header,
			CURLOPT_RETURNTRANSFER  => true,
			CURLOPT_FAILONERROR     => true,
			CURLOPT_TIMEOUT         => 10,
		);

		// POSTデータの設定
		if(!empty($post)){
			$curl_setopt_array[CURLOPT_POST] = true;
			$curl_setopt_array[CURLOPT_POSTFIELDS] = http_build_query($post);
		}

		// curlでAPIエントリーポイントへリクエストを実行
		$ch = curl_init();
		curl_setopt_array($ch, $curl_setopt_array);
		$result = curl_exec($ch);
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		curl_close($ch);

		$ret = array(
			"result" => $result,
			"code" => $code,
			);

		return $ret;
	}

	/*
	 * アクセストークンの更新
	 * 
	 * リフレッシュトークンを元にアクセストークンを更新する
	 * 
	 * @param string $appid アプリケーションID
	 * @param string $secret シークレット
	 * @param string $user_id ユーザー識別子
	 * 
	 * @return string $access_token アクセストークン
	 */
	function refresh_access_token($appid, $secret, $user_id)
	{
		$cred = new ClientCredential( $appid, $secret );
		$client = new YConnectClient( $cred );
		$access_token = '';
		try {
			// リフレッシュトークンの復号化
			$refresh_token = decrypt_token($user_id, 'refresh_token');
			// Tokenエンドポイントにリクエストしてアクセストークンを更新
			$client->refreshAccessToken( $refresh_token );
			$access_token = $client->getAccessToken();
		} catch ( YConnect\Exception\TokenException $te ) {
			// リフレッシュトークンが有効期限切れであるかチェック
			if( $te->invalidGrant() ) {
				// index.phpへアクセスし、認証から実施してください
				$index_url = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"] . "/index.php";
				header("Location: ".$index_url);
			}
		}

		return $access_token;
	}

	/*
	 * トークンの復号化
	 * 
	 * トークンを復号化する
	 * 
	 * @param string $user_id ユーザー識別子
	 * @param string $kind トークンの種別('access_token' or 'refresh_token')
	 * 
	 * @return string $token 復号化したトークン
	 */
	function decrypt_token($user_id, $kind)
	{
		/*
		// アクセストークンの復号化
		$iv = exec("cat /tmp/".$kind."_iv_".$user_id);
		$base64_token = exec("cat /tmp/".$kind."_".$user_id);

		$iv_test=$_SESSION[$kind.'_iv_'.$user_id];
		$base64_token_test=$_SESSION[$kind.'_'.$user_id];
		
		echo $iv_test;
		echo $base64_token_test;

		$enc_token = base64_decode( $base64_token );
		$dec_token = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $user_id, $enc_token, MCRYPT_MODE_CBC, $iv);
		$token = rtrim($dec_token);
		*/
		
		session_start();

		$enc_token=$_SESSION[$kind."_".$user_id];
		//echo "enc_token : ".$enc_token ."\n";
		//echo "key : ".$user_id."\n";

		$token=openssl_decrypt($enc_token,'AES-128-CBC',$user_id);
		//echo "token : ".$token;

		return $token;
	}
?>
customtrigger.php
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<?php
	require("vendor/autoload.php");
	use YConnect\Credential\ClientCredential;
	use YConnect\YConnectClient;

	// カスタムトリガー実行APIエンドポイント
	$url = "https://mythings-developers.yahooapis.jp/v2/services/3be8d46d1715dd2af8111595a44743a0/mythings/3e3c5a118ee11fe5b9ee95e0258c6a25/run";

	// アプリケーションID
	$appid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	// シークレット
	$secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

	// カスタムトリガーに設定したキー名と値を指定
	$post = array();
	$post_args = array();
	$post["entry"] = json_encode($post_args);

	// アクセスしたユーザーのアクセストークン取得
	$user_id = $_POST["user_id"];
	$access_token = decrypt_token($user_id, 'access_token');

	// カスタムトリガーの実行
	$ret = request_with_token($url , $access_token, $post);
	$code = $ret["code"];

	// 401認証エラーが発生した場合はアクセストークンの有効期限が切れている
	if($code === 401){
		// アクセストークンの更新
		$access_token = refresh_access_token($appid, $secret, $user_id);

		// カスタムトリガーの実行
		$ret = request_with_token($url , $access_token, $post);
	}

	$result = $ret["result"];

	if(empty($result) || !$result["flag"]){
		echo "カスタムトリガーの実行に失敗しました。";
		echo "原因は以下の5つの可能性があります。<br>";
		echo "・ユーザー設定が完了していない可能性があります。ステップ2で実装したgeturl.phpにアクセスし、ユーザー設定画面に「利用停止」ボタンが表示されているかを確認してください。 <br>";
		echo "・テストユーザーに登録されていないユーザーでログインしている可能性があります。ログインしているユーザーをmyThings Developersからテストユーザーを設定してください。<br>";
		echo "・IP登録したサーバー以外からアクセスしている可能性があります。サーバーのIPとmyThings Developersに登録しているIPを確認してください。(未設定の場合は関係ありません。)<br>";
		echo "・URLが間違っている可能性があります。サンプルコードの".'$url'."とmyThings Developersに表示されているAPIエントリーポイントが一致している事を確認してください<br>";
		echo "・アクセストークンが正しく取得出来ていない可能性があります。アクセストークンの保存(callback.php)、および、取得を確認してください。<br>";
	} else {
		echo "カスタムトリガーの実行リクエストを受け付けました。";
	}

	/*
	 * APIエンドポイントへのリクエスト
	 * 
	 * トークンをヘッダーに設定してAPIエンドポイントへリクエストする
	 * 
	 * @param string $url APIエンドポイント
	 * @param string $access_token アクセストークン
	 * @param array $post POSTデータ
	 * 
	 * @return array $ret APIの戻り値とHTTPコード
	 */
	function request_with_token($url , $access_token, $post=array())
	{
		// HTTPヘッダーにアクセストークンを設定
		$http_header = array(
			"Content-Type: application/x-www-form-urlencoded; charset=utf-8",
			"Authorization: Bearer " . $access_token,
		);

		// curlのオプション設定
		$curl_setopt_array = array(
			CURLOPT_URL             => $url,
			CURLOPT_HTTPHEADER      => $http_header,
			CURLOPT_RETURNTRANSFER  => true,
			CURLOPT_FAILONERROR     => true,
			CURLOPT_TIMEOUT         => 10,
		);

		// POSTデータの設定
		if(!empty($post)){
			$curl_setopt_array[CURLOPT_POST] = true;
			$curl_setopt_array[CURLOPT_POSTFIELDS] = http_build_query($post);
		}

		// curlでAPIエントリーポイントへリクエストを実行
		$ch = curl_init();
		curl_setopt_array($ch, $curl_setopt_array);
		$result = curl_exec($ch);
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		curl_close($ch);

		$ret = array(
			"result" => $result,
			"code" => $code,
			);

		return $ret;
	}

	/*
	 * アクセストークンの更新
	 * 
	 * リフレッシュトークンを元にアクセストークンを更新する
	 * 
	 * @param string $appid アプリケーションID
	 * @param string $secret シークレット
	 * @param string $user_id ユーザー識別子
	 * 
	 * @return string $access_token アクセストークン
	 */
	function refresh_access_token($appid, $secret, $user_id)
	{
		$cred = new ClientCredential( $appid, $secret );
		$client = new YConnectClient( $cred );
		$access_token = '';
		try {
			// リフレッシュトークンの復号化
			$refresh_token = decrypt_token($user_id, 'refresh_token');
			// Tokenエンドポイントにリクエストしてアクセストークンを更新
			$client->refreshAccessToken( $refresh_token );
			$access_token = $client->getAccessToken();
		} catch ( YConnect\Exception\TokenException $te ) {
			// リフレッシュトークンが有効期限切れであるかチェック
			if( $te->invalidGrant() ) {
				// index.phpへアクセスし、認証から実施してください
				$index_url = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"] . "/index.php";
				header("Location: ".$index_url);
			}
		}

		return $access_token;
	}

	/*
	 * トークンの復号化
	 * 
	 * トークンを復号化する
	 * 
	 * @param string $user_id ユーザー識別子
	 * @param string $kind トークンの種別('access_token' or 'refresh_token')
	 * 
	 * @return string $token 復号化したトークン
	 */
	function decrypt_token($user_id, $kind)
	{
		/*
		// アクセストークンの復号化
		$iv = exec("cat /tmp/".$kind."_iv_".$user_id);
		$base64_token = exec("cat /tmp/".$kind."_".$user_id);
		$enc_token = base64_decode( $base64_token );
		$dec_token = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $user_id, $enc_token, MCRYPT_MODE_CBC, $iv);
		$token = rtrim($dec_token);
		*/

		session_start();

		$enc_token=$_SESSION[$kind."_".$user_id];
		$token=openssl_decrypt($enc_token,'AES-128-CBC',$user_id);

		return $token;
	}
?>
</body>
</html>

・変更点はTokenを保存するのにSESSIONを使っている点と暗号化をOpenSSLで行うようにした点です。

・変更が終わったらすべてのファイルをサイトにアップロードします。

サイトにアクセスしてチュートリアルと同じように動作するか確認してください。

以上 Azure WebAppsでmyThings Devlopersチュートリアルを実行するため説明でした。

うう、公開するのが10日になってしまった・・・

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?