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