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@chromium.org>
23 */
24
25session_start();
26
27require_once 'lib/oauth/OAuth.php';
28require_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 */
39class 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
118try {
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
143if (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