1<?php 2/** 3 * Copyright (c) 2012 The Chromium Authors. All rights reserved. 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 * 7 * A "Hello world!" for the Chrome Web Store Licensing API, in PHP. This 8 * program logs the user in with Google's Federated Login API (OpenID), fetches 9 * their license state with OAuth, and prints one of these greetings as 10 * appropriate: 11 * 12 * 1. This user has FREE_TRIAL access to this application ( appId: 1 ) 13 * 2. This user has FULL access to this application ( appId: 1 ) 14 * 3. This user has NO access to this application ( appId: 1 ) 15 * 16 * This code makes use of a popup ui extension to the OpenID protocol. Instead 17 * of the user being redirected to the Google login page, a popup window opens 18 * to the login page, keeping the user on the main application page. See 19 * popuplib.js 20 * 21 * Eric Bidelman <ericbidelman@chromium.org> 22 */ 23 24session_start(); 25 26require_once 'lib/oauth/OAuth.php'; 27require_once 'lib/lightopenid/openid.php'; 28 29// Full URL of the current application is running under. 30$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') ? 'http' : 31 'https'; 32$selfUrl = "$scheme://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}"; 33 34 35/** 36 * Wrapper class to make calls to the Chrome Web Store License Server. 37 */ 38class LicenseServerClient { 39 40 const LICENSE_SERVER_HOST = 'https://www.googleapis.com'; 41 const CONSUMER_KEY = 'anonymous'; 42 const CONSUMER_SECRET = 'anonymous'; 43 const APP_ID = '1'; // Change to the correct id of your application. 44 const TOKEN = '[REPLACE THIS WITH YOUR OAUTH TOKEN]'; 45 const TOKEN_SECRET = '[REPLACE THIS WITH YOUR OAUTH TOKEN SECRET]'; 46 public $consumer; 47 public $token; 48 public $signatureMethod; 49 50 public function __construct() { 51 $this->consumer = new OAuthConsumer( 52 self::CONSUMER_KEY, self::CONSUMER_SECRET, NULL); 53 $this->token = new OAuthToken(self::TOKEN, self::TOKEN_SECRET); 54 $this->signatureMethod = new OAuthSignatureMethod_HMAC_SHA1(); 55 } 56 57 /** 58 * Makes an HTTP GET request to the specified URL. 59 * 60 * @param string $url Full URL of the resource to access 61 * @param string $request OAuthRequest containing the signed request to make. 62 * @param array $extraHeaders (optional) Array of headers. 63 * @param bool $returnResponseHeaders True if resp headers should be returned. 64 * @return string Response body from the server. 65 */ 66 protected function send_signed_get($request, $extraHeaders=NULL, 67 $returnRequestHeaders=false, 68 $returnResponseHeaders=false) { 69 $url = explode('?', $request->to_url()); 70 $curl = curl_init($url[0]); 71 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 72 curl_setopt($curl, CURLOPT_FAILONERROR, false); 73 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 74 75 // Return request headers in the response. 76 curl_setopt($curl, CURLINFO_HEADER_OUT, $returnRequestHeaders); 77 78 // Return response headers in the response? 79 if ($returnResponseHeaders) { 80 curl_setopt($curl, CURLOPT_HEADER, true); 81 } 82 83 $headers = array($request->to_header()); 84 if (is_array($extraHeaders)) { 85 $headers = array_merge($headers, $extraHeaders); 86 } 87 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 88 89 // Execute the request. If an error occurs fill the response body with it. 90 $response = curl_exec($curl); 91 if (!$response) { 92 $response = curl_error($curl); 93 } 94 95 // Add server's response headers to our response body 96 $response = curl_getinfo($curl, CURLINFO_HEADER_OUT) . $response; 97 98 curl_close($curl); 99 100 return $response; 101 } 102 103 public function checkLicense($userId) { 104 $url = self::LICENSE_SERVER_HOST . '/chromewebstore/v1/licenses/' . 105 self::APP_ID . '/' . urlencode($userId); 106 107 $request = OAuthRequest::from_consumer_and_token( 108 $this->consumer, $this->token, 'GET', $url, array()); 109 110 $request->sign_request($this->signatureMethod, $this->consumer, 111 $this->token); 112 113 return $this->send_signed_get($request); 114 } 115} 116 117try { 118 $openid = new LightOpenID(); 119 $userId = $openid->identity; 120 if (!isset($_GET['openid_mode'])) { 121 // This section performs the OpenID dance with the normal redirect. Use it 122 // if you want an alternative to the popup UI. 123 if (isset($_GET['login'])) { 124 $openid->identity = 'https://www.google.com/accounts/o8/id'; 125 $openid->required = array('namePerson/first', 'namePerson/last', 126 'contact/email'); 127 header('Location: ' . $openid->authUrl()); 128 } 129 } else if ($_GET['openid_mode'] == 'cancel') { 130 echo 'User has canceled authentication!'; 131 } else { 132 $userId = $openid->validate() ? $openid->identity : ''; 133 $_SESSION['userId'] = $userId; 134 $attributes = $openid->getAttributes(); 135 $_SESSION['attributes'] = $attributes; 136 } 137} catch(ErrorException $e) { 138 echo $e->getMessage(); 139 exit; 140} 141 142if (isset($_REQUEST['popup']) && !isset($_SESSION['redirect_to'])) { 143 $_SESSION['redirect_to'] = $selfUrl; 144 echo '<script type = "text/javascript">window.close();</script>'; 145 exit; 146} else if (isset($_SESSION['redirect_to'])) { 147 $redirect = $_SESSION['redirect_to']; 148 unset($_SESSION['redirect_to']); 149 header('Location: ' . $redirect); 150} else if (isset($_REQUEST['queryLicenseServer'])) { 151 $ls = new LicenseServerClient(); 152 echo $ls->checkLicense($_REQUEST['user_id']); 153 exit; 154} else if (isset($_GET['logout'])) { 155 unset($_SESSION['attributes']); 156 unset($_SESSION['userId']); 157 header('Location: ' . $selfUrl); 158} 159?> 160 161<!DOCTYPE html> 162<html> 163 <head> 164 <meta charset="utf-8" /> 165 <link href="main.css" type="text/css" rel="stylesheet" /> 166 <script type="text/javascript" src="popuplib.js"></script> 167 <script type="text/html" id="ls_tmpl"> 168 <div id="access-level"> 169 <% if (result.toLowerCase() == 'yes') { %> 170 This user has <span class="<%= accessLevel.toLowerCase() %>"><%= accessLevel %></span> access to this application ( appId: <%= appId %> ) 171 <% } else { %> 172 This user has <span class="<%= result.toLowerCase() %>"><%= result %></span> access to this application ( appId: <%= appId %> ) 173 <% } %> 174 </div> 175 </script> 176 </head> 177 <body> 178 <nav> 179 <?php if (!isset($_SESSION['userId'])): ?> 180 <a href="javascript:" onclick="openPopup(450, 500, this);">Sign in</a> 181 <?php else: ?> 182 <span>Welcome <?php echo @$_SESSION['attributes']['namePerson/first'] ?> <?php echo @$_SESSION['attributes']['namePerson/last'] ?> ( <?php echo $_SESSION['attributes']['contact/email'] ?> )</span> 183 <a href="?logout">Sign out</a> 184 <?php endif; ?> 185 </nav> 186 <?php if (isset($_SESSION['attributes'])): ?> 187 <div id="container"> 188 <form action="<?php echo "$selfUrl?queryLicenseServer" ?>" onsubmit="return queryLicenseServer(this);"> 189 <input type="hidden" id="user_id" name="user_id" value="<?php echo $_SESSION['userId'] ?>" /> 190 <input type="submit" value="Check user's access" /> 191 </form> 192 <div id="license-server-response"></div> 193 </div> 194 <?php endif; ?> 195 <script> 196 // Simple JavaScript Templating 197 // John Resig - http://ejohn.org/ - MIT Licensed 198 (function(){ 199 var cache = {}; 200 201 this.tmpl = function tmpl(str, data){ 202 // Figure out if we're getting a template, or if we need to 203 // load the template - and be sure to cache the result. 204 var fn = !/\W/.test(str) ? 205 cache[str] = cache[str] || 206 tmpl(document.getElementById(str).innerHTML) : 207 208 // Generate a reusable function that will serve as a template 209 // generator (and which will be cached). 210 new Function("obj", 211 "var p=[],print=function(){p.push.apply(p,arguments);};" + 212 213 // Introduce the data as local variables using with(){} 214 "with(obj){p.push('" + 215 216 // Convert the template into pure JavaScript 217 str 218 .replace(/[\r\t\n]/g, " ") 219 .split("<%").join("\t") 220 .replace(/((^|%>)[^\t]*)'/g, "$1\r") 221 .replace(/\t=(.*?)%>/g, "',$1,'") 222 .split("\t").join("');") 223 .split("%>").join("p.push('") 224 .split("\r").join("\\'") 225 + "');}return p.join('');"); 226 227 // Provide some basic currying to the user 228 return data ? fn( data ) : fn; 229 }; 230 })(); 231 232 function queryLicenseServer(form) { 233 var userId = form.user_id.value; 234 235 if (!userId) { 236 alert('No OpenID specified!'); 237 return false; 238 } 239 240 var req = new XMLHttpRequest(); 241 req.onreadystatechange = function(e) { 242 if (this.readyState == 4) { 243 var resp = JSON.parse(this.responseText); 244 var el = document.getElementById('license-server-response'); 245 if (resp.error) { 246 el.innerHTML = ['<div class="error">Error ', resp.error.code, 247 ': ', resp.error.message, '</div>'].join(''); 248 } else { 249 el.innerHTML = tmpl('ls_tmpl', resp); 250 } 251 } 252 }; 253 var url = 254 [form.action, '&user_id=', encodeURIComponent(userId)].join(''); 255 req.open('GET', url, true); 256 req.send(null); 257 258 return false; 259 } 260 261 function openPopup(w, h, link) { 262 var extensions = { 263 'openid.ns.ext1': 'http://openid.net/srv/ax/1.0', 264 'openid.ext1.mode': 'fetch_request', 265 'openid.ext1.type.email': 'http://axschema.org/contact/email', 266 'openid.ext1.type.first': 'http://axschema.org/namePerson/first', 267 'openid.ext1.type.last': 'http://axschema.org/namePerson/last', 268 'openid.ext1.required': 'email,first,last', 269 'openid.ui.icon': 'true' 270 }; 271 272 var googleOpener = popupManager.createPopupOpener({ 273 opEndpoint: 'https://www.google.com/accounts/o8/ud', 274 returnToUrl: '<?php echo "$selfUrl?popup=true" ?>', 275 onCloseHandler: function() { 276 window.location = '<?php echo $selfUrl ?>'; 277 }, 278 shouldEncodeUrls: false, 279 extensions: extensions 280 }); 281 link.parentNode.appendChild( 282 document.createTextNode('Authenticating...')); 283 link.parentNode.removeChild(link); 284 googleOpener.popup(w, h); 285 } 286 </script> 287 </body> 288</html> 289