diff options
author | kurrik@chromium.org <kurrik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-20 23:27:30 +0000 |
---|---|---|
committer | kurrik@chromium.org <kurrik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-20 23:27:30 +0000 |
commit | 343cd23f33bee4216fcb85baba2d42f08428f48c (patch) | |
tree | 7563c903fc211019d4333a113dc87ae3deb12ba1 /chrome | |
parent | 0cb5c0cf32a111c7e32bd1fc03398660776d36fe (diff) | |
download | chromium_src-343cd23f33bee4216fcb85baba2d42f08428f48c.zip chromium_src-343cd23f33bee4216fcb85baba2d42f08428f48c.tar.gz chromium_src-343cd23f33bee4216fcb85baba2d42f08428f48c.tar.bz2 |
Adding Chrome Web Store PHP Hello World. BUG=None TEST=None
Review URL: http://codereview.chromium.org/3122030
Patch from Eric Bidelman <ericbidelman@chromium.org>.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56936 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
9 files changed, 2243 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE b/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE new file mode 100644 index 0000000..261a409 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE @@ -0,0 +1,97 @@ +====================================================================== +./popuplib.js (PopupManager): +====================================================================== + +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +====================================================================== +./index.php JS templating engine: +====================================================================== + +The MIT License + +Copyright (c) 2010 John Resig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +====================================================================== +./lib/oauth/ is licensed as follows + (Cf. ../lib/oauth/LICENSE.txt): +====================================================================== + +The MIT License + +Copyright (c) 2007 Andy Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +====================================================================== +./lib/lightopenid/ is licensed as follows + (Cf. ../lib/lightopenid/openid.php): +====================================================================== + +The MIT License + +Copyright (c) 2010, Mewp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/README b/chrome/common/extensions/docs/examples/apps/hello-php/README new file mode 100644 index 0000000..c6566e9 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/README @@ -0,0 +1,9 @@ +See the documentation at + http://code.google.com/chrome/webstore/docs/get_started.html +for instructions on how to use these files. + +"lib" contains the necessary OAuth and OpenID libraries to talk to the Chrome +Web Store Licensing API and Google's OpenID endpoint. + +To "index.php", replace APP_ID, TOKEN, and TOKEN_SECRET with your app's id, your +developer OAuth access token, and its token secret, respectively. diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/index.php b/chrome/common/extensions/docs/examples/apps/hello-php/index.php new file mode 100644 index 0000000..f145776 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/index.php @@ -0,0 +1,289 @@ +<?php +/** + * A "Hello world!" for the Chrome Web Store Licensing API, in PHP. This + * program logs the user in with Google's Federated Login API (OpenID), fetches + * their license state with OAuth, and prints one of these greetings as + * appropriate: + * + * 1. This user has FREE_TRIAL access to this application ( appId: 1 ) + * 2. This user has FULL access to this application ( appId: 1 ) + * 3. This user has NO access to this application ( appId: 1 ) + * + * This code makes use of a popup ui extension to the OpenID protocol. Instead + * of the user being redirected to the Google login page, a popup window opens + * to the login page, keeping the user on the main application page. See + * popuplib.js + * + * Copyright 2010 the Chromium Authors + * + * Use of this source code is governed by a BSD-style license that can be found + * in the "LICENSE" file. + * + * Eric Bidelman <ericbidelman@chromium.org> + */ + +session_start(); + +require_once 'lib/oauth/OAuth.php'; +require_once 'lib/lightopenid/openid.php'; + +// Full URL of the current application is running under. +$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') ? 'http' : + 'https'; +$selfUrl = "$scheme://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}"; + + +/** + * Wrapper class to make calls to the Chrome Web Store License Server. + */ +class LicenseServerClient { + + const LICENSE_SERVER_HOST = 'https://www.googleapis.com'; + const CONSUMER_KEY = 'anonymous'; + const CONSUMER_SECRET = 'anonymous'; + const APP_ID = '1'; // Change to the correct id of your application. + const TOKEN = '[REPLACE THIS WITH YOUR OAUTH TOKEN]'; + const TOKEN_SECRET = '[REPLACE THIS WITH YOUR OAUTH TOKEN SECRET]'; + public $consumer; + public $token; + public $signatureMethod; + + public function __construct() { + $this->consumer = new OAuthConsumer( + self::CONSUMER_KEY, self::CONSUMER_SECRET, NULL); + $this->token = new OAuthToken(self::TOKEN, self::TOKEN_SECRET); + $this->signatureMethod = new OAuthSignatureMethod_HMAC_SHA1(); + } + + /** + * Makes an HTTP GET request to the specified URL. + * + * @param string $url Full URL of the resource to access + * @param string $request OAuthRequest containing the signed request to make. + * @param array $extraHeaders (optional) Array of headers. + * @param bool $returnResponseHeaders True if resp headers should be returned. + * @return string Response body from the server. + */ + protected function send_signed_get($request, $extraHeaders=NULL, + $returnRequestHeaders=false, + $returnResponseHeaders=false) { + $url = explode('?', $request->to_url()); + $curl = curl_init($url[0]); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_FAILONERROR, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + + // Return request headers in the response. + curl_setopt($curl, CURLINFO_HEADER_OUT, $returnRequestHeaders); + + // Return response headers in the response? + if ($returnResponseHeaders) { + curl_setopt($curl, CURLOPT_HEADER, true); + } + + $headers = array($request->to_header()); + if (is_array($extraHeaders)) { + $headers = array_merge($headers, $extraHeaders); + } + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + + // Execute the request. If an error occurs fill the response body with it. + $response = curl_exec($curl); + if (!$response) { + $response = curl_error($curl); + } + + // Add server's response headers to our response body + $response = curl_getinfo($curl, CURLINFO_HEADER_OUT) . $response; + + curl_close($curl); + + return $response; + } + + public function checkLicense($userId) { + $url = self::LICENSE_SERVER_HOST . '/chromewebstore/v1/licenses/' . + self::APP_ID . '/' . urlencode($userId); + + $request = OAuthRequest::from_consumer_and_token( + $this->consumer, $this->token, 'GET', $url, array()); + + $request->sign_request($this->signatureMethod, $this->consumer, + $this->token); + + return $this->send_signed_get($request); + } +} + +try { + $openid = new LightOpenID(); + $userId = $openid->identity; + if (!isset($_GET['openid_mode'])) { + // This section performs the OpenID dance with the normal redirect. Use it + // if you want an alternative to the popup UI. + if (isset($_GET['login'])) { + $openid->identity = 'https://www.google.com/accounts/o8/id'; + $openid->required = array('namePerson/first', 'namePerson/last', + 'contact/email'); + header('Location: ' . $openid->authUrl()); + } + } else if ($_GET['openid_mode'] == 'cancel') { + echo 'User has canceled authentication!'; + } else { + $userId = $openid->validate() ? $openid->identity : ''; + $_SESSION['userId'] = $userId; + $attributes = $openid->getAttributes(); + $_SESSION['attributes'] = $attributes; + } +} catch(ErrorException $e) { + echo $e->getMessage(); + exit; +} + +if (isset($_REQUEST['popup']) && !isset($_SESSION['redirect_to'])) { + $_SESSION['redirect_to'] = $selfUrl; + echo '<script type = "text/javascript">window.close();</script>'; + exit; +} else if (isset($_SESSION['redirect_to'])) { + $redirect = $_SESSION['redirect_to']; + unset($_SESSION['redirect_to']); + header('Location: ' . $redirect); +} else if (isset($_REQUEST['queryLicenseServer'])) { + $ls = new LicenseServerClient(); + echo $ls->checkLicense($_REQUEST['user_id']); + exit; +} else if (isset($_GET['logout'])) { + unset($_SESSION['attributes']); + unset($_SESSION['userId']); + header('Location: ' . $selfUrl); +} +?> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <link href="main.css" type="text/css" rel="stylesheet" /> + <script type="text/javascript" src="popuplib.js"></script> + <script type="text/html" id="ls_tmpl"> + <div id="access-level"> + <% if (result.toLowerCase() == 'yes') { %> + This user has <span class="<%= accessLevel.toLowerCase() %>"><%= accessLevel %></span> access to this application ( appId: <%= appId %> ) + <% } else { %> + This user has <span class="<%= result.toLowerCase() %>"><%= result %></span> access to this application ( appId: <%= appId %> ) + <% } %> + </div> + </script> + </head> + <body> + <nav> + <?php if (!isset($_SESSION['userId'])): ?> + <a href="javascript:" onclick="openPopup(450, 500, this);">Sign in</a> + <?php else: ?> + <span>Welcome <?php echo @$_SESSION['attributes']['namePerson/first'] ?> <?php echo @$_SESSION['attributes']['namePerson/last'] ?> ( <?php echo $_SESSION['attributes']['contact/email'] ?> )</span> + <a href="?logout">Sign out</a> + <?php endif; ?> + </nav> + <?php if (isset($_SESSION['attributes'])): ?> + <div id="container"> + <form action="<?php echo "$selfUrl?queryLicenseServer" ?>" onsubmit="return queryLicenseServer(this);"> + <input type="hidden" id="user_id" name="user_id" value="<?php echo $_SESSION['userId'] ?>" /> + <input type="submit" value="Check user's access" /> + </form> + <div id="license-server-response"></div> + </div> + <?php endif; ?> + <script> + // Simple JavaScript Templating + // John Resig - http://ejohn.org/ - MIT Licensed + (function(){ + var cache = {}; + + this.tmpl = function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; + })(); + + function queryLicenseServer(form) { + var userId = form.user_id.value; + + if (!userId) { + alert('No OpenID specified!'); + return false; + } + + var req = new XMLHttpRequest(); + req.onreadystatechange = function(e) { + if (this.readyState == 4) { + var resp = JSON.parse(this.responseText); + var el = document.getElementById('license-server-response'); + if (resp.error) { + el.innerHTML = ['<div class="error">Error ', resp.error.code, + ': ', resp.error.message, '</div>'].join(''); + } else { + el.innerHTML = tmpl('ls_tmpl', resp); + } + } + }; + var url = + [form.action, '&user_id=', encodeURIComponent(userId)].join(''); + req.open('GET', url, true); + req.send(null); + + return false; + } + + function openPopup(w, h, link) { + var extensions = { + 'openid.ns.ext1': 'http://openid.net/srv/ax/1.0', + 'openid.ext1.mode': 'fetch_request', + 'openid.ext1.type.email': 'http://axschema.org/contact/email', + 'openid.ext1.type.first': 'http://axschema.org/namePerson/first', + 'openid.ext1.type.last': 'http://axschema.org/namePerson/last', + 'openid.ext1.required': 'email,first,last', + 'openid.ui.icon': 'true' + }; + + var googleOpener = popupManager.createPopupOpener({ + opEndpoint: 'https://www.google.com/accounts/o8/ud', + returnToUrl: '<?php echo "$selfUrl?popup=true" ?>', + onCloseHandler: function() { + window.location = '<?php echo $selfUrl ?>'; + }, + shouldEncodeUrls: false, + extensions: extensions + }); + link.parentNode.appendChild( + document.createTextNode('Authenticating...')); + link.parentNode.removeChild(link); + googleOpener.popup(w, h); + } + </script> + </body> +</html> diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php b/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php new file mode 100644 index 0000000..2f5b0ff --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php @@ -0,0 +1,563 @@ +<?php +/** + * This class provides a simple interface for OpenID (1.1 and 2.0) authentication. + * Supports Yadis discovery. + * The authentication process is stateless/dumb. + * + * Usage: + * Sign-on with OpenID is a two step process: + * Step one is authentication with the provider: + * <code> + * $openid = new LightOpenID; + * $openid->identity = 'ID supplied by user'; + * header('Location: ' . $openid->authUrl()); + * </code> + * The provider then sends various parameters via GET, one of them is openid_mode. + * Step two is verification: + * <code> + * if ($this->data['openid_mode']) { + * $openid = new LightOpenID; + * echo $openid->validate() ? 'Logged in.' : 'Failed'; + * } + * </code> + * + * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). + * The default values for those are: + * $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; + * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; + * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. + * + * AX and SREG extensions are supported. + * To use them, specify $openid->required and/or $openid->optional. + * These are arrays, with values being AX schema paths (the 'path' part of the URL). + * For example: + * $openid->required = array('namePerson/friendly', 'contact/email'); + * $openid->optional = array('namePerson/first'); + * If the server supports only SREG or OpenID 1.1, these are automaticaly + * mapped to SREG names, so that user doesn't have to know anything about the server. + * + * To get the values, use $openid->getAttributes(). + * + * + * The library depends on curl, and requires PHP 5. + * @author Mewp + * @copyright Copyright (c) 2010, Mewp + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ +class LightOpenID +{ + public $returnUrl + , $required = array() + , $optional = array(); + private $identity, $claimed_id; + protected $server, $version, $trustRoot, $aliases, $identifier_select = false + , $ax = false, $sreg = false, $data; + static protected $ax_to_sreg = array( + 'namePerson/friendly' => 'nickname', + 'contact/email' => 'email', + 'namePerson' => 'fullname', + 'birthDate' => 'dob', + 'person/gender' => 'gender', + 'contact/postalCode/home' => 'postcode', + 'contact/country/home' => 'country', + 'pref/language' => 'language', + 'pref/timezone' => 'timezone', + ); + + function __construct() + { + $this->trustRoot = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; + $this->returnUrl = $this->trustRoot . $_SERVER['REQUEST_URI']; + + if (!function_exists('curl_exec')) { + throw new ErrorException('Curl extension is required.'); + } + + $this->data = $_POST + $_GET; # OPs may send data as POST or GET. + } + + function __set($name, $value) + { + switch ($name) { + case 'identity': + if (strlen($value = trim($value))) { + if (preg_match('#^xri:/*#i', $value, $m)) { + $value = substr($value, strlen($m[0])); + } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { + $value = "http://$value"; + } + if (preg_match('#^https?://[^/]+$#i', $value, $m)) { + $value .= '/'; + } + } + $this->$name = $this->claimed_id = $value; + break; + case 'trustRoot': + case 'realm': + $this->trustRoot = trim($value); + } + } + + function __get($name) + { + switch ($name) { + case 'identity': + # We return claimed_id instead of identity, + # because the developer should see the claimed identifier, + # i.e. what he set as identity, not the op-local identifier (which is what we verify) + return $this->claimed_id; + case 'trustRoot': + case 'realm': + return $this->trustRoot; + } + } + + protected function request($url, $method='GET', $params=array()) + { + $params = http_build_query($params, '', '&'); + $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $params); + } elseif ($method == 'HEAD') { + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_NOBODY, true); + } else { + curl_setopt($curl, CURLOPT_HTTPGET, true); + } + $response = curl_exec($curl); + + if (curl_errno($curl)) { + throw new ErrorException(curl_error($curl), curl_errno($curl)); + } + + return $response; + } + + protected function build_url($url, $parts) + { + if (isset($url['query'], $parts['query'])) { + $parts['query'] = $url['query'] . '&' . $parts['query']; + } + + $url = $parts + $url; + $url = $url['scheme'] . '://' + . (empty($url['username'])?'' + :(empty($url['password'])? "{$url['username']}@" + :"{$url['username']}:{$url['password']}@")) + . $url['host'] + . (empty($url['port'])?'':":{$url['port']}") + . (empty($url['path'])?'':$url['path']) + . (empty($url['query'])?'':"?{$url['query']}") + . (empty($url['fragment'])?'':":{$url['fragment']}"); + return $url; + } + + /** + * Helper function used to scan for <meta>/<link> tags and extract information + * from them + */ + protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) + { + preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); + preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); + + $result = array_merge($matches1[1], $matches2[1]); + return empty($result)?false:$result[0]; + } + + /** + * Performs Yadis and HTML discovery. Normally not used. + * @param $url Identity URL. + * @return String OP Endpoint (i.e. OpenID provider address). + * @throws ErrorException + */ + function discover($url) + { + if (!$url) throw new ErrorException('No identity supplied.'); + # Use xri.net proxy to resolve i-name identities + if (!preg_match('#^https?:#', $url)) { + $url = "https://xri.net/$url"; + } + + # We save the original url in case of Yadis discovery failure. + # It can happen when we'll be lead to an XRDS document + # which does not have any OpenID2 services. + $originalUrl = $url; + + # A flag to disable yadis discovery in case of failure in headers. + $yadis = true; + + # We'll jump a maximum of 5 times, to avoid endless redirections. + for ($i = 0; $i < 5; $i ++) { + if ($yadis) { + $headers = explode("\n",$this->request($url, 'HEAD')); + + $next = false; + foreach ($headers as $header) { + if (preg_match('#X-XRDS-Location\s*:\s*(.*)#', $header, $m)) { + $url = $this->build_url(parse_url($url), parse_url(trim($m[1]))); + $next = true; + } + + if (preg_match('#Content-Type\s*:\s*application/xrds\+xml#i', $header)) { + # Found an XRDS document, now let's find the server, and optionally delegate. + $content = $this->request($url, 'GET'); + + # OpenID 2 + # We ignore it for MyOpenID, as it breaks sreg if using OpenID 2.0 + $ns = preg_quote('http://specs.openid.net/auth/2.0/'); + if (preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'(.*?)\s*</Type>(.*)</Service>#s', $content, $m)) { + $content = ' ' . $m[1] . $m[3]; # The space is added, so that strpos doesn't return 0. + if ($m[2] == 'server') $this->identifier_select = true; + + preg_match('#<URI.*?>(.*)</URI>#', $content, $server); + preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate); + if (empty($server)) { + return false; + } + # Does the server advertise support for either AX or SREG? + $this->ax = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>'); + $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') + || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); + + $server = $server[1]; + if (isset($delegate[2])) $this->identity = trim($delegate[2]); + $this->version = 2; + + $this->server = $server; + return $server; + } + + # OpenID 1.1 + $ns = preg_quote('http://openid.net/signon/1.1'); + if (preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'\s*</Type>(.*)</Service>#s', $content, $m)) { + $content = ' ' . $m[1] . $m[2]; + + preg_match('#<URI.*?>(.*)</URI>#', $content, $server); + preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate); + if (empty($server)) { + return false; + } + # AX can be used only with OpenID 2.0, so checking only SREG + $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') + || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); + + $server = $server[1]; + if (isset($delegate[1])) $this->identity = $delegate[1]; + $this->version = 1; + + $this->server = $server; + return $server; + } + + $next = true; + $yadis = false; + $url = $originalUrl; + $content = null; + break; + } + } + if ($next) continue; + + # There are no relevant information in headers, so we search the body. + $content = $this->request($url, 'GET'); + if ($location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'value')) { + $url = $this->build_url(parse_url($url), parse_url($location)); + continue; + } + } + + if (!$content) $content = $this->request($url, 'GET'); + + # At this point, the YADIS Discovery has failed, so we'll switch + # to openid2 HTML discovery, then fallback to openid 1.1 discovery. + $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); + $this->version = 2; + + if (!$server) { + # The same with openid 1.1 + $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); + $this->version = 1; + } + + if ($server) { + # We found an OpenID2 OP Endpoint + if ($delegate) { + # We have also found an OP-Local ID. + $this->identity = $delegate; + } + $this->server = $server; + return $server; + } + + throw new ErrorException('No servers found!'); + } + throw new ErrorException('Endless redirection!'); + } + + protected function sregParams() + { + $params = array(); + # We always use SREG 1.1, even if the server is advertising only support for 1.0. + # That's because it's fully backwards compatibile with 1.0, and some providers + # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com + $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; + if ($this->required) { + $params['openid.sreg.required'] = array(); + foreach ($this->required as $required) { + if (!isset(self::$ax_to_sreg[$required])) continue; + $params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; + } + $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); + } + + if ($this->optional) { + $params['openid.sreg.optional'] = array(); + foreach ($this->optional as $optional) { + if (!isset(self::$ax_to_sreg[$optional])) continue; + $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; + } + $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); + } + return $params; + } + protected function axParams() + { + $params = array(); + if ($this->required || $this->optional) { + $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; + $params['openid.ax.mode'] = 'fetch_request'; + $this->aliases = array(); + $counts = array(); + $required = array(); + $optional = array(); + foreach (array('required','optional') as $type) { + foreach ($this->$type as $alias => $field) { + if (is_int($alias)) $alias = strtr($field, '/', '_'); + $this->aliases[$alias] = 'http://axschema.org/' . $field; + if (empty($counts[$alias])) $counts[$alias] = 0; + $counts[$alias] += 1; + ${$type}[] = $alias; + } + } + foreach ($this->aliases as $alias => $ns) { + $params['openid.ax.type.' . $alias] = $ns; + } + foreach ($counts as $alias => $count) { + if ($count == 1) continue; + $params['openid.ax.count.' . $alias] = $count; + } + + # Don't send empty ax.requied and ax.if_available. + # Google and possibly other providers refuse to support ax when one of these is empty. + if($required) { + $params['openid.ax.required'] = implode(',', $required); + } + if($optional) { + $params['openid.ax.if_available'] = implode(',', $optional); + } + } + return $params; + } + + protected function authUrl_v1() + { + $returnUrl = $this->returnUrl; + # If we have an openid.delegate that is different from our claimed id, + # we need to somehow preserve the claimed id between requests. + # The simplest way is to just send it along with the return_to url. + if($this->identity != $this->claimed_id) { + $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; + } + + $params = array( + 'openid.return_to' => $returnUrl, + 'openid.mode' => 'checkid_setup', + 'openid.identity' => $this->identity, + 'openid.trust_root' => $this->trustRoot, + ) + $this->sregParams(); + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + protected function authUrl_v2($identifier_select) + { + $params = array( + 'openid.ns' => 'http://specs.openid.net/auth/2.0', + 'openid.mode' => 'checkid_setup', + 'openid.return_to' => $this->returnUrl, + 'openid.realm' => $this->trustRoot, + ); + if ($this->ax) { + $params += $this->axParams(); + } + if ($this->sreg) { + $params += $this->sregParams(); + } + if (!$this->ax && !$this->sreg) { + # If OP doesn't advertise either SREG, nor AX, let's send them both + # in worst case we don't get anything in return. + $params += $this->axParams() + $this->sregParams(); + } + + if ($identifier_select) { + $params['openid.identity'] = $params['openid.claimed_id'] + = 'http://specs.openid.net/auth/2.0/identifier_select'; + } else { + $params['openid.identity'] = $this->identity; + $params['openid.claimed_id'] = $this->claimed_id; + } + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + /** + * Returns authentication url. Usually, you want to redirect your user to it. + * @return String The authentication url. + * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1. + * @throws ErrorException + */ + function authUrl($identifier_select = null) + { + if (!$this->server) $this->discover($this->identity); + + if ($this->version == 2) { + if ($identifier_select === null) { + return $this->authUrl_v2($this->identifier_select); + } + return $this->authUrl_v2($identifier_select); + } + return $this->authUrl_v1(); + } + + /** + * Performs OpenID verification with the OP. + * @return Bool Whether the verification was successful. + * @throws ErrorException + */ + function validate() + { + $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; + $params = array( + 'openid.assoc_handle' => $this->data['openid_assoc_handle'], + 'openid.signed' => $this->data['openid_signed'], + 'openid.sig' => $this->data['openid_sig'], + ); + + if (isset($this->data['openid_op_endpoint'])) { + # We're dealing with an OpenID 2.0 server, so let's set an ns + # Even though we should know location of the endpoint, + # we still need to verify it by discovery, so $server is not set here + $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; + } + $server = $this->discover($this->data['openid_identity']); + + foreach (explode(',', $this->data['openid_signed']) as $item) { + # Checking whether magic_quotes_gpc is turned on, because + # the function may fail if it is. For example, when fetching + # AX namePerson, it might containg an apostrophe, which will be escaped. + # In such case, validation would fail, since we'd send different data than OP + # wants to verify. stripslashes() should solve that problem, but we can't + # use it when magic_quotes is off. + $value = $this->data['openid_' . str_replace('.','_',$item)]; + $params['openid.' . $item] = get_magic_quotes_gpc() ? stripslashes($value) : $value; + } + + $params['openid.mode'] = 'check_authentication'; + + $response = $this->request($server, 'POST', $params); + + return preg_match('/is_valid\s*:\s*true/i', $response); + } + protected function getAxAttributes() + { + $alias = null; + if (isset($this->data['openid_ns_ax']) + && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0' + ) { # It's the most likely case, so we'll check it before + $alias = 'ax'; + } else { + # 'ax' prefix is either undefined, or points to another extension, + # so we search for another prefix + foreach ($this->data as $key => $val) { + if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_' + && $val == 'http://openid.net/srv/ax/1.0' + ) { + $alias = substr($key, strlen('openid_ns_')); + break; + } + } + } + if (!$alias) { + # An alias for AX schema has not been found, + # so there is no AX data in the OP's response + return array(); + } + + foreach ($this->data as $key => $value) { + $keyMatch = 'openid_' . $alias . '_value_'; + if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { + continue; + } + $key = substr($key, strlen($keyMatch)); + if (!isset($this->data['openid_' . $alias . '_type_' . $key])) { + # OP is breaking the spec by returning a field without + # associated ns. This shouldn't happen, but it's better + # to check, than cause an E_NOTICE. + continue; + } + $key = substr($this->data['openid_' . $alias . '_type_' . $key], + strlen('http://axschema.org/')); + $attributes[$key] = $value; + } + # Found the AX attributes, so no need to scan for SREG. + return $attributes; + } + protected function getSregAttributes() + { + $attributes = array(); + $sreg_to_ax = array_flip(self::$ax_to_sreg); + foreach ($this->data as $key => $value) { + $keyMatch = 'openid_sreg_'; + if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { + continue; + } + $key = substr($key, strlen($keyMatch)); + if (!isset($sreg_to_ax[$key])) { + # The field name isn't part of the SREG spec, so we ignore it. + continue; + } + $attributes[$sreg_to_ax[$key]] = $value; + } + return $attributes; + } + /** + * Gets AX/SREG attributes provided by OP. should be used only after successful validaton. + * Note that it does not guarantee that any of the required/optional parameters will be present, + * or that there will be no other attributes besides those specified. + * In other words. OP may provide whatever information it wants to. + * * SREG names will be mapped to AX names. + * * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email' + * @see http://www.axschema.org/types/ + */ + function getAttributes() + { + $attributes; + if (isset($this->data['openid_ns']) + && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' + ) { # OpenID 2.0 + # We search for both AX and SREG attributes, with AX taking precedence. + return $this->getAxAttributes() + $this->getSregAttributes(); + } + return $this->getSregAttributes(); + } +} diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt new file mode 100644 index 0000000..b12ff1c --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt @@ -0,0 +1,15 @@ +== 2008.08.04 == +* Added LICENSE.txt file with MIT license, copyright owner is perhaps + dubious however. +== 2008.07.22 == +* Change to encoding to fix last change to encoding of spaces +== 2008.07.15 == +* Another change to encoding per + http://groups.google.com/group/oauth/browse_thread/thread/d39931d39b4af4bd +* A change to port handling to better deal with https and the like per + http://groups.google.com/group/oauth/browse_thread/thread/1b203a51d9590226 +* Fixed a small bug per + http://code.google.com/p/oauth/issues/detail?id=26 +* Added missing base_string debug info when using RSA-SHA1 +* Increased size of example endpoint input field and added note about + query strings diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt new file mode 100644 index 0000000..89f0591 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2007 Andy Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php new file mode 100644 index 0000000..e9c4bdf --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php @@ -0,0 +1,879 @@ +<?php +// vim: foldmethod=marker + +/* Generic exception class + */ +class OAuthException extends Exception { + // pass +} + +class OAuthConsumer { + public $key; + public $secret; + + function __construct($key, $secret, $callback_url=NULL) { + $this->key = $key; + $this->secret = $secret; + $this->callback_url = $callback_url; + } + + function __toString() { + return "OAuthConsumer[key=$this->key,secret=$this->secret]"; + } +} + +class OAuthToken { + // access tokens and request tokens + public $key; + public $secret; + + /** + * key = the token + * secret = the token secret + */ + function __construct($key, $secret) { + $this->key = $key; + $this->secret = $secret; + } + + /** + * generates the basic string serialization of a token that a server + * would respond to request_token and access_token calls with + */ + function to_string() { + return "oauth_token=" . + OAuthUtil::urlencode_rfc3986($this->key) . + "&oauth_token_secret=" . + OAuthUtil::urlencode_rfc3986($this->secret); + } + + function __toString() { + return $this->to_string(); + } +} + +/** + * A class for implementing a Signature Method + * See section 9 ("Signing Requests") in the spec + */ +abstract class OAuthSignatureMethod { + /** + * Needs to return the name of the Signature Method (ie HMAC-SHA1) + * @return string + */ + abstract public function get_name(); + + /** + * Build up the signature + * NOTE: The output of this function MUST NOT be urlencoded. + * the encoding is handled in OAuthRequest when the final + * request is serialized + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @return string + */ + abstract public function build_signature($request, $consumer, $token); + + /** + * Verifies that a given signature is correct + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @param string $signature + * @return bool + */ + public function check_signature($request, $consumer, $token, $signature) { + $built = $this->build_signature($request, $consumer, $token); + return $built == $signature; + } +} + +/** + * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] + * where the Signature Base String is the text and the key is the concatenated values (each first + * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' + * character (ASCII code 38) even if empty. + * - Chapter 9.2 ("HMAC-SHA1") + */ +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { + function get_name() { + return "HMAC-SHA1"; + } + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + return base64_encode(hash_hmac('sha1', $base_string, $key, true)); + } +} + +/** + * The PLAINTEXT method does not provide any security protection and SHOULD only be used + * over a secure channel such as HTTPS. It does not use the Signature Base String. + * - Chapter 9.4 ("PLAINTEXT") + */ +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { + public function get_name() { + return "PLAINTEXT"; + } + + /** + * oauth_signature is set to the concatenated encoded values of the Consumer Secret and + * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is + * empty. The result MUST be encoded again. + * - Chapter 9.4.1 ("Generating Signatures") + * + * Please note that the second encoding MUST NOT happen in the SignatureMethod, as + * OAuthRequest handles this! + */ + public function build_signature($request, $consumer, $token) { + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + $request->base_string = $key; + + return $key; + } +} + +/** + * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in + * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for + * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a + * verified way to the Service Provider, in a manner which is beyond the scope of this + * specification. + * - Chapter 9.3 ("RSA-SHA1") + */ +abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { + public function get_name() { + return "RSA-SHA1"; + } + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // (2) fetch via http using a url provided by the requester + // (3) some sort of specific discovery code based on request + // + // Either way should return a string representation of the certificate + protected abstract function fetch_public_cert(&$request); + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // + // Either way should return a string representation of the certificate + protected abstract function fetch_private_cert(&$request); + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + // Fetch the private key cert based on the request + $cert = $this->fetch_private_cert($request); + + // Pull the private key ID from the certificate + $privatekeyid = openssl_get_privatekey($cert); + + // Sign using the key + $ok = openssl_sign($base_string, $signature, $privatekeyid); + + // Release the key resource + openssl_free_key($privatekeyid); + + return base64_encode($signature); + } + + public function check_signature($request, $consumer, $token, $signature) { + $decoded_sig = base64_decode($signature); + + $base_string = $request->get_signature_base_string(); + + // Fetch the public key cert based on the request + $cert = $this->fetch_public_cert($request); + + // Pull the public key ID from the certificate + $publickeyid = openssl_get_publickey($cert); + + // Check the computed signature against the one passed in the query + $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); + + // Release the key resource + openssl_free_key($publickeyid); + + return $ok == 1; + } +} + +class OAuthRequest { + protected $parameters; + protected $http_method; + protected $http_url; + // for debug purposes + public $base_string; + public static $version = '1.0'; + public static $POST_INPUT = 'php://input'; + + function __construct($http_method, $http_url, $parameters=NULL) { + $parameters = ($parameters) ? $parameters : array(); + $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; + } + + + /** + * attempt to build up a request from what was passed to the server + */ + public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { + $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") + ? 'http' + : 'https'; + $http_url = ($http_url) ? $http_url : $scheme . + '://' . $_SERVER['HTTP_HOST'] . + ':' . + $_SERVER['SERVER_PORT'] . + $_SERVER['REQUEST_URI']; + $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; + + // We weren't handed any parameters, so let's find the ones relevant to + // this request. + // If you run XML-RPC or similar you should use this to provide your own + // parsed parameter-list + if (!$parameters) { + // Find request headers + $request_headers = OAuthUtil::get_headers(); + + // Parse the query-string to find GET parameters + $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); + + // It's a POST request of the proper content-type, so parse POST + // parameters and add those overriding any duplicates from GET + if ($http_method == "POST" + && isset($request_headers['Content-Type']) + && strstr($request_headers['Content-Type'], + 'application/x-www-form-urlencoded') + ) { + $post_data = OAuthUtil::parse_parameters( + file_get_contents(self::$POST_INPUT) + ); + $parameters = array_merge($parameters, $post_data); + } + + // We have a Authorization-header with OAuth data. Parse the header + // and add those overriding any duplicates from GET or POST + if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { + $header_parameters = OAuthUtil::split_header( + $request_headers['Authorization'] + ); + $parameters = array_merge($parameters, $header_parameters); + } + + } + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + /** + * pretty much a helper function to set up the request + */ + public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { + $parameters = ($parameters) ? $parameters : array(); + $defaults = array("oauth_version" => OAuthRequest::$version, + "oauth_nonce" => OAuthRequest::generate_nonce(), + "oauth_timestamp" => OAuthRequest::generate_timestamp(), + "oauth_consumer_key" => $consumer->key); + if ($token) + $defaults['oauth_token'] = $token->key; + + $parameters = array_merge($defaults, $parameters); + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + public function set_parameter($name, $value, $allow_duplicates = true) { + if ($allow_duplicates && isset($this->parameters[$name])) { + // We have already added parameter(s) with this name, so add to the list + if (is_scalar($this->parameters[$name])) { + // This is the first duplicate, so transform scalar (string) + // into an array so we can add the duplicates + $this->parameters[$name] = array($this->parameters[$name]); + } + + $this->parameters[$name][] = $value; + } else { + $this->parameters[$name] = $value; + } + } + + public function get_parameter($name) { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + public function get_parameters() { + return $this->parameters; + } + + public function unset_parameter($name) { + unset($this->parameters[$name]); + } + + /** + * The request parameters, sorted and concatenated into a normalized string. + * @return string + */ + public function get_signable_parameters() { + // Grab all parameters + $params = $this->parameters; + + // Remove oauth_signature if present + // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") + if (isset($params['oauth_signature'])) { + unset($params['oauth_signature']); + } + + return OAuthUtil::build_http_query($params); + } + + /** + * Returns the base string of this request + * + * The base string defined as the method, the url + * and the parameters (normalized), each urlencoded + * and the concated with &. + */ + public function get_signature_base_string() { + $parts = array( + $this->get_normalized_http_method(), + $this->get_normalized_http_url(), + $this->get_signable_parameters() + ); + + $parts = OAuthUtil::urlencode_rfc3986($parts); + + return implode('&', $parts); + } + + /** + * just uppercases the http method + */ + public function get_normalized_http_method() { + return strtoupper($this->http_method); + } + + /** + * parses the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() { + $parts = parse_url($this->http_url); + + $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; + $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); + $host = (isset($parts['host'])) ? $parts['host'] : ''; + $path = (isset($parts['path'])) ? $parts['path'] : ''; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + return "$scheme://$host$path"; + } + + /** + * builds a url usable for a GET request + */ + public function to_url() { + $post_data = $this->to_postdata(); + $out = $this->get_normalized_http_url(); + if ($post_data) { + $out .= '?'.$post_data; + } + return $out; + } + + /** + * builds the data one would send in a POST request + */ + public function to_postdata() { + return OAuthUtil::build_http_query($this->parameters); + } + + /** + * builds the Authorization: header + */ + public function to_header($realm=null) { + $first = true; + if($realm) { + $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; + $first = false; + } else + $out = 'Authorization: OAuth'; + + $total = array(); + foreach ($this->parameters as $k => $v) { + if (substr($k, 0, 5) != "oauth") continue; + if (is_array($v)) { + throw new OAuthException('Arrays not supported in headers'); + } + $out .= ($first) ? ' ' : ','; + $out .= OAuthUtil::urlencode_rfc3986($k) . + '="' . + OAuthUtil::urlencode_rfc3986($v) . + '"'; + $first = false; + } + return $out; + } + + public function __toString() { + return $this->to_url(); + } + + + public function sign_request($signature_method, $consumer, $token) { + $this->set_parameter( + "oauth_signature_method", + $signature_method->get_name(), + false + ); + $signature = $this->build_signature($signature_method, $consumer, $token); + $this->set_parameter("oauth_signature", $signature, false); + } + + public function build_signature($signature_method, $consumer, $token) { + $signature = $signature_method->build_signature($this, $consumer, $token); + return $signature; + } + + /** + * util function: current timestamp + */ + private static function generate_timestamp() { + return time(); + } + + /** + * util function: current nonce + */ + private static function generate_nonce() { + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); // md5s look nicer than numbers + } +} + +class OAuthServer { + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = '1.0'; // hi blaine + protected $signature_methods = array(); + + protected $data_store; + + function __construct($data_store) { + $this->data_store = $data_store; + } + + public function add_signature_method($signature_method) { + $this->signature_methods[$signature_method->get_name()] = + $signature_method; + } + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // no token required for the initial token request + $token = NULL; + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $callback = $request->get_parameter('oauth_callback'); + $new_token = $this->data_store->new_request_token($consumer, $callback); + + return $new_token; + } + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // requires authorized request token + $token = $this->get_token($request, $consumer, "request"); + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $verifier = $request->get_parameter('oauth_verifier'); + $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); + + return $new_token; + } + + /** + * verify an api call, checks all the parameters + */ + public function verify_request(&$request) { + $this->get_version($request); + $consumer = $this->get_consumer($request); + $token = $this->get_token($request, $consumer, "access"); + $this->check_signature($request, $consumer, $token); + return array($consumer, $token); + } + + // Internals from here + /** + * version 1 + */ + private function get_version(&$request) { + $version = $request->get_parameter("oauth_version"); + if (!$version) { + // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. + // Chapter 7.0 ("Accessing Protected Ressources") + $version = '1.0'; + } + if ($version !== $this->version) { + throw new OAuthException("OAuth version '$version' not supported"); + } + return $version; + } + + /** + * figure out the signature with some defaults + */ + private function get_signature_method($request) { + $signature_method = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_signature_method") + : NULL; + + if (!$signature_method) { + // According to chapter 7 ("Accessing Protected Ressources") the signature-method + // parameter is required, and we can't just fallback to PLAINTEXT + throw new OAuthException('No signature method parameter. This parameter is required'); + } + + if (!in_array($signature_method, + array_keys($this->signature_methods))) { + throw new OAuthException( + "Signature method '$signature_method' not supported " . + "try one of the following: " . + implode(", ", array_keys($this->signature_methods)) + ); + } + return $this->signature_methods[$signature_method]; + } + + /** + * try to find the consumer for the provided request's consumer key + */ + private function get_consumer($request) { + $consumer_key = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_consumer_key") + : NULL; + + if (!$consumer_key) { + throw new OAuthException("Invalid consumer key"); + } + + $consumer = $this->data_store->lookup_consumer($consumer_key); + if (!$consumer) { + throw new OAuthException("Invalid consumer"); + } + + return $consumer; + } + + /** + * try to find the token for the provided request's token key + */ + private function get_token($request, $consumer, $token_type="access") { + $token_field = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_token') + : NULL; + + $token = $this->data_store->lookup_token( + $consumer, $token_type, $token_field + ); + if (!$token) { + throw new OAuthException("Invalid $token_type token: $token_field"); + } + return $token; + } + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + private function check_signature($request, $consumer, $token) { + // this should probably be in a different method + $timestamp = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_timestamp') + : NULL; + $nonce = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_nonce') + : NULL; + + $this->check_timestamp($timestamp); + $this->check_nonce($consumer, $token, $nonce, $timestamp); + + $signature_method = $this->get_signature_method($request); + + $signature = $request->get_parameter('oauth_signature'); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if (!$valid_sig) { + throw new OAuthException("Invalid signature"); + } + } + + /** + * check that the timestamp is new enough + */ + private function check_timestamp($timestamp) { + if( ! $timestamp ) + throw new OAuthException( + 'Missing timestamp parameter. The parameter is required' + ); + + // verify that timestamp is recentish + $now = time(); + if (abs($now - $timestamp) > $this->timestamp_threshold) { + throw new OAuthException( + "Expired timestamp, yours $timestamp, ours $now" + ); + } + } + + /** + * check that the nonce is not repeated + */ + private function check_nonce($consumer, $token, $nonce, $timestamp) { + if( ! $nonce ) + throw new OAuthException( + 'Missing nonce parameter. The parameter is required' + ); + + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce( + $consumer, + $token, + $nonce, + $timestamp + ); + if ($found) { + throw new OAuthException("Nonce already used: $nonce"); + } + } + +} + +class OAuthDataStore { + function lookup_consumer($consumer_key) { + // implement me + } + + function lookup_token($consumer, $token_type, $token) { + // implement me + } + + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + // implement me + } + + function new_request_token($consumer, $callback = null) { + // return a new token attached to this consumer + } + + function new_access_token($token, $consumer, $verifier = null) { + // return a new access token attached to this consumer + // for the user associated with this token if the request token + // is authorized + // should also invalidate the request token + } + +} + +class OAuthUtil { + public static function urlencode_rfc3986($input) { + if (is_array($input)) { + return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); + } else if (is_scalar($input)) { + return str_replace( + '+', + ' ', + str_replace('%7E', '~', rawurlencode($input)) + ); + } else { + return ''; + } +} + + + // This decode function isn't taking into consideration the above + // modifications to the encoding process. However, this method doesn't + // seem to be used anywhere so leaving it as is. + public static function urldecode_rfc3986($string) { + return urldecode($string); + } + + // Utility function for turning the Authorization: header into + // parameters, has to do some unescaping + // Can filter out any non-oauth parameters if needed (default behaviour) + // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. + // see http://code.google.com/p/oauth/issues/detail?id=163 + public static function split_header($header, $only_allow_oauth_parameters = true) { + $params = array(); + if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { + foreach ($matches[1] as $i => $h) { + $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); + } + if (isset($params['realm'])) { + unset($params['realm']); + } + } + return $params; + } + + // helper to try to sort out headers for people who aren't running apache + public static function get_headers() { + if (function_exists('apache_request_headers')) { + // we need this to get the actual Authorization: header + // because apache tends to tell us it doesn't exist + $headers = apache_request_headers(); + + // sanitize the output of apache_request_headers because + // we always want the keys to be Cased-Like-This and arh() + // returns the headers in the same case as they are in the + // request + $out = array(); + foreach ($headers AS $key => $value) { + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("-", " ", $key))) + ); + $out[$key] = $value; + } + } else { + // otherwise we don't have apache and are just going to have to hope + // that $_SERVER actually contains what we need + $out = array(); + if( isset($_SERVER['CONTENT_TYPE']) ) + $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; + if( isset($_ENV['CONTENT_TYPE']) ) + $out['Content-Type'] = $_ENV['CONTENT_TYPE']; + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) == "HTTP_") { + // this is chaos, basically it is just there to capitalize the first + // letter of every word that is not an initial HTTP and strip HTTP + // code from przemek + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) + ); + $out[$key] = $value; + } + } + } + return $out; + } + + // This function takes a input like a=b&a=c&d=e and returns the parsed + // parameters like this + // array('a' => array('b','c'), 'd' => 'e') + public static function parse_parameters( $input ) { + if (!isset($input) || !$input) return array(); + + $pairs = explode('&', $input); + + $parsed_parameters = array(); + foreach ($pairs as $pair) { + $split = explode('=', $pair, 2); + $parameter = OAuthUtil::urldecode_rfc3986($split[0]); + $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; + + if (isset($parsed_parameters[$parameter])) { + // We have already recieved parameter(s) with this name, so add to the list + // of parameters with this name + + if (is_scalar($parsed_parameters[$parameter])) { + // This is the first duplicate, so transform scalar (string) into an array + // so we can add the duplicates + $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); + } + + $parsed_parameters[$parameter][] = $value; + } else { + $parsed_parameters[$parameter] = $value; + } + } + return $parsed_parameters; + } + + public static function build_http_query($params) { + if (!$params) return ''; + + // Urlencode both keys and values + $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); + $values = OAuthUtil::urlencode_rfc3986(array_values($params)); + $params = array_combine($keys, $values); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort($params, 'strcmp'); + + $pairs = array(); + foreach ($params as $parameter => $value) { + if (is_array($value)) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + // June 12th, 2010 - changed to sort because of issue 164 by hidetaka + sort($value, SORT_STRING); + foreach ($value as $duplicate_value) { + $pairs[] = $parameter . '=' . $duplicate_value; + } + } else { + $pairs[] = $parameter . '=' . $value; + } + } + // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) + // Each name-value pair is separated by an '&' character (ASCII code 38) + return implode('&', $pairs); + } +} + +?> diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/main.css b/chrome/common/extensions/docs/examples/apps/hello-php/main.css new file mode 100644 index 0000000..26f8eb4 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/main.css @@ -0,0 +1,90 @@ +body { + font-family: Arial, Verdana, san-serif; + font-size: 11pt; + color: #666; + margin: 0; +} + +nav { + display: block; /* for older browsers */ + text-align: right; + margin-bottom: 10px; + padding: 10px; + border-bottom: 1px solid #C6D2EB; + background: -moz-linear-gradient(top, #fff, #EEF1F9 60%, #EEF1F9); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#EEF1F9), color-stop(.6, #EEF1F9)); + text-shadow: 1px 1px 1px #fff; +} + +nav span { + float: left; + font-weight: bold; +} + +a { + color: #2F58A4; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +form { + margin: 0; +} + +input[type='text'] { + padding: 3px; +} + +li { + list-style: none; +} + +.error { + margin: 10px 0 10px; + padding: 10px; + text-align: center; + border: 1px solid red; + color: red; + font-weight: bold; + background-color: #ffffcc; + -moz-border-radius: 5px; + border-radius: 5px; + text-shadow: 2px 2px 2px #ccc; +} + +#container { + margin-top: 10px; + padding: 10px; +} + +#access-level { + text-align: center; + padding: 15px; + border: 1px dotted #C6D2EB; + font-size: 12pt; + -moz-border-radius: 15px; + border-radius: 15px; + width: 550px; + margin-left: auto; + margin-right: auto; +} + +#license-server-response span { + font-weight: bold; +} + +#license-server-response span.full { + color: green; +} + +#license-server-response span.free_trial { + color: orange; +} + +#license-server-response span.no { + color: red; +} + diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js b/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js new file mode 100644 index 0000000..4b0fd40 --- /dev/null +++ b/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js @@ -0,0 +1,279 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// PopupManager is a library to facilitate integration with OpenID +// identity providers (OP)s that support a pop-up authentication interface. +// To create a popup window, you first construct a popupOpener customized +// for your site and a particular identity provider, E.g.: +// +// var googleOpener = popupManager.createOpener(openidParams); +// +// where 'openidParams' are customized for Google in this instance. +// (typically you just change the openidpoint, the version number +// (the openid.ns parameter) and the extensions based on what +// the OP supports. +// OpenID libraries can often discover these properties +// automatically from the location of an XRD document. +// +// Then, you can either directly call +// googleOpener.popup(width, height), where 'width' and 'height' are your choices +// for popup size, or you can display a button 'Sign in with Google' and set the +//..'onclick' handler of the button to googleOpener.popup() + +var popupManager = {}; + +// Library constants + +popupManager.constants = { + 'darkCover' : 'popupManager_darkCover_div', + 'darkCoverStyle' : ['position:absolute;', + 'top:0px;', + 'left:0px;', + 'padding-right:0px;', + 'padding-bottom:0px;', + 'background-color:#000000;', + 'opacity:0.5;', //standard-compliant browsers + '-moz-opacity:0.5;', // old Mozilla + 'filter:alpha(opacity=0.5);', // IE + 'z-index:10000;', + 'width:100%;', + 'height:100%;' + ].join(''), + 'openidSpec' : { + 'identifier_select' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select', + 'namespace2' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0' + } }; + +// Computes the size of the window contents. Returns a pair of +// coordinates [width, height] which can be [0, 0] if it was not possible +// to compute the values. +popupManager.getWindowInnerSize = function() { + var width = 0; + var height = 0; + var elem = null; + if ('innerWidth' in window) { + // For non-IE + width = window.innerWidth; + height = window.innerHeight; + } else { + // For IE, + if (('BackCompat' === window.document.compatMode) + && ('body' in window.document)) { + elem = window.document.body; + } else if ('documentElement' in window.document) { + elem = window.document.documentElement; + } + if (elem !== null) { + width = elem.offsetWidth; + height = elem.offsetHeight; + } + } + return [width, height]; +}; + +// Computes the coordinates of the parent window. +// Gets the coordinates of the parent frame +popupManager.getParentCoords = function() { + var width = 0; + var height = 0; + if ('screenLeft' in window) { + // IE-compatible variants + width = window.screenLeft; + height = window.screenTop; + } else if ('screenX' in window) { + // Firefox-compatible + width = window.screenX; + height = window.screenY; + } + return [width, height]; +}; + +// Computes the coordinates of the new window, so as to center it +// over the parent frame +popupManager.getCenteredCoords = function(width, height) { + var parentSize = this.getWindowInnerSize(); + var parentPos = this.getParentCoords(); + var xPos = parentPos[0] + + Math.max(0, Math.floor((parentSize[0] - width) / 2)); + var yPos = parentPos[1] + + Math.max(0, Math.floor((parentSize[1] - height) / 2)); + return [xPos, yPos]; +}; + +// A utility class, implements an onOpenHandler that darkens the screen +// by overlaying it with a semi-transparent black layer. To use, ensure that +// no screen element has a z-index at or above 10000. +// This layer will be suppressed automatically after the screen closes. +// +// Note: If you want to perform other operations before opening the popup, but +// also would like the screen to darken, you can define a custom handler +// as such: +// var myOnOpenHandler = function(inputs) { +// .. do something +// popupManager.darkenScreen(); +// .. something else +// }; +// Then you pass myOnOpenHandler as input to the opener, as in: +// var openidParams = {}; +// openidParams.onOpenHandler = myOnOpenHandler; +// ... other customizations +// var myOpener = popupManager.createOpener(openidParams); +popupManager.darkenScreen = function() { + var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); + if (!darkCover) { + darkCover = window.document.createElement('div'); + darkCover['id'] = window.popupManager.constants['darkCover']; + darkCover.setAttribute('style', window.popupManager.constants['darkCoverStyle']); + window.document.body.appendChild(darkCover); + } + darkCover.style.visibility = 'visible'; +}; + +// Returns a an object that can open a popup window customized for an OP & RP. +// to use you call var opener = popupManager.cretePopupOpener(openidParams); +// and then you can assign the 'onclick' handler of a button to +// opener.popup(width, height), where width and height are the values of the popup size; +// +// To use it, you would typically have code such as: +// var myLoginCheckFunction = ... some AJAXy call or page refresh operation +// that will cause the user to see the logged-in experience in the current page. +// var openidParams = { realm : 'openid.realm', returnToUrl : 'openid.return_to', +// opEndpoint : 'openid.op_endpoint', onCloseHandler : myLoginCheckFunction, +// shouldEncodeUrls : 'true' (default) or 'false', extensions : myOpenIDExtensions }; +// +// Here extensions include any OpenID extensions that you support. For instance, +// if you support Attribute Exchange v.1.0, you can say: +// (Example for attribute exchange request for email and name, +// assuming that shouldEncodeUrls = 'true':) +// var myOpenIDExtensions = { +// 'openid.ax.ns' : 'http://openid.net/srv/ax/1.0', +// 'openid.ax.type.email' : 'http://axschema.org/contact/email', +// 'openid.ax.type.name1' : 'http://axschema.org/namePerson/first', +// 'openid.ax.type.name2' : 'http://axschema.org/namePerson/last', +// 'openid.ax.required' : 'email,name1,name2' }; +// Note that the 'ui' namespace is reserved by this library for the OpenID +// UI extension, and that the mode 'popup' is automatically applied. +// If you wish to make use of the 'language' feature of the OpenID UI extension +// simply add the following entry (example assumes the language requested +// is Swiss French: +// var my OpenIDExtensions = { +// ... // other extension parameters +// 'openid.ui.language' : 'fr_CH', +// ... }; +popupManager.createPopupOpener = (function(openidParams) { + var interval_ = null; + var popupWindow_ = null; + var that = this; + var shouldEscape_ = ('shouldEncodeUrls' in openidParams) ? openidParams.shouldEncodeUrls : true; + var encodeIfRequested_ = function(url) { + return (shouldEscape_ ? encodeURIComponent(url) : url); + }; + var identifier_ = ('identifier' in openidParams) ? encodeIfRequested_(openidParams.identifier) : + this.constants.openidSpec.identifier_select; + var identity_ = ('identity' in openidParams) ? encodeIfRequested_(openidParams.identity) : + this.constants.openidSpec.identifier_select; + var openidNs_ = ('namespace' in openidParams) ? encodeIfRequested_(openidParams.namespace) : + this.constants.openidSpec.namespace2; + var onOpenHandler_ = (('onOpenHandler' in openidParams) && + ('function' === typeof(openidParams.onOpenHandler))) ? + openidParams.onOpenHandler : this.darkenScreen; + var onCloseHandler_ = (('onCloseHandler' in openidParams) && + ('function' === typeof(openidParams.onCloseHandler))) ? + openidParams.onCloseHandler : null; + var returnToUrl_ = ('returnToUrl' in openidParams) ? openidParams.returnToUrl : null; + var realm_ = ('realm' in openidParams) ? openidParams.realm : null; + var endpoint_ = ('opEndpoint' in openidParams) ? openidParams.opEndpoint : null; + var extensions_ = ('extensions' in openidParams) ? openidParams.extensions : null; + + // processes key value pairs, escaping any input; + var keyValueConcat_ = function(keyValuePairs) { + var result = ""; + for (key in keyValuePairs) { + result += ['&', key, '=', encodeIfRequested_(keyValuePairs[key])].join(''); + } + return result; + }; + + //Assembles the OpenID request from customizable parameters + var buildUrlToOpen_ = function() { + var connector = '&'; + var encodedUrl = null; + var urlToOpen = null; + if ((null === endpoint_) || (null === returnToUrl_)) { + return; + } + if (endpoint_.indexOf('?') === -1) { + connector = '?'; + } + encodedUrl = encodeIfRequested_(returnToUrl_); + urlToOpen = [ endpoint_, connector, + 'openid.ns=', openidNs_, + '&openid.mode=checkid_setup', + '&openid.claimed_id=', identifier_, + '&openid.identity=', identity_, + '&openid.return_to=', encodedUrl ].join(''); + if (realm_ !== null) { + urlToOpen += "&openid.realm=" + encodeIfRequested_(realm_); + } + if (extensions_ !== null) { + urlToOpen += keyValueConcat_(extensions_); + } + urlToOpen += '&openid.ns.ui=' + encodeURIComponent( + 'http://specs.openid.net/extensions/ui/1.0'); + urlToOpen += '&openid.ui.mode=popup'; + return urlToOpen; + }; + + // Tests that the popup window has closed + var isPopupClosed_ = function() { + return (!popupWindow_ || popupWindow_.closed); + }; + + // Check to perform at each execution of the timed loop. It also triggers + // the action that follows the closing of the popup + var waitForPopupClose_ = function() { + if (isPopupClosed_()) { + popupWindow_ = null; + var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); + if (darkCover) { + darkCover.style.visibility = 'hidden'; + } + if (onCloseHandler_ !== null) { + onCloseHandler_(); + } + if ((null !== interval_)) { + window.clearInterval(interval_); + interval_ = null; + } + } + }; + + return { + // Function that opens the window. + popup: function(width, height) { + var urlToOpen = buildUrlToOpen_(); + if (onOpenHandler_ !== null) { + onOpenHandler_(); + } + var coordinates = that.getCenteredCoords(width, height); + popupWindow_ = window.open(urlToOpen, "", + "width=" + width + ",height=" + height + + ",status=1,location=1,resizable=yes" + + ",left=" + coordinates[0] +",top=" + coordinates[1]); + interval_ = window.setInterval(waitForPopupClose_, 80); + return true; + } + }; +}); |