summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Auth/OpenID.php552
-rw-r--r--Auth/OpenID/AX.php1023
-rw-r--r--Auth/OpenID/Association.php613
-rw-r--r--Auth/OpenID/BigMath.php471
-rw-r--r--Auth/OpenID/Consumer.php2229
-rw-r--r--Auth/OpenID/CryptUtil.php109
-rw-r--r--Auth/OpenID/DatabaseConnection.php131
-rw-r--r--Auth/OpenID/DiffieHellman.php113
-rw-r--r--Auth/OpenID/Discover.php548
-rw-r--r--Auth/OpenID/DumbStore.php100
-rw-r--r--Auth/OpenID/Extension.php62
-rw-r--r--Auth/OpenID/FileStore.php618
-rw-r--r--Auth/OpenID/HMAC.php99
-rw-r--r--Auth/OpenID/Interface.php197
-rw-r--r--Auth/OpenID/KVForm.php112
-rw-r--r--Auth/OpenID/MemcachedStore.php208
-rw-r--r--Auth/OpenID/Message.php920
-rw-r--r--Auth/OpenID/MySQLStore.php78
-rw-r--r--Auth/OpenID/Nonce.php109
-rw-r--r--Auth/OpenID/PAPE.php301
-rw-r--r--Auth/OpenID/Parse.php352
-rw-r--r--Auth/OpenID/PostgreSQLStore.php113
-rw-r--r--Auth/OpenID/SQLStore.php569
-rw-r--r--Auth/OpenID/SQLiteStore.php71
-rw-r--r--Auth/OpenID/SReg.php521
-rw-r--r--Auth/OpenID/Server.php1760
-rw-r--r--Auth/OpenID/ServerRequest.php37
-rw-r--r--Auth/OpenID/TrustRoot.php462
-rw-r--r--Auth/OpenID/URINorm.php249
-rw-r--r--Auth/Yadis/HTTPFetcher.php147
-rw-r--r--Auth/Yadis/Manager.php529
-rw-r--r--Auth/Yadis/Misc.php59
-rw-r--r--Auth/Yadis/ParanoidHTTPFetcher.php226
-rw-r--r--Auth/Yadis/ParseHTML.php259
-rw-r--r--Auth/Yadis/PlainHTTPFetcher.php249
-rw-r--r--Auth/Yadis/XML.php374
-rw-r--r--Auth/Yadis/XRDS.php478
-rw-r--r--Auth/Yadis/XRI.php234
-rw-r--r--Auth/Yadis/XRIRes.php72
-rw-r--r--Auth/Yadis/Yadis.php382
-rw-r--r--CHANGES-2.1.051
-rw-r--r--COPYING202
-rw-r--r--NEWS85
-rw-r--r--README139
-rw-r--r--README.Debian8
-rw-r--r--Tests/Auth/OpenID/AX.php794
-rw-r--r--Tests/Auth/OpenID/Association.php55
-rw-r--r--Tests/Auth/OpenID/AssociationResponse.php379
-rw-r--r--Tests/Auth/OpenID/AuthRequest.php302
-rw-r--r--Tests/Auth/OpenID/BigMath.php235
-rw-r--r--Tests/Auth/OpenID/Consumer.php2532
-rw-r--r--Tests/Auth/OpenID/CryptUtil.php56
-rw-r--r--Tests/Auth/OpenID/DiffieHellman.php160
-rw-r--r--Tests/Auth/OpenID/Discover_OpenID.php783
-rw-r--r--Tests/Auth/OpenID/Extension.php46
-rw-r--r--Tests/Auth/OpenID/HMAC.php166
-rw-r--r--Tests/Auth/OpenID/KVForm.php261
-rw-r--r--Tests/Auth/OpenID/MemStore.php176
-rw-r--r--Tests/Auth/OpenID/Message.php1252
-rw-r--r--Tests/Auth/OpenID/Negotiation.php348
-rw-r--r--Tests/Auth/OpenID/Nonce.php167
-rw-r--r--Tests/Auth/OpenID/OpenID_Yadis.php230
-rw-r--r--Tests/Auth/OpenID/PAPE.php246
-rw-r--r--Tests/Auth/OpenID/Parse.php186
-rw-r--r--Tests/Auth/OpenID/RPVerify.php299
-rw-r--r--Tests/Auth/OpenID/SReg.php675
-rw-r--r--Tests/Auth/OpenID/Server.php2464
-rw-r--r--Tests/Auth/OpenID/StoreTest.php705
-rw-r--r--Tests/Auth/OpenID/TestUtil.php62
-rw-r--r--Tests/Auth/OpenID/TrustRoot.php173
-rw-r--r--Tests/Auth/OpenID/URINorm.php68
-rw-r--r--Tests/Auth/OpenID/Util.php320
-rw-r--r--Tests/Auth/OpenID/VerifyDisco.php424
-rw-r--r--Tests/Auth/OpenID/data/dhexch25
-rw-r--r--Tests/Auth/OpenID/data/dhpriv29
-rw-r--r--Tests/Auth/OpenID/data/hmac-sha1.txt49
-rw-r--r--Tests/Auth/OpenID/data/hmac-sha256.txt29
-rw-r--r--Tests/Auth/OpenID/data/linkparse.txt594
-rw-r--r--Tests/Auth/OpenID/data/n2b64650
-rw-r--r--Tests/Auth/OpenID/data/openid.html11
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid.html11
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid2.html11
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid2_xrds.xml12
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid2_xrds_no_local_id.xml11
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_1_and_2.html11
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds.xml16
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds_bad_delegate.xml17
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_and_yadis.html12
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_no_delegate.html10
-rw-r--r--Tests/Auth/OpenID/data/test_discover_openid_ssl.xml19
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_0entries.xml12
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_2_bad_local_id.xml15
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_2entries_delegate.xml22
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_2entries_idp.xml21
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_another_delegate.xml14
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_idp.xml12
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_idp_delegate.xml13
-rw-r--r--Tests/Auth/OpenID/data/test_discover_yadis_no_delegate.xml11
-rw-r--r--Tests/Auth/OpenID/data/trustroot.txt149
-rw-r--r--Tests/Auth/OpenID/data/urinorm.txt87
-rw-r--r--Tests/Auth/Yadis/DiscoverData.php153
-rw-r--r--Tests/Auth/Yadis/Discover_Yadis.php232
-rw-r--r--Tests/Auth/Yadis/ParseHTML.php88
-rw-r--r--Tests/Auth/Yadis/TestUtil.php30
-rw-r--r--Tests/Auth/Yadis/XRDS.php249
-rw-r--r--Tests/Auth/Yadis/XRI.php144
-rw-r--r--Tests/Auth/Yadis/Yadis.php90
-rw-r--r--Tests/Auth/Yadis/data/README12
-rw-r--r--Tests/Auth/Yadis/data/accept.txt118
-rw-r--r--Tests/Auth/Yadis/data/brian.multi.xrds38
-rw-r--r--Tests/Auth/Yadis/data/brian.multi_uri.xrds16
-rw-r--r--Tests/Auth/Yadis/data/brian.xrds16
-rw-r--r--Tests/Auth/Yadis/data/brian_priority.xrds22
-rw-r--r--Tests/Auth/Yadis/data/delegated-20060809-r1.xrds34
-rw-r--r--Tests/Auth/Yadis/data/delegated-20060809-r2.xrds34
-rw-r--r--Tests/Auth/Yadis/data/delegated-20060809.xrds34
-rw-r--r--Tests/Auth/Yadis/data/example-xrds.xml14
-rw-r--r--Tests/Auth/Yadis/data/no-xrd.xml7
-rw-r--r--Tests/Auth/Yadis/data/not-xrds.xml2
-rw-r--r--Tests/Auth/Yadis/data/pip.xrds22
-rw-r--r--Tests/Auth/Yadis/data/prefixsometimes.xrds34
-rw-r--r--Tests/Auth/Yadis/data/ref.xrds109
-rw-r--r--Tests/Auth/Yadis/data/sometimesprefix.xrds34
-rw-r--r--Tests/Auth/Yadis/data/spoof1.xrds25
-rw-r--r--Tests/Auth/Yadis/data/spoof2.xrds25
-rw-r--r--Tests/Auth/Yadis/data/spoof3.xrds37
-rw-r--r--Tests/Auth/Yadis/data/subsegments.xrds58
-rw-r--r--Tests/Auth/Yadis/data/test1-discover.txt137
-rw-r--r--Tests/Auth/Yadis/data/test1-parsehtml.txt149
-rw-r--r--Tests/Auth/Yadis/data/test1-xrd.xml39
-rw-r--r--Tests/Auth/Yadis/data/uri_priority.xrds16
-rw-r--r--Tests/TestDriver.php202
-rw-r--r--admin/adminutil.php30
-rw-r--r--admin/brace_style.pl81
-rw-r--r--admin/checkimport4
-rw-r--r--admin/checkimports2
-rw-r--r--admin/darcs-ignore3
-rw-r--r--admin/docblocks15
-rw-r--r--admin/docblocks.pl26
-rw-r--r--admin/findallphp8
-rw-r--r--admin/findglobals5
-rw-r--r--admin/findphp8
-rw-r--r--admin/fixperms15
-rw-r--r--admin/gettlds.py47
-rw-r--r--admin/library-name1
-rw-r--r--admin/longlines.pl46
-rw-r--r--admin/makedoc.sh5
-rw-r--r--admin/mathlib18
-rw-r--r--admin/nobadbraces4
-rw-r--r--admin/nobadcase14
-rw-r--r--admin/nolonglines14
-rw-r--r--admin/notabs16
-rw-r--r--admin/open_tag17
-rw-r--r--admin/otb_test.php20
-rw-r--r--admin/package.xml24
-rw-r--r--admin/package2.xml74
-rw-r--r--admin/packagexml.py155
-rw-r--r--admin/phpaliases.py119
-rw-r--r--admin/prepare-release12
-rw-r--r--admin/runtests71
-rw-r--r--admin/syntaxcheck5
-rw-r--r--admin/texttest.php197
-rw-r--r--admin/tutorials/OpenID/OpenID.pkg82
-rw-r--r--admin/webtest.php12
-rw-r--r--admin/xmlconfig.py55
-rw-r--r--contrib/signed_assertions/AP.php180
-rw-r--r--contrib/signed_assertions/SAML.php220
-rw-r--r--contrib/upgrade-store-1.1-to-2.0170
-rw-r--r--examples/README134
-rw-r--r--examples/consumer/common.php97
-rw-r--r--examples/consumer/finish_auth.php98
-rw-r--r--examples/consumer/index.php73
-rw-r--r--examples/consumer/try_auth.php83
-rw-r--r--examples/detect.php517
-rw-r--r--examples/discover.php100
-rw-r--r--examples/server/index.php5
-rw-r--r--examples/server/lib/actions.php164
-rw-r--r--examples/server/lib/common.php95
-rw-r--r--examples/server/lib/render.php114
-rw-r--r--examples/server/lib/render/about.php58
-rw-r--r--examples/server/lib/render/idpXrds.php32
-rw-r--r--examples/server/lib/render/idpage.php31
-rw-r--r--examples/server/lib/render/login.php65
-rw-r--r--examples/server/lib/render/trust.php56
-rw-r--r--examples/server/lib/render/userXrds.php34
-rw-r--r--examples/server/lib/session.php178
-rw-r--r--examples/server/openid-server.css74
-rw-r--r--examples/server/server.php48
-rw-r--r--examples/server/setup.php558
189 files changed, 38267 insertions, 0 deletions
diff --git a/Auth/OpenID.php b/Auth/OpenID.php
new file mode 100644
index 0000000..6556b5b
--- /dev/null
+++ b/Auth/OpenID.php
@@ -0,0 +1,552 @@
+<?php
+
+/**
+ * This is the PHP OpenID library by JanRain, Inc.
+ *
+ * This module contains core utility functionality used by the
+ * library. See Consumer.php and Server.php for the consumer and
+ * server implementations.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * The library version string
+ */
+define('Auth_OpenID_VERSION', '2.1.2');
+
+/**
+ * Require the fetcher code.
+ */
+require_once "Auth/Yadis/PlainHTTPFetcher.php";
+require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
+require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/URINorm.php";
+
+/**
+ * Status code returned by the server when the only option is to show
+ * an error page, since we do not have enough information to redirect
+ * back to the consumer. The associated value is an error message that
+ * should be displayed on an HTML error page.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_LOCAL_ERROR', 'local_error');
+
+/**
+ * Status code returned when there is an error to return in key-value
+ * form to the consumer. The caller should return a 400 Bad Request
+ * response with content-type text/plain and the value as the body.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REMOTE_ERROR', 'remote_error');
+
+/**
+ * Status code returned when there is a key-value form OK response to
+ * the consumer. The value associated with this code is the
+ * response. The caller should return a 200 OK response with
+ * content-type text/plain and the value as the body.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REMOTE_OK', 'remote_ok');
+
+/**
+ * Status code returned when there is a redirect back to the
+ * consumer. The value is the URL to redirect back to. The caller
+ * should return a 302 Found redirect with a Location: header
+ * containing the URL.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REDIRECT', 'redirect');
+
+/**
+ * Status code returned when the caller needs to authenticate the
+ * user. The associated value is a {@link Auth_OpenID_ServerRequest}
+ * object that can be used to complete the authentication. If the user
+ * has taken some authentication action, use the retry() method of the
+ * {@link Auth_OpenID_ServerRequest} object to complete the request.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_DO_AUTH', 'do_auth');
+
+/**
+ * Status code returned when there were no OpenID arguments
+ * passed. This code indicates that the caller should return a 200 OK
+ * response and display an HTML page that says that this is an OpenID
+ * server endpoint.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_DO_ABOUT', 'do_about');
+
+/**
+ * Defines for regexes and format checking.
+ */
+define('Auth_OpenID_letters',
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+define('Auth_OpenID_digits',
+ "0123456789");
+
+define('Auth_OpenID_punct',
+ "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
+
+if (Auth_OpenID_getMathLib() === null) {
+ Auth_OpenID_setNoMathSupport();
+}
+
+/**
+ * The OpenID utility function class.
+ *
+ * @package OpenID
+ * @access private
+ */
+class Auth_OpenID {
+
+ /**
+ * Return true if $thing is an Auth_OpenID_FailureResponse object;
+ * false if not.
+ *
+ * @access private
+ */
+ function isFailure($thing)
+ {
+ return is_a($thing, 'Auth_OpenID_FailureResponse');
+ }
+
+ /**
+ * Gets the query data from the server environment based on the
+ * request method used. If GET was used, this looks at
+ * $_SERVER['QUERY_STRING'] directly. If POST was used, this
+ * fetches data from the special php://input file stream.
+ *
+ * Returns an associative array of the query arguments.
+ *
+ * Skips invalid key/value pairs (i.e. keys with no '=value'
+ * portion).
+ *
+ * Returns an empty array if neither GET nor POST was used, or if
+ * POST was used but php://input cannot be opened.
+ *
+ * @access private
+ */
+ function getQuery($query_str=null)
+ {
+ $data = array();
+
+ if ($query_str !== null) {
+ $data = Auth_OpenID::params_from_string($query_str);
+ } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ // Do nothing.
+ } else {
+ // XXX HACK FIXME HORRIBLE.
+ //
+ // POSTing to a URL with query parameters is acceptable, but
+ // we don't have a clean way to distinguish those parameters
+ // when we need to do things like return_to verification
+ // which only want to look at one kind of parameter. We're
+ // going to emulate the behavior of some other environments
+ // by defaulting to GET and overwriting with POST if POST
+ // data is available.
+ $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']);
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $str = file_get_contents('php://input');
+
+ if ($str === false) {
+ $post = array();
+ } else {
+ $post = Auth_OpenID::params_from_string($str);
+ }
+
+ $data = array_merge($data, $post);
+ }
+ }
+
+ return $data;
+ }
+
+ function params_from_string($str)
+ {
+ $chunks = explode("&", $str);
+
+ $data = array();
+ foreach ($chunks as $chunk) {
+ $parts = explode("=", $chunk, 2);
+
+ if (count($parts) != 2) {
+ continue;
+ }
+
+ list($k, $v) = $parts;
+ $data[$k] = urldecode($v);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Create dir_name as a directory if it does not exist. If it
+ * exists, make sure that it is, in fact, a directory. Returns
+ * true if the operation succeeded; false if not.
+ *
+ * @access private
+ */
+ function ensureDir($dir_name)
+ {
+ if (is_dir($dir_name) || @mkdir($dir_name)) {
+ return true;
+ } else {
+ $parent_dir = dirname($dir_name);
+
+ // Terminal case; there is no parent directory to create.
+ if ($parent_dir == $dir_name) {
+ return true;
+ }
+
+ return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name));
+ }
+ }
+
+ /**
+ * Adds a string prefix to all values of an array. Returns a new
+ * array containing the prefixed values.
+ *
+ * @access private
+ */
+ function addPrefix($values, $prefix)
+ {
+ $new_values = array();
+ foreach ($values as $s) {
+ $new_values[] = $prefix . $s;
+ }
+ return $new_values;
+ }
+
+ /**
+ * Convenience function for getting array values. Given an array
+ * $arr and a key $key, get the corresponding value from the array
+ * or return $default if the key is absent.
+ *
+ * @access private
+ */
+ function arrayGet($arr, $key, $fallback = null)
+ {
+ if (is_array($arr)) {
+ if (array_key_exists($key, $arr)) {
+ return $arr[$key];
+ } else {
+ return $fallback;
+ }
+ } else {
+ trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " .
+ "array as first parameter, got " .
+ gettype($arr), E_USER_WARNING);
+
+ return false;
+ }
+ }
+
+ /**
+ * Replacement for PHP's broken parse_str.
+ */
+ function parse_str($query)
+ {
+ if ($query === null) {
+ return null;
+ }
+
+ $parts = explode('&', $query);
+
+ $new_parts = array();
+ for ($i = 0; $i < count($parts); $i++) {
+ $pair = explode('=', $parts[$i]);
+
+ if (count($pair) != 2) {
+ continue;
+ }
+
+ list($key, $value) = $pair;
+ $new_parts[$key] = urldecode($value);
+ }
+
+ return $new_parts;
+ }
+
+ /**
+ * Implements the PHP 5 'http_build_query' functionality.
+ *
+ * @access private
+ * @param array $data Either an array key/value pairs or an array
+ * of arrays, each of which holding two values: a key and a value,
+ * sequentially.
+ * @return string $result The result of url-encoding the key/value
+ * pairs from $data into a URL query string
+ * (e.g. "username=bob&id=56").
+ */
+ function httpBuildQuery($data)
+ {
+ $pairs = array();
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ $pairs[] = urlencode($value[0])."=".urlencode($value[1]);
+ } else {
+ $pairs[] = urlencode($key)."=".urlencode($value);
+ }
+ }
+ return implode("&", $pairs);
+ }
+
+ /**
+ * "Appends" query arguments onto a URL. The URL may or may not
+ * already have arguments (following a question mark).
+ *
+ * @access private
+ * @param string $url A URL, which may or may not already have
+ * arguments.
+ * @param array $args Either an array key/value pairs or an array of
+ * arrays, each of which holding two values: a key and a value,
+ * sequentially. If $args is an ordinary key/value array, the
+ * parameters will be added to the URL in sorted alphabetical order;
+ * if $args is an array of arrays, their order will be preserved.
+ * @return string $url The original URL with the new parameters added.
+ *
+ */
+ function appendArgs($url, $args)
+ {
+ if (count($args) == 0) {
+ return $url;
+ }
+
+ // Non-empty array; if it is an array of arrays, use
+ // multisort; otherwise use sort.
+ if (array_key_exists(0, $args) &&
+ is_array($args[0])) {
+ // Do nothing here.
+ } else {
+ $keys = array_keys($args);
+ sort($keys);
+ $new_args = array();
+ foreach ($keys as $key) {
+ $new_args[] = array($key, $args[$key]);
+ }
+ $args = $new_args;
+ }
+
+ $sep = '?';
+ if (strpos($url, '?') !== false) {
+ $sep = '&';
+ }
+
+ return $url . $sep . Auth_OpenID::httpBuildQuery($args);
+ }
+
+ /**
+ * Implements python's urlunparse, which is not available in PHP.
+ * Given the specified components of a URL, this function rebuilds
+ * and returns the URL.
+ *
+ * @access private
+ * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'.
+ * @param string $host The host. Required.
+ * @param string $port The port.
+ * @param string $path The path.
+ * @param string $query The query.
+ * @param string $fragment The fragment.
+ * @return string $url The URL resulting from assembling the
+ * specified components.
+ */
+ function urlunparse($scheme, $host, $port = null, $path = '/',
+ $query = '', $fragment = '')
+ {
+
+ if (!$scheme) {
+ $scheme = 'http';
+ }
+
+ if (!$host) {
+ return false;
+ }
+
+ if (!$path) {
+ $path = '';
+ }
+
+ $result = $scheme . "://" . $host;
+
+ if ($port) {
+ $result .= ":" . $port;
+ }
+
+ $result .= $path;
+
+ if ($query) {
+ $result .= "?" . $query;
+ }
+
+ if ($fragment) {
+ $result .= "#" . $fragment;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Given a URL, this "normalizes" it by adding a trailing slash
+ * and / or a leading http:// scheme where necessary. Returns
+ * null if the original URL is malformed and cannot be normalized.
+ *
+ * @access private
+ * @param string $url The URL to be normalized.
+ * @return mixed $new_url The URL after normalization, or null if
+ * $url was malformed.
+ */
+ function normalizeUrl($url)
+ {
+ @$parsed = parse_url($url);
+
+ if (!$parsed) {
+ return null;
+ }
+
+ if (isset($parsed['scheme']) &&
+ isset($parsed['host'])) {
+ $scheme = strtolower($parsed['scheme']);
+ if (!in_array($scheme, array('http', 'https'))) {
+ return null;
+ }
+ } else {
+ $url = 'http://' . $url;
+ }
+
+ $normalized = Auth_OpenID_urinorm($url);
+ if ($normalized === null) {
+ return null;
+ }
+ list($defragged, $frag) = Auth_OpenID::urldefrag($normalized);
+ return $defragged;
+ }
+
+ /**
+ * Replacement (wrapper) for PHP's intval() because it's broken.
+ *
+ * @access private
+ */
+ function intval($value)
+ {
+ $re = "/^\\d+$/";
+
+ if (!preg_match($re, $value)) {
+ return false;
+ }
+
+ return intval($value);
+ }
+
+ /**
+ * Count the number of bytes in a string independently of
+ * multibyte support conditions.
+ *
+ * @param string $str The string of bytes to count.
+ * @return int The number of bytes in $str.
+ */
+ function bytes($str)
+ {
+ return strlen(bin2hex($str)) / 2;
+ }
+
+ /**
+ * Get the bytes in a string independently of multibyte support
+ * conditions.
+ */
+ function toBytes($str)
+ {
+ $hex = bin2hex($str);
+
+ if (!$hex) {
+ return array();
+ }
+
+ $b = array();
+ for ($i = 0; $i < strlen($hex); $i += 2) {
+ $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10));
+ }
+
+ return $b;
+ }
+
+ function urldefrag($url)
+ {
+ $parts = explode("#", $url, 2);
+
+ if (count($parts) == 1) {
+ return array($parts[0], "");
+ } else {
+ return $parts;
+ }
+ }
+
+ function filter($callback, &$sequence)
+ {
+ $result = array();
+
+ foreach ($sequence as $item) {
+ if (call_user_func_array($callback, array($item))) {
+ $result[] = $item;
+ }
+ }
+
+ return $result;
+ }
+
+ function update(&$dest, &$src)
+ {
+ foreach ($src as $k => $v) {
+ $dest[$k] = $v;
+ }
+ }
+
+ /**
+ * Wrap PHP's standard error_log functionality. Use this to
+ * perform all logging. It will interpolate any additional
+ * arguments into the format string before logging.
+ *
+ * @param string $format_string The sprintf format for the message
+ */
+ function log($format_string)
+ {
+ $args = func_get_args();
+ $message = call_user_func_array('sprintf', $args);
+ error_log($message);
+ }
+
+ function autoSubmitHTML($form, $title="OpenId transaction in progress")
+ {
+ return("<html>".
+ "<head><title>".
+ $title .
+ "</title></head>".
+ "<body onload='document.forms[0].submit();'>".
+ $form .
+ "<script>".
+ "var elements = document.forms[0].elements;".
+ "for (var i = 0; i < elements.length; i++) {".
+ " elements[i].style.display = \"none\";".
+ "}".
+ "</script>".
+ "</body>".
+ "</html>");
+ }
+}
+?>
diff --git a/Auth/OpenID/AX.php b/Auth/OpenID/AX.php
new file mode 100644
index 0000000..4a617ae
--- /dev/null
+++ b/Auth/OpenID/AX.php
@@ -0,0 +1,1023 @@
+<?php
+
+/**
+ * Implements the OpenID attribute exchange specification, version 1.0
+ * as of svn revision 370 from openid.net svn.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID/Extension.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/TrustRoot.php";
+
+define('Auth_OpenID_AX_NS_URI',
+ 'http://openid.net/srv/ax/1.0');
+
+// Use this as the 'count' value for an attribute in a FetchRequest to
+// ask for as many values as the OP can provide.
+define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');
+
+// Minimum supported alias length in characters. Here for
+// completeness.
+define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);
+
+/**
+ * AX utility class.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX {
+ /**
+ * @param mixed $thing Any object which may be an
+ * Auth_OpenID_AX_Error object.
+ *
+ * @return bool true if $thing is an Auth_OpenID_AX_Error; false
+ * if not.
+ */
+ function isError($thing)
+ {
+ return is_a($thing, 'Auth_OpenID_AX_Error');
+ }
+}
+
+/**
+ * Check an alias for invalid characters; raise AXError if any are
+ * found. Return None if the alias is valid.
+ */
+function Auth_OpenID_AX_checkAlias($alias)
+{
+ if (strpos($alias, ',') !== false) {
+ return new Auth_OpenID_AX_Error(sprintf(
+ "Alias %s must not contain comma", $alias));
+ }
+ if (strpos($alias, '.') !== false) {
+ return new Auth_OpenID_AX_Error(sprintf(
+ "Alias %s must not contain period", $alias));
+ }
+
+ return true;
+}
+
+/**
+ * Results from data that does not meet the attribute exchange 1.0
+ * specification
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Error {
+ function Auth_OpenID_AX_Error($message=null)
+ {
+ $this->message = $message;
+ }
+}
+
+/**
+ * Abstract class containing common code for attribute exchange
+ * messages.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
+ /**
+ * ns_alias: The preferred namespace alias for attribute exchange
+ * messages
+ */
+ var $ns_alias = 'ax';
+
+ /**
+ * mode: The type of this attribute exchange message. This must be
+ * overridden in subclasses.
+ */
+ var $mode = null;
+
+ var $ns_uri = Auth_OpenID_AX_NS_URI;
+
+ /**
+ * Return Auth_OpenID_AX_Error if the mode in the attribute
+ * exchange arguments does not match what is expected for this
+ * class; true otherwise.
+ *
+ * @access private
+ */
+ function _checkMode($ax_args)
+ {
+ $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
+ if ($mode != $this->mode) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "Expected mode '%s'; got '%s'",
+ $this->mode, $mode));
+ }
+
+ return true;
+ }
+
+ /**
+ * Return a set of attribute exchange arguments containing the
+ * basic information that must be in every attribute exchange
+ * message.
+ *
+ * @access private
+ */
+ function _newArgs()
+ {
+ return array('mode' => $this->mode);
+ }
+}
+
+/**
+ * Represents a single attribute in an attribute exchange
+ * request. This should be added to an AXRequest object in order to
+ * request the attribute.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_AttrInfo {
+ /**
+ * Construct an attribute information object. Do not call this
+ * directly; call make(...) instead.
+ *
+ * @param string $type_uri The type URI for this attribute.
+ *
+ * @param int $count The number of values of this type to request.
+ *
+ * @param bool $required Whether the attribute will be marked as
+ * required in the request.
+ *
+ * @param string $alias The name that should be given to this
+ * attribute in the request.
+ */
+ function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+ $alias)
+ {
+ /**
+ * required: Whether the attribute will be marked as required
+ * when presented to the subject of the attribute exchange
+ * request.
+ */
+ $this->required = $required;
+
+ /**
+ * count: How many values of this type to request from the
+ * subject. Defaults to one.
+ */
+ $this->count = $count;
+
+ /**
+ * type_uri: The identifier that determines what the attribute
+ * represents and how it is serialized. For example, one type
+ * URI representing dates could represent a Unix timestamp in
+ * base 10 and another could represent a human-readable
+ * string.
+ */
+ $this->type_uri = $type_uri;
+
+ /**
+ * alias: The name that should be given to this attribute in
+ * the request. If it is not supplied, a generic name will be
+ * assigned. For example, if you want to call a Unix timestamp
+ * value 'tstamp', set its alias to that value. If two
+ * attributes in the same message request to use the same
+ * alias, the request will fail to be generated.
+ */
+ $this->alias = $alias;
+ }
+
+ /**
+ * Construct an attribute information object. For parameter
+ * details, see the constructor.
+ */
+ function make($type_uri, $count=1, $required=false,
+ $alias=null)
+ {
+ if ($alias !== null) {
+ $result = Auth_OpenID_AX_checkAlias($alias);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+ }
+
+ return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+ $alias);
+ }
+
+ /**
+ * When processing a request for this attribute, the OP should
+ * call this method to determine whether all available attribute
+ * values were requested. If self.count == UNLIMITED_VALUES, this
+ * returns True. Otherwise this returns False, in which case
+ * self.count is an integer.
+ */
+ function wantsUnlimitedValues()
+ {
+ return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
+ }
+}
+
+/**
+ * Given a namespace mapping and a string containing a comma-separated
+ * list of namespace aliases, return a list of type URIs that
+ * correspond to those aliases.
+ *
+ * @param $namespace_map The mapping from namespace URI to alias
+ * @param $alias_list_s The string containing the comma-separated
+ * list of aliases. May also be None for convenience.
+ *
+ * @return $seq The list of namespace URIs that corresponds to the
+ * supplied list of aliases. If the string was zero-length or None, an
+ * empty list will be returned.
+ *
+ * return null If an alias is present in the list of aliases but
+ * is not present in the namespace map.
+ */
+function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
+{
+ $uris = array();
+
+ if ($alias_list_s) {
+ foreach (explode(',', $alias_list_s) as $alias) {
+ $type_uri = $namespace_map->getNamespaceURI($alias);
+ if ($type_uri === null) {
+ // raise KeyError(
+ // 'No type is defined for attribute name %r' % (alias,))
+ return new Auth_OpenID_AX_Error(
+ sprintf('No type is defined for attribute name %s',
+ $alias)
+ );
+ } else {
+ $uris[] = $type_uri;
+ }
+ }
+ }
+
+ return $uris;
+}
+
+/**
+ * An attribute exchange 'fetch_request' message. This message is sent
+ * by a relying party when it wishes to obtain attributes about the
+ * subject of an OpenID authentication request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
+
+ var $mode = 'fetch_request';
+
+ function Auth_OpenID_AX_FetchRequest($update_url=null)
+ {
+ /**
+ * requested_attributes: The attributes that have been
+ * requested thus far, indexed by the type URI.
+ */
+ $this->requested_attributes = array();
+
+ /**
+ * update_url: A URL that will accept responses for this
+ * attribute exchange request, even in the absence of the user
+ * who made this request.
+ */
+ $this->update_url = $update_url;
+ }
+
+ /**
+ * Add an attribute to this attribute exchange request.
+ *
+ * @param attribute: The attribute that is being requested
+ * @return true on success, false when the requested attribute is
+ * already present in this fetch request.
+ */
+ function add($attribute)
+ {
+ if ($this->contains($attribute->type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("The attribute %s has already been requested",
+ $attribute->type_uri));
+ }
+
+ $this->requested_attributes[$attribute->type_uri] = $attribute;
+
+ return true;
+ }
+
+ /**
+ * Get the serialized form of this attribute fetch request.
+ *
+ * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
+ */
+ function getExtensionArgs()
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ $required = array();
+ $if_available = array();
+
+ $ax_args = $this->_newArgs();
+
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->alias === null) {
+ $alias = $aliases->add($type_uri);
+ } else {
+ $alias = $aliases->addAlias($type_uri, $attribute->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attribute->alias, $type_uri
+ ));
+ }
+ }
+
+ if ($attribute->required) {
+ $required[] = $alias;
+ } else {
+ $if_available[] = $alias;
+ }
+
+ if ($attribute->count != 1) {
+ $ax_args['count.' . $alias] = strval($attribute->count);
+ }
+
+ $ax_args['type.' . $alias] = $type_uri;
+ }
+
+ if ($required) {
+ $ax_args['required'] = implode(',', $required);
+ }
+
+ if ($if_available) {
+ $ax_args['if_available'] = implode(',', $if_available);
+ }
+
+ return $ax_args;
+ }
+
+ /**
+ * Get the type URIs for all attributes that have been marked as
+ * required.
+ *
+ * @return A list of the type URIs for attributes that have been
+ * marked as required.
+ */
+ function getRequiredAttrs()
+ {
+ $required = array();
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->required) {
+ $required[] = $type_uri;
+ }
+ }
+
+ return $required;
+ }
+
+ /**
+ * Extract a FetchRequest from an OpenID message
+ *
+ * @param request: The OpenID request containing the attribute
+ * fetch request
+ *
+ * @returns mixed An Auth_OpenID_AX_Error or the
+ * Auth_OpenID_AX_FetchRequest extracted from the request message if
+ * successful
+ */
+ function &fromOpenIDRequest($request)
+ {
+ $m = $request->message;
+ $obj = new Auth_OpenID_AX_FetchRequest();
+ $ax_args = $m->getArgs($obj->ns_uri);
+
+ $result = $obj->parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ if ($obj->update_url) {
+ // Update URL must match the openid.realm of the
+ // underlying OpenID 2 message.
+ $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
+ $m->getArg(
+ Auth_OpenID_OPENID_NS,
+ 'return_to'));
+
+ if (!$realm) {
+ $obj = new Auth_OpenID_AX_Error(
+ sprintf("Cannot validate update_url %s " .
+ "against absent realm", $obj->update_url));
+ } else if (!Auth_OpenID_TrustRoot::match($realm,
+ $obj->update_url)) {
+ $obj = new Auth_OpenID_AX_Error(
+ sprintf("Update URL %s failed validation against realm %s",
+ $obj->update_url, $realm));
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Given attribute exchange arguments, populate this FetchRequest.
+ *
+ * @return $result Auth_OpenID_AX_Error if the data to be parsed
+ * does not follow the attribute exchange specification. At least
+ * when 'if_available' or 'required' is not specified for a
+ * particular attribute type. Returns true otherwise.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $alias = substr($key, 5);
+ $type_uri = $value;
+
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+
+ $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
+ if ($count_s) {
+ $count = Auth_OpenID::intval($count_s);
+ if (($count === false) &&
+ ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
+ $count = $count_s;
+ }
+ } else {
+ $count = 1;
+ }
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count.' . $alias, $count_s));
+ }
+
+ $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
+ false, $alias);
+
+ if (Auth_OpenID_AX::isError($attrinfo)) {
+ return $attrinfo;
+ }
+
+ $this->add($attrinfo);
+ }
+ }
+
+ $required = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'required'));
+
+ foreach ($required as $type_uri) {
+ $attrib =& $this->requested_attributes[$type_uri];
+ $attrib->required = true;
+ }
+
+ $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'if_available'));
+
+ $all_type_uris = array_merge($required, $if_available);
+
+ foreach ($aliases->iterNamespaceURIs() as $type_uri) {
+ if (!in_array($type_uri, $all_type_uris)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf('Type URI %s was in the request but not ' .
+ 'present in "required" or "if_available"',
+ $type_uri));
+
+ }
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /**
+ * Iterate over the AttrInfo objects that are contained in this
+ * fetch_request.
+ */
+ function iterAttrs()
+ {
+ return array_values($this->requested_attributes);
+ }
+
+ function iterTypes()
+ {
+ return array_keys($this->requested_attributes);
+ }
+
+ /**
+ * Is the given type URI present in this fetch_request?
+ */
+ function contains($type_uri)
+ {
+ return in_array($type_uri, $this->iterTypes());
+ }
+}
+
+/**
+ * An abstract class that implements a message that has attribute keys
+ * and values. It contains the common code between fetch_response and
+ * store_request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
+
+ function Auth_OpenID_AX_KeyValueMessage()
+ {
+ $this->data = array();
+ }
+
+ /**
+ * Add a single value for the given attribute type to the
+ * message. If there are already values specified for this type,
+ * this value will be sent in addition to the values already
+ * specified.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param value: The value to add to the response to the relying
+ * party for this attribute
+ * @return null
+ */
+ function addValue($type_uri, $value)
+ {
+ if (!array_key_exists($type_uri, $this->data)) {
+ $this->data[$type_uri] = array();
+ }
+
+ $values =& $this->data[$type_uri];
+ $values[] = $value;
+ }
+
+ /**
+ * Set the values for the given attribute type. This replaces any
+ * values that have already been set for this attribute.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param values: A list of values to send for this attribute.
+ */
+ function setValues($type_uri, &$values)
+ {
+ $this->data[$type_uri] =& $values;
+ }
+
+ /**
+ * Get the extension arguments for the key/value pairs contained
+ * in this message.
+ *
+ * @param aliases: An alias mapping. Set to None if you don't care
+ * about the aliases for this request.
+ *
+ * @access private
+ */
+ function _getExtensionKVArgs(&$aliases)
+ {
+ if ($aliases === null) {
+ $aliases = new Auth_OpenID_NamespaceMap();
+ }
+
+ $ax_args = array();
+
+ foreach ($this->data as $type_uri => $values) {
+ $alias = $aliases->add($type_uri);
+
+ $ax_args['type.' . $alias] = $type_uri;
+ $ax_args['count.' . $alias] = strval(count($values));
+
+ foreach ($values as $i => $value) {
+ $key = sprintf('value.%s.%d', $alias, $i + 1);
+ $ax_args[$key] = $value;
+ }
+ }
+
+ return $ax_args;
+ }
+
+ /**
+ * Parse attribute exchange key/value arguments into this object.
+ *
+ * @param ax_args: The attribute exchange fetch_response
+ * arguments, with namespacing removed.
+ *
+ * @return Auth_OpenID_AX_Error or true
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $type_uri = $value;
+ $alias = substr($key, 5);
+
+ $result = Auth_OpenID_AX_checkAlias($alias);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+ }
+ }
+
+ foreach ($aliases->iteritems() as $pair) {
+ list($type_uri, $alias) = $pair;
+
+ if (array_key_exists('count.' . $alias, $ax_args)) {
+
+ $count_key = 'count.' . $alias;
+ $count_s = $ax_args[$count_key];
+
+ $count = Auth_OpenID::intval($count_s);
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count. %s' . $alias, $count_s,
+ Auth_OpenID_AX_UNLIMITED_VALUES)
+ );
+ }
+
+ $values = array();
+ for ($i = 1; $i < $count + 1; $i++) {
+ $value_key = sprintf('value.%s.%d', $alias, $i);
+
+ if (!array_key_exists($value_key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $value_key));
+ }
+
+ $value = $ax_args[$value_key];
+ $values[] = $value;
+ }
+ } else {
+ $key = 'value.' . $alias;
+
+ if (!array_key_exists($key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $key));
+ }
+
+ $value = $ax_args['value.' . $alias];
+
+ if ($value == '') {
+ $values = array();
+ } else {
+ $values = array($value);
+ }
+ }
+
+ $this->data[$type_uri] = $values;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a single value for an attribute. If no value was sent for
+ * this attribute, use the supplied default. If there is more than
+ * one value for this attribute, this method will fail.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param default: The value to return if the attribute was not
+ * sent in the fetch_response.
+ *
+ * @return $value Auth_OpenID_AX_Error on failure or the value of
+ * the attribute in the fetch_response message, or the default
+ * supplied
+ */
+ function getSingle($type_uri, $default=null)
+ {
+ $values = Auth_OpenID::arrayGet($this->data, $type_uri);
+ if (!$values) {
+ return $default;
+ } else if (count($values) == 1) {
+ return $values[0];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf('More than one value present for %s',
+ $type_uri)
+ );
+ }
+ }
+
+ /**
+ * Get the list of values for this attribute in the
+ * fetch_response.
+ *
+ * XXX: what to do if the values are not present? default
+ * parameter? this is funny because it's always supposed to return
+ * a list, so the default may break that, though it's provided by
+ * the user's code, so it might be okay. If no default is
+ * supplied, should the return be None or []?
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @return $values The list of values for this attribute in the
+ * response. May be an empty list. If the attribute was not sent
+ * in the response, returns Auth_OpenID_AX_Error.
+ */
+ function get($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return $this->data[$type_uri];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+
+ /**
+ * Get the number of responses for a particular attribute in this
+ * fetch_response message.
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @returns int The number of values sent for this attribute. If
+ * the attribute was not sent in the response, returns
+ * Auth_OpenID_AX_Error.
+ */
+ function count($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return count($this->get($type_uri));
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+}
+
+/**
+ * A fetch_response attribute exchange message.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'fetch_response';
+
+ function Auth_OpenID_AX_FetchResponse($update_url=null)
+ {
+ $this->Auth_OpenID_AX_KeyValueMessage();
+ $this->update_url = $update_url;
+ }
+
+ /**
+ * Serialize this object into arguments in the attribute exchange
+ * namespace
+ *
+ * @return $args The dictionary of unqualified attribute exchange
+ * arguments that represent this fetch_response, or
+ * Auth_OpenID_AX_Error on error.
+ */
+ function getExtensionArgs($request=null)
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ $zero_value_types = array();
+
+ if ($request !== null) {
+ // Validate the data in the context of the request (the
+ // same attributes should be present in each, and the
+ // counts in the response must be no more than the counts
+ // in the request)
+
+ foreach ($this->data as $type_uri => $unused) {
+ if (!$request->contains($type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Response attribute not present in request: %s",
+ $type_uri)
+ );
+ }
+ }
+
+ foreach ($request->iterAttrs() as $attr_info) {
+ // Copy the aliases from the request so that reading
+ // the response in light of the request is easier
+ if ($attr_info->alias === null) {
+ $aliases->add($attr_info->type_uri);
+ } else {
+ $alias = $aliases->addAlias($attr_info->type_uri,
+ $attr_info->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attr_info->alias, $attr_info->type_uri)
+ );
+ }
+ }
+
+ if (array_key_exists($attr_info->type_uri, $this->data)) {
+ $values = $this->data[$attr_info->type_uri];
+ } else {
+ $values = array();
+ $zero_value_types[] = $attr_info;
+ }
+
+ if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
+ ($attr_info->count < count($values))) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("More than the number of requested values " .
+ "were specified for %s",
+ $attr_info->type_uri)
+ );
+ }
+ }
+ }
+
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+
+ // Add the KV args into the response with the args that are
+ // unique to the fetch_response
+ $ax_args = $this->_newArgs();
+
+ // For each requested attribute, put its type/alias and count
+ // into the response even if no data were returned.
+ foreach ($zero_value_types as $attr_info) {
+ $alias = $aliases->getAlias($attr_info->type_uri);
+ $kv_args['type.' . $alias] = $attr_info->type_uri;
+ $kv_args['count.' . $alias] = '0';
+ }
+
+ $update_url = null;
+ if ($request) {
+ $update_url = $request->update_url;
+ } else {
+ $update_url = $this->update_url;
+ }
+
+ if ($update_url) {
+ $ax_args['update_url'] = $update_url;
+ }
+
+ Auth_OpenID::update(&$ax_args, $kv_args);
+
+ return $ax_args;
+ }
+
+ /**
+ * @return $result Auth_OpenID_AX_Error on failure or true on
+ * success.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = parent::parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /**
+ * Construct a FetchResponse object from an OpenID library
+ * SuccessResponse object.
+ *
+ * @param success_response: A successful id_res response object
+ *
+ * @param signed: Whether non-signed args should be processsed. If
+ * True (the default), only signed arguments will be processsed.
+ *
+ * @return $response A FetchResponse containing the data from the
+ * OpenID message
+ */
+ function fromSuccessResponse($success_response, $signed=true)
+ {
+ $obj = new Auth_OpenID_AX_FetchResponse();
+ if ($signed) {
+ $ax_args = $success_response->getSignedNS($obj->ns_uri);
+ } else {
+ $ax_args = $success_response->message->getArgs($obj->ns_uri);
+ }
+ if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
+ sizeof($ax_args) == 0) {
+ return null;
+ }
+
+ $result = $obj->parseExtensionArgs($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ #XXX log me
+ return null;
+ }
+ return $obj;
+ }
+}
+
+/**
+ * A store request attribute exchange message representation.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'store_request';
+
+ /**
+ * @param array $aliases The namespace aliases to use when making
+ * this store response. Leave as None to use defaults.
+ */
+ function getExtensionArgs($aliases=null)
+ {
+ $ax_args = $this->_newArgs();
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+ Auth_OpenID::update(&$ax_args, $kv_args);
+ return $ax_args;
+ }
+}
+
+/**
+ * An indication that the store request was processed along with this
+ * OpenID transaction. Use make(), NOT the constructor, to create
+ * response objects.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
+ var $SUCCESS_MODE = 'store_response_success';
+ var $FAILURE_MODE = 'store_response_failure';
+
+ /**
+ * Returns Auth_OpenID_AX_Error on error or an
+ * Auth_OpenID_AX_StoreResponse object on success.
+ */
+ function &make($succeeded=true, $error_message=null)
+ {
+ if (($succeeded) && ($error_message !== null)) {
+ return new Auth_OpenID_AX_Error('An error message may only be '.
+ 'included in a failing fetch response');
+ }
+
+ return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
+ }
+
+ function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
+ {
+ if ($succeeded) {
+ $this->mode = $this->SUCCESS_MODE;
+ } else {
+ $this->mode = $this->FAILURE_MODE;
+ }
+
+ $this->error_message = $error_message;
+ }
+
+ /**
+ * Was this response a success response?
+ */
+ function succeeded()
+ {
+ return $this->mode == $this->SUCCESS_MODE;
+ }
+
+ function getExtensionArgs()
+ {
+ $ax_args = $this->_newArgs();
+ if ((!$this->succeeded()) && $this->error_message) {
+ $ax_args['error'] = $this->error_message;
+ }
+
+ return $ax_args;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Association.php b/Auth/OpenID/Association.php
new file mode 100644
index 0000000..37ce0cb
--- /dev/null
+++ b/Auth/OpenID/Association.php
@@ -0,0 +1,613 @@
+<?php
+
+/**
+ * This module contains code for dealing with associations between
+ * consumers and servers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/KVForm.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/HMAC.php';
+
+/**
+ * This class represents an association between a server and a
+ * consumer. In general, users of this library will never see
+ * instances of this object. The only exception is if you implement a
+ * custom {@link Auth_OpenID_OpenIDStore}.
+ *
+ * If you do implement such a store, it will need to store the values
+ * of the handle, secret, issued, lifetime, and assoc_type instance
+ * variables.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Association {
+
+ /**
+ * This is a HMAC-SHA1 specific value.
+ *
+ * @access private
+ */
+ var $SIG_LENGTH = 20;
+
+ /**
+ * The ordering and name of keys as stored by serialize.
+ *
+ * @access private
+ */
+ var $assoc_keys = array(
+ 'version',
+ 'handle',
+ 'secret',
+ 'issued',
+ 'lifetime',
+ 'assoc_type'
+ );
+
+ var $_macs = array(
+ 'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1',
+ 'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256'
+ );
+
+ /**
+ * This is an alternate constructor (factory method) used by the
+ * OpenID consumer library to create associations. OpenID store
+ * implementations shouldn't use this constructor.
+ *
+ * @access private
+ *
+ * @param integer $expires_in This is the amount of time this
+ * association is good for, measured in seconds since the
+ * association was issued.
+ *
+ * @param string $handle This is the handle the server gave this
+ * association.
+ *
+ * @param string secret This is the shared secret the server
+ * generated for this association.
+ *
+ * @param assoc_type This is the type of association this
+ * instance represents. The only valid values of this field at
+ * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+ * be defined in the future.
+ *
+ * @return association An {@link Auth_OpenID_Association}
+ * instance.
+ */
+ function fromExpiresIn($expires_in, $handle, $secret, $assoc_type)
+ {
+ $issued = time();
+ $lifetime = $expires_in;
+ return new Auth_OpenID_Association($handle, $secret,
+ $issued, $lifetime, $assoc_type);
+ }
+
+ /**
+ * This is the standard constructor for creating an association.
+ * The library should create all of the necessary associations, so
+ * this constructor is not part of the external API.
+ *
+ * @access private
+ *
+ * @param string $handle This is the handle the server gave this
+ * association.
+ *
+ * @param string $secret This is the shared secret the server
+ * generated for this association.
+ *
+ * @param integer $issued This is the time this association was
+ * issued, in seconds since 00:00 GMT, January 1, 1970. (ie, a
+ * unix timestamp)
+ *
+ * @param integer $lifetime This is the amount of time this
+ * association is good for, measured in seconds since the
+ * association was issued.
+ *
+ * @param string $assoc_type This is the type of association this
+ * instance represents. The only valid values of this field at
+ * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+ * be defined in the future.
+ */
+ function Auth_OpenID_Association(
+ $handle, $secret, $issued, $lifetime, $assoc_type)
+ {
+ if (!in_array($assoc_type,
+ Auth_OpenID_getSupportedAssociationTypes())) {
+ $fmt = 'Unsupported association type (%s)';
+ trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR);
+ }
+
+ $this->handle = $handle;
+ $this->secret = $secret;
+ $this->issued = $issued;
+ $this->lifetime = $lifetime;
+ $this->assoc_type = $assoc_type;
+ }
+
+ /**
+ * This returns the number of seconds this association is still
+ * valid for, or 0 if the association is no longer valid.
+ *
+ * @return integer $seconds The number of seconds this association
+ * is still valid for, or 0 if the association is no longer valid.
+ */
+ function getExpiresIn($now = null)
+ {
+ if ($now == null) {
+ $now = time();
+ }
+
+ return max(0, $this->issued + $this->lifetime - $now);
+ }
+
+ /**
+ * This checks to see if two {@link Auth_OpenID_Association}
+ * instances represent the same association.
+ *
+ * @return bool $result true if the two instances represent the
+ * same association, false otherwise.
+ */
+ function equal($other)
+ {
+ return ((gettype($this) == gettype($other))
+ && ($this->handle == $other->handle)
+ && ($this->secret == $other->secret)
+ && ($this->issued == $other->issued)
+ && ($this->lifetime == $other->lifetime)
+ && ($this->assoc_type == $other->assoc_type));
+ }
+
+ /**
+ * Convert an association to KV form.
+ *
+ * @return string $result String in KV form suitable for
+ * deserialization by deserialize.
+ */
+ function serialize()
+ {
+ $data = array(
+ 'version' => '2',
+ 'handle' => $this->handle,
+ 'secret' => base64_encode($this->secret),
+ 'issued' => strval(intval($this->issued)),
+ 'lifetime' => strval(intval($this->lifetime)),
+ 'assoc_type' => $this->assoc_type
+ );
+
+ assert(array_keys($data) == $this->assoc_keys);
+
+ return Auth_OpenID_KVForm::fromArray($data, $strict = true);
+ }
+
+ /**
+ * Parse an association as stored by serialize(). This is the
+ * inverse of serialize.
+ *
+ * @param string $assoc_s Association as serialized by serialize()
+ * @return Auth_OpenID_Association $result instance of this class
+ */
+ function deserialize($class_name, $assoc_s)
+ {
+ $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true);
+ $keys = array();
+ $values = array();
+ foreach ($pairs as $key => $value) {
+ if (is_array($value)) {
+ list($key, $value) = $value;
+ }
+ $keys[] = $key;
+ $values[] = $value;
+ }
+
+ $class_vars = get_class_vars($class_name);
+ $class_assoc_keys = $class_vars['assoc_keys'];
+
+ sort($keys);
+ sort($class_assoc_keys);
+
+ if ($keys != $class_assoc_keys) {
+ trigger_error('Unexpected key values: ' . var_export($keys, true),
+ E_USER_WARNING);
+ return null;
+ }
+
+ $version = $pairs['version'];
+ $handle = $pairs['handle'];
+ $secret = $pairs['secret'];
+ $issued = $pairs['issued'];
+ $lifetime = $pairs['lifetime'];
+ $assoc_type = $pairs['assoc_type'];
+
+ if ($version != '2') {
+ trigger_error('Unknown version: ' . $version, E_USER_WARNING);
+ return null;
+ }
+
+ $issued = intval($issued);
+ $lifetime = intval($lifetime);
+ $secret = base64_decode($secret);
+
+ return new $class_name(
+ $handle, $secret, $issued, $lifetime, $assoc_type);
+ }
+
+ /**
+ * Generate a signature for a sequence of (key, value) pairs
+ *
+ * @access private
+ * @param array $pairs The pairs to sign, in order. This is an
+ * array of two-tuples.
+ * @return string $signature The binary signature of this sequence
+ * of pairs
+ */
+ function sign($pairs)
+ {
+ $kv = Auth_OpenID_KVForm::fromArray($pairs);
+
+ /* Invalid association types should be caught at constructor */
+ $callback = $this->_macs[$this->assoc_type];
+
+ return call_user_func_array($callback, array($this->secret, $kv));
+ }
+
+ /**
+ * Generate a signature for some fields in a dictionary
+ *
+ * @access private
+ * @param array $fields The fields to sign, in order; this is an
+ * array of strings.
+ * @param array $data Dictionary of values to sign (an array of
+ * string => string pairs).
+ * @return string $signature The signature, base64 encoded
+ */
+ function signMessage($message)
+ {
+ if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') ||
+ $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) {
+ // Already has a sig
+ return null;
+ }
+
+ $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ if ($extant_handle && ($extant_handle != $this->handle)) {
+ // raise ValueError("Message has a different association handle")
+ return null;
+ }
+
+ $signed_message = $message;
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
+ $this->handle);
+
+ $message_keys = array_keys($signed_message->toPostArgs());
+ $signed_list = array();
+ $signed_prefix = 'openid.';
+
+ foreach ($message_keys as $k) {
+ if (strpos($k, $signed_prefix) === 0) {
+ $signed_list[] = substr($k, strlen($signed_prefix));
+ }
+ }
+
+ $signed_list[] = 'signed';
+ sort($signed_list);
+
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed',
+ implode(',', $signed_list));
+ $sig = $this->getMessageSignature($signed_message);
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig);
+ return $signed_message;
+ }
+
+ /**
+ * Given a {@link Auth_OpenID_Message}, return the key/value pairs
+ * to be signed according to the signed list in the message. If
+ * the message lacks a signed list, return null.
+ *
+ * @access private
+ */
+ function _makePairs(&$message)
+ {
+ $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ if (!$signed || Auth_OpenID::isFailure($signed)) {
+ // raise ValueError('Message has no signed list: %s' % (message,))
+ return null;
+ }
+
+ $signed_list = explode(',', $signed);
+ $pairs = array();
+ $data = $message->toPostArgs();
+ foreach ($signed_list as $field) {
+ $pairs[] = array($field, Auth_OpenID::arrayGet($data,
+ 'openid.' .
+ $field, ''));
+ }
+ return $pairs;
+ }
+
+ /**
+ * Given an {@link Auth_OpenID_Message}, return the signature for
+ * the signed list in the message.
+ *
+ * @access private
+ */
+ function getMessageSignature(&$message)
+ {
+ $pairs = $this->_makePairs($message);
+ return base64_encode($this->sign($pairs));
+ }
+
+ /**
+ * Confirm that the signature of these fields matches the
+ * signature contained in the data.
+ *
+ * @access private
+ */
+ function checkMessageSignature(&$message)
+ {
+ $sig = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'sig');
+
+ if (!$sig || Auth_OpenID::isFailure($sig)) {
+ return false;
+ }
+
+ $calculated_sig = $this->getMessageSignature($message);
+ return $calculated_sig == $sig;
+ }
+}
+
+function Auth_OpenID_getSecretSize($assoc_type)
+{
+ if ($assoc_type == 'HMAC-SHA1') {
+ return 20;
+ } else if ($assoc_type == 'HMAC-SHA256') {
+ return 32;
+ } else {
+ return null;
+ }
+}
+
+function Auth_OpenID_getAllAssociationTypes()
+{
+ return array('HMAC-SHA1', 'HMAC-SHA256');
+}
+
+function Auth_OpenID_getSupportedAssociationTypes()
+{
+ $a = array('HMAC-SHA1');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $a[] = 'HMAC-SHA256';
+ }
+
+ return $a;
+}
+
+function Auth_OpenID_getSessionTypes($assoc_type)
+{
+ $assoc_to_session = array(
+ 'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'));
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $assoc_to_session['HMAC-SHA256'] =
+ array('DH-SHA256', 'no-encryption');
+ }
+
+ return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
+}
+
+function Auth_OpenID_checkSessionType($assoc_type, $session_type)
+{
+ if (!in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type))) {
+ return false;
+ }
+
+ return true;
+}
+
+function Auth_OpenID_getDefaultAssociationOrder()
+{
+ $order = array();
+
+ if (!Auth_OpenID_noMathSupport()) {
+ $order[] = array('HMAC-SHA1', 'DH-SHA1');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $order[] = array('HMAC-SHA256', 'DH-SHA256');
+ }
+ }
+
+ $order[] = array('HMAC-SHA1', 'no-encryption');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $order[] = array('HMAC-SHA256', 'no-encryption');
+ }
+
+ return $order;
+}
+
+function Auth_OpenID_getOnlyEncryptedOrder()
+{
+ $result = array();
+
+ foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) {
+ list($assoc, $session) = $pair;
+
+ if ($session != 'no-encryption') {
+ if (Auth_OpenID_HMACSHA256_SUPPORTED &&
+ ($assoc == 'HMAC-SHA256')) {
+ $result[] = $pair;
+ } else if ($assoc != 'HMAC-SHA256') {
+ $result[] = $pair;
+ }
+ }
+ }
+
+ return $result;
+}
+
+function &Auth_OpenID_getDefaultNegotiator()
+{
+ $x = new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getDefaultAssociationOrder());
+ return $x;
+}
+
+function &Auth_OpenID_getEncryptedNegotiator()
+{
+ $x = new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getOnlyEncryptedOrder());
+ return $x;
+}
+
+/**
+ * A session negotiator controls the allowed and preferred association
+ * types and association session types. Both the {@link
+ * Auth_OpenID_Consumer} and {@link Auth_OpenID_Server} use
+ * negotiators when creating associations.
+ *
+ * You can create and use negotiators if you:
+
+ * - Do not want to do Diffie-Hellman key exchange because you use
+ * transport-layer encryption (e.g. SSL)
+ *
+ * - Want to use only SHA-256 associations
+ *
+ * - Do not want to support plain-text associations over a non-secure
+ * channel
+ *
+ * It is up to you to set a policy for what kinds of associations to
+ * accept. By default, the library will make any kind of association
+ * that is allowed in the OpenID 2.0 specification.
+ *
+ * Use of negotiators in the library
+ * =================================
+ *
+ * When a consumer makes an association request, it calls {@link
+ * getAllowedType} to get the preferred association type and
+ * association session type.
+ *
+ * The server gets a request for a particular association/session type
+ * and calls {@link isAllowed} to determine if it should create an
+ * association. If it is supported, negotiation is complete. If it is
+ * not, the server calls {@link getAllowedType} to get an allowed
+ * association type to return to the consumer.
+ *
+ * If the consumer gets an error response indicating that the
+ * requested association/session type is not supported by the server
+ * that contains an assocation/session type to try, it calls {@link
+ * isAllowed} to determine if it should try again with the given
+ * combination of association/session type.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SessionNegotiator {
+ function Auth_OpenID_SessionNegotiator($allowed_types)
+ {
+ $this->allowed_types = array();
+ $this->setAllowedTypes($allowed_types);
+ }
+
+ /**
+ * Set the allowed association types, checking to make sure each
+ * combination is valid.
+ *
+ * @access private
+ */
+ function setAllowedTypes($allowed_types)
+ {
+ foreach ($allowed_types as $pair) {
+ list($assoc_type, $session_type) = $pair;
+ if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ return false;
+ }
+ }
+
+ $this->allowed_types = $allowed_types;
+ return true;
+ }
+
+ /**
+ * Add an association type and session type to the allowed types
+ * list. The assocation/session pairs are tried in the order that
+ * they are added.
+ *
+ * @access private
+ */
+ function addAllowedType($assoc_type, $session_type = null)
+ {
+ if ($this->allowed_types === null) {
+ $this->allowed_types = array();
+ }
+
+ if ($session_type === null) {
+ $available = Auth_OpenID_getSessionTypes($assoc_type);
+
+ if (!$available) {
+ return false;
+ }
+
+ foreach ($available as $session_type) {
+ $this->addAllowedType($assoc_type, $session_type);
+ }
+ } else {
+ if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ $this->allowed_types[] = array($assoc_type, $session_type);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Is this combination of association type and session type allowed?
+ function isAllowed($assoc_type, $session_type)
+ {
+ $assoc_good = in_array(array($assoc_type, $session_type),
+ $this->allowed_types);
+
+ $matches = in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type));
+
+ return ($assoc_good && $matches);
+ }
+
+ /**
+ * Get a pair of assocation type and session type that are
+ * supported.
+ */
+ function getAllowedType()
+ {
+ if (!$this->allowed_types) {
+ return array(null, null);
+ }
+
+ return $this->allowed_types[0];
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/BigMath.php b/Auth/OpenID/BigMath.php
new file mode 100644
index 0000000..4510494
--- /dev/null
+++ b/Auth/OpenID/BigMath.php
@@ -0,0 +1,471 @@
+<?php
+
+/**
+ * BigMath: A math library wrapper that abstracts out the underlying
+ * long integer library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Needed for random number generation
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * Need Auth_OpenID::bytes().
+ */
+require_once 'Auth/OpenID.php';
+
+/**
+ * The superclass of all big-integer math implementations
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_MathLibrary {
+ /**
+ * Given a long integer, returns the number converted to a binary
+ * string. This function accepts long integer values of arbitrary
+ * magnitude and uses the local large-number math library when
+ * available.
+ *
+ * @param integer $long The long number (can be a normal PHP
+ * integer or a number created by one of the available long number
+ * libraries)
+ * @return string $binary The binary version of $long
+ */
+ function longToBinary($long)
+ {
+ $cmp = $this->cmp($long, 0);
+ if ($cmp < 0) {
+ $msg = __FUNCTION__ . " takes only positive integers.";
+ trigger_error($msg, E_USER_ERROR);
+ return null;
+ }
+
+ if ($cmp == 0) {
+ return "\x00";
+ }
+
+ $bytes = array();
+
+ while ($this->cmp($long, 0) > 0) {
+ array_unshift($bytes, $this->mod($long, 256));
+ $long = $this->div($long, pow(2, 8));
+ }
+
+ if ($bytes && ($bytes[0] > 127)) {
+ array_unshift($bytes, 0);
+ }
+
+ $string = '';
+ foreach ($bytes as $byte) {
+ $string .= pack('C', $byte);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Given a binary string, returns the binary string converted to a
+ * long number.
+ *
+ * @param string $binary The binary version of a long number,
+ * probably as a result of calling longToBinary
+ * @return integer $long The long number equivalent of the binary
+ * string $str
+ */
+ function binaryToLong($str)
+ {
+ if ($str === null) {
+ return null;
+ }
+
+ // Use array_merge to return a zero-indexed array instead of a
+ // one-indexed array.
+ $bytes = array_merge(unpack('C*', $str));
+
+ $n = $this->init(0);
+
+ if ($bytes && ($bytes[0] > 127)) {
+ trigger_error("bytesToNum works only for positive integers.",
+ E_USER_WARNING);
+ return null;
+ }
+
+ foreach ($bytes as $byte) {
+ $n = $this->mul($n, pow(2, 8));
+ $n = $this->add($n, $byte);
+ }
+
+ return $n;
+ }
+
+ function base64ToLong($str)
+ {
+ $b64 = base64_decode($str);
+
+ if ($b64 === false) {
+ return false;
+ }
+
+ return $this->binaryToLong($b64);
+ }
+
+ function longToBase64($str)
+ {
+ return base64_encode($this->longToBinary($str));
+ }
+
+ /**
+ * Returns a random number in the specified range. This function
+ * accepts $start, $stop, and $step values of arbitrary magnitude
+ * and will utilize the local large-number math library when
+ * available.
+ *
+ * @param integer $start The start of the range, or the minimum
+ * random number to return
+ * @param integer $stop The end of the range, or the maximum
+ * random number to return
+ * @param integer $step The step size, such that $result - ($step
+ * * N) = $start for some N
+ * @return integer $result The resulting randomly-generated number
+ */
+ function rand($stop)
+ {
+ static $duplicate_cache = array();
+
+ // Used as the key for the duplicate cache
+ $rbytes = $this->longToBinary($stop);
+
+ if (array_key_exists($rbytes, $duplicate_cache)) {
+ list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+ } else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = Auth_OpenID::bytes($rbytes) - 1;
+ } else {
+ $nbytes = Auth_OpenID::bytes($rbytes);
+ }
+
+ $mxrand = $this->pow(256, $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = $this->mod($mxrand, $stop);
+
+ if (count($duplicate_cache) > 10) {
+ $duplicate_cache = array();
+ }
+
+ $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+ }
+
+ do {
+ $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
+ $n = $this->binaryToLong($bytes);
+ // Keep looping if this value is in the low duplicated range
+ } while ($this->cmp($n, $duplicate) < 0);
+
+ return $this->mod($n, $stop);
+ }
+}
+
+/**
+ * Exposes BCmath math library functionality.
+ *
+ * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
+ * by the BCMath extension.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
+ var $type = 'bcmath';
+
+ function add($x, $y)
+ {
+ return bcadd($x, $y);
+ }
+
+ function sub($x, $y)
+ {
+ return bcsub($x, $y);
+ }
+
+ function pow($base, $exponent)
+ {
+ return bcpow($base, $exponent);
+ }
+
+ function cmp($x, $y)
+ {
+ return bccomp($x, $y);
+ }
+
+ function init($number, $base = 10)
+ {
+ return $number;
+ }
+
+ function mod($base, $modulus)
+ {
+ return bcmod($base, $modulus);
+ }
+
+ function mul($x, $y)
+ {
+ return bcmul($x, $y);
+ }
+
+ function div($x, $y)
+ {
+ return bcdiv($x, $y);
+ }
+
+ /**
+ * Same as bcpowmod when bcpowmod is missing
+ *
+ * @access private
+ */
+ function _powmod($base, $exponent, $modulus)
+ {
+ $square = $this->mod($base, $modulus);
+ $result = 1;
+ while($this->cmp($exponent, 0) > 0) {
+ if ($this->mod($exponent, 2)) {
+ $result = $this->mod($this->mul($result, $square), $modulus);
+ }
+ $square = $this->mod($this->mul($square, $square), $modulus);
+ $exponent = $this->div($exponent, 2);
+ }
+ return $result;
+ }
+
+ function powmod($base, $exponent, $modulus)
+ {
+ if (function_exists('bcpowmod')) {
+ return bcpowmod($base, $exponent, $modulus);
+ } else {
+ return $this->_powmod($base, $exponent, $modulus);
+ }
+ }
+
+ function toString($num)
+ {
+ return $num;
+ }
+}
+
+/**
+ * Exposes GMP math library functionality.
+ *
+ * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
+ * by the GMP extension.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
+ var $type = 'gmp';
+
+ function add($x, $y)
+ {
+ return gmp_add($x, $y);
+ }
+
+ function sub($x, $y)
+ {
+ return gmp_sub($x, $y);
+ }
+
+ function pow($base, $exponent)
+ {
+ return gmp_pow($base, $exponent);
+ }
+
+ function cmp($x, $y)
+ {
+ return gmp_cmp($x, $y);
+ }
+
+ function init($number, $base = 10)
+ {
+ return gmp_init($number, $base);
+ }
+
+ function mod($base, $modulus)
+ {
+ return gmp_mod($base, $modulus);
+ }
+
+ function mul($x, $y)
+ {
+ return gmp_mul($x, $y);
+ }
+
+ function div($x, $y)
+ {
+ return gmp_div_q($x, $y);
+ }
+
+ function powmod($base, $exponent, $modulus)
+ {
+ return gmp_powm($base, $exponent, $modulus);
+ }
+
+ function toString($num)
+ {
+ return gmp_strval($num);
+ }
+}
+
+/**
+ * Define the supported extensions. An extension array has keys
+ * 'modules', 'extension', and 'class'. 'modules' is an array of PHP
+ * module names which the loading code will attempt to load. These
+ * values will be suffixed with a library file extension (e.g. ".so").
+ * 'extension' is the name of a PHP extension which will be tested
+ * before 'modules' are loaded. 'class' is the string name of a
+ * {@link Auth_OpenID_MathWrapper} subclass which should be
+ * instantiated if a given extension is present.
+ *
+ * You can define new math library implementations and add them to
+ * this array.
+ */
+function Auth_OpenID_math_extensions()
+{
+ $result = array();
+
+ if (!defined('Auth_OpenID_BUGGY_GMP')) {
+ $result[] =
+ array('modules' => array('gmp', 'php_gmp'),
+ 'extension' => 'gmp',
+ 'class' => 'Auth_OpenID_GmpMathWrapper');
+ }
+
+ $result[] = array(
+ 'modules' => array('bcmath', 'php_bcmath'),
+ 'extension' => 'bcmath',
+ 'class' => 'Auth_OpenID_BcMathWrapper');
+
+ return $result;
+}
+
+/**
+ * Detect which (if any) math library is available
+ */
+function Auth_OpenID_detectMathLibrary($exts)
+{
+ $loaded = false;
+
+ foreach ($exts as $extension) {
+ // See if the extension specified is already loaded.
+ if ($extension['extension'] &&
+ extension_loaded($extension['extension'])) {
+ $loaded = true;
+ }
+
+ // Try to load dynamic modules.
+ if (!$loaded) {
+ foreach ($extension['modules'] as $module) {
+ if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
+ $loaded = true;
+ break;
+ }
+ }
+ }
+
+ // If the load succeeded, supply an instance of
+ // Auth_OpenID_MathWrapper which wraps the specified
+ // module's functionality.
+ if ($loaded) {
+ return $extension;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * {@link Auth_OpenID_getMathLib} checks for the presence of long
+ * number extension modules and returns an instance of
+ * {@link Auth_OpenID_MathWrapper} which exposes the module's
+ * functionality.
+ *
+ * Checks for the existence of an extension module described by the
+ * result of {@link Auth_OpenID_math_extensions()} and returns an
+ * instance of a wrapper for that extension module. If no extension
+ * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
+ * returned, which wraps the native PHP integer implementation. The
+ * proper calling convention for this method is $lib =&
+ * Auth_OpenID_getMathLib().
+ *
+ * This function checks for the existence of specific long number
+ * implementations in the following order: GMP followed by BCmath.
+ *
+ * @return Auth_OpenID_MathWrapper $instance An instance of
+ * {@link Auth_OpenID_MathWrapper} or one of its subclasses
+ *
+ * @package OpenID
+ */
+function &Auth_OpenID_getMathLib()
+{
+ // The instance of Auth_OpenID_MathWrapper that we choose to
+ // supply will be stored here, so that subseqent calls to this
+ // method will return a reference to the same object.
+ static $lib = null;
+
+ if (isset($lib)) {
+ return $lib;
+ }
+
+ if (Auth_OpenID_noMathSupport()) {
+ $null = null;
+ return $null;
+ }
+
+ // If this method has not been called before, look at
+ // Auth_OpenID_math_extensions and try to find an extension that
+ // works.
+ $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
+ if ($ext === false) {
+ $tried = array();
+ foreach (Auth_OpenID_math_extensions() as $extinfo) {
+ $tried[] = $extinfo['extension'];
+ }
+ $triedstr = implode(", ", $tried);
+
+ Auth_OpenID_setNoMathSupport();
+
+ $result = null;
+ return $result;
+ }
+
+ // Instantiate a new wrapper
+ $class = $ext['class'];
+ $lib = new $class();
+
+ return $lib;
+}
+
+function Auth_OpenID_setNoMathSupport()
+{
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ define('Auth_OpenID_NO_MATH_SUPPORT', true);
+ }
+}
+
+function Auth_OpenID_noMathSupport()
+{
+ return defined('Auth_OpenID_NO_MATH_SUPPORT');
+}
+
+?>
diff --git a/Auth/OpenID/Consumer.php b/Auth/OpenID/Consumer.php
new file mode 100644
index 0000000..a72684c
--- /dev/null
+++ b/Auth/OpenID/Consumer.php
@@ -0,0 +1,2229 @@
+<?php
+
+/**
+ * This module documents the main interface with the OpenID consumer
+ * library. The only part of the library which has to be used and
+ * isn't documented in full here is the store required to create an
+ * Auth_OpenID_Consumer instance. More on the abstract store type and
+ * concrete implementations of it that are provided in the
+ * documentation for the Auth_OpenID_Consumer constructor.
+ *
+ * OVERVIEW
+ *
+ * The OpenID identity verification process most commonly uses the
+ * following steps, as visible to the user of this library:
+ *
+ * 1. The user enters their OpenID into a field on the consumer's
+ * site, and hits a login button.
+ * 2. The consumer site discovers the user's OpenID server using the
+ * YADIS protocol.
+ * 3. The consumer site sends the browser a redirect to the identity
+ * server. This is the authentication request as described in
+ * the OpenID specification.
+ * 4. The identity server's site sends the browser a redirect back
+ * to the consumer site. This redirect contains the server's
+ * response to the authentication request.
+ *
+ * The most important part of the flow to note is the consumer's site
+ * must handle two separate HTTP requests in order to perform the full
+ * identity check.
+ *
+ * LIBRARY DESIGN
+ *
+ * This consumer library is designed with that flow in mind. The goal
+ * is to make it as easy as possible to perform the above steps
+ * securely.
+ *
+ * At a high level, there are two important parts in the consumer
+ * library. The first important part is this module, which contains
+ * the interface to actually use this library. The second is the
+ * Auth_OpenID_Interface class, which describes the interface to use
+ * if you need to create a custom method for storing the state this
+ * library needs to maintain between requests.
+ *
+ * In general, the second part is less important for users of the
+ * library to know about, as several implementations are provided
+ * which cover a wide variety of situations in which consumers may use
+ * the library.
+ *
+ * This module contains a class, Auth_OpenID_Consumer, with methods
+ * corresponding to the actions necessary in each of steps 2, 3, and 4
+ * described in the overview. Use of this library should be as easy
+ * as creating an Auth_OpenID_Consumer instance and calling the
+ * methods appropriate for the action the site wants to take.
+ *
+ * STORES AND DUMB MODE
+ *
+ * OpenID is a protocol that works best when the consumer site is able
+ * to store some state. This is the normal mode of operation for the
+ * protocol, and is sometimes referred to as smart mode. There is
+ * also a fallback mode, known as dumb mode, which is available when
+ * the consumer site is not able to store state. This mode should be
+ * avoided when possible, as it leaves the implementation more
+ * vulnerable to replay attacks.
+ *
+ * The mode the library works in for normal operation is determined by
+ * the store that it is given. The store is an abstraction that
+ * handles the data that the consumer needs to manage between http
+ * requests in order to operate efficiently and securely.
+ *
+ * Several store implementation are provided, and the interface is
+ * fully documented so that custom stores can be used as well. See
+ * the documentation for the Auth_OpenID_Consumer class for more
+ * information on the interface for stores. The implementations that
+ * are provided allow the consumer site to store the necessary data in
+ * several different ways, including several SQL databases and normal
+ * files on disk.
+ *
+ * There is an additional concrete store provided that puts the system
+ * in dumb mode. This is not recommended, as it removes the library's
+ * ability to stop replay attacks reliably. It still uses time-based
+ * checking to make replay attacks only possible within a small
+ * window, but they remain possible within that window. This store
+ * should only be used if the consumer site has no way to retain data
+ * between requests at all.
+ *
+ * IMMEDIATE MODE
+ *
+ * In the flow described above, the user may need to confirm to the
+ * lidentity server that it's ok to authorize his or her identity.
+ * The server may draw pages asking for information from the user
+ * before it redirects the browser back to the consumer's site. This
+ * is generally transparent to the consumer site, so it is typically
+ * ignored as an implementation detail.
+ *
+ * There can be times, however, where the consumer site wants to get a
+ * response immediately. When this is the case, the consumer can put
+ * the library in immediate mode. In immediate mode, there is an
+ * extra response possible from the server, which is essentially the
+ * server reporting that it doesn't have enough information to answer
+ * the question yet.
+ *
+ * USING THIS LIBRARY
+ *
+ * Integrating this library into an application is usually a
+ * relatively straightforward process. The process should basically
+ * follow this plan:
+ *
+ * Add an OpenID login field somewhere on your site. When an OpenID
+ * is entered in that field and the form is submitted, it should make
+ * a request to the your site which includes that OpenID URL.
+ *
+ * First, the application should instantiate the Auth_OpenID_Consumer
+ * class using the store of choice (Auth_OpenID_FileStore or one of
+ * the SQL-based stores). If the application has a custom
+ * session-management implementation, an object implementing the
+ * {@link Auth_Yadis_PHPSession} interface should be passed as the
+ * second parameter. Otherwise, the default uses $_SESSION.
+ *
+ * Next, the application should call the Auth_OpenID_Consumer object's
+ * 'begin' method. This method takes the OpenID URL. The 'begin'
+ * method returns an Auth_OpenID_AuthRequest object.
+ *
+ * Next, the application should call the 'redirectURL' method of the
+ * Auth_OpenID_AuthRequest object. The 'return_to' URL parameter is
+ * the URL that the OpenID server will send the user back to after
+ * attempting to verify his or her identity. The 'trust_root' is the
+ * URL (or URL pattern) that identifies your web site to the user when
+ * he or she is authorizing it. Send a redirect to the resulting URL
+ * to the user's browser.
+ *
+ * That's the first half of the authentication process. The second
+ * half of the process is done after the user's ID server sends the
+ * user's browser a redirect back to your site to complete their
+ * login.
+ *
+ * When that happens, the user will contact your site at the URL given
+ * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
+ * call made above. The request will have several query parameters
+ * added to the URL by the identity server as the information
+ * necessary to finish the request.
+ *
+ * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
+ * call its 'complete' method, passing in all the received query
+ * arguments.
+ *
+ * There are multiple possible return types possible from that
+ * method. These indicate the whether or not the login was successful,
+ * and include any additional information appropriate for their type.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/HMAC.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/Nonce.php";
+require_once "Auth/OpenID/Discover.php";
+require_once "Auth/OpenID/URINorm.php";
+require_once "Auth/Yadis/Manager.php";
+require_once "Auth/Yadis/XRI.php";
+
+/**
+ * This is the status code returned when the complete method returns
+ * successfully.
+ */
+define('Auth_OpenID_SUCCESS', 'success');
+
+/**
+ * Status to indicate cancellation of OpenID authentication.
+ */
+define('Auth_OpenID_CANCEL', 'cancel');
+
+/**
+ * This is the status code completeAuth returns when the value it
+ * received indicated an invalid login.
+ */
+define('Auth_OpenID_FAILURE', 'failure');
+
+/**
+ * This is the status code completeAuth returns when the
+ * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
+ * identity server sends back a URL to send the user to to complete his
+ * or her login.
+ */
+define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
+
+/**
+ * This is the status code beginAuth returns when the page fetched
+ * from the entered OpenID URL doesn't contain the necessary link tags
+ * to function as an identity page.
+ */
+define('Auth_OpenID_PARSE_ERROR', 'parse error');
+
+/**
+ * An OpenID consumer implementation that performs discovery and does
+ * session management. See the Consumer.php file documentation for
+ * more information.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Consumer {
+
+ /**
+ * @access private
+ */
+ var $discoverMethod = 'Auth_OpenID_discover';
+
+ /**
+ * @access private
+ */
+ var $session_key_prefix = "_openid_consumer_";
+
+ /**
+ * @access private
+ */
+ var $_token_suffix = "last_token";
+
+ /**
+ * Initialize a Consumer instance.
+ *
+ * You should create a new instance of the Consumer object with
+ * every HTTP request that handles OpenID transactions.
+ *
+ * @param Auth_OpenID_OpenIDStore $store This must be an object
+ * that implements the interface in {@link
+ * Auth_OpenID_OpenIDStore}. Several concrete implementations are
+ * provided, to cover most common use cases. For stores backed by
+ * MySQL, PostgreSQL, or SQLite, see the {@link
+ * Auth_OpenID_SQLStore} class and its sublcasses. For a
+ * filesystem-backed store, see the {@link Auth_OpenID_FileStore}
+ * module. As a last resort, if it isn't possible for the server
+ * to store state at all, an instance of {@link
+ * Auth_OpenID_DumbStore} can be used.
+ *
+ * @param mixed $session An object which implements the interface
+ * of the {@link Auth_Yadis_PHPSession} class. Particularly, this
+ * object is expected to have these methods: get($key), set($key),
+ * $value), and del($key). This defaults to a session object
+ * which wraps PHP's native session machinery. You should only
+ * need to pass something here if you have your own sessioning
+ * implementation.
+ *
+ * @param str $consumer_cls The name of the class to instantiate
+ * when creating the internal consumer object. This is used for
+ * testing.
+ */
+ function Auth_OpenID_Consumer(&$store, $session = null,
+ $consumer_cls = null)
+ {
+ if ($session === null) {
+ $session = new Auth_Yadis_PHPSession();
+ }
+
+ $this->session =& $session;
+
+ if ($consumer_cls !== null) {
+ $this->consumer =& new $consumer_cls($store);
+ } else {
+ $this->consumer =& new Auth_OpenID_GenericConsumer($store);
+ }
+
+ $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
+ }
+
+ /**
+ * Used in testing to define the discovery mechanism.
+ *
+ * @access private
+ */
+ function getDiscoveryObject(&$session, $openid_url,
+ $session_key_prefix)
+ {
+ return new Auth_Yadis_Discovery($session, $openid_url,
+ $session_key_prefix);
+ }
+
+ /**
+ * Start the OpenID authentication process. See steps 1-2 in the
+ * overview at the top of this file.
+ *
+ * @param string $user_url Identity URL given by the user. This
+ * method performs a textual transformation of the URL to try and
+ * make sure it is normalized. For example, a user_url of
+ * example.com will be normalized to http://example.com/
+ * normalizing and resolving any redirects the server might issue.
+ *
+ * @param bool $anonymous True if the OpenID request is to be sent
+ * to the server without any identifier information. Use this
+ * when you want to transport data but don't want to do OpenID
+ * authentication with identifiers.
+ *
+ * @return Auth_OpenID_AuthRequest $auth_request An object
+ * containing the discovered information will be returned, with a
+ * method for building a redirect URL to the server, as described
+ * in step 3 of the overview. This object may also be used to add
+ * extension arguments to the request, using its 'addExtensionArg'
+ * method.
+ */
+ function begin($user_url, $anonymous=false)
+ {
+ $openid_url = $user_url;
+
+ $disco = $this->getDiscoveryObject($this->session,
+ $openid_url,
+ $this->session_key_prefix);
+
+ // Set the 'stale' attribute of the manager. If discovery
+ // fails in a fatal way, the stale flag will cause the manager
+ // to be cleaned up next time discovery is attempted.
+
+ $m = $disco->getManager();
+ $loader = new Auth_Yadis_ManagerLoader();
+
+ if ($m) {
+ if ($m->stale) {
+ $disco->destroyManager();
+ } else {
+ $m->stale = true;
+ $disco->session->set($disco->session_key,
+ serialize($loader->toSession($m)));
+ }
+ }
+
+ $endpoint = $disco->getNextService($this->discoverMethod,
+ $this->consumer->fetcher);
+
+ // Reset the 'stale' attribute of the manager.
+ $m =& $disco->getManager();
+ if ($m) {
+ $m->stale = false;
+ $disco->session->set($disco->session_key,
+ serialize($loader->toSession($m)));
+ }
+
+ if ($endpoint === null) {
+ return null;
+ } else {
+ return $this->beginWithoutDiscovery($endpoint,
+ $anonymous);
+ }
+ }
+
+ /**
+ * Start OpenID verification without doing OpenID server
+ * discovery. This method is used internally by Consumer.begin
+ * after discovery is performed, and exists to provide an
+ * interface for library users needing to perform their own
+ * discovery.
+ *
+ * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
+ * endpoint descriptor.
+ *
+ * @param bool anonymous Set to true if you want to perform OpenID
+ * without identifiers.
+ *
+ * @return Auth_OpenID_AuthRequest $auth_request An OpenID
+ * authentication request object.
+ */
+ function &beginWithoutDiscovery($endpoint, $anonymous=false)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $auth_req = $this->consumer->begin($endpoint);
+ $this->session->set($this->_token_key,
+ $loader->toSession($auth_req->endpoint));
+ if (!$auth_req->setAnonymous($anonymous)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "OpenID 1 requests MUST include the identifier " .
+ "in the request.");
+ }
+ return $auth_req;
+ }
+
+ /**
+ * Called to interpret the server's response to an OpenID
+ * request. It is called in step 4 of the flow described in the
+ * consumer overview.
+ *
+ * @param string $current_url The URL used to invoke the application.
+ * Extract the URL from your application's web
+ * request framework and specify it here to have it checked
+ * against the openid.current_url value in the response. If
+ * the current_url URL check fails, the status of the
+ * completion will be FAILURE.
+ *
+ * @param array $query An array of the query parameters (key =>
+ * value pairs) for this HTTP request. Defaults to null. If
+ * null, the GET or POST data are automatically gotten from the
+ * PHP environment. It is only useful to override $query for
+ * testing.
+ *
+ * @return Auth_OpenID_ConsumerResponse $response A instance of an
+ * Auth_OpenID_ConsumerResponse subclass. The type of response is
+ * indicated by the status attribute, which will be one of
+ * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
+ */
+ function complete($current_url, $query=null)
+ {
+ if ($current_url && !is_string($current_url)) {
+ // This is ugly, but we need to complain loudly when
+ // someone uses the API incorrectly.
+ trigger_error("current_url must be a string; see NEWS file " .
+ "for upgrading notes.",
+ E_USER_ERROR);
+ }
+
+ if ($query === null) {
+ $query = Auth_OpenID::getQuery();
+ }
+
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $endpoint_data = $this->session->get($this->_token_key);
+ $endpoint =
+ $loader->fromSession($endpoint_data);
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $response = $this->consumer->complete($message, $endpoint,
+ $current_url);
+ $this->session->del($this->_token_key);
+
+ if (in_array($response->status, array(Auth_OpenID_SUCCESS,
+ Auth_OpenID_CANCEL))) {
+ if ($response->identity_url !== null) {
+ $disco = $this->getDiscoveryObject($this->session,
+ $response->identity_url,
+ $this->session_key_prefix);
+ $disco->cleanup(true);
+ }
+ }
+
+ return $response;
+ }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA1 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+ var $session_type = 'DH-SHA1';
+ var $hash_func = 'Auth_OpenID_SHA1';
+ var $secret_size = 20;
+ var $allowed_assoc_types = array('HMAC-SHA1');
+
+ function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
+ {
+ if ($dh === null) {
+ $dh = new Auth_OpenID_DiffieHellman();
+ }
+
+ $this->dh = $dh;
+ }
+
+ function getRequest()
+ {
+ $math =& Auth_OpenID_getMathLib();
+
+ $cpub = $math->longToBase64($this->dh->public);
+
+ $args = array('dh_consumer_public' => $cpub);
+
+ if (!$this->dh->usingDefaultValues()) {
+ $args = array_merge($args, array(
+ 'dh_modulus' =>
+ $math->longToBase64($this->dh->mod),
+ 'dh_gen' =>
+ $math->longToBase64($this->dh->gen)));
+ }
+
+ return $args;
+ }
+
+ function extractSecret($response)
+ {
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+ 'dh_server_public')) {
+ return null;
+ }
+
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+ 'enc_mac_key')) {
+ return null;
+ }
+
+ $math =& Auth_OpenID_getMathLib();
+
+ $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
+ 'dh_server_public'));
+ $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+ 'enc_mac_key'));
+
+ return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
+ }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA256 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
+ Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+ var $session_type = 'DH-SHA256';
+ var $hash_func = 'Auth_OpenID_SHA256';
+ var $secret_size = 32;
+ var $allowed_assoc_types = array('HMAC-SHA256');
+}
+
+/**
+ * A class implementing plaintext consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextConsumerSession {
+ var $session_type = 'no-encryption';
+ var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
+
+ function getRequest()
+ {
+ return array();
+ }
+
+ function extractSecret($response)
+ {
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) {
+ return null;
+ }
+
+ return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+ 'mac_key'));
+ }
+}
+
+/**
+ * Returns available session types.
+ */
+function Auth_OpenID_getAvailableSessionTypes()
+{
+ $types = array(
+ 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
+ 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
+
+ return $types;
+}
+
+/**
+ * This class is the interface to the OpenID consumer logic.
+ * Instances of it maintain no per-request state, so they can be
+ * reused (or even used by multiple threads concurrently) as needed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_GenericConsumer {
+ /**
+ * @access private
+ */
+ var $discoverMethod = 'Auth_OpenID_discover';
+
+ /**
+ * This consumer's store object.
+ */
+ var $store;
+
+ /**
+ * @access private
+ */
+ var $_use_assocs;
+
+ /**
+ * @access private
+ */
+ var $openid1_nonce_query_arg_name = 'janrain_nonce';
+
+ /**
+ * Another query parameter that gets added to the return_to for
+ * OpenID 1; if the user's session state is lost, use this claimed
+ * identifier to do discovery when verifying the response.
+ */
+ var $openid1_return_to_identifier_name = 'openid1_claimed_id';
+
+ /**
+ * This method initializes a new {@link Auth_OpenID_Consumer}
+ * instance to access the library.
+ *
+ * @param Auth_OpenID_OpenIDStore $store This must be an object
+ * that implements the interface in {@link Auth_OpenID_OpenIDStore}.
+ * Several concrete implementations are provided, to cover most common use
+ * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see
+ * the {@link Auth_OpenID_SQLStore} class and its sublcasses. For a
+ * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
+ * As a last resort, if it isn't possible for the server to store
+ * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
+ *
+ * @param bool $immediate This is an optional boolean value. It
+ * controls whether the library uses immediate mode, as explained
+ * in the module description. The default value is False, which
+ * disables immediate mode.
+ */
+ function Auth_OpenID_GenericConsumer(&$store)
+ {
+ $this->store =& $store;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
+ $this->_use_assocs = ($this->store ? true : false);
+
+ $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ $this->session_types = Auth_OpenID_getAvailableSessionTypes();
+ }
+
+ /**
+ * Called to begin OpenID authentication using the specified
+ * {@link Auth_OpenID_ServiceEndpoint}.
+ *
+ * @access private
+ */
+ function begin($service_endpoint)
+ {
+ $assoc = $this->_getAssociation($service_endpoint);
+ $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
+ $r->return_to_args[$this->openid1_nonce_query_arg_name] =
+ Auth_OpenID_mkNonce();
+
+ if ($r->message->isOpenID1()) {
+ $r->return_to_args[$this->openid1_return_to_identifier_name] =
+ $r->endpoint->claimed_id;
+ }
+
+ return $r;
+ }
+
+ /**
+ * Given an {@link Auth_OpenID_Message}, {@link
+ * Auth_OpenID_ServiceEndpoint} and optional return_to URL,
+ * complete OpenID authentication.
+ *
+ * @access private
+ */
+ function complete($message, $endpoint, $return_to)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+ '<no mode set>');
+
+ $mode_methods = array(
+ 'cancel' => '_complete_cancel',
+ 'error' => '_complete_error',
+ 'setup_needed' => '_complete_setup_needed',
+ 'id_res' => '_complete_id_res',
+ );
+
+ $method = Auth_OpenID::arrayGet($mode_methods, $mode,
+ '_completeInvalid');
+
+ return call_user_func_array(array(&$this, $method),
+ array($message, $endpoint, $return_to));
+ }
+
+ /**
+ * @access private
+ */
+ function _completeInvalid($message, &$endpoint, $unused)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+ '<No mode set>');
+
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf("Invalid openid.mode '%s'", $mode));
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_cancel($message, &$endpoint, $unused)
+ {
+ return new Auth_OpenID_CancelResponse($endpoint);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_error($message, &$endpoint, $unused)
+ {
+ $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
+ $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
+ $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
+
+ return new Auth_OpenID_FailureResponse($endpoint, $error,
+ $contact, $reference);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_setup_needed($message, &$endpoint, $unused)
+ {
+ if (!$message->isOpenID2()) {
+ return $this->_completeInvalid($message, $endpoint);
+ }
+
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'user_setup_url');
+ return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_id_res($message, &$endpoint, $return_to)
+ {
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'user_setup_url');
+
+ if ($this->_checkSetupNeeded($message)) {
+ return new Auth_OpenID_SetupNeededResponse(
+ $endpoint, $user_setup_url);
+ } else {
+ return $this->_doIdRes($message, $endpoint, $return_to);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _checkSetupNeeded($message)
+ {
+ // In OpenID 1, we check to see if this is a cancel from
+ // immediate mode by the presence of the user_setup_url
+ // parameter.
+ if ($message->isOpenID1()) {
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'user_setup_url');
+ if ($user_setup_url !== null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _doIdRes($message, $endpoint, $return_to)
+ {
+ // Checks for presence of appropriate fields (and checks
+ // signed list fields)
+ $result = $this->_idResCheckForFields($message);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ if (!$this->_checkReturnTo($message, $return_to)) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("return_to does not match return URL. Expected %s, got %s",
+ $return_to,
+ $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
+ }
+
+ // Verify discovery information:
+ $result = $this->_verifyDiscoveryResults($message, $endpoint);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $endpoint = $result;
+
+ $result = $this->_idResCheckSignature($message,
+ $endpoint->server_url);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $result = $this->_idResCheckNonce($message, $endpoint);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
+ Auth_OpenID_NO_DEFAULT);
+ if (Auth_OpenID::isFailure($signed_list_str)) {
+ return $signed_list_str;
+ }
+ $signed_list = explode(',', $signed_list_str);
+
+ $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
+
+ return new Auth_OpenID_SuccessResponse($endpoint, $message,
+ $signed_fields);
+
+ }
+
+ /**
+ * @access private
+ */
+ function _checkReturnTo($message, $return_to)
+ {
+ // Check an OpenID message and its openid.return_to value
+ // against a return_to URL from an application. Return True
+ // on success, False on failure.
+
+ // Check the openid.return_to args against args in the
+ // original message.
+ $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
+ $message->toPostArgs());
+ if (Auth_OpenID::isFailure($result)) {
+ return false;
+ }
+
+ // Check the return_to base URL against the one in the
+ // message.
+ $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+ if (Auth_OpenID::isFailure($return_to)) {
+ // XXX log me
+ return false;
+ }
+
+ $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
+ $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
+
+ // If port is absent from both, add it so it's equal in the
+ // check below.
+ if ((!array_key_exists('port', $return_to_parts)) &&
+ (!array_key_exists('port', $msg_return_to_parts))) {
+ $return_to_parts['port'] = null;
+ $msg_return_to_parts['port'] = null;
+ }
+
+ // If path is absent from both, add it so it's equal in the
+ // check below.
+ if ((!array_key_exists('path', $return_to_parts)) &&
+ (!array_key_exists('path', $msg_return_to_parts))) {
+ $return_to_parts['path'] = null;
+ $msg_return_to_parts['path'] = null;
+ }
+
+ // The URL scheme, authority, and path MUST be the same
+ // between the two URLs.
+ foreach (array('scheme', 'host', 'port', 'path') as $component) {
+ // If the url component is absent in either URL, fail.
+ // There should always be a scheme, host, port, and path.
+ if (!array_key_exists($component, $return_to_parts)) {
+ return false;
+ }
+
+ if (!array_key_exists($component, $msg_return_to_parts)) {
+ return false;
+ }
+
+ if (Auth_OpenID::arrayGet($return_to_parts, $component) !==
+ Auth_OpenID::arrayGet($msg_return_to_parts, $component)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyReturnToArgs($query)
+ {
+ // Verify that the arguments in the return_to URL are present in this
+ // response.
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
+
+ if (Auth_OpenID::isFailure($return_to)) {
+ return $return_to;
+ }
+ // XXX: this should be checked by _idResCheckForFields
+ if (!$return_to) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Response has no return_to");
+ }
+
+ $parsed_url = parse_url($return_to);
+
+ $q = array();
+ if (array_key_exists('query', $parsed_url)) {
+ $rt_query = $parsed_url['query'];
+ $q = Auth_OpenID::parse_str($rt_query);
+ }
+
+ foreach ($q as $rt_key => $rt_value) {
+ if (!array_key_exists($rt_key, $query)) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("return_to parameter %s absent from query", $rt_key));
+ } else {
+ $value = $query[$rt_key];
+ if ($rt_value != $value) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("parameter %s value %s does not match " .
+ "return_to value %s", $rt_key,
+ $value, $rt_value));
+ }
+ }
+ }
+
+ // Make sure all non-OpenID arguments in the response are also
+ // in the signed return_to.
+ $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
+ foreach ($bare_args as $key => $value) {
+ if (Auth_OpenID::arrayGet($q, $key) != $value) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("Parameter %s = %s not in return_to URL",
+ $key, $value));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckSignature($message, $server_url)
+ {
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+ if (Auth_OpenID::isFailure($assoc_handle)) {
+ return $assoc_handle;
+ }
+
+ $assoc = $this->store->getAssociation($server_url, $assoc_handle);
+
+ if ($assoc) {
+ if ($assoc->getExpiresIn() <= 0) {
+ // XXX: It might be a good idea sometimes to re-start
+ // the authentication with a new association. Doing it
+ // automatically opens the possibility for
+ // denial-of-service by a server that just returns
+ // expired associations (or really short-lived
+ // associations)
+ return new Auth_OpenID_FailureResponse(null,
+ 'Association with ' . $server_url . ' expired');
+ }
+
+ if (!$assoc->checkMessageSignature($message)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Bad signature");
+ }
+ } else {
+ // It's not an association we know about. Stateless mode
+ // is our only possible path for recovery. XXX - async
+ // framework will not want to block on this call to
+ // _checkAuth.
+ if (!$this->_checkAuth($message, $server_url)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Server denied check_authentication");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResults($message, $endpoint=null)
+ {
+ if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) {
+ return $this->_verifyDiscoveryResultsOpenID2($message,
+ $endpoint);
+ } else {
+ return $this->_verifyDiscoveryResultsOpenID1($message,
+ $endpoint);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResultsOpenID1($message, $endpoint)
+ {
+ $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
+ $this->openid1_return_to_identifier_name);
+
+ if (($endpoint === null) && ($claimed_id === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'When using OpenID 1, the claimed ID must be supplied, ' .
+ 'either by passing it through as a return_to parameter ' .
+ 'or by using a session, and supplied to the GenericConsumer ' .
+ 'as the argument to complete()');
+ } else if (($endpoint !== null) && ($claimed_id === null)) {
+ $claimed_id = $endpoint->claimed_id;
+ }
+
+ $to_match = new Auth_OpenID_ServiceEndpoint();
+ $to_match->type_uris = array(Auth_OpenID_TYPE_1_1);
+ $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'identity');
+
+ // Restore delegate information from the initiation phase
+ $to_match->claimed_id = $claimed_id;
+
+ if ($to_match->local_id === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Missing required field openid.identity");
+ }
+
+ $to_match_1_0 = $to_match->copy();
+ $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
+
+ if ($endpoint !== null) {
+ $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+ if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
+ $result = $this->_verifyDiscoverySingle($endpoint,
+ $to_match_1_0);
+ }
+
+ if (Auth_OpenID::isFailure($result)) {
+ // oidutil.log("Error attempting to use stored
+ // discovery information: " + str(e))
+ // oidutil.log("Attempting discovery to
+ // verify endpoint")
+ } else {
+ return $endpoint;
+ }
+ }
+
+ // Endpoint is either bad (failed verification) or None
+ return $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match, $to_match_1_0));
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoverySingle($endpoint, $to_match)
+ {
+ // Every type URI that's in the to_match endpoint has to be
+ // present in the discovered endpoint.
+ foreach ($to_match->type_uris as $type_uri) {
+ if (!$endpoint->usesExtension($type_uri)) {
+ return new Auth_OpenID_TypeURIMismatch($endpoint,
+ "Required type ".$type_uri." not present");
+ }
+ }
+
+ // Fragments do not influence discovery, so we can't compare a
+ // claimed identifier with a fragment to discovered
+ // information.
+ list($defragged_claimed_id, $_) =
+ Auth_OpenID::urldefrag($to_match->claimed_id);
+
+ if ($defragged_claimed_id != $endpoint->claimed_id) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('Claimed ID does not match (different subjects!), ' .
+ 'Expected %s, got %s', $defragged_claimed_id,
+ $endpoint->claimed_id));
+ }
+
+ if ($to_match->getLocalID() != $endpoint->getLocalID()) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('local_id mismatch. Expected %s, got %s',
+ $to_match->getLocalID(), $endpoint->getLocalID()));
+ }
+
+ // If the server URL is None, this must be an OpenID 1
+ // response, because op_endpoint is a required parameter in
+ // OpenID 2. In that case, we don't actually care what the
+ // discovered server_url is, because signature checking or
+ // check_auth should take care of that check for us.
+ if ($to_match->server_url === null) {
+ if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Preferred namespace mismatch (bug)");
+ }
+ } else if ($to_match->server_url != $endpoint->server_url) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('OP Endpoint mismatch. Expected %s, got %s',
+ $to_match->server_url, $endpoint->server_url));
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResultsOpenID2($message, $endpoint)
+ {
+ $to_match = new Auth_OpenID_ServiceEndpoint();
+ $to_match->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'claimed_id');
+
+ $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'identity');
+
+ $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'op_endpoint');
+
+ if ($to_match->server_url === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "OP Endpoint URL missing");
+ }
+
+ // claimed_id and identifier must both be present or both be
+ // absent
+ if (($to_match->claimed_id === null) &&
+ ($to_match->local_id !== null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'openid.identity is present without openid.claimed_id');
+ }
+
+ if (($to_match->claimed_id !== null) &&
+ ($to_match->local_id === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'openid.claimed_id is present without openid.identity');
+ }
+
+ if ($to_match->claimed_id === null) {
+ // This is a response without identifiers, so there's
+ // really no checking that we can do, so return an
+ // endpoint that's for the specified `openid.op_endpoint'
+ return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
+ $to_match->server_url);
+ }
+
+ if (!$endpoint) {
+ // The claimed ID doesn't match, so we have to do
+ // discovery again. This covers not using sessions, OP
+ // identifier endpoints and responses that didn't match
+ // the original request.
+ // oidutil.log('No pre-discovered information supplied.')
+ return $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match));
+ } else {
+
+ // The claimed ID matches, so we use the endpoint that we
+ // discovered in initiation. This should be the most
+ // common case.
+ $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+ if (Auth_OpenID::isFailure($result)) {
+ $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match));
+ if (Auth_OpenID::isFailure($endpoint)) {
+ return $endpoint;
+ }
+ }
+ }
+
+ // The endpoint we return should have the claimed ID from the
+ // message we just verified, fragment and all.
+ if ($endpoint->claimed_id != $to_match->claimed_id) {
+ $endpoint->claimed_id = $to_match->claimed_id;
+ }
+
+ return $endpoint;
+ }
+
+ /**
+ * @access private
+ */
+ function _discoverAndVerify($claimed_id, $to_match_endpoints)
+ {
+ // oidutil.log('Performing discovery on %s' % (claimed_id,))
+ list($unused, $services) = call_user_func($this->discoverMethod,
+ $claimed_id,
+ $this->fetcher);
+
+ if (!$services) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("No OpenID information found at %s",
+ $claimed_id));
+ }
+
+ return $this->_verifyDiscoveryServices($claimed_id, $services,
+ $to_match_endpoints);
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryServices($claimed_id,
+ &$services, &$to_match_endpoints)
+ {
+ // Search the services resulting from discovery to find one
+ // that matches the information from the assertion
+
+ foreach ($services as $endpoint) {
+ foreach ($to_match_endpoints as $to_match_endpoint) {
+ $result = $this->_verifyDiscoverySingle($endpoint,
+ $to_match_endpoint);
+
+ if (!Auth_OpenID::isFailure($result)) {
+ // It matches, so discover verification has
+ // succeeded. Return this endpoint.
+ return $endpoint;
+ }
+ }
+ }
+
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf('No matching endpoint found after discovering %s',
+ $claimed_id));
+ }
+
+ /**
+ * Extract the nonce from an OpenID 1 response. Return the nonce
+ * from the BARE_NS since we independently check the return_to
+ * arguments are the same as those in the response message.
+ *
+ * See the openid1_nonce_query_arg_name class variable
+ *
+ * @returns $nonce The nonce as a string or null
+ *
+ * @access private
+ */
+ function _idResGetNonceOpenID1($message, $endpoint)
+ {
+ return $message->getArg(Auth_OpenID_BARE_NS,
+ $this->openid1_nonce_query_arg_name);
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckNonce($message, $endpoint)
+ {
+ if ($message->isOpenID1()) {
+ // This indicates that the nonce was generated by the consumer
+ $nonce = $this->_idResGetNonceOpenID1($message, $endpoint);
+ $server_url = '';
+ } else {
+ $nonce = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'response_nonce');
+
+ $server_url = $endpoint->server_url;
+ }
+
+ if ($nonce === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Nonce missing from response");
+ }
+
+ $parts = Auth_OpenID_splitNonce($nonce);
+
+ if ($parts === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Malformed nonce in response");
+ }
+
+ list($timestamp, $salt) = $parts;
+
+ if (!$this->store->useNonce($server_url, $timestamp, $salt)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Nonce already used or out of range");
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckForFields($message)
+ {
+ $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
+ $basic_sig_fields = array('return_to', 'identity');
+
+ $require_fields = array(
+ Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
+ array('op_endpoint')),
+
+ Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
+ array('identity'))
+ );
+
+ $require_sigs = array(
+ Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
+ array('response_nonce',
+ 'claimed_id',
+ 'assoc_handle')),
+ Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
+ array('nonce'))
+ );
+
+ foreach ($require_fields[$message->getOpenIDNamespace()] as $field) {
+ if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Missing required field '".$field."'");
+ }
+ }
+
+ $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'signed',
+ Auth_OpenID_NO_DEFAULT);
+ if (Auth_OpenID::isFailure($signed_list_str)) {
+ return $signed_list_str;
+ }
+ $signed_list = explode(',', $signed_list_str);
+
+ foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
+ // Field is present and not in signed list
+ if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
+ (!in_array($field, $signed_list))) {
+ return new Auth_OpenID_FailureResponse(null,
+ "'".$field."' not signed");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _checkAuth($message, $server_url)
+ {
+ $request = $this->_createCheckAuthRequest($message);
+ if ($request === null) {
+ return false;
+ }
+
+ $resp_message = $this->_makeKVPost($request, $server_url);
+ if (($resp_message === null) ||
+ (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) {
+ return false;
+ }
+
+ return $this->_processCheckAuthResponse($resp_message, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _createCheckAuthRequest($message)
+ {
+ $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ if ($signed) {
+ foreach (explode(',', $signed) as $k) {
+ $value = $message->getAliasedArg($k);
+ if ($value === null) {
+ return null;
+ }
+ }
+ }
+ $ca_message = $message->copy();
+ $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode',
+ 'check_authentication');
+ return $ca_message;
+ }
+
+ /**
+ * @access private
+ */
+ function _processCheckAuthResponse($response, $server_url)
+ {
+ $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid',
+ 'false');
+
+ $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle');
+
+ if ($invalidate_handle !== null) {
+ $this->store->removeAssociation($server_url,
+ $invalidate_handle);
+ }
+
+ if ($is_valid == 'true') {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adapt a POST response to a Message.
+ *
+ * @param $response Result of a POST to an OpenID endpoint.
+ *
+ * @access private
+ */
+ function _httpResponseToMessage($response, $server_url)
+ {
+ // Should this function be named Message.fromHTTPResponse instead?
+ $response_message = Auth_OpenID_Message::fromKVForm($response->body);
+
+ if ($response->status == 400) {
+ return Auth_OpenID_ServerErrorContainer::fromMessage(
+ $response_message);
+ } else if ($response->status != 200 and $response->status != 206) {
+ return null;
+ }
+
+ return $response_message;
+ }
+
+ /**
+ * @access private
+ */
+ function _makeKVPost($message, $server_url)
+ {
+ $body = $message->toURLEncoded();
+ $resp = $this->fetcher->post($server_url, $body);
+
+ if ($resp === null) {
+ return null;
+ }
+
+ return $this->_httpResponseToMessage($resp, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($endpoint)
+ {
+ if (!$this->_use_assocs) {
+ return null;
+ }
+
+ $assoc = $this->store->getAssociation($endpoint->server_url);
+
+ if (($assoc === null) ||
+ ($assoc->getExpiresIn() <= 0)) {
+
+ $assoc = $this->_negotiateAssociation($endpoint);
+
+ if ($assoc !== null) {
+ $this->store->storeAssociation($endpoint->server_url,
+ $assoc);
+ }
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * Handle ServerErrors resulting from association requests.
+ *
+ * @return $result If server replied with an C{unsupported-type}
+ * error, return a tuple of supported C{association_type},
+ * C{session_type}. Otherwise logs the error and returns null.
+ *
+ * @access private
+ */
+ function _extractSupportedAssociationType(&$server_error, &$endpoint,
+ $assoc_type)
+ {
+ // Any error message whose code is not 'unsupported-type'
+ // should be considered a total failure.
+ if (($server_error->error_code != 'unsupported-type') ||
+ ($server_error->message->isOpenID1())) {
+ return null;
+ }
+
+ // The server didn't like the association/session type that we
+ // sent, and it sent us back a message that might tell us how
+ // to handle it.
+
+ // Extract the session_type and assoc_type from the error
+ // message
+ $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type');
+
+ $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if (($assoc_type === null) || ($session_type === null)) {
+ return null;
+ } else if (!$this->negotiator->isAllowed($assoc_type,
+ $session_type)) {
+ return null;
+ } else {
+ return array($assoc_type, $session_type);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _negotiateAssociation($endpoint)
+ {
+ // Get our preferred session/association type from the negotiatior.
+ list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
+
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (Auth_OpenID::isFailure($assoc)) {
+ return null;
+ }
+
+ if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+ $why = $assoc;
+
+ $supportedTypes = $this->_extractSupportedAssociationType(
+ $why, $endpoint, $assoc_type);
+
+ if ($supportedTypes !== null) {
+ list($assoc_type, $session_type) = $supportedTypes;
+
+ // Attempt to create an association from the assoc_type
+ // and session_type that the server told us it
+ // supported.
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+ // Do not keep trying, since it rejected the
+ // association type that it told us to use.
+ // oidutil.log('Server %s refused its suggested association
+ // 'type: session_type=%s, assoc_type=%s'
+ // % (endpoint.server_url, session_type,
+ // assoc_type))
+ return null;
+ } else {
+ return $assoc;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ return $assoc;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _requestAssociation($endpoint, $assoc_type, $session_type)
+ {
+ list($assoc_session, $args) = $this->_createAssociateRequest(
+ $endpoint, $assoc_type, $session_type);
+
+ $response_message = $this->_makeKVPost($args, $endpoint->server_url);
+
+ if ($response_message === null) {
+ // oidutil.log('openid.associate request failed: %s' % (why[0],))
+ return null;
+ } else if (is_a($response_message,
+ 'Auth_OpenID_ServerErrorContainer')) {
+ return $response_message;
+ }
+
+ return $this->_extractAssociation($response_message, $assoc_session);
+ }
+
+ /**
+ * @access private
+ */
+ function _extractAssociation(&$assoc_response, &$assoc_session)
+ {
+ // Extract the common fields from the response, raising an
+ // exception if they are not found
+ $assoc_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($assoc_type)) {
+ return $assoc_type;
+ }
+
+ $assoc_handle = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_handle',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($assoc_handle)) {
+ return $assoc_handle;
+ }
+
+ // expires_in is a base-10 string. The Python parsing will
+ // accept literals that have whitespace around them and will
+ // accept negative values. Neither of these are really in-spec,
+ // but we think it's OK to accept them.
+ $expires_in_str = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'expires_in',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($expires_in_str)) {
+ return $expires_in_str;
+ }
+
+ $expires_in = Auth_OpenID::intval($expires_in_str);
+ if ($expires_in === false) {
+
+ $err = sprintf("Could not parse expires_in from association ".
+ "response %s", print_r($assoc_response, true));
+ return new Auth_OpenID_FailureResponse(null, $err);
+ }
+
+ // OpenID 1 has funny association session behaviour.
+ if ($assoc_response->isOpenID1()) {
+ $session_type = $this->_getOpenID1SessionType($assoc_response);
+ } else {
+ $session_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID2_NS, 'session_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($session_type)) {
+ return $session_type;
+ }
+ }
+
+ // Session type mismatch
+ if ($assoc_session->session_type != $session_type) {
+ if ($assoc_response->isOpenID1() &&
+ ($session_type == 'no-encryption')) {
+ // In OpenID 1, any association request can result in
+ // a 'no-encryption' association response. Setting
+ // assoc_session to a new no-encryption session should
+ // make the rest of this function work properly for
+ // that case.
+ $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ // Any other mismatch, regardless of protocol version
+ // results in the failure of the association session
+ // altogether.
+ return null;
+ }
+ }
+
+ // Make sure assoc_type is valid for session_type
+ if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
+ return null;
+ }
+
+ // Delegate to the association session to extract the secret
+ // from the response, however is appropriate for that session
+ // type.
+ $secret = $assoc_session->extractSecret($assoc_response);
+
+ if ($secret === null) {
+ return null;
+ }
+
+ return Auth_OpenID_Association::fromExpiresIn(
+ $expires_in, $assoc_handle, $secret, $assoc_type);
+ }
+
+ /**
+ * @access private
+ */
+ function _createAssociateRequest($endpoint, $assoc_type, $session_type)
+ {
+ if (array_key_exists($session_type, $this->session_types)) {
+ $session_type_class = $this->session_types[$session_type];
+
+ if (is_callable($session_type_class)) {
+ $assoc_session = $session_type_class();
+ } else {
+ $assoc_session = new $session_type_class();
+ }
+ } else {
+ return null;
+ }
+
+ $args = array(
+ 'mode' => 'associate',
+ 'assoc_type' => $assoc_type);
+
+ if (!$endpoint->compatibilityMode()) {
+ $args['ns'] = Auth_OpenID_OPENID2_NS;
+ }
+
+ // Leave out the session type if we're in compatibility mode
+ // *and* it's no-encryption.
+ if ((!$endpoint->compatibilityMode()) ||
+ ($assoc_session->session_type != 'no-encryption')) {
+ $args['session_type'] = $assoc_session->session_type;
+ }
+
+ $args = array_merge($args, $assoc_session->getRequest());
+ $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+ return array($assoc_session, $message);
+ }
+
+ /**
+ * Given an association response message, extract the OpenID 1.X
+ * session type.
+ *
+ * This function mostly takes care of the 'no-encryption' default
+ * behavior in OpenID 1.
+ *
+ * If the association type is plain-text, this function will
+ * return 'no-encryption'
+ *
+ * @access private
+ * @return $typ The association type for this message
+ */
+ function _getOpenID1SessionType($assoc_response)
+ {
+ // If it's an OpenID 1 message, allow session_type to default
+ // to None (which signifies "no-encryption")
+ $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
+ 'session_type');
+
+ // Handle the differences between no-encryption association
+ // respones in OpenID 1 and 2:
+
+ // no-encryption is not really a valid session type for OpenID
+ // 1, but we'll accept it anyway, while issuing a warning.
+ if ($session_type == 'no-encryption') {
+ // oidutil.log('WARNING: OpenID server sent "no-encryption"'
+ // 'for OpenID 1.X')
+ } else if (($session_type == '') || ($session_type === null)) {
+ // Missing or empty session type is the way to flag a
+ // 'no-encryption' response. Change the session type to
+ // 'no-encryption' so that it can be handled in the same
+ // way as OpenID 2 'no-encryption' respones.
+ $session_type = 'no-encryption';
+ }
+
+ return $session_type;
+ }
+}
+
+/**
+ * This class represents an authentication request from a consumer to
+ * an OpenID server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AuthRequest {
+
+ /**
+ * Initialize an authentication request with the specified token,
+ * association, and endpoint.
+ *
+ * Users of this library should not create instances of this
+ * class. Instances of this class are created by the library when
+ * needed.
+ */
+ function Auth_OpenID_AuthRequest(&$endpoint, $assoc)
+ {
+ $this->assoc = $assoc;
+ $this->endpoint =& $endpoint;
+ $this->return_to_args = array();
+ $this->message = new Auth_OpenID_Message(
+ $endpoint->preferredNamespace());
+ $this->_anonymous = false;
+ }
+
+ /**
+ * Add an extension to this checkid request.
+ *
+ * $extension_request: An object that implements the extension
+ * request interface for adding arguments to an OpenID message.
+ */
+ function addExtension(&$extension_request)
+ {
+ $extension_request->toMessage($this->message);
+ }
+
+ /**
+ * Add an extension argument to this OpenID authentication
+ * request.
+ *
+ * Use caution when adding arguments, because they will be
+ * URL-escaped and appended to the redirect URL, which can easily
+ * get quite long.
+ *
+ * @param string $namespace The namespace for the extension. For
+ * example, the simple registration extension uses the namespace
+ * 'sreg'.
+ *
+ * @param string $key The key within the extension namespace. For
+ * example, the nickname field in the simple registration
+ * extension's key is 'nickname'.
+ *
+ * @param string $value The value to provide to the server for
+ * this argument.
+ */
+ function addExtensionArg($namespace, $key, $value)
+ {
+ return $this->message->setArg($namespace, $key, $value);
+ }
+
+ /**
+ * Set whether this request should be made anonymously. If a
+ * request is anonymous, the identifier will not be sent in the
+ * request. This is only useful if you are making another kind of
+ * request with an extension in this request.
+ *
+ * Anonymous requests are not allowed when the request is made
+ * with OpenID 1.
+ */
+ function setAnonymous($is_anonymous)
+ {
+ if ($is_anonymous && $this->message->isOpenID1()) {
+ return false;
+ } else {
+ $this->_anonymous = $is_anonymous;
+ return true;
+ }
+ }
+
+ /**
+ * Produce a {@link Auth_OpenID_Message} representing this
+ * request.
+ *
+ * @param string $realm The URL (or URL pattern) that identifies
+ * your web site to the user when she is authorizing it.
+ *
+ * @param string $return_to The URL that the OpenID provider will
+ * send the user back to after attempting to verify her identity.
+ *
+ * Not specifying a return_to URL means that the user will not be
+ * returned to the site issuing the request upon its completion.
+ *
+ * @param bool $immediate If true, the OpenID provider is to send
+ * back a response immediately, useful for behind-the-scenes
+ * authentication attempts. Otherwise the OpenID provider may
+ * engage the user before providing a response. This is the
+ * default case, as the user may need to provide credentials or
+ * approve the request before a positive response can be sent.
+ */
+ function getMessage($realm, $return_to=null, $immediate=false)
+ {
+ if ($return_to) {
+ $return_to = Auth_OpenID::appendArgs($return_to,
+ $this->return_to_args);
+ } else if ($immediate) {
+ // raise ValueError(
+ // '"return_to" is mandatory when
+ //using "checkid_immediate"')
+ return new Auth_OpenID_FailureResponse(null,
+ "'return_to' is mandatory when using checkid_immediate");
+ } else if ($this->message->isOpenID1()) {
+ // raise ValueError('"return_to" is
+ // mandatory for OpenID 1 requests')
+ return new Auth_OpenID_FailureResponse(null,
+ "'return_to' is mandatory for OpenID 1 requests");
+ } else if ($this->return_to_args) {
+ // raise ValueError('extra "return_to" arguments
+ // were specified, but no return_to was specified')
+ return new Auth_OpenID_FailureResponse(null,
+ "extra 'return_to' arguments where specified, " .
+ "but no return_to was specified");
+ }
+
+ if ($immediate) {
+ $mode = 'checkid_immediate';
+ } else {
+ $mode = 'checkid_setup';
+ }
+
+ $message = $this->message->copy();
+ if ($message->isOpenID1()) {
+ $realm_key = 'trust_root';
+ } else {
+ $realm_key = 'realm';
+ }
+
+ $message->updateArgs(Auth_OpenID_OPENID_NS,
+ array(
+ $realm_key => $realm,
+ 'mode' => $mode,
+ 'return_to' => $return_to));
+
+ if (!$this->_anonymous) {
+ if ($this->endpoint->isOPIdentifier()) {
+ // This will never happen when we're in compatibility
+ // mode, as long as isOPIdentifier() returns False
+ // whenever preferredNamespace() returns OPENID1_NS.
+ $claimed_id = $request_identity =
+ Auth_OpenID_IDENTIFIER_SELECT;
+ } else {
+ $request_identity = $this->endpoint->getLocalID();
+ $claimed_id = $this->endpoint->claimed_id;
+ }
+
+ // This is true for both OpenID 1 and 2
+ $message->setArg(Auth_OpenID_OPENID_NS, 'identity',
+ $request_identity);
+
+ if ($message->isOpenID2()) {
+ $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id',
+ $claimed_id);
+ }
+ }
+
+ if ($this->assoc) {
+ $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
+ $this->assoc->handle);
+ }
+
+ return $message;
+ }
+
+ function redirectURL($realm, $return_to = null,
+ $immediate = false)
+ {
+ $message = $this->getMessage($realm, $return_to, $immediate);
+
+ if (Auth_OpenID::isFailure($message)) {
+ return $message;
+ }
+
+ return $message->toURL($this->endpoint->server_url);
+ }
+
+ /**
+ * Get html for a form to submit this request to the IDP.
+ *
+ * form_tag_attrs: An array of attributes to be added to the form
+ * tag. 'accept-charset' and 'enctype' have defaults that can be
+ * overridden. If a value is supplied for 'action' or 'method', it
+ * will be replaced.
+ */
+ function formMarkup($realm, $return_to=null, $immediate=false,
+ $form_tag_attrs=null)
+ {
+ $message = $this->getMessage($realm, $return_to, $immediate);
+
+ if (Auth_OpenID::isFailure($message)) {
+ return $message;
+ }
+
+ return $message->toFormMarkup($this->endpoint->server_url,
+ $form_tag_attrs);
+ }
+
+ /**
+ * Get a complete html document that will autosubmit the request
+ * to the IDP.
+ *
+ * Wraps formMarkup. See the documentation for that function.
+ */
+ function htmlMarkup($realm, $return_to=null, $immediate=false,
+ $form_tag_attrs=null)
+ {
+ $form = $this->formMarkup($realm, $return_to, $immediate,
+ $form_tag_attrs);
+
+ if (Auth_OpenID::isFailure($form)) {
+ return $form;
+ }
+ return Auth_OpenID::autoSubmitHTML($form);
+ }
+
+ function shouldSendRedirect()
+ {
+ return $this->endpoint->compatibilityMode();
+ }
+}
+
+/**
+ * The base class for responses from the Auth_OpenID_Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ConsumerResponse {
+ var $status = null;
+
+ function setEndpoint($endpoint)
+ {
+ $this->endpoint = $endpoint;
+ if ($endpoint === null) {
+ $this->identity_url = null;
+ } else {
+ $this->identity_url = $endpoint->claimed_id;
+ }
+ }
+
+ /**
+ * Return the display identifier for this response.
+ *
+ * The display identifier is related to the Claimed Identifier, but the
+ * two are not always identical. The display identifier is something the
+ * user should recognize as what they entered, whereas the response's
+ * claimed identifier (in the identity_url attribute) may have extra
+ * information for better persistence.
+ *
+ * URLs will be stripped of their fragments for display. XRIs will
+ * display the human-readable identifier (i-name) instead of the
+ * persistent identifier (i-number).
+ *
+ * Use the display identifier in your user interface. Use
+ * identity_url for querying your database or authorization server.
+ *
+ */
+ function getDisplayIdentifier()
+ {
+ if ($this->endpoint !== null) {
+ return $this->endpoint->getDisplayIdentifier();
+ }
+ return null;
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SUCCESS. Indicates that
+ * this request is a successful acknowledgement from the OpenID server
+ * that the supplied URL is, indeed controlled by the requesting
+ * agent. This has three relevant attributes:
+ *
+ * claimed_id - The identity URL that has been authenticated
+ *
+ * signed_args - The arguments in the server's response that were
+ * signed and verified.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_SUCCESS;
+
+ /**
+ * @access private
+ */
+ function Auth_OpenID_SuccessResponse($endpoint, $message, $signed_args=null)
+ {
+ $this->endpoint = $endpoint;
+ $this->identity_url = $endpoint->claimed_id;
+ $this->signed_args = $signed_args;
+ $this->message = $message;
+
+ if ($this->signed_args === null) {
+ $this->signed_args = array();
+ }
+ }
+
+ /**
+ * Extract signed extension data from the server's response.
+ *
+ * @param string $prefix The extension namespace from which to
+ * extract the extension data.
+ */
+ function extensionResponse($namespace_uri, $require_signed)
+ {
+ if ($require_signed) {
+ return $this->getSignedNS($namespace_uri);
+ } else {
+ return $this->message->getArgs($namespace_uri);
+ }
+ }
+
+ function isOpenID1()
+ {
+ return $this->message->isOpenID1();
+ }
+
+ function isSigned($ns_uri, $ns_key)
+ {
+ // Return whether a particular key is signed, regardless of
+ // its namespace alias
+ return in_array($this->message->getKey($ns_uri, $ns_key),
+ $this->signed_args);
+ }
+
+ function getSigned($ns_uri, $ns_key, $default = null)
+ {
+ // Return the specified signed field if available, otherwise
+ // return default
+ if ($this->isSigned($ns_uri, $ns_key)) {
+ return $this->message->getArg($ns_uri, $ns_key, $default);
+ } else {
+ return $default;
+ }
+ }
+
+ function getSignedNS($ns_uri)
+ {
+ $args = array();
+
+ $msg_args = $this->message->getArgs($ns_uri);
+ if (Auth_OpenID::isFailure($msg_args)) {
+ return null;
+ }
+
+ foreach ($msg_args as $key => $value) {
+ if (!$this->isSigned($ns_uri, $key)) {
+ return null;
+ }
+ }
+
+ return $msg_args;
+ }
+
+ /**
+ * Get the openid.return_to argument from this response.
+ *
+ * This is useful for verifying that this request was initiated by
+ * this consumer.
+ *
+ * @return string $return_to The return_to URL supplied to the
+ * server on the initial request, or null if the response did not
+ * contain an 'openid.return_to' argument.
+ */
+ function getReturnTo()
+ {
+ return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to');
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_FAILURE. Indicates that the
+ * OpenID protocol has failed. This could be locally or remotely
+ * triggered. This has three relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined. Otherwise, null.
+ *
+ * message - A message indicating why the request failed, if one is
+ * supplied. Otherwise, null.
+ *
+ * status - Auth_OpenID_FAILURE.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_FAILURE;
+
+ function Auth_OpenID_FailureResponse($endpoint, $message = null,
+ $contact = null, $reference = null)
+ {
+ $this->setEndpoint($endpoint);
+ $this->message = $message;
+ $this->contact = $contact;
+ $this->reference = $reference;
+ }
+}
+
+/**
+ * A specific, internal failure used to detect type URI mismatch.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse {
+}
+
+/**
+ * Exception that is raised when the server returns a 400 response
+ * code to a direct request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerErrorContainer {
+ function Auth_OpenID_ServerErrorContainer($error_text,
+ $error_code,
+ $message)
+ {
+ $this->error_text = $error_text;
+ $this->error_code = $error_code;
+ $this->message = $message;
+ }
+
+ /**
+ * @access private
+ */
+ function fromMessage($message)
+ {
+ $error_text = $message->getArg(
+ Auth_OpenID_OPENID_NS, 'error', '<no error message supplied>');
+ $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code');
+ return new Auth_OpenID_ServerErrorContainer($error_text,
+ $error_code,
+ $message);
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_CANCEL. Indicates that the
+ * user cancelled the OpenID authentication request. This has two
+ * relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined. Otherwise, null.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_CANCEL;
+
+ function Auth_OpenID_CancelResponse($endpoint)
+ {
+ $this->setEndpoint($endpoint);
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates
+ * that the request was in immediate mode, and the server is unable to
+ * authenticate the user without further interaction.
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted.
+ *
+ * setup_url - A URL that can be used to send the user to the server
+ * to set up for authentication. The user should be redirected in to
+ * the setup_url, either in the current window or in a new browser
+ * window. Null in OpenID 2.
+ *
+ * status - Auth_OpenID_SETUP_NEEDED.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_SETUP_NEEDED;
+
+ function Auth_OpenID_SetupNeededResponse($endpoint,
+ $setup_url = null)
+ {
+ $this->setEndpoint($endpoint);
+ $this->setup_url = $setup_url;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/CryptUtil.php b/Auth/OpenID/CryptUtil.php
new file mode 100644
index 0000000..aacc3cd
--- /dev/null
+++ b/Auth/OpenID/CryptUtil.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * CryptUtil: A suite of wrapper utility functions for the OpenID
+ * library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+if (!defined('Auth_OpenID_RAND_SOURCE')) {
+ /**
+ * The filename for a source of random bytes. Define this yourself
+ * if you have a different source of randomness.
+ */
+ define('Auth_OpenID_RAND_SOURCE', '/dev/urandom');
+}
+
+class Auth_OpenID_CryptUtil {
+ /**
+ * Get the specified number of random bytes.
+ *
+ * Attempts to use a cryptographically secure (not predictable)
+ * source of randomness if available. If there is no high-entropy
+ * randomness source available, it will fail. As a last resort,
+ * for non-critical systems, define
+ * <code>Auth_OpenID_RAND_SOURCE</code> as <code>null</code>, and
+ * the code will fall back on a pseudo-random number generator.
+ *
+ * @param int $num_bytes The length of the return value
+ * @return string $bytes random bytes
+ */
+ function getBytes($num_bytes)
+ {
+ static $f = null;
+ $bytes = '';
+ if ($f === null) {
+ if (Auth_OpenID_RAND_SOURCE === null) {
+ $f = false;
+ } else {
+ $f = @fopen(Auth_OpenID_RAND_SOURCE, "r");
+ if ($f === false) {
+ $msg = 'Define Auth_OpenID_RAND_SOURCE as null to ' .
+ ' continue with an insecure random number generator.';
+ trigger_error($msg, E_USER_ERROR);
+ }
+ }
+ }
+ if ($f === false) {
+ // pseudorandom used
+ $bytes = '';
+ for ($i = 0; $i < $num_bytes; $i += 4) {
+ $bytes .= pack('L', mt_rand());
+ }
+ $bytes = substr($bytes, 0, $num_bytes);
+ } else {
+ $bytes = fread($f, $num_bytes);
+ }
+ return $bytes;
+ }
+
+ /**
+ * Produce a string of length random bytes, chosen from chrs. If
+ * $chrs is null, the resulting string may contain any characters.
+ *
+ * @param integer $length The length of the resulting
+ * randomly-generated string
+ * @param string $chrs A string of characters from which to choose
+ * to build the new string
+ * @return string $result A string of randomly-chosen characters
+ * from $chrs
+ */
+ function randomString($length, $population = null)
+ {
+ if ($population === null) {
+ return Auth_OpenID_CryptUtil::getBytes($length);
+ }
+
+ $popsize = strlen($population);
+
+ if ($popsize > 256) {
+ $msg = 'More than 256 characters supplied to ' . __FUNCTION__;
+ trigger_error($msg, E_USER_ERROR);
+ }
+
+ $duplicate = 256 % $popsize;
+
+ $str = "";
+ for ($i = 0; $i < $length; $i++) {
+ do {
+ $n = ord(Auth_OpenID_CryptUtil::getBytes(1));
+ } while ($n < $duplicate);
+
+ $n %= $popsize;
+ $str .= $population[$n];
+ }
+
+ return $str;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/DatabaseConnection.php b/Auth/OpenID/DatabaseConnection.php
new file mode 100644
index 0000000..9db6e0e
--- /dev/null
+++ b/Auth/OpenID/DatabaseConnection.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * The Auth_OpenID_DatabaseConnection class, which is used to emulate
+ * a PEAR database connection.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * An empty base class intended to emulate PEAR connection
+ * functionality in applications that supply their own database
+ * abstraction mechanisms. See {@link Auth_OpenID_SQLStore} for more
+ * information. You should subclass this class if you need to create
+ * an SQL store that needs to access its database using an
+ * application's database abstraction layer instead of a PEAR database
+ * connection. Any subclass of Auth_OpenID_DatabaseConnection MUST
+ * adhere to the interface specified here.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DatabaseConnection {
+ /**
+ * Sets auto-commit mode on this database connection.
+ *
+ * @param bool $mode True if auto-commit is to be used; false if
+ * not.
+ */
+ function autoCommit($mode)
+ {
+ }
+
+ /**
+ * Run an SQL query with the specified parameters, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return mixed $result The result of calling this connection's
+ * internal query function. The type of result depends on the
+ * underlying database engine. This method is usually used when
+ * the result of a query is not important, like a DDL query.
+ */
+ function query($sql, $params = array())
+ {
+ }
+
+ /**
+ * Starts a transaction on this connection, if supported.
+ */
+ function begin()
+ {
+ }
+
+ /**
+ * Commits a transaction on this connection, if supported.
+ */
+ function commit()
+ {
+ }
+
+ /**
+ * Performs a rollback on this connection, if supported.
+ */
+ function rollback()
+ {
+ }
+
+ /**
+ * Run an SQL query and return the first column of the first row
+ * of the result set, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return mixed $result The value of the first column of the
+ * first row of the result set. False if no such result was
+ * found.
+ */
+ function getOne($sql, $params = array())
+ {
+ }
+
+ /**
+ * Run an SQL query and return the first row of the result set, if
+ * any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return array $result The first row of the result set, if any,
+ * keyed on column name. False if no such result was found.
+ */
+ function getRow($sql, $params = array())
+ {
+ }
+
+ /**
+ * Run an SQL query with the specified parameters, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return array $result An array of arrays representing the
+ * result of the query; each array is keyed on column name.
+ */
+ function getAll($sql, $params = array())
+ {
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/DiffieHellman.php b/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 0000000..f4ded7e
--- /dev/null
+++ b/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * The OpenID library's Diffie-Hellman implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/BigMath.php';
+
+function Auth_OpenID_getDefaultMod()
+{
+ return '155172898181473697471232257763715539915724801'.
+ '966915404479707795314057629378541917580651227423'.
+ '698188993727816152646631438561595825688188889951'.
+ '272158842675419950341258706556549803580104870537'.
+ '681476726513255747040765857479291291572334510643'.
+ '245094715007229621094194349783925984760375594985'.
+ '848253359305585439638443';
+}
+
+function Auth_OpenID_getDefaultGen()
+{
+ return '2';
+}
+
+/**
+ * The Diffie-Hellman key exchange class. This class relies on
+ * {@link Auth_OpenID_MathLibrary} to perform large number operations.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellman {
+
+ var $mod;
+ var $gen;
+ var $private;
+ var $lib = null;
+
+ function Auth_OpenID_DiffieHellman($mod = null, $gen = null,
+ $private = null, $lib = null)
+ {
+ if ($lib === null) {
+ $this->lib =& Auth_OpenID_getMathLib();
+ } else {
+ $this->lib =& $lib;
+ }
+
+ if ($mod === null) {
+ $this->mod = $this->lib->init(Auth_OpenID_getDefaultMod());
+ } else {
+ $this->mod = $mod;
+ }
+
+ if ($gen === null) {
+ $this->gen = $this->lib->init(Auth_OpenID_getDefaultGen());
+ } else {
+ $this->gen = $gen;
+ }
+
+ if ($private === null) {
+ $r = $this->lib->rand($this->mod);
+ $this->private = $this->lib->add($r, 1);
+ } else {
+ $this->private = $private;
+ }
+
+ $this->public = $this->lib->powmod($this->gen, $this->private,
+ $this->mod);
+ }
+
+ function getSharedSecret($composite)
+ {
+ return $this->lib->powmod($composite, $this->private, $this->mod);
+ }
+
+ function getPublicKey()
+ {
+ return $this->public;
+ }
+
+ function usingDefaultValues()
+ {
+ return ($this->mod == Auth_OpenID_getDefaultMod() &&
+ $this->gen == Auth_OpenID_getDefaultGen());
+ }
+
+ function xorSecret($composite, $secret, $hash_func)
+ {
+ $dh_shared = $this->getSharedSecret($composite);
+ $dh_shared_str = $this->lib->longToBinary($dh_shared);
+ $hash_dh_shared = $hash_func($dh_shared_str);
+
+ $xsecret = "";
+ for ($i = 0; $i < Auth_OpenID::bytes($secret); $i++) {
+ $xsecret .= chr(ord($secret[$i]) ^ ord($hash_dh_shared[$i]));
+ }
+
+ return $xsecret;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/Discover.php b/Auth/OpenID/Discover.php
new file mode 100644
index 0000000..62aeb1d
--- /dev/null
+++ b/Auth/OpenID/Discover.php
@@ -0,0 +1,548 @@
+<?php
+
+/**
+ * The OpenID and Yadis discovery implementation for OpenID 1.2.
+ */
+
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Parse.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/Yadis/XRIRes.php";
+require_once "Auth/Yadis/Yadis.php";
+
+// XML namespace value
+define('Auth_OpenID_XMLNS_1_0', 'http://openid.net/xmlns/1.0');
+
+// Yadis service types
+define('Auth_OpenID_TYPE_1_2', 'http://openid.net/signon/1.2');
+define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
+define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
+define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
+define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
+define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
+ 'http://specs.openid.net/auth/2.0/return_to');
+
+function Auth_OpenID_getOpenIDTypeURIs()
+{
+ return array(Auth_OpenID_TYPE_2_0_IDP,
+ Auth_OpenID_TYPE_2_0,
+ Auth_OpenID_TYPE_1_2,
+ Auth_OpenID_TYPE_1_1,
+ Auth_OpenID_TYPE_1_0,
+ Auth_OpenID_RP_RETURN_TO_URL_TYPE);
+}
+
+/**
+ * Object representing an OpenID service endpoint.
+ */
+class Auth_OpenID_ServiceEndpoint {
+ function Auth_OpenID_ServiceEndpoint()
+ {
+ $this->claimed_id = null;
+ $this->server_url = null;
+ $this->type_uris = array();
+ $this->local_id = null;
+ $this->canonicalID = null;
+ $this->used_yadis = false; // whether this came from an XRDS
+ $this->display_identifier = null;
+ }
+
+ function getDisplayIdentifier()
+ {
+ if ($this->display_identifier) {
+ return $this->display_identifier;
+ }
+ if (! $this->claimed_id) {
+ return $this->claimed_id;
+ }
+ $parsed = parse_url($this->claimed_id);
+ $scheme = $parsed['scheme'];
+ $host = $parsed['host'];
+ $path = $parsed['path'];
+ if (array_key_exists('query', $parsed)) {
+ $query = $parsed['query'];
+ $no_frag = "$scheme://$host$path?$query";
+ } else {
+ $no_frag = "$scheme://$host$path";
+ }
+ return $no_frag;
+ }
+
+ function usesExtension($extension_uri)
+ {
+ return in_array($extension_uri, $this->type_uris);
+ }
+
+ function preferredNamespace()
+ {
+ if (in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris) ||
+ in_array(Auth_OpenID_TYPE_2_0, $this->type_uris)) {
+ return Auth_OpenID_OPENID2_NS;
+ } else {
+ return Auth_OpenID_OPENID1_NS;
+ }
+ }
+
+ /*
+ * Query this endpoint to see if it has any of the given type
+ * URIs. This is useful for implementing other endpoint classes
+ * that e.g. need to check for the presence of multiple versions
+ * of a single protocol.
+ *
+ * @param $type_uris The URIs that you wish to check
+ *
+ * @return all types that are in both in type_uris and
+ * $this->type_uris
+ */
+ function matchTypes($type_uris)
+ {
+ $result = array();
+ foreach ($type_uris as $test_uri) {
+ if ($this->supportsType($test_uri)) {
+ $result[] = $test_uri;
+ }
+ }
+
+ return $result;
+ }
+
+ function supportsType($type_uri)
+ {
+ // Does this endpoint support this type?
+ return ((in_array($type_uri, $this->type_uris)) ||
+ (($type_uri == Auth_OpenID_TYPE_2_0) &&
+ $this->isOPIdentifier()));
+ }
+
+ function compatibilityMode()
+ {
+ return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS;
+ }
+
+ function isOPIdentifier()
+ {
+ return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris);
+ }
+
+ function fromOPEndpointURL($op_endpoint_url)
+ {
+ // Construct an OP-Identifier OpenIDServiceEndpoint object for
+ // a given OP Endpoint URL
+ $obj = new Auth_OpenID_ServiceEndpoint();
+ $obj->server_url = $op_endpoint_url;
+ $obj->type_uris = array(Auth_OpenID_TYPE_2_0_IDP);
+ return $obj;
+ }
+
+ function parseService($yadis_url, $uri, $type_uris, $service_element)
+ {
+ // Set the state of this object based on the contents of the
+ // service element. Return true if successful, false if not
+ // (if findOPLocalIdentifier returns false).
+ $this->type_uris = $type_uris;
+ $this->server_url = $uri;
+ $this->used_yadis = true;
+
+ if (!$this->isOPIdentifier()) {
+ $this->claimed_id = $yadis_url;
+ $this->local_id = Auth_OpenID_findOPLocalIdentifier(
+ $service_element,
+ $this->type_uris);
+ if ($this->local_id === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function getLocalID()
+ {
+ // Return the identifier that should be sent as the
+ // openid.identity_url parameter to the server.
+ if ($this->local_id === null && $this->canonicalID === null) {
+ return $this->claimed_id;
+ } else {
+ if ($this->local_id) {
+ return $this->local_id;
+ } else {
+ return $this->canonicalID;
+ }
+ }
+ }
+
+ /*
+ * Parse the given document as XRDS looking for OpenID services.
+ *
+ * @return array of Auth_OpenID_ServiceEndpoint or null if the
+ * document cannot be parsed.
+ */
+ function fromXRDS($uri, $xrds_text)
+ {
+ $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text);
+
+ if ($xrds) {
+ $yadis_services =
+ $xrds->services(array('filter_MatchesAnyOpenIDType'));
+ return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
+ }
+
+ return null;
+ }
+
+ /*
+ * Create endpoints from a DiscoveryResult.
+ *
+ * @param discoveryResult Auth_Yadis_DiscoveryResult
+ * @return array of Auth_OpenID_ServiceEndpoint or null if
+ * endpoints cannot be created.
+ */
+ function fromDiscoveryResult($discoveryResult)
+ {
+ if ($discoveryResult->isXRDS()) {
+ return Auth_OpenID_ServiceEndpoint::fromXRDS(
+ $discoveryResult->normalized_uri,
+ $discoveryResult->response_text);
+ } else {
+ return Auth_OpenID_ServiceEndpoint::fromHTML(
+ $discoveryResult->normalized_uri,
+ $discoveryResult->response_text);
+ }
+ }
+
+ function fromHTML($uri, $html)
+ {
+ $discovery_types = array(
+ array(Auth_OpenID_TYPE_2_0,
+ 'openid2.provider', 'openid2.local_id'),
+ array(Auth_OpenID_TYPE_1_1,
+ 'openid.server', 'openid.delegate')
+ );
+
+ $services = array();
+
+ foreach ($discovery_types as $triple) {
+ list($type_uri, $server_rel, $delegate_rel) = $triple;
+
+ $urls = Auth_OpenID_legacy_discover($html, $server_rel,
+ $delegate_rel);
+
+ if ($urls === false) {
+ continue;
+ }
+
+ list($delegate_url, $server_url) = $urls;
+
+ $service = new Auth_OpenID_ServiceEndpoint();
+ $service->claimed_id = $uri;
+ $service->local_id = $delegate_url;
+ $service->server_url = $server_url;
+ $service->type_uris = array($type_uri);
+
+ $services[] = $service;
+ }
+
+ return $services;
+ }
+
+ function copy()
+ {
+ $x = new Auth_OpenID_ServiceEndpoint();
+
+ $x->claimed_id = $this->claimed_id;
+ $x->server_url = $this->server_url;
+ $x->type_uris = $this->type_uris;
+ $x->local_id = $this->local_id;
+ $x->canonicalID = $this->canonicalID;
+ $x->used_yadis = $this->used_yadis;
+
+ return $x;
+ }
+}
+
+function Auth_OpenID_findOPLocalIdentifier($service, $type_uris)
+{
+ // Extract a openid:Delegate value from a Yadis Service element.
+ // If no delegate is found, returns null. Returns false on
+ // discovery failure (when multiple delegate/localID tags have
+ // different values).
+
+ $service->parser->registerNamespace('openid',
+ Auth_OpenID_XMLNS_1_0);
+
+ $service->parser->registerNamespace('xrd',
+ Auth_Yadis_XMLNS_XRD_2_0);
+
+ $parser =& $service->parser;
+
+ $permitted_tags = array();
+
+ if (in_array(Auth_OpenID_TYPE_1_1, $type_uris) ||
+ in_array(Auth_OpenID_TYPE_1_0, $type_uris)) {
+ $permitted_tags[] = 'openid:Delegate';
+ }
+
+ if (in_array(Auth_OpenID_TYPE_2_0, $type_uris)) {
+ $permitted_tags[] = 'xrd:LocalID';
+ }
+
+ $local_id = null;
+
+ foreach ($permitted_tags as $tag_name) {
+ $tags = $service->getElements($tag_name);
+
+ foreach ($tags as $tag) {
+ $content = $parser->content($tag);
+
+ if ($local_id === null) {
+ $local_id = $content;
+ } else if ($local_id != $content) {
+ return false;
+ }
+ }
+ }
+
+ return $local_id;
+}
+
+function filter_MatchesAnyOpenIDType(&$service)
+{
+ $uris = $service->getTypes();
+
+ foreach ($uris as $uri) {
+ if (in_array($uri, Auth_OpenID_getOpenIDTypeURIs())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function Auth_OpenID_bestMatchingService($service, $preferred_types)
+{
+ // Return the index of the first matching type, or something
+ // higher if no type matches.
+ //
+ // This provides an ordering in which service elements that
+ // contain a type that comes earlier in the preferred types list
+ // come before service elements that come later. If a service
+ // element has more than one type, the most preferred one wins.
+
+ foreach ($preferred_types as $index => $typ) {
+ if (in_array($typ, $service->type_uris)) {
+ return $index;
+ }
+ }
+
+ return count($preferred_types);
+}
+
+function Auth_OpenID_arrangeByType($service_list, $preferred_types)
+{
+ // Rearrange service_list in a new list so services are ordered by
+ // types listed in preferred_types. Return the new list.
+
+ // Build a list with the service elements in tuples whose
+ // comparison will prefer the one with the best matching service
+ $prio_services = array();
+ foreach ($service_list as $index => $service) {
+ $prio_services[] = array(Auth_OpenID_bestMatchingService($service,
+ $preferred_types),
+ $index, $service);
+ }
+
+ sort($prio_services);
+
+ // Now that the services are sorted by priority, remove the sort
+ // keys from the list.
+ foreach ($prio_services as $index => $s) {
+ $prio_services[$index] = $prio_services[$index][2];
+ }
+
+ return $prio_services;
+}
+
+// Extract OP Identifier services. If none found, return the rest,
+// sorted with most preferred first according to
+// OpenIDServiceEndpoint.openid_type_uris.
+//
+// openid_services is a list of OpenIDServiceEndpoint objects.
+//
+// Returns a list of OpenIDServiceEndpoint objects."""
+function Auth_OpenID_getOPOrUserServices($openid_services)
+{
+ $op_services = Auth_OpenID_arrangeByType($openid_services,
+ array(Auth_OpenID_TYPE_2_0_IDP));
+
+ $openid_services = Auth_OpenID_arrangeByType($openid_services,
+ Auth_OpenID_getOpenIDTypeURIs());
+
+ if ($op_services) {
+ return $op_services;
+ } else {
+ return $openid_services;
+ }
+}
+
+function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services)
+{
+ $s = array();
+
+ if (!$yadis_services) {
+ return $s;
+ }
+
+ foreach ($yadis_services as $service) {
+ $type_uris = $service->getTypes();
+ $uris = $service->getURIs();
+
+ // If any Type URIs match and there is an endpoint URI
+ // specified, then this is an OpenID endpoint
+ if ($type_uris &&
+ $uris) {
+ foreach ($uris as $service_uri) {
+ $openid_endpoint = new Auth_OpenID_ServiceEndpoint();
+ if ($openid_endpoint->parseService($uri,
+ $service_uri,
+ $type_uris,
+ $service)) {
+ $s[] = $openid_endpoint;
+ }
+ }
+ }
+ }
+
+ return $s;
+}
+
+function Auth_OpenID_discoverWithYadis($uri, &$fetcher,
+ $endpoint_filter='Auth_OpenID_getOPOrUserServices',
+ $discover_function=null)
+{
+ // Discover OpenID services for a URI. Tries Yadis and falls back
+ // on old-style <link rel='...'> discovery if Yadis fails.
+
+ // Might raise a yadis.discover.DiscoveryFailure if no document
+ // came back for that URI at all. I don't think falling back to
+ // OpenID 1.0 discovery on the same URL will help, so don't bother
+ // to catch it.
+ if ($discover_function === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $openid_services = array();
+
+ $response = call_user_func_array($discover_function,
+ array($uri, &$fetcher));
+
+ $yadis_url = $response->normalized_uri;
+ $yadis_services = array();
+
+ if ($response->isFailure()) {
+ return array($uri, array());
+ }
+
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS(
+ $yadis_url,
+ $response->response_text);
+
+ if (!$openid_services) {
+ if ($response->isXRDS()) {
+ return Auth_OpenID_discoverWithoutYadis($uri,
+ $fetcher);
+ }
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1
+ // <link rel="...">
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
+ $yadis_url,
+ $response->response_text);
+ }
+
+ $openid_services = call_user_func_array($endpoint_filter,
+ array(&$openid_services));
+
+ return array($yadis_url, $openid_services);
+}
+
+function Auth_OpenID_discoverURI($uri, &$fetcher)
+{
+ $uri = Auth_OpenID::normalizeUrl($uri);
+ return Auth_OpenID_discoverWithYadis($uri, $fetcher);
+}
+
+function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher)
+{
+ $http_resp = @$fetcher->get($uri);
+
+ if ($http_resp->status != 200 and $http_resp->status != 206) {
+ return array($uri, array());
+ }
+
+ $identity_url = $http_resp->final_url;
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1 <link
+ // rel="...">
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
+ $identity_url,
+ $http_resp->body);
+
+ return array($identity_url, $openid_services);
+}
+
+function Auth_OpenID_discoverXRI($iname, &$fetcher)
+{
+ $resolver = new Auth_Yadis_ProxyResolver($fetcher);
+ list($canonicalID, $yadis_services) =
+ $resolver->query($iname,
+ Auth_OpenID_getOpenIDTypeURIs(),
+ array('filter_MatchesAnyOpenIDType'));
+
+ $openid_services = Auth_OpenID_makeOpenIDEndpoints($iname,
+ $yadis_services);
+
+ $openid_services = Auth_OpenID_getOPOrUserServices($openid_services);
+
+ for ($i = 0; $i < count($openid_services); $i++) {
+ $openid_services[$i]->canonicalID = $canonicalID;
+ $openid_services[$i]->claimed_id = $canonicalID;
+ $openid_services[$i]->display_identifier = $iname;
+ }
+
+ // FIXME: returned xri should probably be in some normal form
+ return array($iname, $openid_services);
+}
+
+function Auth_OpenID_discover($uri, &$fetcher)
+{
+ // If the fetcher (i.e., PHP) doesn't support SSL, we can't do
+ // discovery on an HTTPS URL.
+ if ($fetcher->isHTTPS($uri) && !$fetcher->supportsSSL()) {
+ return array($uri, array());
+ }
+
+ if (Auth_Yadis_identifierScheme($uri) == 'XRI') {
+ $result = Auth_OpenID_discoverXRI($uri, $fetcher);
+ } else {
+ $result = Auth_OpenID_discoverURI($uri, $fetcher);
+ }
+
+ // If the fetcher doesn't support SSL, we can't interact with
+ // HTTPS server URLs; remove those endpoints from the list.
+ if (!$fetcher->supportsSSL()) {
+ $http_endpoints = array();
+ list($new_uri, $endpoints) = $result;
+
+ foreach ($endpoints as $e) {
+ if (!$fetcher->isHTTPS($e->server_url)) {
+ $http_endpoints[] = $e;
+ }
+ }
+
+ $result = array($new_uri, $http_endpoints);
+ }
+
+ return $result;
+}
+
+?>
diff --git a/Auth/OpenID/DumbStore.php b/Auth/OpenID/DumbStore.php
new file mode 100644
index 0000000..22fd2d3
--- /dev/null
+++ b/Auth/OpenID/DumbStore.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * This file supplies a dumb store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMAC.php';
+
+/**
+ * This is a store for use in the worst case, when you have no way of
+ * saving state on the consumer site. Using this store makes the
+ * consumer vulnerable to replay attacks, as it's unable to use
+ * nonces. Avoid using this store if it is at all possible.
+ *
+ * Most of the methods of this class are implementation details.
+ * Users of this class need to worry only about the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DumbStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Creates a new {@link Auth_OpenID_DumbStore} instance. For the security
+ * of the tokens generated by the library, this class attempts to
+ * at least have a secure implementation of getAuthKey.
+ *
+ * When you create an instance of this class, pass in a secret
+ * phrase. The phrase is hashed with sha1 to make it the correct
+ * length and form for an auth key. That allows you to use a long
+ * string as the secret phrase, which means you can make it very
+ * difficult to guess.
+ *
+ * Each {@link Auth_OpenID_DumbStore} instance that is created for use by
+ * your consumer site needs to use the same $secret_phrase.
+ *
+ * @param string secret_phrase The phrase used to create the auth
+ * key returned by getAuthKey
+ */
+ function Auth_OpenID_DumbStore($secret_phrase)
+ {
+ $this->auth_key = Auth_OpenID_SHA1($secret_phrase);
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ }
+
+ /**
+ * This implementation always returns null.
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ return null;
+ }
+
+ /**
+ * This implementation always returns false.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ return false;
+ }
+
+ /**
+ * In a system truly limited to dumb mode, nonces must all be
+ * accepted. This therefore always returns true, which makes
+ * replay attacks feasible.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ return true;
+ }
+
+ /**
+ * This method returns the auth key generated by the constructor.
+ */
+ function getAuthKey()
+ {
+ return $this->auth_key;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Extension.php b/Auth/OpenID/Extension.php
new file mode 100644
index 0000000..f362a4b
--- /dev/null
+++ b/Auth/OpenID/Extension.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * An interface for OpenID extensions.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the Message implementation.
+ */
+require_once 'Auth/OpenID/Message.php';
+
+/**
+ * A base class for accessing extension request and response data for
+ * the OpenID 2 protocol.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Extension {
+ /**
+ * ns_uri: The namespace to which to add the arguments for this
+ * extension
+ */
+ var $ns_uri = null;
+ var $ns_alias = null;
+
+ /**
+ * Get the string arguments that should be added to an OpenID
+ * message for this extension.
+ */
+ function getExtensionArgs()
+ {
+ return null;
+ }
+
+ /**
+ * Add the arguments from this extension to the provided message.
+ *
+ * Returns the message with the extension arguments added.
+ */
+ function toMessage(&$message)
+ {
+ $implicit = $message->isOpenID1();
+ $added = $message->namespaces->addAlias($this->ns_uri,
+ $this->ns_alias,
+ $implicit);
+
+ if ($added === null) {
+ if ($message->namespaces->getAlias($this->ns_uri) !=
+ $this->ns_alias) {
+ return null;
+ }
+ }
+
+ $message->updateArgs($this->ns_uri,
+ $this->getExtensionArgs());
+ return $message;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/FileStore.php b/Auth/OpenID/FileStore.php
new file mode 100644
index 0000000..29d8d20
--- /dev/null
+++ b/Auth/OpenID/FileStore.php
@@ -0,0 +1,618 @@
+<?php
+
+/**
+ * This file supplies a Memcached store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require base class for creating a new interface.
+ */
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMAC.php';
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * This is a filesystem-based store for OpenID associations and
+ * nonces. This store should be safe for use in concurrent systems on
+ * both windows and unix (excluding NFS filesystems). There are a
+ * couple race conditions in the system, but those failure cases have
+ * been set up in such a way that the worst-case behavior is someone
+ * having to try to log in a second time.
+ *
+ * Most of the methods of this class are implementation details.
+ * People wishing to just use this store need only pay attention to
+ * the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Initializes a new {@link Auth_OpenID_FileStore}. This
+ * initializes the nonce and association directories, which are
+ * subdirectories of the directory passed in.
+ *
+ * @param string $directory This is the directory to put the store
+ * directories in.
+ */
+ function Auth_OpenID_FileStore($directory)
+ {
+ if (!Auth_OpenID::ensureDir($directory)) {
+ trigger_error('Not a directory and failed to create: '
+ . $directory, E_USER_ERROR);
+ }
+ $directory = realpath($directory);
+
+ $this->directory = $directory;
+ $this->active = true;
+
+ $this->nonce_dir = $directory . DIRECTORY_SEPARATOR . 'nonces';
+
+ $this->association_dir = $directory . DIRECTORY_SEPARATOR .
+ 'associations';
+
+ // Temp dir must be on the same filesystem as the assciations
+ // $directory.
+ $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp';
+
+ $this->max_nonce_age = 6 * 60 * 60; // Six hours, in seconds
+
+ if (!$this->_setup()) {
+ trigger_error('Failed to initialize OpenID file store in ' .
+ $directory, E_USER_ERROR);
+ }
+ }
+
+ function destroy()
+ {
+ Auth_OpenID_FileStore::_rmtree($this->directory);
+ $this->active = false;
+ }
+
+ /**
+ * Make sure that the directories in which we store our data
+ * exist.
+ *
+ * @access private
+ */
+ function _setup()
+ {
+ return (Auth_OpenID::ensureDir($this->nonce_dir) &&
+ Auth_OpenID::ensureDir($this->association_dir) &&
+ Auth_OpenID::ensureDir($this->temp_dir));
+ }
+
+ /**
+ * Create a temporary file on the same filesystem as
+ * $this->association_dir.
+ *
+ * The temporary directory should not be cleaned if there are any
+ * processes using the store. If there is no active process using
+ * the store, it is safe to remove all of the files in the
+ * temporary directory.
+ *
+ * @return array ($fd, $filename)
+ * @access private
+ */
+ function _mktemp()
+ {
+ $name = Auth_OpenID_FileStore::_mkstemp($dir = $this->temp_dir);
+ $file_obj = @fopen($name, 'wb');
+ if ($file_obj !== false) {
+ return array($file_obj, $name);
+ } else {
+ Auth_OpenID_FileStore::_removeIfPresent($name);
+ }
+ }
+
+ function cleanupNonces()
+ {
+ global $Auth_OpenID_SKEW;
+
+ $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir);
+ $now = time();
+
+ $removed = 0;
+ // Check all nonces for expiry
+ foreach ($nonces as $nonce_fname) {
+ $base = basename($nonce_fname);
+ $parts = explode('-', $base, 2);
+ $timestamp = $parts[0];
+ $timestamp = intval($timestamp, 16);
+ if (abs($timestamp - $now) > $Auth_OpenID_SKEW) {
+ Auth_OpenID_FileStore::_removeIfPresent($nonce_fname);
+ $removed += 1;
+ }
+ }
+ return $removed;
+ }
+
+ /**
+ * Create a unique filename for a given server url and
+ * handle. This implementation does not assume anything about the
+ * format of the handle. The filename that is returned will
+ * contain the domain name from the server URL for ease of human
+ * inspection of the data directory.
+ *
+ * @return string $filename
+ */
+ function getAssociationFilename($server_url, $handle)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if (strpos($server_url, '://') === false) {
+ trigger_error(sprintf("Bad server URL: %s", $server_url),
+ E_USER_WARNING);
+ return null;
+ }
+
+ list($proto, $rest) = explode('://', $server_url, 2);
+ $parts = explode('/', $rest);
+ $domain = Auth_OpenID_FileStore::_filenameEscape($parts[0]);
+ $url_hash = Auth_OpenID_FileStore::_safe64($server_url);
+ if ($handle) {
+ $handle_hash = Auth_OpenID_FileStore::_safe64($handle);
+ } else {
+ $handle_hash = '';
+ }
+
+ $filename = sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash,
+ $handle_hash);
+
+ return $this->association_dir. DIRECTORY_SEPARATOR . $filename;
+ }
+
+ /**
+ * Store an association in the association directory.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return false;
+ }
+
+ $association_s = $association->serialize();
+ $filename = $this->getAssociationFilename($server_url,
+ $association->handle);
+ list($tmp_file, $tmp) = $this->_mktemp();
+
+ if (!$tmp_file) {
+ trigger_error("_mktemp didn't return a valid file descriptor",
+ E_USER_WARNING);
+ return false;
+ }
+
+ fwrite($tmp_file, $association_s);
+
+ fflush($tmp_file);
+
+ fclose($tmp_file);
+
+ if (@rename($tmp, $filename)) {
+ return true;
+ } else {
+ // In case we are running on Windows, try unlinking the
+ // file in case it exists.
+ @unlink($filename);
+
+ // Now the target should not exist. Try renaming again,
+ // giving up if it fails.
+ if (@rename($tmp, $filename)) {
+ return true;
+ }
+ }
+
+ // If there was an error, don't leave the temporary file
+ // around.
+ Auth_OpenID_FileStore::_removeIfPresent($tmp);
+ return false;
+ }
+
+ /**
+ * Retrieve an association. If no handle is specified, return the
+ * association with the most recent issue time.
+ *
+ * @return mixed $association
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if ($handle === null) {
+ $handle = '';
+ }
+
+ // The filename with the empty handle is a prefix of all other
+ // associations for the given server URL.
+ $filename = $this->getAssociationFilename($server_url, $handle);
+
+ if ($handle) {
+ return $this->_getAssociation($filename);
+ } else {
+ $association_files =
+ Auth_OpenID_FileStore::_listdir($this->association_dir);
+ $matching_files = array();
+
+ // strip off the path to do the comparison
+ $name = basename($filename);
+ foreach ($association_files as $association_file) {
+ $base = basename($association_file);
+ if (strpos($base, $name) === 0) {
+ $matching_files[] = $association_file;
+ }
+ }
+
+ $matching_associations = array();
+ // read the matching files and sort by time issued
+ foreach ($matching_files as $full_name) {
+ $association = $this->_getAssociation($full_name);
+ if ($association !== null) {
+ $matching_associations[] = array($association->issued,
+ $association);
+ }
+ }
+
+ $issued = array();
+ $assocs = array();
+ foreach ($matching_associations as $key => $assoc) {
+ $issued[$key] = $assoc[0];
+ $assocs[$key] = $assoc[1];
+ }
+
+ array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
+ $matching_associations);
+
+ // return the most recently issued one.
+ if ($matching_associations) {
+ list($issued, $assoc) = $matching_associations[0];
+ return $assoc;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($filename)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $assoc_file = @fopen($filename, 'rb');
+
+ if ($assoc_file === false) {
+ return null;
+ }
+
+ $assoc_s = fread($assoc_file, filesize($filename));
+ fclose($assoc_file);
+
+ if (!$assoc_s) {
+ return null;
+ }
+
+ $association =
+ Auth_OpenID_Association::deserialize('Auth_OpenID_Association',
+ $assoc_s);
+
+ if (!$association) {
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ return null;
+ }
+
+ if ($association->getExpiresIn() == 0) {
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ return null;
+ } else {
+ return $association;
+ }
+ }
+
+ /**
+ * Remove an association if it exists. Do nothing if it does not.
+ *
+ * @return bool $success
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $assoc = $this->getAssociation($server_url, $handle);
+ if ($assoc === null) {
+ return false;
+ } else {
+ $filename = $this->getAssociationFilename($server_url, $handle);
+ return Auth_OpenID_FileStore::_removeIfPresent($filename);
+ }
+ }
+
+ /**
+ * Return whether this nonce is present. As a side effect, mark it
+ * as no longer present.
+ *
+ * @return bool $present
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+ return False;
+ }
+
+ if ($server_url) {
+ list($proto, $rest) = explode('://', $server_url, 2);
+ } else {
+ $proto = '';
+ $rest = '';
+ }
+
+ $parts = explode('/', $rest, 2);
+ $domain = $this->_filenameEscape($parts[0]);
+ $url_hash = $this->_safe64($server_url);
+ $salt_hash = $this->_safe64($salt);
+
+ $filename = sprintf('%08x-%s-%s-%s-%s', $timestamp, $proto,
+ $domain, $url_hash, $salt_hash);
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $filename;
+
+ $result = @fopen($filename, 'x');
+
+ if ($result === false) {
+ return false;
+ } else {
+ fclose($result);
+ return true;
+ }
+ }
+
+ /**
+ * Remove expired entries from the database. This is potentially
+ * expensive, so only run when it is acceptable to take time.
+ *
+ * @access private
+ */
+ function _allAssocs()
+ {
+ $all_associations = array();
+
+ $association_filenames =
+ Auth_OpenID_FileStore::_listdir($this->association_dir);
+
+ foreach ($association_filenames as $association_filename) {
+ $association_file = fopen($association_filename, 'rb');
+
+ if ($association_file !== false) {
+ $assoc_s = fread($association_file,
+ filesize($association_filename));
+ fclose($association_file);
+
+ // Remove expired or corrupted associations
+ $association =
+ Auth_OpenID_Association::deserialize(
+ 'Auth_OpenID_Association', $assoc_s);
+
+ if ($association === null) {
+ Auth_OpenID_FileStore::_removeIfPresent(
+ $association_filename);
+ } else {
+ if ($association->getExpiresIn() == 0) {
+ $all_associations[] = array($association_filename,
+ $association);
+ }
+ }
+ }
+ }
+
+ return $all_associations;
+ }
+
+ function clean()
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir);
+ $now = time();
+
+ // Check all nonces for expiry
+ foreach ($nonces as $nonce) {
+ if (!Auth_OpenID_checkTimestamp($nonce, $now)) {
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce;
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ }
+ }
+
+ foreach ($this->_allAssocs() as $pair) {
+ list($assoc_filename, $assoc) = $pair;
+ if ($assoc->getExpiresIn() == 0) {
+ Auth_OpenID_FileStore::_removeIfPresent($assoc_filename);
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _rmtree($dir)
+ {
+ if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR) {
+ $dir .= DIRECTORY_SEPARATOR;
+ }
+
+ if ($handle = opendir($dir)) {
+ while ($item = readdir($handle)) {
+ if (!in_array($item, array('.', '..'))) {
+ if (is_dir($dir . $item)) {
+
+ if (!Auth_OpenID_FileStore::_rmtree($dir . $item)) {
+ return false;
+ }
+ } else if (is_file($dir . $item)) {
+ if (!unlink($dir . $item)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ closedir($handle);
+
+ if (!@rmdir($dir)) {
+ return false;
+ }
+
+ return true;
+ } else {
+ // Couldn't open directory.
+ return false;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _mkstemp($dir)
+ {
+ foreach (range(0, 4) as $i) {
+ $name = tempnam($dir, "php_openid_filestore_");
+
+ if ($name !== false) {
+ return $name;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _mkdtemp($dir)
+ {
+ foreach (range(0, 4) as $i) {
+ $name = $dir . strval(DIRECTORY_SEPARATOR) . strval(getmypid()) .
+ "-" . strval(rand(1, time()));
+ if (!mkdir($name, 0700)) {
+ return false;
+ } else {
+ return $name;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _listdir($dir)
+ {
+ $handle = opendir($dir);
+ $files = array();
+ while (false !== ($filename = readdir($handle))) {
+ if (!in_array($filename, array('.', '..'))) {
+ $files[] = $dir . DIRECTORY_SEPARATOR . $filename;
+ }
+ }
+ return $files;
+ }
+
+ /**
+ * @access private
+ */
+ function _isFilenameSafe($char)
+ {
+ $_Auth_OpenID_filename_allowed = Auth_OpenID_letters .
+ Auth_OpenID_digits . ".";
+ return (strpos($_Auth_OpenID_filename_allowed, $char) !== false);
+ }
+
+ /**
+ * @access private
+ */
+ function _safe64($str)
+ {
+ $h64 = base64_encode(Auth_OpenID_SHA1($str));
+ $h64 = str_replace('+', '_', $h64);
+ $h64 = str_replace('/', '.', $h64);
+ $h64 = str_replace('=', '', $h64);
+ return $h64;
+ }
+
+ /**
+ * @access private
+ */
+ function _filenameEscape($str)
+ {
+ $filename = "";
+ $b = Auth_OpenID::toBytes($str);
+
+ for ($i = 0; $i < count($b); $i++) {
+ $c = $b[$i];
+ if (Auth_OpenID_FileStore::_isFilenameSafe($c)) {
+ $filename .= $c;
+ } else {
+ $filename .= sprintf("_%02X", ord($c));
+ }
+ }
+ return $filename;
+ }
+
+ /**
+ * Attempt to remove a file, returning whether the file existed at
+ * the time of the call.
+ *
+ * @access private
+ * @return bool $result True if the file was present, false if not.
+ */
+ function _removeIfPresent($filename)
+ {
+ return @unlink($filename);
+ }
+
+ function cleanupAssociations()
+ {
+ $removed = 0;
+ foreach ($this->_allAssocs() as $pair) {
+ list($assoc_filename, $assoc) = $pair;
+ if ($assoc->getExpiresIn() == 0) {
+ $this->_removeIfPresent($assoc_filename);
+ $removed += 1;
+ }
+ }
+ return $removed;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/HMAC.php b/Auth/OpenID/HMAC.php
new file mode 100644
index 0000000..ec42db8
--- /dev/null
+++ b/Auth/OpenID/HMAC.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * This is the HMACSHA1 implementation for the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID.php';
+
+/**
+ * SHA1_BLOCKSIZE is this module's SHA1 blocksize used by the fallback
+ * implementation.
+ */
+define('Auth_OpenID_SHA1_BLOCKSIZE', 64);
+
+function Auth_OpenID_SHA1($text)
+{
+ if (function_exists('hash') &&
+ function_exists('hash_algos') &&
+ (in_array('sha1', hash_algos()))) {
+ // PHP 5 case (sometimes): 'hash' available and 'sha1' algo
+ // supported.
+ return hash('sha1', $text, true);
+ } else if (function_exists('sha1')) {
+ // PHP 4 case: 'sha1' available.
+ $hex = sha1($text);
+ $raw = '';
+ for ($i = 0; $i < 40; $i += 2) {
+ $hexcode = substr($hex, $i, 2);
+ $charcode = (int)base_convert($hexcode, 16, 10);
+ $raw .= chr($charcode);
+ }
+ return $raw;
+ } else {
+ // Explode.
+ trigger_error('No SHA1 function found', E_USER_ERROR);
+ }
+}
+
+/**
+ * Compute an HMAC/SHA1 hash.
+ *
+ * @access private
+ * @param string $key The HMAC key
+ * @param string $text The message text to hash
+ * @return string $mac The MAC
+ */
+function Auth_OpenID_HMACSHA1($key, $text)
+{
+ if (Auth_OpenID::bytes($key) > Auth_OpenID_SHA1_BLOCKSIZE) {
+ $key = Auth_OpenID_SHA1($key, true);
+ }
+
+ $key = str_pad($key, Auth_OpenID_SHA1_BLOCKSIZE, chr(0x00));
+ $ipad = str_repeat(chr(0x36), Auth_OpenID_SHA1_BLOCKSIZE);
+ $opad = str_repeat(chr(0x5c), Auth_OpenID_SHA1_BLOCKSIZE);
+ $hash1 = Auth_OpenID_SHA1(($key ^ $ipad) . $text, true);
+ $hmac = Auth_OpenID_SHA1(($key ^ $opad) . $hash1, true);
+ return $hmac;
+}
+
+if (function_exists('hash') &&
+ function_exists('hash_algos') &&
+ (in_array('sha256', hash_algos()))) {
+ function Auth_OpenID_SHA256($text)
+ {
+ // PHP 5 case: 'hash' available and 'sha256' algo supported.
+ return hash('sha256', $text, true);
+ }
+ define('Auth_OpenID_SHA256_SUPPORTED', true);
+} else {
+ define('Auth_OpenID_SHA256_SUPPORTED', false);
+}
+
+if (function_exists('hash_hmac') &&
+ function_exists('hash_algos') &&
+ (in_array('sha256', hash_algos()))) {
+
+ function Auth_OpenID_HMACSHA256($key, $text)
+ {
+ // Return raw MAC (not hex string).
+ return hash_hmac('sha256', $text, $key, true);
+ }
+
+ define('Auth_OpenID_HMACSHA256_SUPPORTED', true);
+} else {
+ define('Auth_OpenID_HMACSHA256_SUPPORTED', false);
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Interface.php b/Auth/OpenID/Interface.php
new file mode 100644
index 0000000..f4c6062
--- /dev/null
+++ b/Auth/OpenID/Interface.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * This file specifies the interface for PHP OpenID store implementations.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * This is the interface for the store objects the OpenID library
+ * uses. It is a single class that provides all of the persistence
+ * mechanisms that the OpenID library needs, for both servers and
+ * consumers. If you want to create an SQL-driven store, please see
+ * then {@link Auth_OpenID_SQLStore} class.
+ *
+ * Change: Version 2.0 removed the storeNonce, getAuthKey, and isDumb
+ * methods, and changed the behavior of the useNonce method to support
+ * one-way nonces.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ */
+class Auth_OpenID_OpenIDStore {
+ /**
+ * This method puts an Association object into storage,
+ * retrievable by server URL and handle.
+ *
+ * @param string $server_url The URL of the identity server that
+ * this association is with. Because of the way the server portion
+ * of the library uses this interface, don't assume there are any
+ * limitations on the character set of the input string. In
+ * particular, expect to see unescaped non-url-safe characters in
+ * the server_url field.
+ *
+ * @param Association $association The Association to store.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::storeAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Remove expired nonces from the store.
+ *
+ * Discards any nonce from storage that is old enough that its
+ * timestamp would not pass useNonce().
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ *
+ * @return the number of nonces expired
+ */
+ function cleanupNonces()
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::cleanupNonces ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Remove expired associations from the store.
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ *
+ * @return the number of associations expired.
+ */
+ function cleanupAssociations()
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::cleanupAssociations ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Shortcut for cleanupNonces(), cleanupAssociations().
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ */
+ function cleanup()
+ {
+ return array($this->cleanupNonces(),
+ $this->cleanupAssociations());
+ }
+
+ /**
+ * Report whether this storage supports cleanup
+ */
+ function supportsCleanup()
+ {
+ return true;
+ }
+
+ /**
+ * This method returns an Association object from storage that
+ * matches the server URL and, if specified, handle. It returns
+ * null if no such association is found or if the matching
+ * association is expired.
+ *
+ * If no handle is specified, the store may return any association
+ * which matches the server URL. If multiple associations are
+ * valid, the recommended return value for this method is the one
+ * most recently issued.
+ *
+ * This method is allowed (and encouraged) to garbage collect
+ * expired associations when found. This method must not return
+ * expired associations.
+ *
+ * @param string $server_url The URL of the identity server to get
+ * the association for. Because of the way the server portion of
+ * the library uses this interface, don't assume there are any
+ * limitations on the character set of the input string. In
+ * particular, expect to see unescaped non-url-safe characters in
+ * the server_url field.
+ *
+ * @param mixed $handle This optional parameter is the handle of
+ * the specific association to get. If no specific handle is
+ * provided, any valid association matching the server URL is
+ * returned.
+ *
+ * @return Association The Association for the given identity
+ * server.
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::getAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This method removes the matching association if it's found, and
+ * returns whether the association was removed or not.
+ *
+ * @param string $server_url The URL of the identity server the
+ * association to remove belongs to. Because of the way the server
+ * portion of the library uses this interface, don't assume there
+ * are any limitations on the character set of the input
+ * string. In particular, expect to see unescaped non-url-safe
+ * characters in the server_url field.
+ *
+ * @param string $handle This is the handle of the association to
+ * remove. If there isn't an association found that matches both
+ * the given URL and handle, then there was no matching handle
+ * found.
+ *
+ * @return mixed Returns whether or not the given association existed.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::removeAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Called when using a nonce.
+ *
+ * This method should return C{True} if the nonce has not been
+ * used before, and store it for a while to make sure nobody
+ * tries to use the same value again. If the nonce has already
+ * been used, return C{False}.
+ *
+ * Change: In earlier versions, round-trip nonces were used and a
+ * nonce was only valid if it had been previously stored with
+ * storeNonce. Version 2.0 uses one-way nonces, requiring a
+ * different implementation here that does not depend on a
+ * storeNonce call. (storeNonce is no longer part of the
+ * interface.
+ *
+ * @param string $nonce The nonce to use.
+ *
+ * @return bool Whether or not the nonce was valid.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::useNonce ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Removes all entries from the store; implementation is optional.
+ */
+ function reset()
+ {
+ }
+
+}
+?> \ No newline at end of file
diff --git a/Auth/OpenID/KVForm.php b/Auth/OpenID/KVForm.php
new file mode 100644
index 0000000..fb342a0
--- /dev/null
+++ b/Auth/OpenID/KVForm.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * OpenID protocol key-value/comma-newline format parsing and
+ * serialization
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Container for key-value/comma-newline OpenID format and parsing
+ */
+class Auth_OpenID_KVForm {
+ /**
+ * Convert an OpenID colon/newline separated string into an
+ * associative array
+ *
+ * @static
+ * @access private
+ */
+ function toArray($kvs, $strict=false)
+ {
+ $lines = explode("\n", $kvs);
+
+ $last = array_pop($lines);
+ if ($last !== '') {
+ array_push($lines, $last);
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $values = array();
+
+ for ($lineno = 0; $lineno < count($lines); $lineno++) {
+ $line = $lines[$lineno];
+ $kv = explode(':', $line, 2);
+ if (count($kv) != 2) {
+ if ($strict) {
+ return false;
+ }
+ continue;
+ }
+
+ $key = $kv[0];
+ $tkey = trim($key);
+ if ($tkey != $key) {
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $value = $kv[1];
+ $tval = trim($value);
+ if ($tval != $value) {
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $values[$tkey] = $tval;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Convert an array into an OpenID colon/newline separated string
+ *
+ * @static
+ * @access private
+ */
+ function fromArray($values)
+ {
+ if ($values === null) {
+ return null;
+ }
+
+ ksort($values);
+
+ $serialized = '';
+ foreach ($values as $key => $value) {
+ if (is_array($value)) {
+ list($key, $value) = array($value[0], $value[1]);
+ }
+
+ if (strpos($key, ':') !== false) {
+ return null;
+ }
+
+ if (strpos($key, "\n") !== false) {
+ return null;
+ }
+
+ if (strpos($value, "\n") !== false) {
+ return null;
+ }
+ $serialized .= "$key:$value\n";
+ }
+ return $serialized;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/MemcachedStore.php b/Auth/OpenID/MemcachedStore.php
new file mode 100644
index 0000000..d357c6b
--- /dev/null
+++ b/Auth/OpenID/MemcachedStore.php
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * This file supplies a memcached store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author Artemy Tregubenko <me@arty.name>
+ * @copyright 2008 JanRain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ * Contributed by Open Web Technologies <http://openwebtech.ru/>
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+
+/**
+ * This is a memcached-based store for OpenID associations and
+ * nonces.
+ *
+ * As memcache has limit of 250 chars for key length,
+ * server_url, handle and salt are hashed with sha1().
+ *
+ * Most of the methods of this class are implementation details.
+ * People wishing to just use this store need only pay attention to
+ * the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MemcachedStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Initializes a new {@link Auth_OpenID_MemcachedStore} instance.
+ * Just saves memcached object as property.
+ *
+ * @param resource connection Memcache connection resourse
+ */
+ function Auth_OpenID_MemcachedStore($connection, $compress = false)
+ {
+ $this->connection = $connection;
+ $this->compress = $compress ? MEMCACHE_COMPRESSED : 0;
+ }
+
+ /**
+ * Store association until its expiration time in memcached.
+ * Overwrites any existing association with same server_url and
+ * handle. Handles list of associations for every server.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ // create memcached keys for association itself
+ // and list of associations for this server
+ $associationKey = $this->associationKey($server_url,
+ $association->handle);
+ $serverKey = $this->associationServerKey($server_url);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+
+ // if no such list, initialize it with empty array
+ if (!$serverAssociations) {
+ $serverAssociations = array();
+ }
+ // and store given association key in it
+ $serverAssociations[$association->issued] = $associationKey;
+
+ // save associations' keys list
+ $this->connection->set(
+ $serverKey,
+ $serverAssociations,
+ $this->compress
+ );
+ // save association itself
+ $this->connection->set(
+ $associationKey,
+ $association,
+ $this->compress,
+ $association->issued + $association->lifetime);
+ }
+
+ /**
+ * Read association from memcached. If no handle given
+ * and multiple associations found, returns latest issued
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ // simple case: handle given
+ if ($handle !== null) {
+ // get association, return null if failed
+ $association = $this->connection->get(
+ $this->associationKey($server_url, $handle));
+ return $association ? $association : null;
+ }
+
+ // no handle given, working with list
+ // create key for list of associations
+ $serverKey = $this->associationServerKey($server_url);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+ // return null if failed or got empty list
+ if (!$serverAssociations) {
+ return null;
+ }
+
+ // get key of most recently issued association
+ $keys = array_keys($serverAssociations);
+ sort($keys);
+ $lastKey = $serverAssociations[array_pop($keys)];
+
+ // get association, return null if failed
+ $association = $this->connection->get($lastKey);
+ return $association ? $association : null;
+ }
+
+ /**
+ * Immediately delete association from memcache.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ // create memcached keys for association itself
+ // and list of associations for this server
+ $serverKey = $this->associationServerKey($server_url);
+ $associationKey = $this->associationKey($server_url,
+ $handle);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+ // return null if failed or got empty list
+ if (!$serverAssociations) {
+ return false;
+ }
+
+ // ensure that given association key exists in list
+ $serverAssociations = array_flip($serverAssociations);
+ if (!array_key_exists($associationKey, $serverAssociations)) {
+ return false;
+ }
+
+ // remove given association key from list
+ unset($serverAssociations[$associationKey]);
+ $serverAssociations = array_flip($serverAssociations);
+
+ // save updated list
+ $this->connection->set(
+ $serverKey,
+ $serverAssociations,
+ $this->compress
+ );
+
+ // delete association
+ return $this->connection->delete($associationKey);
+ }
+
+ /**
+ * Create nonce for server and salt, expiring after
+ * $Auth_OpenID_SKEW seconds.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ // save one request to memcache when nonce obviously expired
+ if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
+ return false;
+ }
+
+ // returns false when nonce already exists
+ // otherwise adds nonce
+ return $this->connection->add(
+ 'openid_nonce_' . sha1($server_url) . '_' . sha1($salt),
+ 1, // any value here
+ $this->compress,
+ $Auth_OpenID_SKEW);
+ }
+
+ /**
+ * Memcache key is prefixed with 'openid_association_' string.
+ */
+ function associationKey($server_url, $handle = null)
+ {
+ return 'openid_association_' . sha1($server_url) . '_' . sha1($handle);
+ }
+
+ /**
+ * Memcache key is prefixed with 'openid_association_' string.
+ */
+ function associationServerKey($server_url)
+ {
+ return 'openid_association_server_' . sha1($server_url);
+ }
+
+ /**
+ * Report that this storage doesn't support cleanup
+ */
+ function supportsCleanup()
+ {
+ return false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Message.php b/Auth/OpenID/Message.php
new file mode 100644
index 0000000..5ab115a
--- /dev/null
+++ b/Auth/OpenID/Message.php
@@ -0,0 +1,920 @@
+<?php
+
+/**
+ * Extension argument processing code
+ *
+ * @package OpenID
+ */
+
+/**
+ * Import tools needed to deal with messages.
+ */
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/KVForm.php';
+require_once 'Auth/Yadis/XML.php';
+require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse
+
+// This doesn't REALLY belong here, but where is better?
+define('Auth_OpenID_IDENTIFIER_SELECT',
+ "http://specs.openid.net/auth/2.0/identifier_select");
+
+// URI for Simple Registration extension, the only commonly deployed
+// OpenID 1.x extension, and so a special case
+define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');
+
+// The OpenID 1.X namespace URI
+define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
+define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');
+
+function Auth_OpenID_isOpenID1($ns)
+{
+ return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
+ ($ns == Auth_OpenID_OPENID1_NS);
+}
+
+// The OpenID 2.0 namespace URI
+define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');
+
+// The namespace consisting of pairs with keys that are prefixed with
+// "openid." but not in another namespace.
+define('Auth_OpenID_NULL_NAMESPACE', 'Null namespace');
+
+// The null namespace, when it is an allowed OpenID namespace
+define('Auth_OpenID_OPENID_NS', 'OpenID namespace');
+
+// The top-level namespace, excluding all pairs with keys that start
+// with "openid."
+define('Auth_OpenID_BARE_NS', 'Bare namespace');
+
+// Sentinel for Message implementation to indicate that getArg should
+// return null instead of returning a default.
+define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');
+
+// Limit, in bytes, of identity provider and return_to URLs, including
+// response payload. See OpenID 1.1 specification, Appendix D.
+define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);
+
+// All OpenID protocol fields. Used to check namespace aliases.
+global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
+$Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
+ 'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
+ 'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
+ 'dh_consumer_public', 'claimed_id', 'identity', 'realm',
+ 'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
+ 'assoc_handle', 'trust_root', 'openid');
+
+// Global namespace / alias registration map. See
+// Auth_OpenID_registerNamespaceAlias.
+global $Auth_OpenID_registered_aliases;
+$Auth_OpenID_registered_aliases = array();
+
+/**
+ * Registers a (namespace URI, alias) mapping in a global namespace
+ * alias map. Raises NamespaceAliasRegistrationError if either the
+ * namespace URI or alias has already been registered with a different
+ * value. This function is required if you want to use a namespace
+ * with an OpenID 1 message.
+ */
+function Auth_OpenID_registerNamespaceAlias($namespace_uri, $alias)
+{
+ global $Auth_OpenID_registered_aliases;
+
+ if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
+ $alias) == $namespace_uri) {
+ return true;
+ }
+
+ if (in_array($namespace_uri,
+ array_values($Auth_OpenID_registered_aliases))) {
+ return false;
+ }
+
+ if (in_array($alias, array_keys($Auth_OpenID_registered_aliases))) {
+ return false;
+ }
+
+ $Auth_OpenID_registered_aliases[$alias] = $namespace_uri;
+ return true;
+}
+
+/**
+ * Removes a (namespace_uri, alias) registration from the global
+ * namespace alias map. Returns true if the removal succeeded; false
+ * if not (if the mapping did not exist).
+ */
+function Auth_OpenID_removeNamespaceAlias($namespace_uri, $alias)
+{
+ global $Auth_OpenID_registered_aliases;
+
+ if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
+ $alias) === $namespace_uri) {
+ unset($Auth_OpenID_registered_aliases[$alias]);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * An Auth_OpenID_Mapping maintains a mapping from arbitrary keys to
+ * arbitrary values. (This is unlike an ordinary PHP array, whose
+ * keys may be only simple scalars.)
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Mapping {
+ /**
+ * Initialize a mapping. If $classic_array is specified, its keys
+ * and values are used to populate the mapping.
+ */
+ function Auth_OpenID_Mapping($classic_array = null)
+ {
+ $this->keys = array();
+ $this->values = array();
+
+ if (is_array($classic_array)) {
+ foreach ($classic_array as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+ }
+
+ /**
+ * Returns true if $thing is an Auth_OpenID_Mapping object; false
+ * if not.
+ */
+ function isA($thing)
+ {
+ return (is_object($thing) &&
+ strtolower(get_class($thing)) == 'auth_openid_mapping');
+ }
+
+ /**
+ * Returns an array of the keys in the mapping.
+ */
+ function keys()
+ {
+ return $this->keys;
+ }
+
+ /**
+ * Returns an array of values in the mapping.
+ */
+ function values()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Returns an array of (key, value) pairs in the mapping.
+ */
+ function items()
+ {
+ $temp = array();
+
+ for ($i = 0; $i < count($this->keys); $i++) {
+ $temp[] = array($this->keys[$i],
+ $this->values[$i]);
+ }
+ return $temp;
+ }
+
+ /**
+ * Returns the "length" of the mapping, or the number of keys.
+ */
+ function len()
+ {
+ return count($this->keys);
+ }
+
+ /**
+ * Sets a key-value pair in the mapping. If the key already
+ * exists, its value is replaced with the new value.
+ */
+ function set($key, $value)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ $this->values[$index] = $value;
+ } else {
+ $this->keys[] = $key;
+ $this->values[] = $value;
+ }
+ }
+
+ /**
+ * Gets a specified value from the mapping, associated with the
+ * specified key. If the key does not exist in the mapping,
+ * $default is returned instead.
+ */
+ function get($key, $default = null)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ return $this->values[$index];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _reflow()
+ {
+ // PHP is broken yet again. Sort the arrays to remove the
+ // hole in the numeric indexes that make up the array.
+ $old_keys = $this->keys;
+ $old_values = $this->values;
+
+ $this->keys = array();
+ $this->values = array();
+
+ foreach ($old_keys as $k) {
+ $this->keys[] = $k;
+ }
+
+ foreach ($old_values as $v) {
+ $this->values[] = $v;
+ }
+ }
+
+ /**
+ * Deletes a key-value pair from the mapping with the specified
+ * key.
+ */
+ function del($key)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ unset($this->keys[$index]);
+ unset($this->values[$index]);
+ $this->_reflow();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the specified value has a key in the mapping;
+ * false if not.
+ */
+ function contains($value)
+ {
+ return (array_search($value, $this->keys) !== false);
+ }
+}
+
+/**
+ * Maintains a bijective map between namespace uris and aliases.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_NamespaceMap {
+ function Auth_OpenID_NamespaceMap()
+ {
+ $this->alias_to_namespace = new Auth_OpenID_Mapping();
+ $this->namespace_to_alias = new Auth_OpenID_Mapping();
+ $this->implicit_namespaces = array();
+ }
+
+ function getAlias($namespace_uri)
+ {
+ return $this->namespace_to_alias->get($namespace_uri);
+ }
+
+ function getNamespaceURI($alias)
+ {
+ return $this->alias_to_namespace->get($alias);
+ }
+
+ function iterNamespaceURIs()
+ {
+ // Return an iterator over the namespace URIs
+ return $this->namespace_to_alias->keys();
+ }
+
+ function iterAliases()
+ {
+ // Return an iterator over the aliases"""
+ return $this->alias_to_namespace->keys();
+ }
+
+ function iteritems()
+ {
+ return $this->namespace_to_alias->items();
+ }
+
+ function isImplicit($namespace_uri)
+ {
+ return in_array($namespace_uri, $this->implicit_namespaces);
+ }
+
+ function addAlias($namespace_uri, $desired_alias, $implicit=false)
+ {
+ // Add an alias from this namespace URI to the desired alias
+ global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
+
+ // Check that desired_alias is not an openid protocol field as
+ // per the spec.
+ if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) {
+ Auth_OpenID::log("\"%s\" is not an allowed namespace alias",
+ $desired_alias);
+ return null;
+ }
+
+ // Check that desired_alias does not contain a period as per
+ // the spec.
+ if (strpos($desired_alias, '.') !== false) {
+ Auth_OpenID::log('"%s" must not contain a dot', $desired_alias);
+ return null;
+ }
+
+ // Check that there is not a namespace already defined for the
+ // desired alias
+ $current_namespace_uri =
+ $this->alias_to_namespace->get($desired_alias);
+
+ if (($current_namespace_uri !== null) &&
+ ($current_namespace_uri != $namespace_uri)) {
+ Auth_OpenID::log('Cannot map "%s" because previous mapping exists',
+ $namespace_uri);
+ return null;
+ }
+
+ // Check that there is not already a (different) alias for
+ // this namespace URI
+ $alias = $this->namespace_to_alias->get($namespace_uri);
+
+ if (($alias !== null) && ($alias != $desired_alias)) {
+ Auth_OpenID::log('Cannot map %s to alias %s. ' .
+ 'It is already mapped to alias %s',
+ $namespace_uri, $desired_alias, $alias);
+ return null;
+ }
+
+ assert((Auth_OpenID_NULL_NAMESPACE === $desired_alias) ||
+ is_string($desired_alias));
+
+ $this->alias_to_namespace->set($desired_alias, $namespace_uri);
+ $this->namespace_to_alias->set($namespace_uri, $desired_alias);
+ if ($implicit) {
+ array_push($this->implicit_namespaces, $namespace_uri);
+ }
+
+ return $desired_alias;
+ }
+
+ function add($namespace_uri)
+ {
+ // Add this namespace URI to the mapping, without caring what
+ // alias it ends up with
+
+ // See if this namespace is already mapped to an alias
+ $alias = $this->namespace_to_alias->get($namespace_uri);
+
+ if ($alias !== null) {
+ return $alias;
+ }
+
+ // Fall back to generating a numerical alias
+ $i = 0;
+ while (1) {
+ $alias = 'ext' . strval($i);
+ if ($this->addAlias($namespace_uri, $alias) === null) {
+ $i += 1;
+ } else {
+ return $alias;
+ }
+ }
+
+ // Should NEVER be reached!
+ return null;
+ }
+
+ function contains($namespace_uri)
+ {
+ return $this->isDefined($namespace_uri);
+ }
+
+ function isDefined($namespace_uri)
+ {
+ return $this->namespace_to_alias->contains($namespace_uri);
+ }
+}
+
+/**
+ * In the implementation of this object, null represents the global
+ * namespace as well as a namespace with no key.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Message {
+
+ function Auth_OpenID_Message($openid_namespace = null)
+ {
+ // Create an empty Message
+ $this->allowed_openid_namespaces = array(
+ Auth_OpenID_OPENID1_NS,
+ Auth_OpenID_THE_OTHER_OPENID1_NS,
+ Auth_OpenID_OPENID2_NS);
+
+ $this->args = new Auth_OpenID_Mapping();
+ $this->namespaces = new Auth_OpenID_NamespaceMap();
+ if ($openid_namespace === null) {
+ $this->_openid_ns_uri = null;
+ } else {
+ $implicit = Auth_OpenID_isOpenID1($openid_namespace);
+ $this->setOpenIDNamespace($openid_namespace, $implicit);
+ }
+ }
+
+ function isOpenID1()
+ {
+ return Auth_OpenID_isOpenID1($this->getOpenIDNamespace());
+ }
+
+ function isOpenID2()
+ {
+ return $this->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS;
+ }
+
+ function fromPostArgs($args)
+ {
+ // Construct a Message containing a set of POST arguments
+ $obj = new Auth_OpenID_Message();
+
+ // Partition into "openid." args and bare args
+ $openid_args = array();
+ foreach ($args as $key => $value) {
+
+ if (is_array($value)) {
+ return null;
+ }
+
+ $parts = explode('.', $key, 2);
+
+ if (count($parts) == 2) {
+ list($prefix, $rest) = $parts;
+ } else {
+ $prefix = null;
+ }
+
+ if ($prefix != 'openid') {
+ $obj->args->set(array(Auth_OpenID_BARE_NS, $key), $value);
+ } else {
+ $openid_args[$rest] = $value;
+ }
+ }
+
+ if ($obj->_fromOpenIDArgs($openid_args)) {
+ return $obj;
+ } else {
+ return null;
+ }
+ }
+
+ function fromOpenIDArgs($openid_args)
+ {
+ // Takes an array.
+
+ // Construct a Message from a parsed KVForm message
+ $obj = new Auth_OpenID_Message();
+ if ($obj->_fromOpenIDArgs($openid_args)) {
+ return $obj;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _fromOpenIDArgs($openid_args)
+ {
+ global $Auth_OpenID_registered_aliases;
+
+ // Takes an Auth_OpenID_Mapping instance OR an array.
+
+ if (!Auth_OpenID_Mapping::isA($openid_args)) {
+ $openid_args = new Auth_OpenID_Mapping($openid_args);
+ }
+
+ $ns_args = array();
+
+ // Resolve namespaces
+ foreach ($openid_args->items() as $pair) {
+ list($rest, $value) = $pair;
+
+ $parts = explode('.', $rest, 2);
+
+ if (count($parts) == 2) {
+ list($ns_alias, $ns_key) = $parts;
+ } else {
+ $ns_alias = Auth_OpenID_NULL_NAMESPACE;
+ $ns_key = $rest;
+ }
+
+ if ($ns_alias == 'ns') {
+ if ($this->namespaces->addAlias($value, $ns_key) === null) {
+ return false;
+ }
+ } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) &&
+ ($ns_key == 'ns')) {
+ // null namespace
+ if ($this->setOpenIDNamespace($value, false) === false) {
+ return false;
+ }
+ } else {
+ $ns_args[] = array($ns_alias, $ns_key, $value);
+ }
+ }
+
+ if (!$this->getOpenIDNamespace()) {
+ if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) ===
+ false) {
+ return false;
+ }
+ }
+
+ // Actually put the pairs into the appropriate namespaces
+ foreach ($ns_args as $triple) {
+ list($ns_alias, $ns_key, $value) = $triple;
+ $ns_uri = $this->namespaces->getNamespaceURI($ns_alias);
+ if ($ns_uri === null) {
+ $ns_uri = $this->_getDefaultNamespace($ns_alias);
+ if ($ns_uri === null) {
+
+ $ns_uri = Auth_OpenID_OPENID_NS;
+ $ns_key = sprintf('%s.%s', $ns_alias, $ns_key);
+ } else {
+ $this->namespaces->addAlias($ns_uri, $ns_alias, true);
+ }
+ }
+
+ $this->setArg($ns_uri, $ns_key, $value);
+ }
+
+ return true;
+ }
+
+ function _getDefaultNamespace($mystery_alias)
+ {
+ global $Auth_OpenID_registered_aliases;
+ if ($this->isOpenID1()) {
+ return @$Auth_OpenID_registered_aliases[$mystery_alias];
+ }
+ return null;
+ }
+
+ function setOpenIDNamespace($openid_ns_uri, $implicit)
+ {
+ if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) {
+ Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri);
+ return false;
+ }
+
+ $succeeded = $this->namespaces->addAlias($openid_ns_uri,
+ Auth_OpenID_NULL_NAMESPACE,
+ $implicit);
+ if ($succeeded === false) {
+ return false;
+ }
+
+ $this->_openid_ns_uri = $openid_ns_uri;
+
+ return true;
+ }
+
+ function getOpenIDNamespace()
+ {
+ return $this->_openid_ns_uri;
+ }
+
+ function fromKVForm($kvform_string)
+ {
+ // Create a Message from a KVForm string
+ return Auth_OpenID_Message::fromOpenIDArgs(
+ Auth_OpenID_KVForm::toArray($kvform_string));
+ }
+
+ function copy()
+ {
+ return $this;
+ }
+
+ function toPostArgs()
+ {
+ // Return all arguments with openid. in front of namespaced
+ // arguments.
+
+ $args = array();
+
+ // Add namespace definitions to the output
+ foreach ($this->namespaces->iteritems() as $pair) {
+ list($ns_uri, $alias) = $pair;
+ if ($this->namespaces->isImplicit($ns_uri)) {
+ continue;
+ }
+ if ($alias == Auth_OpenID_NULL_NAMESPACE) {
+ $ns_key = 'openid.ns';
+ } else {
+ $ns_key = 'openid.ns.' . $alias;
+ }
+ $args[$ns_key] = $ns_uri;
+ }
+
+ foreach ($this->args->items() as $pair) {
+ list($ns_parts, $value) = $pair;
+ list($ns_uri, $ns_key) = $ns_parts;
+ $key = $this->getKey($ns_uri, $ns_key);
+ $args[$key] = $value;
+ }
+
+ return $args;
+ }
+
+ function toArgs()
+ {
+ // Return all namespaced arguments, failing if any
+ // non-namespaced arguments exist.
+ $post_args = $this->toPostArgs();
+ $kvargs = array();
+ foreach ($post_args as $k => $v) {
+ if (strpos($k, 'openid.') !== 0) {
+ // raise ValueError(
+ // 'This message can only be encoded as a POST, because it '
+ // 'contains arguments that are not prefixed with "openid."')
+ return null;
+ } else {
+ $kvargs[substr($k, 7)] = $v;
+ }
+ }
+
+ return $kvargs;
+ }
+
+ function toFormMarkup($action_url, $form_tag_attrs = null,
+ $submit_text = "Continue")
+ {
+ $form = "<form accept-charset=\"UTF-8\" ".
+ "enctype=\"application/x-www-form-urlencoded\"";
+
+ if (!$form_tag_attrs) {
+ $form_tag_attrs = array();
+ }
+
+ $form_tag_attrs['action'] = $action_url;
+ $form_tag_attrs['method'] = 'post';
+
+ unset($form_tag_attrs['enctype']);
+ unset($form_tag_attrs['accept-charset']);
+
+ if ($form_tag_attrs) {
+ foreach ($form_tag_attrs as $name => $attr) {
+ $form .= sprintf(" %s=\"%s\"", $name, $attr);
+ }
+ }
+
+ $form .= ">\n";
+
+ foreach ($this->toPostArgs() as $name => $value) {
+ $form .= sprintf(
+ "<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
+ $name, $value);
+ }
+
+ $form .= sprintf("<input type=\"submit\" value=\"%s\" />\n",
+ $submit_text);
+
+ $form .= "</form>\n";
+
+ return $form;
+ }
+
+ function toURL($base_url)
+ {
+ // Generate a GET URL with the parameters in this message
+ // attached as query parameters.
+ return Auth_OpenID::appendArgs($base_url, $this->toPostArgs());
+ }
+
+ function toKVForm()
+ {
+ // Generate a KVForm string that contains the parameters in
+ // this message. This will fail if the message contains
+ // arguments outside of the 'openid.' prefix.
+ return Auth_OpenID_KVForm::fromArray($this->toArgs());
+ }
+
+ function toURLEncoded()
+ {
+ // Generate an x-www-urlencoded string
+ $args = array();
+
+ foreach ($this->toPostArgs() as $k => $v) {
+ $args[] = array($k, $v);
+ }
+
+ sort($args);
+ return Auth_OpenID::httpBuildQuery($args);
+ }
+
+ /**
+ * @access private
+ */
+ function _fixNS($namespace)
+ {
+ // Convert an input value into the internally used values of
+ // this object
+
+ if ($namespace == Auth_OpenID_OPENID_NS) {
+ if ($this->_openid_ns_uri === null) {
+ return new Auth_OpenID_FailureResponse(null,
+ 'OpenID namespace not set');
+ } else {
+ $namespace = $this->_openid_ns_uri;
+ }
+ }
+
+ if (($namespace != Auth_OpenID_BARE_NS) &&
+ (!is_string($namespace))) {
+ //TypeError
+ $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ".
+ "Auth_OpenID_OPENID_NS or a string. got %s",
+ print_r($namespace, true));
+ return new Auth_OpenID_FailureResponse(null, $err_msg);
+ }
+
+ if (($namespace != Auth_OpenID_BARE_NS) &&
+ (strpos($namespace, ':') === false)) {
+ // fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
+ // warnings.warn(fmt % (namespace,), DeprecationWarning)
+
+ if ($namespace == 'sreg') {
+ // fmt = 'Using %r instead of "sreg" as namespace'
+ // warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
+ return Auth_OpenID_SREG_URI;
+ }
+ }
+
+ return $namespace;
+ }
+
+ function hasKey($namespace, $ns_key)
+ {
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ // XXX log me
+ return false;
+ } else {
+ return $this->args->contains(array($namespace, $ns_key));
+ }
+ }
+
+ function getKey($namespace, $ns_key)
+ {
+ // Get the key for a particular namespaced argument
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ }
+ if ($namespace == Auth_OpenID_BARE_NS) {
+ return $ns_key;
+ }
+
+ $ns_alias = $this->namespaces->getAlias($namespace);
+
+ // No alias is defined, so no key can exist
+ if ($ns_alias === null) {
+ return null;
+ }
+
+ if ($ns_alias == Auth_OpenID_NULL_NAMESPACE) {
+ $tail = $ns_key;
+ } else {
+ $tail = sprintf('%s.%s', $ns_alias, $ns_key);
+ }
+
+ return 'openid.' . $tail;
+ }
+
+ function getArg($namespace, $key, $default = null)
+ {
+ // Get a value for a namespaced key.
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ if ((!$this->args->contains(array($namespace, $key))) &&
+ ($default == Auth_OpenID_NO_DEFAULT)) {
+ $err_msg = sprintf("Namespace %s missing required field %s",
+ $namespace, $key);
+ return new Auth_OpenID_FailureResponse(null, $err_msg);
+ } else {
+ return $this->args->get(array($namespace, $key), $default);
+ }
+ }
+ }
+
+ function getArgs($namespace)
+ {
+ // Get the arguments that are defined for this namespace URI
+
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ $stuff = array();
+ foreach ($this->args->items() as $pair) {
+ list($key, $value) = $pair;
+ list($pair_ns, $ns_key) = $key;
+ if ($pair_ns == $namespace) {
+ $stuff[$ns_key] = $value;
+ }
+ }
+
+ return $stuff;
+ }
+ }
+
+ function updateArgs($namespace, $updates)
+ {
+ // Set multiple key/value pairs in one call
+
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ foreach ($updates as $k => $v) {
+ $this->setArg($namespace, $k, $v);
+ }
+ return true;
+ }
+ }
+
+ function setArg($namespace, $key, $value)
+ {
+ // Set a single argument in this namespace
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ $this->args->set(array($namespace, $key), $value);
+ if ($namespace !== Auth_OpenID_BARE_NS) {
+ $this->namespaces->add($namespace);
+ }
+ return true;
+ }
+ }
+
+ function delArg($namespace, $key)
+ {
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ return $this->args->del(array($namespace, $key));
+ }
+ }
+
+ function getAliasedArg($aliased_key, $default = null)
+ {
+ if ($aliased_key == 'ns') {
+ // Return the namespace URI for the OpenID namespace
+ return $this->getOpenIDNamespace();
+ }
+
+ $parts = explode('.', $aliased_key, 2);
+
+ if (count($parts) != 2) {
+ $ns = null;
+ } else {
+ list($alias, $key) = $parts;
+
+ if ($alias == 'ns') {
+ // Return the namespace URI for a namespace alias
+ // parameter.
+ return $this->namespaces->getNamespaceURI($key);
+ } else {
+ $ns = $this->namespaces->getNamespaceURI($alias);
+ }
+ }
+
+ if ($ns === null) {
+ $key = $aliased_key;
+ $ns = $this->getOpenIDNamespace();
+ }
+
+ return $this->getArg($ns, $key, $default);
+ }
+}
+
+?>
diff --git a/Auth/OpenID/MySQLStore.php b/Auth/OpenID/MySQLStore.php
new file mode 100644
index 0000000..eb08af0
--- /dev/null
+++ b/Auth/OpenID/MySQLStore.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * A MySQL store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses MySQL as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
+ /**
+ * @access private
+ */
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (\n".
+ " server_url VARCHAR(2047) NOT NULL,\n".
+ " timestamp INTEGER NOT NULL,\n".
+ " salt CHAR(40) NOT NULL,\n".
+ " UNIQUE (server_url(255), timestamp, salt)\n".
+ ") ENGINE=InnoDB";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (\n".
+ " server_url BLOB NOT NULL,\n".
+ " handle VARCHAR(255) NOT NULL,\n".
+ " secret BLOB NOT NULL,\n".
+ " issued INTEGER NOT NULL,\n".
+ " lifetime INTEGER NOT NULL,\n".
+ " assoc_type VARCHAR(64) NOT NULL,\n".
+ " PRIMARY KEY (server_url(255), handle)\n".
+ ") ENGINE=InnoDB";
+
+ $this->sql['set_assoc'] =
+ "REPLACE INTO %s (server_url, handle, secret, issued,\n".
+ " lifetime, assoc_type) VALUES (?, ?, !, ?, ?, ?)";
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function blobEncode($blob)
+ {
+ return "0x" . bin2hex($blob);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Nonce.php b/Auth/OpenID/Nonce.php
new file mode 100644
index 0000000..effecac
--- /dev/null
+++ b/Auth/OpenID/Nonce.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Nonce-related functionality.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Need CryptUtil to generate random strings.
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * This is the characters that the nonces are made from.
+ */
+define('Auth_OpenID_Nonce_CHRS',"abcdefghijklmnopqrstuvwxyz" .
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+// Keep nonces for five hours (allow five hours for the combination of
+// request time and clock skew). This is probably way more than is
+// necessary, but there is not much overhead in storing nonces.
+global $Auth_OpenID_SKEW;
+$Auth_OpenID_SKEW = 60 * 60 * 5;
+
+define('Auth_OpenID_Nonce_REGEX',
+ '/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/');
+
+define('Auth_OpenID_Nonce_TIME_FMT',
+ '%Y-%m-%dT%H:%M:%SZ');
+
+function Auth_OpenID_splitNonce($nonce_string)
+{
+ // Extract a timestamp from the given nonce string
+ $result = preg_match(Auth_OpenID_Nonce_REGEX, $nonce_string, $matches);
+ if ($result != 1 || count($matches) != 8) {
+ return null;
+ }
+
+ list($unused,
+ $tm_year,
+ $tm_mon,
+ $tm_mday,
+ $tm_hour,
+ $tm_min,
+ $tm_sec,
+ $uniquifier) = $matches;
+
+ $timestamp =
+ @gmmktime($tm_hour, $tm_min, $tm_sec, $tm_mon, $tm_mday, $tm_year);
+
+ if ($timestamp === false || $timestamp < 0) {
+ return null;
+ }
+
+ return array($timestamp, $uniquifier);
+}
+
+function Auth_OpenID_checkTimestamp($nonce_string,
+ $allowed_skew = null,
+ $now = null)
+{
+ // Is the timestamp that is part of the specified nonce string
+ // within the allowed clock-skew of the current time?
+ global $Auth_OpenID_SKEW;
+
+ if ($allowed_skew === null) {
+ $allowed_skew = $Auth_OpenID_SKEW;
+ }
+
+ $parts = Auth_OpenID_splitNonce($nonce_string);
+ if ($parts == null) {
+ return false;
+ }
+
+ if ($now === null) {
+ $now = time();
+ }
+
+ $stamp = $parts[0];
+
+ // Time after which we should not use the nonce
+ $past = $now - $allowed_skew;
+
+ // Time that is too far in the future for us to allow
+ $future = $now + $allowed_skew;
+
+ // the stamp is not too far in the future and is not too far
+ // in the past
+ return (($past <= $stamp) && ($stamp <= $future));
+}
+
+function Auth_OpenID_mkNonce($when = null)
+{
+ // Generate a nonce with the current timestamp
+ $salt = Auth_OpenID_CryptUtil::randomString(
+ 6, Auth_OpenID_Nonce_CHRS);
+ if ($when === null) {
+ // It's safe to call time() with no arguments; it returns a
+ // GMT unix timestamp on PHP 4 and PHP 5. gmmktime() with no
+ // args returns a local unix timestamp on PHP 4, so don't use
+ // that.
+ $when = time();
+ }
+ $time_str = gmstrftime(Auth_OpenID_Nonce_TIME_FMT, $when);
+ return $time_str . $salt;
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/PAPE.php b/Auth/OpenID/PAPE.php
new file mode 100644
index 0000000..62cba8a
--- /dev/null
+++ b/Auth/OpenID/PAPE.php
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * An implementation of the OpenID Provider Authentication Policy
+ * Extension 1.0
+ *
+ * See:
+ * http://openid.net/developers/specs/
+ */
+
+require_once "Auth/OpenID/Extension.php";
+
+define('Auth_OpenID_PAPE_NS_URI',
+ "http://specs.openid.net/extensions/pape/1.0");
+
+define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
+ 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
+define('PAPE_AUTH_MULTI_FACTOR',
+ 'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
+define('PAPE_AUTH_PHISHING_RESISTANT',
+ 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');
+
+define('PAPE_TIME_VALIDATOR',
+ '^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$');
+/**
+ * A Provider Authentication Policy request, sent from a relying party
+ * to a provider
+ *
+ * preferred_auth_policies: The authentication policies that
+ * the relying party prefers
+ *
+ * max_auth_age: The maximum time, in seconds, that the relying party
+ * wants to allow to have elapsed before the user must re-authenticate
+ */
+class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {
+
+ var $ns_alias = 'pape';
+ var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+ function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
+ $max_auth_age=null)
+ {
+ if ($preferred_auth_policies === null) {
+ $preferred_auth_policies = array();
+ }
+
+ $this->preferred_auth_policies = $preferred_auth_policies;
+ $this->max_auth_age = $max_auth_age;
+ }
+
+ /**
+ * Add an acceptable authentication policy URI to this request
+ *
+ * This method is intended to be used by the relying party to add
+ * acceptable authentication types to the request.
+ *
+ * policy_uri: The identifier for the preferred type of
+ * authentication.
+ */
+ function addPolicyURI($policy_uri)
+ {
+ if (!in_array($policy_uri, $this->preferred_auth_policies)) {
+ $this->preferred_auth_policies[] = $policy_uri;
+ }
+ }
+
+ function getExtensionArgs()
+ {
+ $ns_args = array(
+ 'preferred_auth_policies' =>
+ implode(' ', $this->preferred_auth_policies)
+ );
+
+ if ($this->max_auth_age !== null) {
+ $ns_args['max_auth_age'] = strval($this->max_auth_age);
+ }
+
+ return $ns_args;
+ }
+
+ /**
+ * Instantiate a Request object from the arguments in a checkid_*
+ * OpenID message
+ */
+ function fromOpenIDRequest($request)
+ {
+ $obj = new Auth_OpenID_PAPE_Request();
+ $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);
+
+ if ($args === null || $args === array()) {
+ return null;
+ }
+
+ $obj->parseExtensionArgs($args);
+ return $obj;
+ }
+
+ /**
+ * Set the state of this request to be that expressed in these
+ * PAPE arguments
+ *
+ * @param args: The PAPE arguments without a namespace
+ */
+ function parseExtensionArgs($args)
+ {
+ // preferred_auth_policies is a space-separated list of policy
+ // URIs
+ $this->preferred_auth_policies = array();
+
+ $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
+ if ($policies_str) {
+ foreach (explode(' ', $policies_str) as $uri) {
+ if (!in_array($uri, $this->preferred_auth_policies)) {
+ $this->preferred_auth_policies[] = $uri;
+ }
+ }
+ }
+
+ // max_auth_age is base-10 integer number of seconds
+ $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
+ if ($max_auth_age_str) {
+ $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
+ } else {
+ $this->max_auth_age = null;
+ }
+ }
+
+ /**
+ * Given a list of authentication policy URIs that a provider
+ * supports, this method returns the subsequence of those types
+ * that are preferred by the relying party.
+ *
+ * @param supported_types: A sequence of authentication policy
+ * type URIs that are supported by a provider
+ *
+ * @return array The sub-sequence of the supported types that are
+ * preferred by the relying party. This list will be ordered in
+ * the order that the types appear in the supported_types
+ * sequence, and may be empty if the provider does not prefer any
+ * of the supported authentication types.
+ */
+ function preferredTypes($supported_types)
+ {
+ $result = array();
+
+ foreach ($supported_types as $st) {
+ if (in_array($st, $this->preferred_auth_policies)) {
+ $result[] = $st;
+ }
+ }
+ return $result;
+ }
+}
+
+/**
+ * A Provider Authentication Policy response, sent from a provider to
+ * a relying party
+ */
+class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {
+
+ var $ns_alias = 'pape';
+ var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+ function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null,
+ $nist_auth_level=null)
+ {
+ if ($auth_policies) {
+ $this->auth_policies = $auth_policies;
+ } else {
+ $this->auth_policies = array();
+ }
+
+ $this->auth_time = $auth_time;
+ $this->nist_auth_level = $nist_auth_level;
+ }
+
+ /**
+ * Add a authentication policy to this response
+ *
+ * This method is intended to be used by the provider to add a
+ * policy that the provider conformed to when authenticating the
+ * user.
+ *
+ * @param policy_uri: The identifier for the preferred type of
+ * authentication.
+ */
+ function addPolicyURI($policy_uri)
+ {
+ if (!in_array($policy_uri, $this->auth_policies)) {
+ $this->auth_policies[] = $policy_uri;
+ }
+ }
+
+ /**
+ * Create an Auth_OpenID_PAPE_Response object from a successful
+ * OpenID library response.
+ *
+ * @param success_response $success_response A SuccessResponse
+ * from Auth_OpenID_Consumer::complete()
+ *
+ * @returns: A provider authentication policy response from the
+ * data that was supplied with the id_res response.
+ */
+ function fromSuccessResponse($success_response)
+ {
+ $obj = new Auth_OpenID_PAPE_Response();
+
+ // PAPE requires that the args be signed.
+ $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);
+
+ if ($args === null || $args === array()) {
+ return null;
+ }
+
+ $result = $obj->parseExtensionArgs($args);
+
+ if ($result === false) {
+ return null;
+ } else {
+ return $obj;
+ }
+ }
+
+ /**
+ * Parse the provider authentication policy arguments into the
+ * internal state of this object
+ *
+ * @param args: unqualified provider authentication policy
+ * arguments
+ *
+ * @param strict: Whether to return false when bad data is
+ * encountered
+ *
+ * @return null The data is parsed into the internal fields of
+ * this object.
+ */
+ function parseExtensionArgs($args, $strict=false)
+ {
+ $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
+ if ($policies_str && $policies_str != "none") {
+ $this->auth_policies = explode(" ", $policies_str);
+ }
+
+ $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
+ if ($nist_level_str !== null) {
+ $nist_level = Auth_OpenID::intval($nist_level_str);
+
+ if ($nist_level === false) {
+ if ($strict) {
+ return false;
+ } else {
+ $nist_level = null;
+ }
+ }
+
+ if (0 <= $nist_level && $nist_level < 5) {
+ $this->nist_auth_level = $nist_level;
+ } else if ($strict) {
+ return false;
+ }
+ }
+
+ $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
+ if ($auth_time !== null) {
+ if (ereg(PAPE_TIME_VALIDATOR, $auth_time)) {
+ $this->auth_time = $auth_time;
+ } else if ($strict) {
+ return false;
+ }
+ }
+ }
+
+ function getExtensionArgs()
+ {
+ $ns_args = array();
+ if (count($this->auth_policies) > 0) {
+ $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
+ } else {
+ $ns_args['auth_policies'] = 'none';
+ }
+
+ if ($this->nist_auth_level !== null) {
+ if (!in_array($this->nist_auth_level, range(0, 4), true)) {
+ return false;
+ }
+ $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
+ }
+
+ if ($this->auth_time !== null) {
+ if (!ereg(PAPE_TIME_VALIDATOR, $this->auth_time)) {
+ return false;
+ }
+
+ $ns_args['auth_time'] = $this->auth_time;
+ }
+
+ return $ns_args;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Parse.php b/Auth/OpenID/Parse.php
new file mode 100644
index 0000000..546f34f
--- /dev/null
+++ b/Auth/OpenID/Parse.php
@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * This module implements a VERY limited parser that finds <link> tags
+ * in the head of HTML or XHTML documents and parses out their
+ * attributes according to the OpenID spec. It is a liberal parser,
+ * but it requires these things from the data in order to work:
+ *
+ * - There must be an open <html> tag
+ *
+ * - There must be an open <head> tag inside of the <html> tag
+ *
+ * - Only <link>s that are found inside of the <head> tag are parsed
+ * (this is by design)
+ *
+ * - The parser follows the OpenID specification in resolving the
+ * attributes of the link tags. This means that the attributes DO
+ * NOT get resolved as they would by an XML or HTML parser. In
+ * particular, only certain entities get replaced, and href
+ * attributes do not get resolved relative to a base URL.
+ *
+ * From http://openid.net/specs.bml:
+ *
+ * - The openid.server URL MUST be an absolute URL. OpenID consumers
+ * MUST NOT attempt to resolve relative URLs.
+ *
+ * - The openid.server URL MUST NOT include entities other than &amp;,
+ * &lt;, &gt;, and &quot;.
+ *
+ * The parser ignores SGML comments and <![CDATA[blocks]]>. Both kinds
+ * of quoting are allowed for attributes.
+ *
+ * The parser deals with invalid markup in these ways:
+ *
+ * - Tag names are not case-sensitive
+ *
+ * - The <html> tag is accepted even when it is not at the top level
+ *
+ * - The <head> tag is accepted even when it is not a direct child of
+ * the <html> tag, but a <html> tag must be an ancestor of the
+ * <head> tag
+ *
+ * - <link> tags are accepted even when they are not direct children
+ * of the <head> tag, but a <head> tag must be an ancestor of the
+ * <link> tag
+ *
+ * - If there is no closing tag for an open <html> or <head> tag, the
+ * remainder of the document is viewed as being inside of the
+ * tag. If there is no closing tag for a <link> tag, the link tag is
+ * treated as a short tag. Exceptions to this rule are that <html>
+ * closes <html> and <body> or <head> closes <head>
+ *
+ * - Attributes of the <link> tag are not required to be quoted.
+ *
+ * - In the case of duplicated attribute names, the attribute coming
+ * last in the tag will be the value returned.
+ *
+ * - Any text that does not parse as an attribute within a link tag
+ * will be ignored. (e.g. <link pumpkin rel='openid.server' /> will
+ * ignore pumpkin)
+ *
+ * - If there are more than one <html> or <head> tag, the parser only
+ * looks inside of the first one.
+ *
+ * - The contents of <script> tags are ignored entirely, except
+ * unclosed <script> tags. Unclosed <script> tags are ignored.
+ *
+ * - Any other invalid markup is ignored, including unclosed SGML
+ * comments and unclosed <![CDATA[blocks.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require Auth_OpenID::arrayGet().
+ */
+require_once "Auth/OpenID.php";
+
+class Auth_OpenID_Parse {
+
+ /**
+ * Specify some flags for use with regex matching.
+ */
+ var $_re_flags = "si";
+
+ /**
+ * Stuff to remove before we start looking for tags
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * Starts with the tag name at a word boundary, where the tag name
+ * is not a namespace
+ */
+ var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*?)(?:<\/?%s\s*>|\Z))";
+
+ var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
+
+ var $_open_tag_expr = "<%s\b";
+ var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";
+
+ function Auth_OpenID_Parse()
+ {
+ $this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
+ $this->_re_flags);
+
+ $this->_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $this->_re_flags);
+
+ $this->_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $this->_entity_replacements));
+ }
+
+ /**
+ * Returns a regular expression that will match a given tag in an
+ * SGML string.
+ */
+ function tagMatcher($tag_name, $close_tags = null)
+ {
+ $expr = $this->_tag_expr;
+
+ if ($close_tags) {
+ $options = implode("|", array_merge(array($tag_name), $close_tags));
+ $closer = sprintf("(?:%s)", $options);
+ } else {
+ $closer = $tag_name;
+ }
+
+ $expr = sprintf($expr, $tag_name, $closer);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function openTag($tag_name)
+ {
+ $expr = sprintf($this->_open_tag_expr, $tag_name);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function closeTag($tag_name)
+ {
+ $expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function htmlBegin($s)
+ {
+ $matches = array();
+ $result = preg_match($this->openTag('html'), $s,
+ $matches, PREG_OFFSET_CAPTURE);
+ if ($result === false || !$matches) {
+ return false;
+ }
+ // Return the offset of the first match.
+ return $matches[0][1];
+ }
+
+ function htmlEnd($s)
+ {
+ $matches = array();
+ $result = preg_match($this->closeTag('html'), $s,
+ $matches, PREG_OFFSET_CAPTURE);
+ if ($result === false || !$matches) {
+ return false;
+ }
+ // Return the offset of the first match.
+ return $matches[count($matches) - 1][1];
+ }
+
+ function headFind()
+ {
+ return $this->tagMatcher('head', array('body', 'html'));
+ }
+
+ function replaceEntities($str)
+ {
+ foreach ($this->_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+ return $str;
+ }
+
+ function removeQuotes($str)
+ {
+ $matches = array();
+ $double = '/^"(.*)"$/';
+ $single = "/^\'(.*)\'$/";
+
+ if (preg_match($double, $str, $matches)) {
+ return $matches[1];
+ } else if (preg_match($single, $str, $matches)) {
+ return $matches[1];
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * Find all link tags in a string representing a HTML document and
+ * return a list of their attributes.
+ *
+ * @param string $html The text to parse
+ * @return array $list An array of arrays of attributes, one for each
+ * link tag
+ */
+ function parseLinkAttrs($html)
+ {
+ $stripped = preg_replace($this->_removed_re,
+ "",
+ $html);
+
+ $html_begin = $this->htmlBegin($stripped);
+ $html_end = $this->htmlEnd($stripped);
+
+ if ($html_begin === false) {
+ return array();
+ }
+
+ if ($html_end === false) {
+ $html_end = strlen($stripped);
+ }
+
+ $stripped = substr($stripped, $html_begin,
+ $html_end - $html_begin);
+
+ // Try to find the <HEAD> tag.
+ $head_re = $this->headFind();
+ $head_matches = array();
+ if (!preg_match($head_re, $stripped, $head_matches)) {
+ return array();
+ }
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->_link_find, $head_matches[0],
+ $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($this->_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = $this->replaceEntities(
+ $this->removeQuotes($attr_matches[2][$index]));
+
+ $link_attrs[strtolower($name)] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+ }
+
+ function relMatches($rel_attr, $target_rel)
+ {
+ // Does this target_rel appear in the rel_str?
+ // XXX: TESTME
+ $rels = preg_split("/\s+/", trim($rel_attr));
+ foreach ($rels as $rel) {
+ $rel = strtolower($rel);
+ if ($rel == $target_rel) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ function linkHasRel($link_attrs, $target_rel)
+ {
+ // Does this link have target_rel as a relationship?
+ // XXX: TESTME
+ $rel_attr = Auth_OpeniD::arrayGet($link_attrs, 'rel', null);
+ return ($rel_attr && $this->relMatches($rel_attr,
+ $target_rel));
+ }
+
+ function findLinksRel($link_attrs_list, $target_rel)
+ {
+ // Filter the list of link attributes on whether it has
+ // target_rel as a relationship.
+ // XXX: TESTME
+ $result = array();
+ foreach ($link_attrs_list as $attr) {
+ if ($this->linkHasRel($attr, $target_rel)) {
+ $result[] = $attr;
+ }
+ }
+
+ return $result;
+ }
+
+ function findFirstHref($link_attrs_list, $target_rel)
+ {
+ // Return the value of the href attribute for the first link
+ // tag in the list that has target_rel as a relationship.
+ // XXX: TESTME
+ $matches = $this->findLinksRel($link_attrs_list,
+ $target_rel);
+ if (!$matches) {
+ return null;
+ }
+ $first = $matches[0];
+ return Auth_OpenID::arrayGet($first, 'href', null);
+ }
+}
+
+function Auth_OpenID_legacy_discover($html_text, $server_rel,
+ $delegate_rel)
+{
+ $p = new Auth_OpenID_Parse();
+
+ $link_attrs = $p->parseLinkAttrs($html_text);
+
+ $server_url = $p->findFirstHref($link_attrs,
+ $server_rel);
+
+ if ($server_url === null) {
+ return false;
+ } else {
+ $delegate_url = $p->findFirstHref($link_attrs,
+ $delegate_rel);
+ return array($delegate_url, $server_url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/PostgreSQLStore.php b/Auth/OpenID/PostgreSQLStore.php
new file mode 100644
index 0000000..69d95e7
--- /dev/null
+++ b/Auth/OpenID/PostgreSQLStore.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * A PostgreSQL store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses PostgreSQL as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
+ /**
+ * @access private
+ */
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
+ "timestamp INTEGER NOT NULL, ".
+ "salt CHAR(40) NOT NULL, ".
+ "UNIQUE (server_url, timestamp, salt))";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
+ "handle VARCHAR(255) NOT NULL, ".
+ "secret BYTEA NOT NULL, ".
+ "issued INTEGER NOT NULL, ".
+ "lifetime INTEGER NOT NULL, ".
+ "assoc_type VARCHAR(64) NOT NULL, ".
+ "PRIMARY KEY (server_url, handle), ".
+ "CONSTRAINT secret_length_constraint CHECK ".
+ "(LENGTH(secret) <= 128))";
+
+ $this->sql['set_assoc'] =
+ array(
+ 'insert_assoc' => "INSERT INTO %s (server_url, handle, ".
+ "secret, issued, lifetime, assoc_type) VALUES ".
+ "(?, ?, '!', ?, ?, ?)",
+ 'update_assoc' => "UPDATE %s SET secret = '!', issued = ?, ".
+ "lifetime = ?, assoc_type = ? WHERE server_url = ? AND ".
+ "handle = ?"
+ );
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES ".
+ "(?, ?, ?)"
+ ;
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function _set_assoc($server_url, $handle, $secret, $issued, $lifetime,
+ $assoc_type)
+ {
+ $result = $this->_get_assoc($server_url, $handle);
+ if ($result) {
+ // Update the table since this associations already exists.
+ $this->connection->query($this->sql['set_assoc']['update_assoc'],
+ array($secret, $issued, $lifetime,
+ $assoc_type, $server_url, $handle));
+ } else {
+ // Insert a new record because this association wasn't
+ // found.
+ $this->connection->query($this->sql['set_assoc']['insert_assoc'],
+ array($server_url, $handle, $secret,
+ $issued, $lifetime, $assoc_type));
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function blobEncode($blob)
+ {
+ return $this->_octify($blob);
+ }
+
+ /**
+ * @access private
+ */
+ function blobDecode($blob)
+ {
+ return $this->_unoctify($blob);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/SQLStore.php b/Auth/OpenID/SQLStore.php
new file mode 100644
index 0000000..da93c6a
--- /dev/null
+++ b/Auth/OpenID/SQLStore.php
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * SQL-backed OpenID stores.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the PEAR DB module because we'll need it for the SQL-based
+ * stores implemented here. We silence any errors from the inclusion
+ * because it might not be present, and a user of the SQL stores may
+ * supply an Auth_OpenID_DatabaseConnection instance that implements
+ * its own storage.
+ */
+global $__Auth_OpenID_PEAR_AVAILABLE;
+$__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * This is the parent class for the SQL stores, which contains the
+ * logic common to all of the SQL stores.
+ *
+ * The table names used are determined by the class variables
+ * associations_table_name and nonces_table_name. To change the name
+ * of the tables used, pass new table names into the constructor.
+ *
+ * To create the tables with the proper schema, see the createTables
+ * method.
+ *
+ * This class shouldn't be used directly. Use one of its subclasses
+ * instead, as those contain the code necessary to use a specific
+ * database. If you're an OpenID integrator and you'd like to create
+ * an SQL-driven store that wraps an application's database
+ * abstraction, be sure to create a subclass of
+ * {@link Auth_OpenID_DatabaseConnection} that calls the application's
+ * database abstraction calls. Then, pass an instance of your new
+ * database connection class to your SQLStore subclass constructor.
+ *
+ * All methods other than the constructor and createTables should be
+ * considered implementation details.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * This creates a new SQLStore instance. It requires an
+ * established database connection be given to it, and it allows
+ * overriding the default table names.
+ *
+ * @param connection $connection This must be an established
+ * connection to a database of the correct type for the SQLStore
+ * subclass you're using. This must either be an PEAR DB
+ * connection handle or an instance of a subclass of
+ * Auth_OpenID_DatabaseConnection.
+ *
+ * @param associations_table: This is an optional parameter to
+ * specify the name of the table used for storing associations.
+ * The default value is 'oid_associations'.
+ *
+ * @param nonces_table: This is an optional parameter to specify
+ * the name of the table used for storing nonces. The default
+ * value is 'oid_nonces'.
+ */
+ function Auth_OpenID_SQLStore($connection,
+ $associations_table = null,
+ $nonces_table = null)
+ {
+ global $__Auth_OpenID_PEAR_AVAILABLE;
+
+ $this->associations_table_name = "oid_associations";
+ $this->nonces_table_name = "oid_nonces";
+
+ // Check the connection object type to be sure it's a PEAR
+ // database connection.
+ if (!(is_object($connection) &&
+ (is_subclass_of($connection, 'db_common') ||
+ is_subclass_of($connection,
+ 'auth_openid_databaseconnection')))) {
+ trigger_error("Auth_OpenID_SQLStore expected PEAR connection " .
+ "object (got ".get_class($connection).")",
+ E_USER_ERROR);
+ return;
+ }
+
+ $this->connection = $connection;
+
+ // Be sure to set the fetch mode so the results are keyed on
+ // column name instead of column index. This is a PEAR
+ // constant, so only try to use it if PEAR is present. Note
+ // that Auth_Openid_Databaseconnection instances need not
+ // implement ::setFetchMode for this reason.
+ if ($__Auth_OpenID_PEAR_AVAILABLE) {
+ $this->connection->setFetchMode(DB_FETCHMODE_ASSOC);
+ }
+
+ if ($associations_table) {
+ $this->associations_table_name = $associations_table;
+ }
+
+ if ($nonces_table) {
+ $this->nonces_table_name = $nonces_table;
+ }
+
+ $this->max_nonce_age = 6 * 60 * 60;
+
+ // Be sure to run the database queries with auto-commit mode
+ // turned OFF, because we want every function to run in a
+ // transaction, implicitly. As a rule, methods named with a
+ // leading underscore will NOT control transaction behavior.
+ // Callers of these methods will worry about transactions.
+ $this->connection->autoCommit(false);
+
+ // Create an empty SQL strings array.
+ $this->sql = array();
+
+ // Call this method (which should be overridden by subclasses)
+ // to populate the $this->sql array with SQL strings.
+ $this->setSQL();
+
+ // Verify that all required SQL statements have been set, and
+ // raise an error if any expected SQL strings were either
+ // absent or empty.
+ list($missing, $empty) = $this->_verifySQL();
+
+ if ($missing) {
+ trigger_error("Expected keys in SQL query list: " .
+ implode(", ", $missing),
+ E_USER_ERROR);
+ return;
+ }
+
+ if ($empty) {
+ trigger_error("SQL list keys have no SQL strings: " .
+ implode(", ", $empty),
+ E_USER_ERROR);
+ return;
+ }
+
+ // Add table names to queries.
+ $this->_fixSQL();
+ }
+
+ function tableExists($table_name)
+ {
+ return !$this->isError(
+ $this->connection->query(
+ sprintf("SELECT * FROM %s LIMIT 0",
+ $table_name)));
+ }
+
+ /**
+ * Returns true if $value constitutes a database error; returns
+ * false otherwise.
+ */
+ function isError($value)
+ {
+ return PEAR::isError($value);
+ }
+
+ /**
+ * Converts a query result to a boolean. If the result is a
+ * database error according to $this->isError(), this returns
+ * false; otherwise, this returns true.
+ */
+ function resultToBool($obj)
+ {
+ if ($this->isError($obj)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * This method should be overridden by subclasses. This method is
+ * called by the constructor to set values in $this->sql, which is
+ * an array keyed on sql name.
+ */
+ function setSQL()
+ {
+ }
+
+ /**
+ * Resets the store by removing all records from the store's
+ * tables.
+ */
+ function reset()
+ {
+ $this->connection->query(sprintf("DELETE FROM %s",
+ $this->associations_table_name));
+
+ $this->connection->query(sprintf("DELETE FROM %s",
+ $this->nonces_table_name));
+ }
+
+ /**
+ * @access private
+ */
+ function _verifySQL()
+ {
+ $missing = array();
+ $empty = array();
+
+ $required_sql_keys = array(
+ 'nonce_table',
+ 'assoc_table',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc'
+ );
+
+ foreach ($required_sql_keys as $key) {
+ if (!array_key_exists($key, $this->sql)) {
+ $missing[] = $key;
+ } else if (!$this->sql[$key]) {
+ $empty[] = $key;
+ }
+ }
+
+ return array($missing, $empty);
+ }
+
+ /**
+ * @access private
+ */
+ function _fixSQL()
+ {
+ $replacements = array(
+ array(
+ 'value' => $this->nonces_table_name,
+ 'keys' => array('nonce_table',
+ 'add_nonce',
+ 'clean_nonce')
+ ),
+ array(
+ 'value' => $this->associations_table_name,
+ 'keys' => array('assoc_table',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc',
+ 'clean_assoc')
+ )
+ );
+
+ foreach ($replacements as $item) {
+ $value = $item['value'];
+ $keys = $item['keys'];
+
+ foreach ($keys as $k) {
+ if (is_array($this->sql[$k])) {
+ foreach ($this->sql[$k] as $part_key => $part_value) {
+ $this->sql[$k][$part_key] = sprintf($part_value,
+ $value);
+ }
+ } else {
+ $this->sql[$k] = sprintf($this->sql[$k], $value);
+ }
+ }
+ }
+ }
+
+ function blobDecode($blob)
+ {
+ return $blob;
+ }
+
+ function blobEncode($str)
+ {
+ return $str;
+ }
+
+ function createTables()
+ {
+ $this->connection->autoCommit(true);
+ $n = $this->create_nonce_table();
+ $a = $this->create_assoc_table();
+ $this->connection->autoCommit(false);
+
+ if ($n && $a) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function create_nonce_table()
+ {
+ if (!$this->tableExists($this->nonces_table_name)) {
+ $r = $this->connection->query($this->sql['nonce_table']);
+ return $this->resultToBool($r);
+ }
+ return true;
+ }
+
+ function create_assoc_table()
+ {
+ if (!$this->tableExists($this->associations_table_name)) {
+ $r = $this->connection->query($this->sql['assoc_table']);
+ return $this->resultToBool($r);
+ }
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _set_assoc($server_url, $handle, $secret, $issued,
+ $lifetime, $assoc_type)
+ {
+ return $this->connection->query($this->sql['set_assoc'],
+ array(
+ $server_url,
+ $handle,
+ $secret,
+ $issued,
+ $lifetime,
+ $assoc_type));
+ }
+
+ function storeAssociation($server_url, $association)
+ {
+ if ($this->resultToBool($this->_set_assoc(
+ $server_url,
+ $association->handle,
+ $this->blobEncode(
+ $association->secret),
+ $association->issued,
+ $association->lifetime,
+ $association->assoc_type
+ ))) {
+ $this->connection->commit();
+ } else {
+ $this->connection->rollback();
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _get_assoc($server_url, $handle)
+ {
+ $result = $this->connection->getRow($this->sql['get_assoc'],
+ array($server_url, $handle));
+ if ($this->isError($result)) {
+ return null;
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _get_assocs($server_url)
+ {
+ $result = $this->connection->getAll($this->sql['get_assocs'],
+ array($server_url));
+
+ if ($this->isError($result)) {
+ return array();
+ } else {
+ return $result;
+ }
+ }
+
+ function removeAssociation($server_url, $handle)
+ {
+ if ($this->_get_assoc($server_url, $handle) == null) {
+ return false;
+ }
+
+ if ($this->resultToBool($this->connection->query(
+ $this->sql['remove_assoc'],
+ array($server_url, $handle)))) {
+ $this->connection->commit();
+ } else {
+ $this->connection->rollback();
+ }
+
+ return true;
+ }
+
+ function getAssociation($server_url, $handle = null)
+ {
+ if ($handle !== null) {
+ $assoc = $this->_get_assoc($server_url, $handle);
+
+ $assocs = array();
+ if ($assoc) {
+ $assocs[] = $assoc;
+ }
+ } else {
+ $assocs = $this->_get_assocs($server_url);
+ }
+
+ if (!$assocs || (count($assocs) == 0)) {
+ return null;
+ } else {
+ $associations = array();
+
+ foreach ($assocs as $assoc_row) {
+ $assoc = new Auth_OpenID_Association($assoc_row['handle'],
+ $assoc_row['secret'],
+ $assoc_row['issued'],
+ $assoc_row['lifetime'],
+ $assoc_row['assoc_type']);
+
+ $assoc->secret = $this->blobDecode($assoc->secret);
+
+ if ($assoc->getExpiresIn() == 0) {
+ $this->removeAssociation($server_url, $assoc->handle);
+ } else {
+ $associations[] = array($assoc->issued, $assoc);
+ }
+ }
+
+ if ($associations) {
+ $issued = array();
+ $assocs = array();
+ foreach ($associations as $key => $assoc) {
+ $issued[$key] = $assoc[0];
+ $assocs[$key] = $assoc[1];
+ }
+
+ array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
+ $associations);
+
+ // return the most recently issued one.
+ list($issued, $assoc) = $associations[0];
+ return $assoc;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _add_nonce($server_url, $timestamp, $salt)
+ {
+ $sql = $this->sql['add_nonce'];
+ $result = $this->connection->query($sql, array($server_url,
+ $timestamp,
+ $salt));
+ if ($this->isError($result)) {
+ $this->connection->rollback();
+ } else {
+ $this->connection->commit();
+ }
+ return $this->resultToBool($result);
+ }
+
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+ return False;
+ }
+
+ return $this->_add_nonce($server_url, $timestamp, $salt);
+ }
+
+ /**
+ * "Octifies" a binary string by returning a string with escaped
+ * octal bytes. This is used for preparing binary data for
+ * PostgreSQL BYTEA fields.
+ *
+ * @access private
+ */
+ function _octify($str)
+ {
+ $result = "";
+ for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) {
+ $ch = substr($str, $i, 1);
+ if ($ch == "\\") {
+ $result .= "\\\\\\\\";
+ } else if (ord($ch) == 0) {
+ $result .= "\\\\000";
+ } else {
+ $result .= "\\" . strval(decoct(ord($ch)));
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * "Unoctifies" octal-escaped data from PostgreSQL and returns the
+ * resulting ASCII (possibly binary) string.
+ *
+ * @access private
+ */
+ function _unoctify($str)
+ {
+ $result = "";
+ $i = 0;
+ while ($i < strlen($str)) {
+ $char = $str[$i];
+ if ($char == "\\") {
+ // Look to see if the next char is a backslash and
+ // append it.
+ if ($str[$i + 1] != "\\") {
+ $octal_digits = substr($str, $i + 1, 3);
+ $dec = octdec($octal_digits);
+ $char = chr($dec);
+ $i += 4;
+ } else {
+ $char = "\\";
+ $i += 2;
+ }
+ } else {
+ $i += 1;
+ }
+
+ $result .= $char;
+ }
+
+ return $result;
+ }
+
+ function cleanupNonces()
+ {
+ global $Auth_OpenID_SKEW;
+ $v = time() - $Auth_OpenID_SKEW;
+
+ $this->connection->query($this->sql['clean_nonce'], array($v));
+ $num = $this->connection->affectedRows();
+ $this->connection->commit();
+ return $num;
+ }
+
+ function cleanupAssociations()
+ {
+ $this->connection->query($this->sql['clean_assoc'],
+ array(time()));
+ $num = $this->connection->affectedRows();
+ $this->connection->commit();
+ return $num;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/SQLiteStore.php b/Auth/OpenID/SQLiteStore.php
new file mode 100644
index 0000000..ec2bf58
--- /dev/null
+++ b/Auth/OpenID/SQLiteStore.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * An SQLite store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses SQLite as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047), timestamp INTEGER, ".
+ "salt CHAR(40), UNIQUE (server_url, timestamp, salt))";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
+ "secret BLOB(128), issued INTEGER, lifetime INTEGER, ".
+ "assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle))";
+
+ $this->sql['set_assoc'] =
+ "INSERT OR REPLACE INTO %s VALUES (?, ?, ?, ?, ?, ?)";
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function _add_nonce($server_url, $timestamp, $salt)
+ {
+ // PECL SQLite extensions 1.0.3 and older (1.0.3 is the
+ // current release at the time of this writing) have a broken
+ // sqlite_escape_string function that breaks when passed the
+ // empty string. Prefixing all strings with one character
+ // keeps them unique and avoids this bug. The nonce table is
+ // write-only, so we don't have to worry about updating other
+ // functions with this same bad hack.
+ return parent::_add_nonce('x' . $server_url, $timestamp, $salt);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/SReg.php b/Auth/OpenID/SReg.php
new file mode 100644
index 0000000..6328076
--- /dev/null
+++ b/Auth/OpenID/SReg.php
@@ -0,0 +1,521 @@
+<?php
+
+/**
+ * Simple registration request and response parsing and object
+ * representation.
+ *
+ * This module contains objects representing simple registration
+ * requests and responses that can be used with both OpenID relying
+ * parties and OpenID providers.
+ *
+ * 1. The relying party creates a request object and adds it to the
+ * {@link Auth_OpenID_AuthRequest} object before making the
+ * checkid request to the OpenID provider:
+ *
+ * $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
+ * $auth_request->addExtension($sreg_req);
+ *
+ * 2. The OpenID provider extracts the simple registration request
+ * from the OpenID request using {@link
+ * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
+ * approval and data, creates an {@link Auth_OpenID_SRegResponse}
+ * object and adds it to the id_res response:
+ *
+ * $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
+ * $checkid_request);
+ * // [ get the user's approval and data, informing the user that
+ * // the fields in sreg_response were requested ]
+ * $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
+ * $sreg_req, $user_data);
+ * $sreg_resp->toMessage($openid_response->fields);
+ *
+ * 3. The relying party uses {@link
+ * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
+ * from the OpenID response:
+ *
+ * $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
+ * $success_response);
+ *
+ * @package OpenID
+ */
+
+/**
+ * Import message and extension internals.
+ */
+require_once 'Auth/OpenID/Message.php';
+require_once 'Auth/OpenID/Extension.php';
+
+// The data fields that are listed in the sreg spec
+global $Auth_OpenID_sreg_data_fields;
+$Auth_OpenID_sreg_data_fields = array(
+ 'fullname' => 'Full Name',
+ 'nickname' => 'Nickname',
+ 'dob' => 'Date of Birth',
+ 'email' => 'E-mail Address',
+ 'gender' => 'Gender',
+ 'postcode' => 'Postal Code',
+ 'country' => 'Country',
+ 'language' => 'Language',
+ 'timezone' => 'Time Zone');
+
+/**
+ * Check to see that the given value is a valid simple registration
+ * data field name. Return true if so, false if not.
+ */
+function Auth_OpenID_checkFieldName($field_name)
+{
+ global $Auth_OpenID_sreg_data_fields;
+
+ if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
+ return false;
+ }
+ return true;
+}
+
+// URI used in the wild for Yadis documents advertising simple
+// registration support
+define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');
+
+// URI in the draft specification for simple registration 1.1
+// <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
+define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');
+
+// This attribute will always hold the preferred URI to use when
+// adding sreg support to an XRDS file or in an OpenID namespace
+// declaration.
+define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);
+
+Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');
+
+/**
+ * Does the given endpoint advertise support for simple
+ * registration?
+ *
+ * $endpoint: The endpoint object as returned by OpenID discovery.
+ * returns whether an sreg type was advertised by the endpoint
+ */
+function Auth_OpenID_supportsSReg(&$endpoint)
+{
+ return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
+ $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
+}
+
+/**
+ * A base class for classes dealing with Simple Registration protocol
+ * messages.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
+ /**
+ * Extract the simple registration namespace URI from the given
+ * OpenID message. Handles OpenID 1 and 2, as well as both sreg
+ * namespace URIs found in the wild, as well as missing namespace
+ * definitions (for OpenID 1)
+ *
+ * $message: The OpenID message from which to parse simple
+ * registration fields. This may be a request or response message.
+ *
+ * Returns the sreg namespace URI for the supplied message. The
+ * message may be modified to define a simple registration
+ * namespace.
+ *
+ * @access private
+ */
+ function _getSRegNS(&$message)
+ {
+ $alias = null;
+ $found_ns_uri = null;
+
+ // See if there exists an alias for one of the two defined
+ // simple registration types.
+ foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
+ Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
+ $alias = $message->namespaces->getAlias($sreg_ns_uri);
+ if ($alias !== null) {
+ $found_ns_uri = $sreg_ns_uri;
+ break;
+ }
+ }
+
+ if ($alias === null) {
+ // There is no alias for either of the types, so try to
+ // add one. We default to using the modern value (1.1)
+ $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
+ if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
+ 'sreg') === null) {
+ // An alias for the string 'sreg' already exists, but
+ // it's defined for something other than simple
+ // registration
+ return null;
+ }
+ }
+
+ return $found_ns_uri;
+ }
+}
+
+/**
+ * An object to hold the state of a simple registration request.
+ *
+ * required: A list of the required fields in this simple registration
+ * request
+ *
+ * optional: A list of the optional fields in this simple registration
+ * request
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
+
+ var $ns_alias = 'sreg';
+
+ /**
+ * Initialize an empty simple registration request.
+ */
+ function build($required=null, $optional=null,
+ $policy_url=null,
+ $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
+ $cls='Auth_OpenID_SRegRequest')
+ {
+ $obj = new $cls();
+
+ $obj->required = array();
+ $obj->optional = array();
+ $obj->policy_url = $policy_url;
+ $obj->ns_uri = $sreg_ns_uri;
+
+ if ($required) {
+ if (!$obj->requestFields($required, true, true)) {
+ return null;
+ }
+ }
+
+ if ($optional) {
+ if (!$obj->requestFields($optional, false, true)) {
+ return null;
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Create a simple registration request that contains the fields
+ * that were requested in the OpenID request with the given
+ * arguments
+ *
+ * $request: The OpenID authentication request from which to
+ * extract an sreg request.
+ *
+ * $cls: name of class to use when creating sreg request object.
+ * Used for testing.
+ *
+ * Returns the newly created simple registration request
+ */
+ function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
+ {
+
+ $obj = call_user_func_array(array($cls, 'build'),
+ array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
+
+ // Since we're going to mess with namespace URI mapping, don't
+ // mutate the object that was passed in.
+ $m = $request->message;
+
+ $obj->ns_uri = $obj->_getSRegNS($m);
+ $args = $m->getArgs($obj->ns_uri);
+
+ if ($args === null || Auth_OpenID::isFailure($args)) {
+ return null;
+ }
+
+ $obj->parseExtensionArgs($args);
+
+ return $obj;
+ }
+
+ /**
+ * Parse the unqualified simple registration request parameters
+ * and add them to this object.
+ *
+ * This method is essentially the inverse of
+ * getExtensionArgs. This method restores the serialized simple
+ * registration request fields.
+ *
+ * If you are extracting arguments from a standard OpenID
+ * checkid_* request, you probably want to use fromOpenIDRequest,
+ * which will extract the sreg namespace and arguments from the
+ * OpenID request. This method is intended for cases where the
+ * OpenID server needs more control over how the arguments are
+ * parsed than that method provides.
+ *
+ * $args == $message->getArgs($ns_uri);
+ * $request->parseExtensionArgs($args);
+ *
+ * $args: The unqualified simple registration arguments
+ *
+ * strict: Whether requests with fields that are not defined in
+ * the simple registration specification should be tolerated (and
+ * ignored)
+ */
+ function parseExtensionArgs($args, $strict=false)
+ {
+ foreach (array('required', 'optional') as $list_name) {
+ $required = ($list_name == 'required');
+ $items = Auth_OpenID::arrayGet($args, $list_name);
+ if ($items) {
+ foreach (explode(',', $items) as $field_name) {
+ if (!$this->requestField($field_name, $required, $strict)) {
+ if ($strict) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');
+
+ return true;
+ }
+
+ /**
+ * A list of all of the simple registration fields that were
+ * requested, whether they were required or optional.
+ */
+ function allRequestedFields()
+ {
+ return array_merge($this->required, $this->optional);
+ }
+
+ /**
+ * Have any simple registration fields been requested?
+ */
+ function wereFieldsRequested()
+ {
+ return count($this->allRequestedFields());
+ }
+
+ /**
+ * Was this field in the request?
+ */
+ function contains($field_name)
+ {
+ return (in_array($field_name, $this->required) ||
+ in_array($field_name, $this->optional));
+ }
+
+ /**
+ * Request the specified field from the OpenID user
+ *
+ * $field_name: the unqualified simple registration field name
+ *
+ * required: whether the given field should be presented to the
+ * user as being a required to successfully complete the request
+ *
+ * strict: whether to raise an exception when a field is added to
+ * a request more than once
+ */
+ function requestField($field_name,
+ $required=false, $strict=false)
+ {
+ if (!Auth_OpenID_checkFieldName($field_name)) {
+ return false;
+ }
+
+ if ($strict) {
+ if ($this->contains($field_name)) {
+ return false;
+ }
+ } else {
+ if (in_array($field_name, $this->required)) {
+ return true;
+ }
+
+ if (in_array($field_name, $this->optional)) {
+ if ($required) {
+ unset($this->optional[array_search($field_name,
+ $this->optional)]);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ if ($required) {
+ $this->required[] = $field_name;
+ } else {
+ $this->optional[] = $field_name;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add the given list of fields to the request
+ *
+ * field_names: The simple registration data fields to request
+ *
+ * required: Whether these values should be presented to the user
+ * as required
+ *
+ * strict: whether to raise an exception when a field is added to
+ * a request more than once
+ */
+ function requestFields($field_names, $required=false, $strict=false)
+ {
+ if (!is_array($field_names)) {
+ return false;
+ }
+
+ foreach ($field_names as $field_name) {
+ if (!$this->requestField($field_name, $required, $strict=$strict)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a dictionary of unqualified simple registration arguments
+ * representing this request.
+ *
+ * This method is essentially the inverse of
+ * C{L{parseExtensionArgs}}. This method serializes the simple
+ * registration request fields.
+ */
+ function getExtensionArgs()
+ {
+ $args = array();
+
+ if ($this->required) {
+ $args['required'] = implode(',', $this->required);
+ }
+
+ if ($this->optional) {
+ $args['optional'] = implode(',', $this->optional);
+ }
+
+ if ($this->policy_url) {
+ $args['policy_url'] = $this->policy_url;
+ }
+
+ return $args;
+ }
+}
+
+/**
+ * Represents the data returned in a simple registration response
+ * inside of an OpenID C{id_res} response. This object will be created
+ * by the OpenID server, added to the C{id_res} response object, and
+ * then extracted from the C{id_res} message by the Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
+
+ var $ns_alias = 'sreg';
+
+ function Auth_OpenID_SRegResponse($data=null,
+ $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
+ {
+ if ($data === null) {
+ $this->data = array();
+ } else {
+ $this->data = $data;
+ }
+
+ $this->ns_uri = $sreg_ns_uri;
+ }
+
+ /**
+ * Take a C{L{SRegRequest}} and a dictionary of simple
+ * registration values and create a C{L{SRegResponse}} object
+ * containing that data.
+ *
+ * request: The simple registration request object
+ *
+ * data: The simple registration data for this response, as a
+ * dictionary from unqualified simple registration field name to
+ * string (unicode) value. For instance, the nickname should be
+ * stored under the key 'nickname'.
+ */
+ function extractResponse($request, $data)
+ {
+ $obj = new Auth_OpenID_SRegResponse();
+ $obj->ns_uri = $request->ns_uri;
+
+ foreach ($request->allRequestedFields() as $field) {
+ $value = Auth_OpenID::arrayGet($data, $field);
+ if ($value !== null) {
+ $obj->data[$field] = $value;
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Create a C{L{SRegResponse}} object from a successful OpenID
+ * library response
+ * (C{L{openid.consumer.consumer.SuccessResponse}}) response
+ * message
+ *
+ * success_response: A SuccessResponse from consumer.complete()
+ *
+ * signed_only: Whether to process only data that was
+ * signed in the id_res message from the server.
+ *
+ * Returns a simple registration response containing the data that
+ * was supplied with the C{id_res} response.
+ */
+ function fromSuccessResponse(&$success_response, $signed_only=true)
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ $obj = new Auth_OpenID_SRegResponse();
+ $obj->ns_uri = $obj->_getSRegNS($success_response->message);
+
+ if ($signed_only) {
+ $args = $success_response->getSignedNS($obj->ns_uri);
+ } else {
+ $args = $success_response->message->getArgs($obj->ns_uri);
+ }
+
+ if ($args === null || Auth_OpenID::isFailure($args)) {
+ return null;
+ }
+
+ foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
+ if (in_array($field_name, array_keys($args))) {
+ $obj->data[$field_name] = $args[$field_name];
+ }
+ }
+
+ return $obj;
+ }
+
+ function getExtensionArgs()
+ {
+ return $this->data;
+ }
+
+ // Read-only dictionary interface
+ function get($field_name, $default=null)
+ {
+ if (!Auth_OpenID_checkFieldName($field_name)) {
+ return null;
+ }
+
+ return Auth_OpenID::arrayGet($this->data, $field_name, $default);
+ }
+
+ function contents()
+ {
+ return $this->data;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/Server.php b/Auth/OpenID/Server.php
new file mode 100644
index 0000000..f1db4d8
--- /dev/null
+++ b/Auth/OpenID/Server.php
@@ -0,0 +1,1760 @@
+<?php
+
+/**
+ * OpenID server protocol and logic.
+ *
+ * Overview
+ *
+ * An OpenID server must perform three tasks:
+ *
+ * 1. Examine the incoming request to determine its nature and validity.
+ * 2. Make a decision about how to respond to this request.
+ * 3. Format the response according to the protocol.
+ *
+ * The first and last of these tasks may performed by the {@link
+ * Auth_OpenID_Server::decodeRequest()} and {@link
+ * Auth_OpenID_Server::encodeResponse} methods. Who gets to do the
+ * intermediate task -- deciding how to respond to the request -- will
+ * depend on what type of request it is.
+ *
+ * If it's a request to authenticate a user (a 'checkid_setup' or
+ * 'checkid_immediate' request), you need to decide if you will assert
+ * that this user may claim the identity in question. Exactly how you
+ * do that is a matter of application policy, but it generally
+ * involves making sure the user has an account with your system and
+ * is logged in, checking to see if that identity is hers to claim,
+ * and verifying with the user that she does consent to releasing that
+ * information to the party making the request.
+ *
+ * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
+ * object, and if and when you've come to a decision, form a response
+ * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
+ *
+ * Other types of requests relate to establishing associations between
+ * client and server and verifing the authenticity of previous
+ * communications. {@link Auth_OpenID_Server} contains all the logic
+ * and data necessary to respond to such requests; just pass it to
+ * {@link Auth_OpenID_Server::handleRequest()}.
+ *
+ * OpenID Extensions
+ *
+ * Do you want to provide other information for your users in addition
+ * to authentication? Version 1.2 of the OpenID protocol allows
+ * consumers to add extensions to their requests. For example, with
+ * sites using the Simple Registration
+ * Extension
+ * (http://www.openidenabled.com/openid/simple-registration-extension/),
+ * a user can agree to have their nickname and e-mail address sent to
+ * a site when they sign up.
+ *
+ * Since extensions do not change the way OpenID authentication works,
+ * code to handle extension requests may be completely separate from
+ * the {@link Auth_OpenID_Request} class here. But you'll likely want
+ * data sent back by your extension to be signed. {@link
+ * Auth_OpenID_ServerResponse} provides methods with which you can add
+ * data to it which can be signed with the other data in the OpenID
+ * signature.
+ *
+ * For example:
+ *
+ * <pre> // when request is a checkid_* request
+ * $response = $request->answer(true);
+ * // this will a signed 'openid.sreg.timezone' parameter to the response
+ * response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
+ *
+ * Stores
+ *
+ * The OpenID server needs to maintain state between requests in order
+ * to function. Its mechanism for doing this is called a store. The
+ * store interface is defined in Interface.php. Additionally, several
+ * concrete store implementations are provided, so that most sites
+ * won't need to implement a custom store. For a store backed by flat
+ * files on disk, see {@link Auth_OpenID_FileStore}. For stores based
+ * on MySQL, SQLite, or PostgreSQL, see the {@link
+ * Auth_OpenID_SQLStore} subclasses.
+ *
+ * Upgrading
+ *
+ * The keys by which a server looks up associations in its store have
+ * changed in version 1.2 of this library. If your store has entries
+ * created from version 1.0 code, you should empty it.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Required imports
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/TrustRoot.php";
+require_once "Auth/OpenID/ServerRequest.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Nonce.php";
+
+define('AUTH_OPENID_HTTP_OK', 200);
+define('AUTH_OPENID_HTTP_REDIRECT', 302);
+define('AUTH_OPENID_HTTP_ERROR', 400);
+
+/**
+ * @access private
+ */
+global $_Auth_OpenID_Request_Modes;
+$_Auth_OpenID_Request_Modes = array('checkid_setup',
+ 'checkid_immediate');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
+
+/**
+ * @access private
+ */
+function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
+{
+ return is_a($obj, $cls);
+}
+
+/**
+ * An error class which gets instantiated and returned whenever an
+ * OpenID protocol error occurs. Be prepared to use this in place of
+ * an ordinary server response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerError {
+ /**
+ * @access private
+ */
+ function Auth_OpenID_ServerError($message = null, $text = null,
+ $reference = null, $contact = null)
+ {
+ $this->message = $message;
+ $this->text = $text;
+ $this->contact = $contact;
+ $this->reference = $reference;
+ }
+
+ function getReturnTo()
+ {
+ if ($this->message &&
+ $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
+ return $this->message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the return_to URL for the request which caused this
+ * error.
+ */
+ function hasReturnTo()
+ {
+ return $this->getReturnTo() !== null;
+ }
+
+ /**
+ * Encodes this error's response as a URL suitable for
+ * redirection. If the response has no return_to, another
+ * Auth_OpenID_ServerError is returned.
+ */
+ function encodeToURL()
+ {
+ if (!$this->message) {
+ return null;
+ }
+
+ $msg = $this->toMessage();
+ return $msg->toURL($this->getReturnTo());
+ }
+
+ /**
+ * Encodes the response to key-value form. This is a
+ * machine-readable format used to respond to messages which came
+ * directly from the consumer and not through the user-agent. See
+ * the OpenID specification.
+ */
+ function encodeToKVForm()
+ {
+ return Auth_OpenID_KVForm::fromArray(
+ array('mode' => 'error',
+ 'error' => $this->toString()));
+ }
+
+ function toFormMarkup($form_tag_attrs=null)
+ {
+ $msg = $this->toMessage();
+ return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
+ }
+
+ function toHTML($form_tag_attrs=null)
+ {
+ return Auth_OpenID::autoSubmitHTML(
+ $this->toFormMarkup($form_tag_attrs));
+ }
+
+ function toMessage()
+ {
+ // Generate a Message object for sending to the relying party,
+ // after encoding.
+ $namespace = $this->message->getOpenIDNamespace();
+ $reply = new Auth_OpenID_Message($namespace);
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());
+
+ if ($this->contact !== null) {
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
+ }
+
+ if ($this->reference !== null) {
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
+ $this->reference);
+ }
+
+ return $reply;
+ }
+
+ /**
+ * Returns one of Auth_OpenID_ENCODE_URL,
+ * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
+ * encoding expected for this error's payload.
+ */
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Request_Modes;
+
+ if ($this->hasReturnTo()) {
+ if ($this->message->isOpenID2() &&
+ (strlen($this->encodeToURL()) >
+ Auth_OpenID_OPENID1_URL_LIMIT)) {
+ return Auth_OpenID_ENCODE_HTML_FORM;
+ } else {
+ return Auth_OpenID_ENCODE_URL;
+ }
+ }
+
+ if (!$this->message) {
+ return null;
+ }
+
+ $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
+ 'mode');
+
+ if ($mode) {
+ if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
+ return Auth_OpenID_ENCODE_KVFORM;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns this error message.
+ */
+ function toString()
+ {
+ if ($this->text) {
+ return $this->text;
+ } else {
+ return get_class($this) . " error";
+ }
+ }
+}
+
+/**
+ * Error returned by the server code when a return_to is absent from a
+ * request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
+ function Auth_OpenID_NoReturnToError($message = null,
+ $text = "No return_to URL available")
+ {
+ parent::Auth_OpenID_ServerError($message, $text);
+ }
+
+ function toString()
+ {
+ return "No return_to available";
+ }
+}
+
+/**
+ * An error indicating that the return_to URL is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedReturnURL($message, $return_to)
+ {
+ $this->return_to = $return_to;
+ parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
+ }
+}
+
+/**
+ * This error is returned when the trust_root value is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedTrustRoot($message = null,
+ $text = "Malformed trust root")
+ {
+ parent::Auth_OpenID_ServerError($message, $text);
+ }
+
+ function toString()
+ {
+ return "Malformed trust root";
+ }
+}
+
+/**
+ * The base class for all server request classes.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Request {
+ var $mode = null;
+}
+
+/**
+ * A request to verify the validity of a previous response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
+ var $mode = "check_authentication";
+ var $invalidate_handle = null;
+
+ function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
+ $invalidate_handle = null)
+ {
+ $this->assoc_handle = $assoc_handle;
+ $this->signed = $signed;
+ if ($invalidate_handle !== null) {
+ $this->invalidate_handle = $invalidate_handle;
+ }
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->message = null;
+ }
+
+ function fromMessage($message, $server=null)
+ {
+ $required_keys = array('assoc_handle', 'sig', 'signed');
+
+ foreach ($required_keys as $k) {
+ if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
+ return new Auth_OpenID_ServerError($message,
+ sprintf("%s request missing required parameter %s from \
+ query", "check_authentication", $k));
+ }
+ }
+
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
+ $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');
+
+ $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ $signed_list = explode(",", $signed_list);
+
+ $signed = $message;
+ if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
+ $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
+ }
+
+ $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
+ $result->message = $message;
+ $result->sig = $sig;
+ $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle');
+ return $result;
+ }
+
+ function answer(&$signatory)
+ {
+ $is_valid = $signatory->verify($this->assoc_handle, $this->signed);
+
+ // Now invalidate that assoc_handle so it this checkAuth
+ // message cannot be replayed.
+ $signatory->invalidate($this->assoc_handle, true);
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'is_valid',
+ ($is_valid ? "true" : "false"));
+
+ if ($this->invalidate_handle) {
+ $assoc = $signatory->getAssociation($this->invalidate_handle,
+ false);
+ if (!$assoc) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle',
+ $this->invalidate_handle);
+ }
+ }
+ return $response;
+ }
+}
+
+/**
+ * A class implementing plaintext server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextServerSession {
+ /**
+ * An object that knows how to handle association requests with no
+ * session type.
+ */
+ var $session_type = 'no-encryption';
+ var $needs_math = false;
+ var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
+
+ function fromMessage($unused_request)
+ {
+ return new Auth_OpenID_PlainTextServerSession();
+ }
+
+ function answer($secret)
+ {
+ return array('mac_key' => base64_encode($secret));
+ }
+}
+
+/**
+ * A class implementing DH-SHA1 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ServerSession {
+ /**
+ * An object that knows how to handle association requests with
+ * the Diffie-Hellman session type.
+ */
+
+ var $session_type = 'DH-SHA1';
+ var $needs_math = true;
+ var $allowed_assoc_types = array('HMAC-SHA1');
+ var $hash_func = 'Auth_OpenID_SHA1';
+
+ function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
+ {
+ $this->dh = $dh;
+ $this->consumer_pubkey = $consumer_pubkey;
+ }
+
+ function getDH($message)
+ {
+ $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
+ $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');
+
+ if ((($dh_modulus === null) && ($dh_gen !== null)) ||
+ (($dh_gen === null) && ($dh_modulus !== null))) {
+
+ if ($dh_modulus === null) {
+ $missing = 'modulus';
+ } else {
+ $missing = 'generator';
+ }
+
+ return new Auth_OpenID_ServerError($message,
+ 'If non-default modulus or generator is '.
+ 'supplied, both must be supplied. Missing '.
+ $missing);
+ }
+
+ $lib =& Auth_OpenID_getMathLib();
+
+ if ($dh_modulus || $dh_gen) {
+ $dh_modulus = $lib->base64ToLong($dh_modulus);
+ $dh_gen = $lib->base64ToLong($dh_gen);
+ if ($lib->cmp($dh_modulus, 0) == 0 ||
+ $lib->cmp($dh_gen, 0) == 0) {
+ return new Auth_OpenID_ServerError(
+ $message, "Failed to parse dh_mod or dh_gen");
+ }
+ $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
+ } else {
+ $dh = new Auth_OpenID_DiffieHellman();
+ }
+
+ $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'dh_consumer_public');
+ if ($consumer_pubkey === null) {
+ return new Auth_OpenID_ServerError($message,
+ 'Public key for DH-SHA1 session '.
+ 'not found in query');
+ }
+
+ $consumer_pubkey =
+ $lib->base64ToLong($consumer_pubkey);
+
+ if ($consumer_pubkey === false) {
+ return new Auth_OpenID_ServerError($message,
+ "dh_consumer_public is not base64");
+ }
+
+ return array($dh, $consumer_pubkey);
+ }
+
+ function fromMessage($message)
+ {
+ $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+ if (is_a($result, 'Auth_OpenID_ServerError')) {
+ return $result;
+ } else {
+ list($dh, $consumer_pubkey) = $result;
+ return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
+ $consumer_pubkey);
+ }
+ }
+
+ function answer($secret)
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
+ $this->hash_func);
+ return array(
+ 'dh_server_public' =>
+ $lib->longToBase64($this->dh->public),
+ 'enc_mac_key' => base64_encode($mac_key));
+ }
+}
+
+/**
+ * A class implementing DH-SHA256 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ServerSession
+ extends Auth_OpenID_DiffieHellmanSHA1ServerSession {
+
+ var $session_type = 'DH-SHA256';
+ var $hash_func = 'Auth_OpenID_SHA256';
+ var $allowed_assoc_types = array('HMAC-SHA256');
+
+ function fromMessage($message)
+ {
+ $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+ if (is_a($result, 'Auth_OpenID_ServerError')) {
+ return $result;
+ } else {
+ list($dh, $consumer_pubkey) = $result;
+ return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
+ $consumer_pubkey);
+ }
+ }
+}
+
+/**
+ * A request to associate with the server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
+ var $mode = "associate";
+
+ function getSessionClasses()
+ {
+ return array(
+ 'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
+ 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
+ }
+
+ function Auth_OpenID_AssociateRequest(&$session, $assoc_type)
+ {
+ $this->session =& $session;
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->assoc_type = $assoc_type;
+ }
+
+ function fromMessage($message, $server=null)
+ {
+ if ($message->isOpenID1()) {
+ $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if ($session_type == 'no-encryption') {
+ // oidutil.log('Received OpenID 1 request with a no-encryption '
+ // 'assocaition session type. Continuing anyway.')
+ } else if (!$session_type) {
+ $session_type = 'no-encryption';
+ }
+ } else {
+ $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+ if ($session_type === null) {
+ return new Auth_OpenID_ServerError($message,
+ "session_type missing from request");
+ }
+ }
+
+ $session_class = Auth_OpenID::arrayGet(
+ Auth_OpenID_AssociateRequest::getSessionClasses(),
+ $session_type);
+
+ if ($session_class === null) {
+ return new Auth_OpenID_ServerError($message,
+ "Unknown session type " .
+ $session_type);
+ }
+
+ $session = call_user_func(array($session_class, 'fromMessage'),
+ $message);
+ if (is_a($session, 'Auth_OpenID_ServerError')) {
+ return $session;
+ }
+
+ $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type', 'HMAC-SHA1');
+
+ if (!in_array($assoc_type, $session->allowed_assoc_types)) {
+ $fmt = "Session type %s does not support association type %s";
+ return new Auth_OpenID_ServerError($message,
+ sprintf($fmt, $session_type, $assoc_type));
+ }
+
+ $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
+ $obj->message = $message;
+ $obj->namespace = $message->getOpenIDNamespace();
+ return $obj;
+ }
+
+ function answer($assoc)
+ {
+ $response = new Auth_OpenID_ServerResponse($this);
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ array(
+ 'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
+ 'assoc_type' => $this->assoc_type,
+ 'assoc_handle' => $assoc->handle));
+
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ $this->session->answer($assoc->secret));
+
+ if (! ($this->session->session_type == 'no-encryption'
+ && $this->message->isOpenID1())) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'session_type',
+ $this->session->session_type);
+ }
+
+ return $response;
+ }
+
+ function answerUnsupported($text_message,
+ $preferred_association_type=null,
+ $preferred_session_type=null)
+ {
+ if ($this->message->isOpenID1()) {
+ return new Auth_OpenID_ServerError($this->message);
+ }
+
+ $response = new Auth_OpenID_ServerResponse($this);
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'error_code', 'unsupported-type');
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'error', $text_message);
+
+ if ($preferred_association_type) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type',
+ $preferred_association_type);
+ }
+
+ if ($preferred_session_type) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'session_type',
+ $preferred_session_type);
+ }
+
+ return $response;
+ }
+}
+
+/**
+ * A request to confirm the identity of a user.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+ /**
+ * Return-to verification callback. Default is
+ * Auth_OpenID_verifyReturnTo from TrustRoot.php.
+ */
+ var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
+
+ /**
+ * The mode of this request.
+ */
+ var $mode = "checkid_setup"; // or "checkid_immediate"
+
+ /**
+ * Whether this request is for immediate mode.
+ */
+ var $immediate = false;
+
+ /**
+ * The trust_root value for this request.
+ */
+ var $trust_root = null;
+
+ /**
+ * The OpenID namespace for this request.
+ * deprecated since version 2.0.2
+ */
+ var $namespace;
+
+ function make(&$message, $identity, $return_to, $trust_root = null,
+ $immediate = false, $assoc_handle = null, $server = null)
+ {
+ if ($server === null) {
+ return new Auth_OpenID_ServerError($message,
+ "server must not be null");
+ }
+
+ if ($return_to &&
+ !Auth_OpenID_TrustRoot::_parse($return_to)) {
+ return new Auth_OpenID_MalformedReturnURL($message, $return_to);
+ }
+
+ $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root, $immediate,
+ $assoc_handle, $server);
+
+ $r->namespace = $message->getOpenIDNamespace();
+ $r->message =& $message;
+
+ if (!$r->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL($message,
+ $return_to,
+ $trust_root);
+ } else {
+ return $r;
+ }
+ }
+
+ function Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root = null, $immediate = false,
+ $assoc_handle = null, $server = null,
+ $claimed_id = null)
+ {
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->assoc_handle = $assoc_handle;
+ $this->identity = $identity;
+ if ($claimed_id === null) {
+ $this->claimed_id = $identity;
+ } else {
+ $this->claimed_id = $claimed_id;
+ }
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ $this->server =& $server;
+
+ if ($immediate) {
+ $this->immediate = true;
+ $this->mode = "checkid_immediate";
+ } else {
+ $this->immediate = false;
+ $this->mode = "checkid_setup";
+ }
+ }
+
+ function equals($other)
+ {
+ return (
+ (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
+ ($this->namespace == $other->namespace) &&
+ ($this->assoc_handle == $other->assoc_handle) &&
+ ($this->identity == $other->identity) &&
+ ($this->claimed_id == $other->claimed_id) &&
+ ($this->return_to == $other->return_to) &&
+ ($this->trust_root == $other->trust_root));
+ }
+
+ /*
+ * Does the relying party publish the return_to URL for this
+ * response under the realm? It is up to the provider to set a
+ * policy for what kinds of realms should be allowed. This
+ * return_to URL verification reduces vulnerability to data-theft
+ * attacks based on open proxies, corss-site-scripting, or open
+ * redirectors.
+ *
+ * This check should only be performed after making sure that the
+ * return_to URL matches the realm.
+ *
+ * @return true if the realm publishes a document with the
+ * return_to URL listed, false if not or if discovery fails
+ */
+ function returnToVerified()
+ {
+ return call_user_func_array($this->verifyReturnTo,
+ array($this->trust_root, $this->return_to));
+ }
+
+ function fromMessage(&$message, $server)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+ $immediate = null;
+
+ if ($mode == "checkid_immediate") {
+ $immediate = true;
+ $mode = "checkid_immediate";
+ } else {
+ $immediate = false;
+ $mode = "checkid_setup";
+ }
+
+ $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+
+ if (($message->isOpenID1()) &&
+ (!$return_to)) {
+ $fmt = "Missing required field 'return_to' from checkid request";
+ return new Auth_OpenID_ServerError($message, $fmt);
+ }
+
+ $identity = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'identity');
+ $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
+ if ($message->isOpenID1()) {
+ if ($identity === null) {
+ $s = "OpenID 1 message did not contain openid.identity";
+ return new Auth_OpenID_ServerError($message, $s);
+ }
+ } else {
+ if ($identity && !$claimed_id) {
+ $s = "OpenID 2.0 message contained openid.identity but not " .
+ "claimed_id";
+ return new Auth_OpenID_ServerError($message, $s);
+ } else if ($claimed_id && !$identity) {
+ $s = "OpenID 2.0 message contained openid.claimed_id " .
+ "but not identity";
+ return new Auth_OpenID_ServerError($message, $s);
+ }
+ }
+
+ // There's a case for making self.trust_root be a TrustRoot
+ // here. But if TrustRoot isn't currently part of the
+ // "public" API, I'm not sure it's worth doing.
+ if ($message->isOpenID1()) {
+ $trust_root_param = 'trust_root';
+ } else {
+ $trust_root_param = 'realm';
+ }
+ $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
+ $trust_root_param);
+ if (! $trust_root) {
+ $trust_root = $return_to;
+ }
+
+ if (! $message->isOpenID1() &&
+ ($return_to === null) &&
+ ($trust_root === null)) {
+ return new Auth_OpenID_ServerError($message,
+ "openid.realm required when openid.return_to absent");
+ }
+
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ $obj = Auth_OpenID_CheckIDRequest::make($message,
+ $identity,
+ $return_to,
+ $trust_root,
+ $immediate,
+ $assoc_handle,
+ $server);
+
+ if (is_a($obj, 'Auth_OpenID_ServerError')) {
+ return $obj;
+ }
+
+ $obj->claimed_id = $claimed_id;
+
+ return $obj;
+ }
+
+ function idSelect()
+ {
+ // Is the identifier to be selected by the IDP?
+ // So IDPs don't have to import the constant
+ return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
+ }
+
+ function trustRootValid()
+ {
+ if (!$this->trust_root) {
+ return true;
+ }
+
+ $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
+ if ($tr === false) {
+ return new Auth_OpenID_MalformedTrustRoot($this->message,
+ $this->trust_root);
+ }
+
+ if ($this->return_to !== null) {
+ return Auth_OpenID_TrustRoot::match($this->trust_root,
+ $this->return_to);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Respond to this request. Return either an
+ * {@link Auth_OpenID_ServerResponse} or
+ * {@link Auth_OpenID_ServerError}.
+ *
+ * @param bool $allow Allow this user to claim this identity, and
+ * allow the consumer to have this information?
+ *
+ * @param string $server_url DEPRECATED. Passing $op_endpoint to
+ * the {@link Auth_OpenID_Server} constructor makes this optional.
+ *
+ * When an OpenID 1.x immediate mode request does not succeed, it
+ * gets back a URL where the request may be carried out in a
+ * not-so-immediate fashion. Pass my URL in here (the fully
+ * qualified address of this server's endpoint, i.e.
+ * http://example.com/server), and I will use it as a base for the
+ * URL for a new request.
+ *
+ * Optional for requests where {@link $immediate} is false or
+ * $allow is true.
+ *
+ * @param string $identity The OP-local identifier to answer with.
+ * Only for use when the relying party requested identifier
+ * selection.
+ *
+ * @param string $claimed_id The claimed identifier to answer
+ * with, for use with identifier selection in the case where the
+ * claimed identifier and the OP-local identifier differ,
+ * i.e. when the claimed_id uses delegation.
+ *
+ * If $identity is provided but this is not, $claimed_id will
+ * default to the value of $identity. When answering requests
+ * that did not ask for identifier selection, the response
+ * $claimed_id will default to that of the request.
+ *
+ * This parameter is new in OpenID 2.0.
+ *
+ * @return mixed
+ */
+ function answer($allow, $server_url = null, $identity = null,
+ $claimed_id = null)
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ if (!$server_url) {
+ if ((!$this->message->isOpenID1()) &&
+ (!$this->server->op_endpoint)) {
+ return new Auth_OpenID_ServerError(null,
+ "server should be constructed with op_endpoint to " .
+ "respond to OpenID 2.0 messages.");
+ }
+
+ $server_url = $this->server->op_endpoint;
+ }
+
+ if ($allow) {
+ $mode = 'id_res';
+ } else if ($this->message->isOpenID1()) {
+ if ($this->immediate) {
+ $mode = 'id_res';
+ } else {
+ $mode = 'cancel';
+ }
+ } else {
+ if ($this->immediate) {
+ $mode = 'setup_needed';
+ } else {
+ $mode = 'cancel';
+ }
+ }
+
+ if (!$this->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL(null,
+ $this->return_to,
+ $this->trust_root);
+ }
+
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ if ($claimed_id &&
+ ($this->message->isOpenID1())) {
+ return new Auth_OpenID_ServerError(null,
+ "claimed_id is new in OpenID 2.0 and not " .
+ "available for ".$this->namespace);
+ }
+
+ if ($identity && !$claimed_id) {
+ $claimed_id = $identity;
+ }
+
+ if ($allow) {
+
+ if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
+ if (!$identity) {
+ return new Auth_OpenID_ServerError(null,
+ "This request uses IdP-driven identifier selection. " .
+ "You must supply an identifier in the response.");
+ }
+
+ $response_identity = $identity;
+ $response_claimed_id = $claimed_id;
+
+ } else if ($this->identity) {
+ if ($identity &&
+ ($this->identity != $identity)) {
+ $fmt = "Request was for %s, cannot reply with identity %s";
+ return new Auth_OpenID_ServerError(null,
+ sprintf($fmt, $this->identity, $identity));
+ }
+
+ $response_identity = $this->identity;
+ $response_claimed_id = $this->claimed_id;
+ } else {
+ if ($identity) {
+ return new Auth_OpenID_ServerError(null,
+ "This request specified no identity and " .
+ "you supplied ".$identity);
+ }
+
+ $response_identity = null;
+ }
+
+ if (($this->message->isOpenID1()) &&
+ ($response_identity === null)) {
+ return new Auth_OpenID_ServerError(null,
+ "Request was an OpenID 1 request, so response must " .
+ "include an identifier.");
+ }
+
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ array('mode' => $mode,
+ 'return_to' => $this->return_to,
+ 'response_nonce' => Auth_OpenID_mkNonce()));
+
+ if (!$this->message->isOpenID1()) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'op_endpoint', $server_url);
+ }
+
+ if ($response_identity !== null) {
+ $response->fields->setArg(
+ Auth_OpenID_OPENID_NS,
+ 'identity',
+ $response_identity);
+ if ($this->message->isOpenID2()) {
+ $response->fields->setArg(
+ Auth_OpenID_OPENID_NS,
+ 'claimed_id',
+ $response_claimed_id);
+ }
+ }
+
+ } else {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'mode', $mode);
+
+ if ($this->immediate) {
+ if (($this->message->isOpenID1()) &&
+ (!$server_url)) {
+ return new Auth_OpenID_ServerError(null,
+ 'setup_url is required for $allow=false \
+ in OpenID 1.x immediate mode.');
+ }
+
+ $setup_request =& new Auth_OpenID_CheckIDRequest(
+ $this->identity,
+ $this->return_to,
+ $this->trust_root,
+ false,
+ $this->assoc_handle,
+ $this->server,
+ $this->claimed_id);
+ $setup_request->message = $this->message;
+
+ $setup_url = $setup_request->encodeToURL($server_url);
+
+ if ($setup_url === null) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'user_setup_url',
+ $setup_url);
+ }
+ }
+
+ return $response;
+ }
+
+ function encodeToURL($server_url)
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ // Imported from the alternate reality where these classes are
+ // used in both the client and server code, so Requests are
+ // Encodable too. That's right, code imported from alternate
+ // realities all for the love of you, id_res/user_setup_url.
+
+ $q = array('mode' => $this->mode,
+ 'identity' => $this->identity,
+ 'claimed_id' => $this->claimed_id,
+ 'return_to' => $this->return_to);
+
+ if ($this->trust_root) {
+ if ($this->message->isOpenID1()) {
+ $q['trust_root'] = $this->trust_root;
+ } else {
+ $q['realm'] = $this->trust_root;
+ }
+ }
+
+ if ($this->assoc_handle) {
+ $q['assoc_handle'] = $this->assoc_handle;
+ }
+
+ $response = new Auth_OpenID_Message(
+ $this->message->getOpenIDNamespace());
+ $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
+ return $response->toURL($server_url);
+ }
+
+ function getCancelURL()
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ if ($this->immediate) {
+ return new Auth_OpenID_ServerError(null,
+ "Cancel is not an appropriate \
+ response to immediate mode \
+ requests.");
+ }
+
+ $response = new Auth_OpenID_Message(
+ $this->message->getOpenIDNamespace());
+ $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
+ return $response->toURL($this->return_to);
+ }
+}
+
+/**
+ * This class encapsulates the response to an OpenID server request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_ServerResponse(&$request)
+ {
+ $this->request =& $request;
+ $this->fields = new Auth_OpenID_Message($this->request->namespace);
+ }
+
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Request_Modes;
+
+ if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
+ if ($this->fields->isOpenID2() &&
+ (strlen($this->encodeToURL()) >
+ Auth_OpenID_OPENID1_URL_LIMIT)) {
+ return Auth_OpenID_ENCODE_HTML_FORM;
+ } else {
+ return Auth_OpenID_ENCODE_URL;
+ }
+ } else {
+ return Auth_OpenID_ENCODE_KVFORM;
+ }
+ }
+
+ /*
+ * Returns the form markup for this response.
+ *
+ * @return str
+ */
+ function toFormMarkup($form_tag_attrs=null)
+ {
+ return $this->fields->toFormMarkup($this->request->return_to,
+ $form_tag_attrs);
+ }
+
+ /*
+ * Returns an HTML document containing the form markup for this
+ * response that autosubmits with javascript.
+ */
+ function toHTML()
+ {
+ return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
+ }
+
+ /*
+ * Returns True if this response's encoding is ENCODE_HTML_FORM.
+ * Convenience method for server authors.
+ *
+ * @return bool
+ */
+ function renderAsForm()
+ {
+ return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
+ }
+
+
+ function encodeToURL()
+ {
+ return $this->fields->toURL($this->request->return_to);
+ }
+
+ function addExtension($extension_response)
+ {
+ $extension_response->toMessage($this->fields);
+ }
+
+ function needsSigning()
+ {
+ return $this->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'mode') == 'id_res';
+ }
+
+ function encodeToKVForm()
+ {
+ return $this->fields->toKVForm();
+ }
+}
+
+/**
+ * A web-capable response object which you can use to generate a
+ * user-agent response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_WebResponse {
+ var $code = AUTH_OPENID_HTTP_OK;
+ var $body = "";
+
+ function Auth_OpenID_WebResponse($code = null, $headers = null,
+ $body = null)
+ {
+ if ($code) {
+ $this->code = $code;
+ }
+
+ if ($headers !== null) {
+ $this->headers = $headers;
+ } else {
+ $this->headers = array();
+ }
+
+ if ($body !== null) {
+ $this->body = $body;
+ }
+ }
+}
+
+/**
+ * Responsible for the signature of query data and the verification of
+ * OpenID signature values.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Signatory {
+
+ // = 14 * 24 * 60 * 60; # 14 days, in seconds
+ var $SECRET_LIFETIME = 1209600;
+
+ // keys have a bogus server URL in them because the filestore
+ // really does expect that key to be a URL. This seems a little
+ // silly for the server store, since I expect there to be only one
+ // server URL.
+ var $normal_key = 'http://localhost/|normal';
+ var $dumb_key = 'http://localhost/|dumb';
+
+ /**
+ * Create a new signatory using a given store.
+ */
+ function Auth_OpenID_Signatory(&$store)
+ {
+ // assert store is not None
+ $this->store =& $store;
+ }
+
+ /**
+ * Verify, using a given association handle, a signature with
+ * signed key-value pairs from an HTTP request.
+ */
+ function verify($assoc_handle, $message)
+ {
+ $assoc = $this->getAssociation($assoc_handle, true);
+ if (!$assoc) {
+ // oidutil.log("failed to get assoc with handle %r to verify sig %r"
+ // % (assoc_handle, sig))
+ return false;
+ }
+
+ return $assoc->checkMessageSignature($message);
+ }
+
+ /**
+ * Given a response, sign the fields in the response's 'signed'
+ * list, and insert the signature into the response.
+ */
+ function sign($response)
+ {
+ $signed_response = $response;
+ $assoc_handle = $response->request->assoc_handle;
+
+ if ($assoc_handle) {
+ // normal mode
+ $assoc = $this->getAssociation($assoc_handle, false, false);
+ if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
+ // fall back to dumb mode
+ $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle', $assoc_handle);
+ $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');
+
+ if ($assoc && ($assoc->getExpiresIn() <= 0)) {
+ $this->invalidate($assoc_handle, false);
+ }
+
+ $assoc = $this->createAssociation(true, $assoc_type);
+ }
+ } else {
+ // dumb mode.
+ $assoc = $this->createAssociation(true);
+ }
+
+ $signed_response->fields = $assoc->signMessage(
+ $signed_response->fields);
+ return $signed_response;
+ }
+
+ /**
+ * Make a new association.
+ */
+ function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
+ {
+ $secret = Auth_OpenID_CryptUtil::getBytes(
+ Auth_OpenID_getSecretSize($assoc_type));
+
+ $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
+ $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
+
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
+
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+
+ $this->store->storeAssociation($key, $assoc);
+ return $assoc;
+ }
+
+ /**
+ * Given an association handle, get the association from the
+ * store, or return a ServerError or null if something goes wrong.
+ */
+ function getAssociation($assoc_handle, $dumb, $check_expiration=true)
+ {
+ if ($assoc_handle === null) {
+ return new Auth_OpenID_ServerError(null,
+ "assoc_handle must not be null");
+ }
+
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+
+ $assoc = $this->store->getAssociation($key, $assoc_handle);
+
+ if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
+ if ($check_expiration) {
+ $this->store->removeAssociation($key, $assoc_handle);
+ $assoc = null;
+ }
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * Invalidate a given association handle.
+ */
+ function invalidate($assoc_handle, $dumb)
+ {
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+ $this->store->removeAssociation($key, $assoc_handle);
+ }
+}
+
+/**
+ * Encode an {@link Auth_OpenID_ServerResponse} to an
+ * {@link Auth_OpenID_WebResponse}.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Encoder {
+
+ var $responseFactory = 'Auth_OpenID_WebResponse';
+
+ /**
+ * Encode an {@link Auth_OpenID_ServerResponse} and return an
+ * {@link Auth_OpenID_WebResponse}.
+ */
+ function encode(&$response)
+ {
+ $cls = $this->responseFactory;
+
+ $encode_as = $response->whichEncoding();
+ if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
+ $wr = new $cls(null, null, $response->encodeToKVForm());
+ if (is_a($response, 'Auth_OpenID_ServerError')) {
+ $wr->code = AUTH_OPENID_HTTP_ERROR;
+ }
+ } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
+ $location = $response->encodeToURL();
+ $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
+ array('location' => $location));
+ } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
+ $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
+ $response->toFormMarkup());
+ } else {
+ return new Auth_OpenID_EncodingError($response);
+ }
+ return $wr;
+ }
+}
+
+/**
+ * An encoder which also takes care of signing fields when required.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
+
+ function Auth_OpenID_SigningEncoder(&$signatory)
+ {
+ $this->signatory =& $signatory;
+ }
+
+ /**
+ * Sign an {@link Auth_OpenID_ServerResponse} and return an
+ * {@link Auth_OpenID_WebResponse}.
+ */
+ function encode(&$response)
+ {
+ // the isinstance is a bit of a kludge... it means there isn't
+ // really an adapter to make the interfaces quite match.
+ if (!is_a($response, 'Auth_OpenID_ServerError') &&
+ $response->needsSigning()) {
+
+ if (!$this->signatory) {
+ return new Auth_OpenID_ServerError(null,
+ "Must have a store to sign request");
+ }
+
+ if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
+ return new Auth_OpenID_AlreadySigned($response);
+ }
+ $response = $this->signatory->sign($response);
+ }
+
+ return parent::encode($response);
+ }
+}
+
+/**
+ * Decode an incoming query into an Auth_OpenID_Request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Decoder {
+
+ function Auth_OpenID_Decoder(&$server)
+ {
+ $this->server =& $server;
+
+ $this->handlers = array(
+ 'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
+ 'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
+ 'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
+ 'associate' => 'Auth_OpenID_AssociateRequest'
+ );
+ }
+
+ /**
+ * Given an HTTP query in an array (key-value pairs), decode it
+ * into an Auth_OpenID_Request object.
+ */
+ function decode($query)
+ {
+ if (!$query) {
+ return null;
+ }
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ if ($message === null) {
+ /*
+ * It's useful to have a Message attached to a
+ * ProtocolError, so we override the bad ns value to build
+ * a Message out of it. Kinda kludgy, since it's made of
+ * lies, but the parts that aren't lies are more useful
+ * than a 'None'.
+ */
+ $old_ns = $query['openid.ns'];
+
+ $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ return new Auth_OpenID_ServerError(
+ $message,
+ sprintf("Invalid OpenID namespace URI: %s", $old_ns));
+ }
+
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+ if (!$mode) {
+ return new Auth_OpenID_ServerError($message,
+ "No mode value in message");
+ }
+
+ if (Auth_OpenID::isFailure($mode)) {
+ return new Auth_OpenID_ServerError($message,
+ $mode->message);
+ }
+
+ $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
+ $this->defaultDecoder($message));
+
+ if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
+ return call_user_func_array(array($handlerCls, 'fromMessage'),
+ array($message, $this->server));
+ } else {
+ return $handlerCls;
+ }
+ }
+
+ function defaultDecoder($message)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+
+ if (Auth_OpenID::isFailure($mode)) {
+ return new Auth_OpenID_ServerError($message,
+ $mode->message);
+ }
+
+ return new Auth_OpenID_ServerError($message,
+ sprintf("Unrecognized OpenID mode %s", $mode));
+ }
+}
+
+/**
+ * An error that indicates an encoding problem occurred.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_EncodingError {
+ function Auth_OpenID_EncodingError(&$response)
+ {
+ $this->response =& $response;
+ }
+}
+
+/**
+ * An error that indicates that a response was already signed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
+ // This response is already signed.
+}
+
+/**
+ * An error that indicates that the given return_to is not under the
+ * given trust_root.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_UntrustedReturnURL($message, $return_to,
+ $trust_root)
+ {
+ parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ }
+
+ function toString()
+ {
+ return sprintf("return_to %s not under trust_root %s",
+ $this->return_to, $this->trust_root);
+ }
+}
+
+/**
+ * I handle requests for an OpenID server.
+ *
+ * Some types of requests (those which are not checkid requests) may
+ * be handed to my {@link handleRequest} method, and I will take care
+ * of it and return a response.
+ *
+ * For your convenience, I also provide an interface to {@link
+ * Auth_OpenID_Decoder::decode()} and {@link
+ * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
+ * decodeRequest} and {@link encodeResponse}.
+ *
+ * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
+ *
+ * Example:
+ *
+ * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
+ * "http://example.com/op");
+ * $request = $oserver->decodeRequest();
+ * if (in_array($request->mode, array('checkid_immediate',
+ * 'checkid_setup'))) {
+ * if ($app->isAuthorized($request->identity, $request->trust_root)) {
+ * $response = $request->answer(true);
+ * } else if ($request->immediate) {
+ * $response = $request->answer(false);
+ * } else {
+ * $app->showDecidePage($request);
+ * return;
+ * }
+ * } else {
+ * $response = $oserver->handleRequest($request);
+ * }
+ *
+ * $webresponse = $oserver->encode($response);</pre>
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Server {
+ function Auth_OpenID_Server(&$store, $op_endpoint=null)
+ {
+ $this->store =& $store;
+ $this->signatory =& new Auth_OpenID_Signatory($this->store);
+ $this->encoder =& new Auth_OpenID_SigningEncoder($this->signatory);
+ $this->decoder =& new Auth_OpenID_Decoder($this);
+ $this->op_endpoint = $op_endpoint;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
+ }
+
+ /**
+ * Handle a request. Given an {@link Auth_OpenID_Request} object,
+ * call the appropriate {@link Auth_OpenID_Server} method to
+ * process the request and generate a response.
+ *
+ * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
+ * returned by {@link Auth_OpenID_Server::decodeRequest()}.
+ *
+ * @return Auth_OpenID_ServerResponse $response A response object
+ * capable of generating a user-agent reply.
+ */
+ function handleRequest($request)
+ {
+ if (method_exists($this, "openid_" . $request->mode)) {
+ $handler = array($this, "openid_" . $request->mode);
+ return call_user_func($handler, $request);
+ }
+ return null;
+ }
+
+ /**
+ * The callback for 'check_authentication' messages.
+ */
+ function openid_check_authentication(&$request)
+ {
+ return $request->answer($this->signatory);
+ }
+
+ /**
+ * The callback for 'associate' messages.
+ */
+ function openid_associate(&$request)
+ {
+ $assoc_type = $request->assoc_type;
+ $session_type = $request->session->session_type;
+ if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
+ $assoc = $this->signatory->createAssociation(false,
+ $assoc_type);
+ return $request->answer($assoc);
+ } else {
+ $message = sprintf('Association type %s is not supported with '.
+ 'session type %s', $assoc_type, $session_type);
+ list($preferred_assoc_type, $preferred_session_type) =
+ $this->negotiator->getAllowedType();
+ return $request->answerUnsupported($message,
+ $preferred_assoc_type,
+ $preferred_session_type);
+ }
+ }
+
+ /**
+ * Encodes as response in the appropriate format suitable for
+ * sending to the user agent.
+ */
+ function encodeResponse(&$response)
+ {
+ return $this->encoder->encode($response);
+ }
+
+ /**
+ * Decodes a query args array into the appropriate
+ * {@link Auth_OpenID_Request} object.
+ */
+ function decodeRequest($query=null)
+ {
+ if ($query === null) {
+ $query = Auth_OpenID::getQuery();
+ }
+
+ return $this->decoder->decode($query);
+ }
+}
+
+?>
diff --git a/Auth/OpenID/ServerRequest.php b/Auth/OpenID/ServerRequest.php
new file mode 100644
index 0000000..33a8556
--- /dev/null
+++ b/Auth/OpenID/ServerRequest.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * OpenID Server Request
+ *
+ * @see Auth_OpenID_Server
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Imports
+ */
+require_once "Auth/OpenID.php";
+
+/**
+ * Object that holds the state of a request to the OpenID server
+ *
+ * With accessor functions to get at the internal request data.
+ *
+ * @see Auth_OpenID_Server
+ * @package OpenID
+ */
+class Auth_OpenID_ServerRequest {
+ function Auth_OpenID_ServerRequest()
+ {
+ $this->mode = null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/TrustRoot.php b/Auth/OpenID/TrustRoot.php
new file mode 100644
index 0000000..4919a60
--- /dev/null
+++ b/Auth/OpenID/TrustRoot.php
@@ -0,0 +1,462 @@
+<?php
+/**
+ * Functions for dealing with OpenID trust roots
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID/Discover.php';
+
+/**
+ * A regular expression that matches a domain ending in a top-level domains.
+ * Used in checking trust roots for sanity.
+ *
+ * @access private
+ */
+define('Auth_OpenID___TLDs',
+ '/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
+ '|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
+ '|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
+ '|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
+ '|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
+ '|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
+ '|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
+ '|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
+ '|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
+ '|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
+ '|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
+ '|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
+ '|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
+ '|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
+ '|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
+ '|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
+ '|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
+ '|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');
+
+define('Auth_OpenID___HostSegmentRe',
+ "/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");
+
+/**
+ * A wrapper for trust-root related functions
+ */
+class Auth_OpenID_TrustRoot {
+ /*
+ * Return a discovery URL for this realm.
+ *
+ * Return null if the realm could not be parsed or was not valid.
+ *
+ * @param return_to The relying party return URL of the OpenID
+ * authentication request
+ *
+ * @return The URL upon which relying party discovery should be
+ * run in order to verify the return_to URL
+ */
+ function buildDiscoveryURL($realm)
+ {
+ $parsed = Auth_OpenID_TrustRoot::_parse($realm);
+
+ if ($parsed === false) {
+ return false;
+ }
+
+ if ($parsed['wildcard']) {
+ // Use "www." in place of the star
+ if ($parsed['host'][0] != '.') {
+ return false;
+ }
+
+ $www_domain = 'www' . $parsed['host'];
+
+ return sprintf('%s://%s%s', $parsed['scheme'],
+ $www_domain, $parsed['path']);
+ } else {
+ return $parsed['unparsed'];
+ }
+ }
+
+ /**
+ * Parse a URL into its trust_root parts.
+ *
+ * @static
+ *
+ * @access private
+ *
+ * @param string $trust_root The url to parse
+ *
+ * @return mixed $parsed Either an associative array of trust root
+ * parts or false if parsing failed.
+ */
+ function _parse($trust_root)
+ {
+ $trust_root = Auth_OpenID_urinorm($trust_root);
+ if ($trust_root === null) {
+ return false;
+ }
+
+ if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+ return false;
+ }
+
+ $parts = @parse_url($trust_root);
+ if ($parts === false) {
+ return false;
+ }
+
+ $required_parts = array('scheme', 'host');
+ $forbidden_parts = array('user', 'pass', 'fragment');
+ $keys = array_keys($parts);
+ if (array_intersect($keys, $required_parts) != $required_parts) {
+ return false;
+ }
+
+ if (array_intersect($keys, $forbidden_parts) != array()) {
+ return false;
+ }
+
+ if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
+ return false;
+ }
+
+ $scheme = strtolower($parts['scheme']);
+ $allowed_schemes = array('http', 'https');
+ if (!in_array($scheme, $allowed_schemes)) {
+ return false;
+ }
+ $parts['scheme'] = $scheme;
+
+ $host = strtolower($parts['host']);
+ $hostparts = explode('*', $host);
+ switch (count($hostparts)) {
+ case 1:
+ $parts['wildcard'] = false;
+ break;
+ case 2:
+ if ($hostparts[0] ||
+ ($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
+ return false;
+ }
+ $host = $hostparts[1];
+ $parts['wildcard'] = true;
+ break;
+ default:
+ return false;
+ }
+ if (strpos($host, ':') !== false) {
+ return false;
+ }
+
+ $parts['host'] = $host;
+
+ if (isset($parts['path'])) {
+ $path = strtolower($parts['path']);
+ if (substr($path, 0, 1) != '/') {
+ return false;
+ }
+ } else {
+ $path = '/';
+ }
+
+ $parts['path'] = $path;
+ if (!isset($parts['port'])) {
+ $parts['port'] = false;
+ }
+
+
+ $parts['unparsed'] = $trust_root;
+
+ return $parts;
+ }
+
+ /**
+ * Is this trust root sane?
+ *
+ * A trust root is sane if it is syntactically valid and it has a
+ * reasonable domain name. Specifically, the domain name must be
+ * more than one level below a standard TLD or more than two
+ * levels below a two-letter tld.
+ *
+ * For example, '*.com' is not a sane trust root, but '*.foo.com'
+ * is. '*.co.uk' is not sane, but '*.bbc.co.uk' is.
+ *
+ * This check is not always correct, but it attempts to err on the
+ * side of marking sane trust roots insane instead of marking
+ * insane trust roots sane. For example, 'kink.fm' is marked as
+ * insane even though it "should" (for some meaning of should) be
+ * marked sane.
+ *
+ * This function should be used when creating OpenID servers to
+ * alert the users of the server when a consumer attempts to get
+ * the user to accept a suspicious trust root.
+ *
+ * @static
+ * @param string $trust_root The trust root to check
+ * @return bool $sanity Whether the trust root looks OK
+ */
+ function isSane($trust_root)
+ {
+ $parts = Auth_OpenID_TrustRoot::_parse($trust_root);
+ if ($parts === false) {
+ return false;
+ }
+
+ // Localhost is a special case
+ if ($parts['host'] == 'localhost') {
+ return true;
+ }
+
+ $host_parts = explode('.', $parts['host']);
+ if ($parts['wildcard']) {
+ // Remove the empty string from the beginning of the array
+ array_shift($host_parts);
+ }
+
+ if ($host_parts && !$host_parts[count($host_parts) - 1]) {
+ array_pop($host_parts);
+ }
+
+ if (!$host_parts) {
+ return false;
+ }
+
+ // Don't allow adjacent dots
+ if (in_array('', $host_parts, true)) {
+ return false;
+ }
+
+ // Get the top-level domain of the host. If it is not a valid TLD,
+ // it's not sane.
+ preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
+ if (!$matches) {
+ return false;
+ }
+ $tld = $matches[1];
+
+ if (count($host_parts) == 1) {
+ return false;
+ }
+
+ if ($parts['wildcard']) {
+ // It's a 2-letter tld with a short second to last segment
+ // so there needs to be more than two segments specified
+ // (e.g. *.co.uk is insane)
+ $second_level = $host_parts[count($host_parts) - 2];
+ if (strlen($tld) == 2 && strlen($second_level) <= 3) {
+ return count($host_parts) > 2;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Does this URL match the given trust root?
+ *
+ * Return whether the URL falls under the given trust root. This
+ * does not check whether the trust root is sane. If the URL or
+ * trust root do not parse, this function will return false.
+ *
+ * @param string $trust_root The trust root to match against
+ *
+ * @param string $url The URL to check
+ *
+ * @return bool $matches Whether the URL matches against the
+ * trust root
+ */
+ function match($trust_root, $url)
+ {
+ $trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
+ $url_parsed = Auth_OpenID_TrustRoot::_parse($url);
+ if (!$trust_root_parsed || !$url_parsed) {
+ return false;
+ }
+
+ // Check hosts matching
+ if ($url_parsed['wildcard']) {
+ return false;
+ }
+ if ($trust_root_parsed['wildcard']) {
+ $host_tail = $trust_root_parsed['host'];
+ $host = $url_parsed['host'];
+ if ($host_tail &&
+ substr($host, -(strlen($host_tail))) != $host_tail &&
+ substr($host_tail, 1) != $host) {
+ return false;
+ }
+ } else {
+ if ($trust_root_parsed['host'] != $url_parsed['host']) {
+ return false;
+ }
+ }
+
+ // Check path and query matching
+ $base_path = $trust_root_parsed['path'];
+ $path = $url_parsed['path'];
+ if (!isset($trust_root_parsed['query'])) {
+ if ($base_path != $path) {
+ if (substr($path, 0, strlen($base_path)) != $base_path) {
+ return false;
+ }
+ if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
+ substr($path, strlen($base_path), 1) != '/') {
+ return false;
+ }
+ }
+ } else {
+ $base_query = $trust_root_parsed['query'];
+ $query = @$url_parsed['query'];
+ $qplus = substr($query, 0, strlen($base_query) + 1);
+ $bqplus = $base_query . '&';
+ if ($base_path != $path ||
+ ($base_query != $query && $qplus != $bqplus)) {
+ return false;
+ }
+ }
+
+ // The port and scheme need to match exactly
+ return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
+ $url_parsed['port'] === $trust_root_parsed['port']);
+ }
+}
+
+/*
+ * If the endpoint is a relying party OpenID return_to endpoint,
+ * return the endpoint URL. Otherwise, return None.
+ *
+ * This function is intended to be used as a filter for the Yadis
+ * filtering interface.
+ *
+ * @see: C{L{openid.yadis.services}}
+ * @see: C{L{openid.yadis.filters}}
+ *
+ * @param endpoint: An XRDS BasicServiceEndpoint, as returned by
+ * performing Yadis dicovery.
+ *
+ * @returns: The endpoint URL or None if the endpoint is not a
+ * relying party endpoint.
+ */
+function filter_extractReturnURL(&$endpoint)
+{
+ if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
+ return $endpoint;
+ } else {
+ return null;
+ }
+}
+
+function &Auth_OpenID_extractReturnURL(&$endpoint_list)
+{
+ $result = array();
+
+ foreach ($endpoint_list as $endpoint) {
+ if (filter_extractReturnURL($endpoint)) {
+ $result[] = $endpoint;
+ }
+ }
+
+ return $result;
+}
+
+/*
+ * Is the return_to URL under one of the supplied allowed return_to
+ * URLs?
+ */
+function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
+{
+ foreach ($allowed_return_to_urls as $allowed_return_to) {
+ // A return_to pattern works the same as a realm, except that
+ // it's not allowed to use a wildcard. We'll model this by
+ // parsing it as a realm, and not trying to match it if it has
+ // a wildcard.
+
+ $return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
+ if (// Parses as a trust root
+ ($return_realm !== false) &&
+ // Does not have a wildcard
+ (!$return_realm['wildcard']) &&
+ // Matches the return_to that we passed in with it
+ (Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
+ return true;
+ }
+ }
+
+ // No URL in the list matched
+ return false;
+}
+
+/*
+ * Given a relying party discovery URL return a list of return_to
+ * URLs.
+ */
+function Auth_OpenID_getAllowedReturnURLs($relying_party_url, &$fetcher,
+ $discover_function=null)
+{
+ if ($discover_function === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'fromXRDS');
+
+ list($rp_url_after_redirects, $endpoints) =
+ Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
+ $discover_function, $fetcher);
+
+ if ($rp_url_after_redirects != $relying_party_url) {
+ // Verification caused a redirect
+ return false;
+ }
+
+ call_user_func_array($discover_function,
+ array($relying_party_url, $fetcher));
+
+ $return_to_urls = array();
+ $matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);
+
+ foreach ($matching_endpoints as $e) {
+ $return_to_urls[] = $e->server_url;
+ }
+
+ return $return_to_urls;
+}
+
+/*
+ * Verify that a return_to URL is valid for the given realm.
+ *
+ * This function builds a discovery URL, performs Yadis discovery on
+ * it, makes sure that the URL does not redirect, parses out the
+ * return_to URLs, and finally checks to see if the current return_to
+ * URL matches the return_to.
+ *
+ * @return true if the return_to URL is valid for the realm
+ */
+function Auth_OpenID_verifyReturnTo($realm_str, $return_to, &$fetcher,
+ $_vrfy='Auth_OpenID_getAllowedReturnURLs')
+{
+ $disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);
+
+ if ($disco_url === false) {
+ return false;
+ }
+
+ $allowable_urls = call_user_func_array($_vrfy,
+ array($disco_url, &$fetcher));
+
+ // The realm_str could not be parsed.
+ if ($allowable_urls === false) {
+ return false;
+ }
+
+ if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/URINorm.php b/Auth/OpenID/URINorm.php
new file mode 100644
index 0000000..f821d83
--- /dev/null
+++ b/Auth/OpenID/URINorm.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * URI normalization routines.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/Yadis/Misc.php';
+
+// from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt)
+function Auth_OpenID_getURIPattern()
+{
+ return '&^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?&';
+}
+
+function Auth_OpenID_getAuthorityPattern()
+{
+ return '/^([^@]*@)?([^:]*)(:.*)?/';
+}
+
+function Auth_OpenID_getEncodedPattern()
+{
+ return '/%([0-9A-Fa-f]{2})/';
+}
+
+# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+#
+# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+# / "*" / "+" / "," / ";" / "="
+#
+# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+function Auth_OpenID_getURLIllegalCharRE()
+{
+ return "/([^-A-Za-z0-9:\/\?#\[\]@\!\$&'\(\)\*\+,;=\._~\%])/";
+}
+
+function Auth_OpenID_getUnreserved()
+{
+ $_unreserved = array();
+ for ($i = 0; $i < 256; $i++) {
+ $_unreserved[$i] = false;
+ }
+
+ for ($i = ord('A'); $i <= ord('Z'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ for ($i = ord('0'); $i <= ord('9'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ for ($i = ord('a'); $i <= ord('z'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ $_unreserved[ord('-')] = true;
+ $_unreserved[ord('.')] = true;
+ $_unreserved[ord('_')] = true;
+ $_unreserved[ord('~')] = true;
+
+ return $_unreserved;
+}
+
+function Auth_OpenID_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Auth_Yadis_getUCSChars(),
+ Auth_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('[%s]', implode('', $parts));
+}
+
+function Auth_OpenID_pct_encoded_replace_unreserved($mo)
+{
+ $_unreserved = Auth_OpenID_getUnreserved();
+
+ $i = intval($mo[1], 16);
+ if ($_unreserved[$i]) {
+ return chr($i);
+ } else {
+ return strtoupper($mo[0]);
+ }
+
+ return $mo[0];
+}
+
+function Auth_OpenID_pct_encoded_replace($mo)
+{
+ return chr(intval($mo[1], 16));
+}
+
+function Auth_OpenID_remove_dot_segments($path)
+{
+ $result_segments = array();
+
+ while ($path) {
+ if (Auth_Yadis_startswith($path, '../')) {
+ $path = substr($path, 3);
+ } else if (Auth_Yadis_startswith($path, './')) {
+ $path = substr($path, 2);
+ } else if (Auth_Yadis_startswith($path, '/./')) {
+ $path = substr($path, 2);
+ } else if ($path == '/.') {
+ $path = '/';
+ } else if (Auth_Yadis_startswith($path, '/../')) {
+ $path = substr($path, 3);
+ if ($result_segments) {
+ array_pop($result_segments);
+ }
+ } else if ($path == '/..') {
+ $path = '/';
+ if ($result_segments) {
+ array_pop($result_segments);
+ }
+ } else if (($path == '..') ||
+ ($path == '.')) {
+ $path = '';
+ } else {
+ $i = 0;
+ if ($path[0] == '/') {
+ $i = 1;
+ }
+ $i = strpos($path, '/', $i);
+ if ($i === false) {
+ $i = strlen($path);
+ }
+ $result_segments[] = substr($path, 0, $i);
+ $path = substr($path, $i);
+ }
+ }
+
+ return implode('', $result_segments);
+}
+
+function Auth_OpenID_urinorm($uri)
+{
+ $uri_matches = array();
+ preg_match(Auth_OpenID_getURIPattern(), $uri, $uri_matches);
+
+ if (count($uri_matches) < 9) {
+ for ($i = count($uri_matches); $i <= 9; $i++) {
+ $uri_matches[] = '';
+ }
+ }
+
+ $illegal_matches = array();
+ preg_match(Auth_OpenID_getURLIllegalCharRE(),
+ $uri, $illegal_matches);
+ if ($illegal_matches) {
+ return null;
+ }
+
+ $scheme = $uri_matches[2];
+ if ($scheme) {
+ $scheme = strtolower($scheme);
+ }
+
+ $scheme = $uri_matches[2];
+ if ($scheme === '') {
+ // No scheme specified
+ return null;
+ }
+
+ $scheme = strtolower($scheme);
+ if (!in_array($scheme, array('http', 'https'))) {
+ // Not an absolute HTTP or HTTPS URI
+ return null;
+ }
+
+ $authority = $uri_matches[4];
+ if ($authority === '') {
+ // Not an absolute URI
+ return null;
+ }
+
+ $authority_matches = array();
+ preg_match(Auth_OpenID_getAuthorityPattern(),
+ $authority, $authority_matches);
+ if (count($authority_matches) === 0) {
+ // URI does not have a valid authority
+ return null;
+ }
+
+ if (count($authority_matches) < 4) {
+ for ($i = count($authority_matches); $i <= 4; $i++) {
+ $authority_matches[] = '';
+ }
+ }
+
+ list($_whole, $userinfo, $host, $port) = $authority_matches;
+
+ if ($userinfo === null) {
+ $userinfo = '';
+ }
+
+ if (strpos($host, '%') !== -1) {
+ $host = strtolower($host);
+ $host = preg_replace_callback(
+ Auth_OpenID_getEncodedPattern(),
+ 'Auth_OpenID_pct_encoded_replace', $host);
+ // NO IDNA.
+ // $host = unicode($host, 'utf-8').encode('idna');
+ } else {
+ $host = strtolower($host);
+ }
+
+ if ($port) {
+ if (($port == ':') ||
+ ($scheme == 'http' && $port == ':80') ||
+ ($scheme == 'https' && $port == ':443')) {
+ $port = '';
+ }
+ } else {
+ $port = '';
+ }
+
+ $authority = $userinfo . $host . $port;
+
+ $path = $uri_matches[5];
+ $path = preg_replace_callback(
+ Auth_OpenID_getEncodedPattern(),
+ 'Auth_OpenID_pct_encoded_replace_unreserved', $path);
+
+ $path = Auth_OpenID_remove_dot_segments($path);
+ if (!$path) {
+ $path = '/';
+ }
+
+ $query = $uri_matches[6];
+ if ($query === null) {
+ $query = '';
+ }
+
+ $fragment = $uri_matches[8];
+ if ($fragment === null) {
+ $fragment = '';
+ }
+
+ return $scheme . '://' . $authority . $path . $query . $fragment;
+}
+
+?>
diff --git a/Auth/Yadis/HTTPFetcher.php b/Auth/Yadis/HTTPFetcher.php
new file mode 100644
index 0000000..963b9a4
--- /dev/null
+++ b/Auth/Yadis/HTTPFetcher.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require logging functionality
+ */
+require_once "Auth/OpenID.php";
+
+define('Auth_OpenID_FETCHER_MAX_RESPONSE_KB', 1024);
+define('Auth_OpenID_USER_AGENT',
+ 'php-openid/'.Auth_OpenID_VERSION.' (php/'.phpversion().')');
+
+class Auth_Yadis_HTTPResponse {
+ function Auth_Yadis_HTTPResponse($final_url = null, $status = null,
+ $headers = null, $body = null)
+ {
+ $this->final_url = $final_url;
+ $this->status = $status;
+ $this->headers = $headers;
+ $this->body = $body;
+ }
+}
+
+/**
+ * This class is the interface for HTTP fetchers the Yadis library
+ * uses. This interface is only important if you need to write a new
+ * fetcher for some reason.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_Yadis_HTTPFetcher {
+
+ var $timeout = 20; // timeout in seconds.
+
+ /**
+ * Return whether a URL can be fetched. Returns false if the URL
+ * scheme is not allowed or is not supported by this fetcher
+ * implementation; returns true otherwise.
+ *
+ * @return bool
+ */
+ function canFetchURL($url)
+ {
+ if ($this->isHTTPS($url) && !$this->supportsSSL()) {
+ Auth_OpenID::log("HTTPS URL unsupported fetching %s",
+ $url);
+ return false;
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("URL fetching not allowed for '%s'",
+ $url);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether a URL should be allowed. Override this method to
+ * conform to your local policy.
+ *
+ * By default, will attempt to fetch any http or https URL.
+ */
+ function allowedURL($url)
+ {
+ return $this->URLHasAllowedScheme($url);
+ }
+
+ /**
+ * Does this fetcher implementation (and runtime) support fetching
+ * HTTPS URLs? May inspect the runtime environment.
+ *
+ * @return bool $support True if this fetcher supports HTTPS
+ * fetching; false if not.
+ */
+ function supportsSSL()
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Is this an https URL?
+ *
+ * @access private
+ */
+ function isHTTPS($url)
+ {
+ return (bool)preg_match('/^https:\/\//i', $url);
+ }
+
+ /**
+ * Is this an http or https URL?
+ *
+ * @access private
+ */
+ function URLHasAllowedScheme($url)
+ {
+ return (bool)preg_match('/^https?:\/\//i', $url);
+ }
+
+ /**
+ * @access private
+ */
+ function _findRedirect($headers)
+ {
+ foreach ($headers as $line) {
+ if (strpos(strtolower($line), "location: ") === 0) {
+ $parts = explode(" ", $line, 2);
+ return $parts[1];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fetches the specified URL using optional extra headers and
+ * returns the server's response.
+ *
+ * @param string $url The URL to be fetched.
+ * @param array $extra_headers An array of header strings
+ * (e.g. "Accept: text/html").
+ * @return mixed $result An array of ($code, $url, $headers,
+ * $body) if the URL could be fetched; null if the URL does not
+ * pass the URLHasAllowedScheme check or if the server's response
+ * is malformed.
+ */
+ function get($url, $headers = null)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/Manager.php b/Auth/Yadis/Manager.php
new file mode 100644
index 0000000..d50cf7a
--- /dev/null
+++ b/Auth/Yadis/Manager.php
@@ -0,0 +1,529 @@
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base session class used by the Auth_Yadis_Manager. This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_PHPSession {
+ /**
+ * Set a session key/value pair.
+ *
+ * @param string $name The name of the session key to add.
+ * @param string $value The value to add to the session.
+ */
+ function set($name, $value)
+ {
+ $_SESSION[$name] = $value;
+ }
+
+ /**
+ * Get a key's value from the session.
+ *
+ * @param string $name The name of the key to retrieve.
+ * @param string $default The optional value to return if the key
+ * is not found in the session.
+ * @return string $result The key's value in the session or
+ * $default if it isn't found.
+ */
+ function get($name, $default=null)
+ {
+ if (array_key_exists($name, $_SESSION)) {
+ return $_SESSION[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Remove a key/value pair from the session.
+ *
+ * @param string $name The name of the key to remove.
+ */
+ function del($name)
+ {
+ unset($_SESSION[$name]);
+ }
+
+ /**
+ * Return the contents of the session in array form.
+ */
+ function contents()
+ {
+ return $_SESSION;
+ }
+}
+
+/**
+ * A session helper class designed to translate between arrays and
+ * objects. Note that the class used must have a constructor that
+ * takes no parameters. This is not a general solution, but it works
+ * for dumb objects that just need to have attributes set. The idea
+ * is that you'll subclass this and override $this->check($data) ->
+ * bool to implement your own session data validation.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_SessionLoader {
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function check($data)
+ {
+ return true;
+ }
+
+ /**
+ * Given a session data value (an array), this creates an object
+ * (returned by $this->newObject()) whose attributes and values
+ * are those in $data. Returns null if $data lacks keys found in
+ * $this->requiredKeys(). Returns null if $this->check($data)
+ * evaluates to false. Returns null if $this->newObject()
+ * evaluates to false.
+ *
+ * @access private
+ */
+ function fromSession($data)
+ {
+ if (!$data) {
+ return null;
+ }
+
+ $required = $this->requiredKeys();
+
+ foreach ($required as $k) {
+ if (!array_key_exists($k, $data)) {
+ return null;
+ }
+ }
+
+ if (!$this->check($data)) {
+ return null;
+ }
+
+ $data = array_merge($data, $this->prepareForLoad($data));
+ $obj = $this->newObject($data);
+
+ if (!$obj) {
+ return null;
+ }
+
+ foreach ($required as $k) {
+ $obj->$k = $data[$k];
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Prepares the data array by making any necessary changes.
+ * Returns an array whose keys and values will be used to update
+ * the original data array before calling $this->newObject($data).
+ *
+ * @access private
+ */
+ function prepareForLoad($data)
+ {
+ return array();
+ }
+
+ /**
+ * Returns a new instance of this loader's class, using the
+ * session data to construct it if necessary. The object need
+ * only be created; $this->fromSession() will take care of setting
+ * the object's attributes.
+ *
+ * @access private
+ */
+ function newObject($data)
+ {
+ return null;
+ }
+
+ /**
+ * Returns an array of keys and values built from the attributes
+ * of $obj. If $this->prepareForSave($obj) returns an array, its keys
+ * and values are used to update the $data array of attributes
+ * from $obj.
+ *
+ * @access private
+ */
+ function toSession($obj)
+ {
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[$k] = $v;
+ }
+
+ $extra = $this->prepareForSave($obj);
+
+ if ($extra && is_array($extra)) {
+ foreach ($extra as $k => $v) {
+ $data[$k] = $v;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function prepareForSave($obj)
+ {
+ return array();
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
+ function newObject($data)
+ {
+ return new Auth_OpenID_ServiceEndpoint();
+ }
+
+ function requiredKeys()
+ {
+ $obj = new Auth_OpenID_ServiceEndpoint();
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[] = $k;
+ }
+ return $data;
+ }
+
+ function check($data)
+ {
+ return is_array($data['type_uris']);
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_Yadis_Managers.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
+ function requiredKeys()
+ {
+ return array('starting_url',
+ 'yadis_url',
+ 'services',
+ 'session_key',
+ '_current',
+ 'stale');
+ }
+
+ function newObject($data)
+ {
+ return new Auth_Yadis_Manager($data['starting_url'],
+ $data['yadis_url'],
+ $data['services'],
+ $data['session_key']);
+ }
+
+ function check($data)
+ {
+ return is_array($data['services']);
+ }
+
+ function prepareForLoad($data)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($data['services'] as $s) {
+ $services[] = $loader->fromSession($s);
+ }
+ return array('services' => $services);
+ }
+
+ function prepareForSave($obj)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($obj->services as $s) {
+ $services[] = $loader->toSession($s);
+ }
+ return array('services' => $services);
+ }
+}
+
+/**
+ * The Yadis service manager which stores state in a session and
+ * iterates over <Service> elements in a Yadis XRDS document and lets
+ * a caller attempt to use each one. This is used by the Yadis
+ * library internally.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Manager {
+
+ /**
+ * Intialize a new yadis service manager.
+ *
+ * @access private
+ */
+ function Auth_Yadis_Manager($starting_url, $yadis_url,
+ $services, $session_key)
+ {
+ // The URL that was used to initiate the Yadis protocol
+ $this->starting_url = $starting_url;
+
+ // The URL after following redirects (the identifier)
+ $this->yadis_url = $yadis_url;
+
+ // List of service elements
+ $this->services = $services;
+
+ $this->session_key = $session_key;
+
+ // Reference to the current service object
+ $this->_current = null;
+
+ // Stale flag for cleanup if PHP lib has trouble.
+ $this->stale = false;
+ }
+
+ /**
+ * @access private
+ */
+ function length()
+ {
+ // How many untried services remain?
+ return count($this->services);
+ }
+
+ /**
+ * Return the next service
+ *
+ * $this->current() will continue to return that service until the
+ * next call to this method.
+ */
+ function nextService()
+ {
+
+ if ($this->services) {
+ $this->_current = array_shift($this->services);
+ } else {
+ $this->_current = null;
+ }
+
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function current()
+ {
+ // Return the current service.
+ // Returns None if there are no services left.
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function forURL($url)
+ {
+ return in_array($url, array($this->starting_url, $this->yadis_url));
+ }
+
+ /**
+ * @access private
+ */
+ function started()
+ {
+ // Has the first service been returned?
+ return $this->_current !== null;
+ }
+}
+
+/**
+ * State management for discovery.
+ *
+ * High-level usage pattern is to call .getNextService(discover) in
+ * order to find the next available service for this user for this
+ * session. Once a request completes, call .cleanup() to clean up the
+ * session state.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Discovery {
+
+ /**
+ * @access private
+ */
+ var $DEFAULT_SUFFIX = 'auth';
+
+ /**
+ * @access private
+ */
+ var $PREFIX = '_yadis_services_';
+
+ /**
+ * Initialize a discovery object.
+ *
+ * @param Auth_Yadis_PHPSession $session An object which
+ * implements the Auth_Yadis_PHPSession API.
+ * @param string $url The URL on which to attempt discovery.
+ * @param string $session_key_suffix The optional session key
+ * suffix override.
+ */
+ function Auth_Yadis_Discovery(&$session, $url,
+ $session_key_suffix = null)
+ {
+ /// Initialize a discovery object
+ $this->session =& $session;
+ $this->url = $url;
+ if ($session_key_suffix === null) {
+ $session_key_suffix = $this->DEFAULT_SUFFIX;
+ }
+
+ $this->session_key_suffix = $session_key_suffix;
+ $this->session_key = $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * Return the next authentication service for the pair of
+ * user_input and session. This function handles fallback.
+ */
+ function getNextService($discover_cb, &$fetcher)
+ {
+ $manager = $this->getManager();
+ if (!$manager || (!$manager->services)) {
+ $this->destroyManager();
+
+ list($yadis_url, $services) = call_user_func($discover_cb,
+ $this->url,
+ $fetcher);
+
+ $manager = $this->createManager($services, $yadis_url);
+ }
+
+ if ($manager) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $service = $manager->nextService();
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($manager)));
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * Clean up Yadis-related services in the session and return the
+ * most-recently-attempted service from the manager, if one
+ * exists.
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function cleanup($force=false)
+ {
+ $manager = $this->getManager($force);
+ if ($manager) {
+ $service = $manager->current();
+ $this->destroyManager($force);
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * @access private
+ */
+ function getSessionKey()
+ {
+ // Get the session key for this starting URL and suffix
+ return $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * @access private
+ *
+ * @param $force True if the manager should be returned regardless
+ * of whether it's a manager for $this->url.
+ */
+ function &getManager($force=false)
+ {
+ // Extract the YadisServiceManager for this object's URL and
+ // suffix from the session.
+
+ $manager_str = $this->session->get($this->getSessionKey());
+ $manager = null;
+
+ if ($manager_str !== null) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = $loader->fromSession(unserialize($manager_str));
+ }
+
+ if ($manager && ($manager->forURL($this->url) || $force)) {
+ return $manager;
+ } else {
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function &createManager($services, $yadis_url = null)
+ {
+ $key = $this->getSessionKey();
+ if ($this->getManager()) {
+ return $this->getManager();
+ }
+
+ if ($services) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
+ $services, $key);
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($manager)));
+ return $manager;
+ } else {
+ // Oh, PHP.
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function destroyManager($force=false)
+ {
+ if ($this->getManager($force) !== null) {
+ $key = $this->getSessionKey();
+ $this->session->del($key);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/Misc.php b/Auth/Yadis/Misc.php
new file mode 100644
index 0000000..1134a4f
--- /dev/null
+++ b/Auth/Yadis/Misc.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Miscellaneous utility values and functions for OpenID and Yadis.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+function Auth_Yadis_getUCSChars()
+{
+ return array(
+ array(0xA0, 0xD7FF),
+ array(0xF900, 0xFDCF),
+ array(0xFDF0, 0xFFEF),
+ array(0x10000, 0x1FFFD),
+ array(0x20000, 0x2FFFD),
+ array(0x30000, 0x3FFFD),
+ array(0x40000, 0x4FFFD),
+ array(0x50000, 0x5FFFD),
+ array(0x60000, 0x6FFFD),
+ array(0x70000, 0x7FFFD),
+ array(0x80000, 0x8FFFD),
+ array(0x90000, 0x9FFFD),
+ array(0xA0000, 0xAFFFD),
+ array(0xB0000, 0xBFFFD),
+ array(0xC0000, 0xCFFFD),
+ array(0xD0000, 0xDFFFD),
+ array(0xE1000, 0xEFFFD)
+ );
+}
+
+function Auth_Yadis_getIPrivateChars()
+{
+ return array(
+ array(0xE000, 0xF8FF),
+ array(0xF0000, 0xFFFFD),
+ array(0x100000, 0x10FFFD)
+ );
+}
+
+function Auth_Yadis_pct_escape_unicode($char_match)
+{
+ $c = $char_match[0];
+ $result = "";
+ for ($i = 0; $i < strlen($c); $i++) {
+ $result .= "%".sprintf("%X", ord($c[$i]));
+ }
+ return $result;
+}
+
+function Auth_Yadis_startswith($s, $stuff)
+{
+ return strpos($s, $stuff) === 0;
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/ParanoidHTTPFetcher.php b/Auth/Yadis/ParanoidHTTPFetcher.php
new file mode 100644
index 0000000..6a41826
--- /dev/null
+++ b/Auth/Yadis/ParanoidHTTPFetcher.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * This module contains the CURL-based HTTP fetcher implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+require_once "Auth/OpenID.php";
+
+/**
+ * A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
+ * for fetching.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ function Auth_Yadis_ParanoidHTTPFetcher()
+ {
+ $this->reset();
+ }
+
+ function reset()
+ {
+ $this->headers = array();
+ $this->data = "";
+ }
+
+ /**
+ * @access private
+ */
+ function _writeHeader($ch, $header)
+ {
+ array_push($this->headers, rtrim($header));
+ return strlen($header);
+ }
+
+ /**
+ * @access private
+ */
+ function _writeData($ch, $data)
+ {
+ if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
+ return 0;
+ } else {
+ $this->data .= $data;
+ return strlen($data);
+ }
+ }
+
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ $v = curl_version();
+ if(is_array($v)) {
+ return in_array('https', $v['protocols']);
+ } elseif (is_string($v)) {
+ return preg_match('/OpenSSL/i', $v);
+ } else {
+ return 0;
+ }
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ $redir = true;
+
+ while ($redir && ($off > 0)) {
+ $this->reset();
+
+ $c = curl_init();
+
+ if ($c === false) {
+ Auth_OpenID::log(
+ "curl_init returned false; could not " .
+ "initialize for URL '%s'", $url);
+ return null;
+ }
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("Fetching URL not allowed: %s",
+ $url);
+ return null;
+ }
+
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+ curl_setopt($c, CURLOPT_HEADERFUNCTION,
+ array(&$this, "_writeHeader"));
+
+ if ($extra_headers) {
+ curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
+ }
+
+ $cv = curl_version();
+ if(is_array($cv)) {
+ $curl_user_agent = 'curl/'.$cv['version'];
+ } else {
+ $curl_user_agent = $cv;
+ }
+ curl_setopt($c, CURLOPT_USERAGENT,
+ Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
+ curl_setopt($c, CURLOPT_TIMEOUT, $off);
+ curl_setopt($c, CURLOPT_URL, $url);
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+ $body = $this->data;
+ $headers = $this->headers;
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ Auth_OpenID::log("CURL error (%s): %s",
+ curl_errno($c), curl_error($c));
+ return null;
+ }
+
+ if (in_array($code, array(301, 302, 303, 307))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ curl_close($c);
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+ }
+
+ Auth_OpenID::log(
+ "Successfully fetched '%s': GET response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+
+ $off = $stop - time();
+ }
+
+ return null;
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $this->reset();
+
+ $c = curl_init();
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ curl_setopt($c, CURLOPT_POST, true);
+ curl_setopt($c, CURLOPT_POSTFIELDS, $body);
+ curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ return null;
+ }
+
+ $body = $this->data;
+
+ curl_close($c);
+
+ $new_headers = $extra_headers;
+
+ foreach ($this->headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ Auth_OpenID::log("Successfully fetched '%s': POST response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/ParseHTML.php b/Auth/Yadis/ParseHTML.php
new file mode 100644
index 0000000..297ccbd
--- /dev/null
+++ b/Auth/Yadis/ParseHTML.php
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * This is the HTML pseudo-parser for the Yadis library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * This class is responsible for scanning an HTML string to find META
+ * tags and their attributes. This is used by the Yadis discovery
+ * process. This class must be instantiated to be used.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ParseHTML {
+
+ /**
+ * @access private
+ */
+ var $_re_flags = "si";
+
+ /**
+ * @access private
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * @access private
+ */
+ var $_tag_expr = "<%s%s(?:\s.*?)?%s>";
+
+ /**
+ * @access private
+ */
+ var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\/\s>]';
+
+ function Auth_Yadis_ParseHTML()
+ {
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $this->_re_flags);
+
+ $this->_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+ $this->_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $this->_entity_replacements));
+ }
+
+ /**
+ * Replace HTML entities (amp, lt, gt, and quot) as well as
+ * numeric entities (e.g. #x9f;) with their actual values and
+ * return the new string.
+ *
+ * @access private
+ * @param string $str The string in which to look for entities
+ * @return string $new_str The new string entities decoded
+ */
+ function replaceEntities($str)
+ {
+ foreach ($this->_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+
+ // Replace numeric entities because html_entity_decode doesn't
+ // do it for us.
+ $str = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $str);
+ $str = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $str);
+
+ return $str;
+ }
+
+ /**
+ * Strip single and double quotes off of a string, if they are
+ * present.
+ *
+ * @access private
+ * @param string $str The original string
+ * @return string $new_str The new string with leading and
+ * trailing quotes removed
+ */
+ function removeQuotes($str)
+ {
+ $matches = array();
+ $double = '/^"(.*)"$/';
+ $single = "/^\'(.*)\'$/";
+
+ if (preg_match($double, $str, $matches)) {
+ return $matches[1];
+ } else if (preg_match($single, $str, $matches)) {
+ return $matches[1];
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * Create a regular expression that will match an opening
+ * or closing tag from a set of names.
+ *
+ * @access private
+ * @param mixed $tag_names Tag names to match
+ * @param mixed $close false/0 = no, true/1 = yes, other = maybe
+ * @param mixed $self_close false/0 = no, true/1 = yes, other = maybe
+ * @return string $regex A regular expression string to be used
+ * in, say, preg_match.
+ */
+ function tagPattern($tag_names, $close, $self_close)
+ {
+ if (is_array($tag_names)) {
+ $tag_names = '(?:'.implode('|',$tag_names).')';
+ }
+ if ($close) {
+ $close = '\/' . (($close == 1)? '' : '?');
+ } else {
+ $close = '';
+ }
+ if ($self_close) {
+ $self_close = '(?:\/\s*)' . (($self_close == 1)? '' : '?');
+ } else {
+ $self_close = '';
+ }
+ $expr = sprintf($this->_tag_expr, $close, $tag_names, $self_close);
+
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ /**
+ * Given an HTML document string, this finds all the META tags in
+ * the document, provided they are found in the
+ * <HTML><HEAD>...</HEAD> section of the document. The <HTML> tag
+ * may be missing.
+ *
+ * @access private
+ * @param string $html_string An HTMl document string
+ * @return array $tag_list Array of tags; each tag is an array of
+ * attribute -> value.
+ */
+ function getMetaTags($html_string)
+ {
+ $html_string = preg_replace($this->_removed_re,
+ "",
+ $html_string);
+
+ $key_tags = array($this->tagPattern('html', false, false),
+ $this->tagPattern('head', false, false),
+ $this->tagPattern('head', true, false),
+ $this->tagPattern('html', true, false),
+ $this->tagPattern(array(
+ 'body', 'frameset', 'frame', 'p', 'div',
+ 'table','span','a'), 'maybe', 'maybe'));
+ $key_tags_pos = array();
+ foreach ($key_tags as $pat) {
+ $matches = array();
+ preg_match($pat, $html_string, $matches, PREG_OFFSET_CAPTURE);
+ if($matches) {
+ $key_tags_pos[] = $matches[0][1];
+ } else {
+ $key_tags_pos[] = null;
+ }
+ }
+ // no opening head tag
+ if (is_null($key_tags_pos[1])) {
+ return array();
+ }
+ // the effective </head> is the min of the following
+ if (is_null($key_tags_pos[2])) {
+ $key_tags_pos[2] = strlen($html_string);
+ }
+ foreach (array($key_tags_pos[3], $key_tags_pos[4]) as $pos) {
+ if (!is_null($pos) && $pos < $key_tags_pos[2]) {
+ $key_tags_pos[2] = $pos;
+ }
+ }
+ // closing head tag comes before opening head tag
+ if ($key_tags_pos[1] > $key_tags_pos[2]) {
+ return array();
+ }
+ // if there is an opening html tag, make sure the opening head tag
+ // comes after it
+ if (!is_null($key_tags_pos[0]) && $key_tags_pos[1] < $key_tags_pos[0]) {
+ return array();
+ }
+ $html_string = substr($html_string, $key_tags_pos[1],
+ ($key_tags_pos[2]-$key_tags_pos[1]));
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->tagPattern('meta', false, 'maybe'),
+ $html_string, $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($this->_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = $this->replaceEntities(
+ $this->removeQuotes($attr_matches[2][$index]));
+
+ $link_attrs[strtolower($name)] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+ }
+
+ /**
+ * Looks for a META tag with an "http-equiv" attribute whose value
+ * is one of ("x-xrds-location", "x-yadis-location"), ignoring
+ * case. If such a META tag is found, its "content" attribute
+ * value is returned.
+ *
+ * @param string $html_string An HTML document in string format
+ * @return mixed $content The "content" attribute value of the
+ * META tag, if found, or null if no such tag was found.
+ */
+ function getHTTPEquiv($html_string)
+ {
+ $meta_tags = $this->getMetaTags($html_string);
+
+ if ($meta_tags) {
+ foreach ($meta_tags as $tag) {
+ if (array_key_exists('http-equiv', $tag) &&
+ (in_array(strtolower($tag['http-equiv']),
+ array('x-xrds-location', 'x-yadis-location'))) &&
+ array_key_exists('content', $tag)) {
+ return $tag['content'];
+ }
+ }
+ }
+
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/PlainHTTPFetcher.php b/Auth/Yadis/PlainHTTPFetcher.php
new file mode 100644
index 0000000..3e0ca2b
--- /dev/null
+++ b/Auth/Yadis/PlainHTTPFetcher.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * This module contains the plain non-curl HTTP fetcher
+ * implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+/**
+ * This class implements a plain, hand-built socket-based fetcher
+ * which will be used in the event that CURL is unavailable.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ return function_exists('openssl_open');
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $redir = true;
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ while ($redir && ($off > 0)) {
+
+ $parts = parse_url($url);
+
+ $specify_port = true;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ $specify_port = false;
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if (!array_key_exists('path', $parts)) {
+ $parts['path'] = '/';
+ }
+
+ $host = $parts['host'];
+
+ if ($parts['scheme'] == 'https') {
+ $host = 'ssl://' . $host;
+ }
+
+ $user_agent = Auth_OpenID_USER_AGENT;
+
+ $headers = array(
+ "GET ".$parts['path'].
+ (array_key_exists('query', $parts) ?
+ "?".$parts['query'] : "").
+ " HTTP/1.0",
+ "User-Agent: $user_agent",
+ "Host: ".$parts['host'].
+ ($specify_port ? ":".$parts['port'] : ""),
+ "Port: ".$parts['port']);
+
+ $errno = 0;
+ $errstr = '';
+
+ if ($extra_headers) {
+ foreach ($extra_headers as $h) {
+ $headers[] = $h;
+ }
+ }
+
+ @$sock = fsockopen($host, $parts['port'], $errno, $errstr,
+ $this->timeout);
+ if ($sock === false) {
+ return false;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
+
+ $data = "";
+ $kilobytes = 0;
+ while (!feof($sock) &&
+ $kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
+ $data .= fgets($sock, 1024);
+ $kilobytes += 1;
+ }
+
+ fclose($sock);
+
+ // Split response into header and body sections
+ list($headers, $body) = explode("\r\n\r\n", $data, 2);
+ $headers = explode("\r\n", $headers);
+
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ if (in_array($code, array('301', '302'))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ }
+
+ $off = $stop - time();
+ }
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ $parts = explode(": ", $header, 2);
+
+ if (count($parts) == 2) {
+ list($name, $value) = $parts;
+ $new_headers[$name] = $value;
+ }
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code, $new_headers, $body);
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $parts = parse_url($url);
+
+ $headers = array();
+
+ $post_path = $parts['path'];
+ if (isset($parts['query'])) {
+ $post_path .= '?' . $parts['query'];
+ }
+
+ $headers[] = "POST ".$post_path." HTTP/1.0";
+ $headers[] = "Host: " . $parts['host'];
+ $headers[] = "Content-type: application/x-www-form-urlencoded";
+ $headers[] = "Content-length: " . strval(strlen($body));
+
+ if ($extra_headers &&
+ is_array($extra_headers)) {
+ $headers = array_merge($headers, $extra_headers);
+ }
+
+ // Join all headers together.
+ $all_headers = implode("\r\n", $headers);
+
+ // Add headers, two newlines, and request body.
+ $request = $all_headers . "\r\n\r\n" . $body;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if ($parts['scheme'] == 'https') {
+ $parts['host'] = sprintf("ssl://%s", $parts['host']);
+ }
+
+ // Connect to the remote server.
+ $errno = 0;
+ $errstr = '';
+
+ $sock = fsockopen($parts['host'], $parts['port'], $errno, $errstr,
+ $this->timeout);
+
+ if ($sock === false) {
+ return null;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ // Write the POST request.
+ fputs($sock, $request);
+
+ // Get the response from the server.
+ $response = "";
+ while (!feof($sock)) {
+ if ($data = fgets($sock, 128)) {
+ $response .= $data;
+ } else {
+ break;
+ }
+ }
+
+ // Split the request into headers and body.
+ list($headers, $response_body) = explode("\r\n\r\n", $response, 2);
+
+ $headers = explode("\r\n", $headers);
+
+ // Expect the first line of the headers data to be something
+ // like HTTP/1.1 200 OK. Split the line on spaces and take
+ // the second token, which should be the return code.
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $response_body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/XML.php b/Auth/Yadis/XML.php
new file mode 100644
index 0000000..81b2ce2
--- /dev/null
+++ b/Auth/Yadis/XML.php
@@ -0,0 +1,374 @@
+<?php
+
+/**
+ * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
+ * and 5, respectively.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base class for wrappers for available PHP XML-parsing
+ * extensions. To work with this Yadis library, subclasses of this
+ * class MUST implement the API as defined in the remarks for this
+ * class. Subclasses of Auth_Yadis_XMLParser are used to wrap
+ * particular PHP XML extensions such as 'domxml'. These are used
+ * internally by the library depending on the availability of
+ * supported PHP XML extensions.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XMLParser {
+ /**
+ * Initialize an instance of Auth_Yadis_XMLParser with some
+ * XML and namespaces. This SHOULD NOT be overridden by
+ * subclasses.
+ *
+ * @param string $xml_string A string of XML to be parsed.
+ * @param array $namespace_map An array of ($ns_name => $ns_uri)
+ * to be registered with the XML parser. May be empty.
+ * @return boolean $result True if the initialization and
+ * namespace registration(s) succeeded; false otherwise.
+ */
+ function init($xml_string, $namespace_map)
+ {
+ if (!$this->setXML($xml_string)) {
+ return false;
+ }
+
+ foreach ($namespace_map as $prefix => $uri) {
+ if (!$this->registerNamespace($prefix, $uri)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Register a namespace with the XML parser. This should be
+ * overridden by subclasses.
+ *
+ * @param string $prefix The namespace prefix to appear in XML tag
+ * names.
+ *
+ * @param string $uri The namespace URI to be used to identify the
+ * namespace in the XML.
+ *
+ * @return boolean $result True if the registration succeeded;
+ * false otherwise.
+ */
+ function registerNamespace($prefix, $uri)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Set this parser object's XML payload. This should be
+ * overridden by subclasses.
+ *
+ * @param string $xml_string The XML string to pass to this
+ * object's XML parser.
+ *
+ * @return boolean $result True if the initialization succeeded;
+ * false otherwise.
+ */
+ function setXML($xml_string)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Evaluate an XPath expression and return the resulting node
+ * list. This should be overridden by subclasses.
+ *
+ * @param string $xpath The XPath expression to be evaluated.
+ *
+ * @param mixed $node A node object resulting from a previous
+ * evalXPath call. This node, if specified, provides the context
+ * for the evaluation of this xpath expression.
+ *
+ * @return array $node_list An array of matching opaque node
+ * objects to be used with other methods of this parser class.
+ */
+ function &evalXPath($xpath, $node = null)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the textual content of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return string $content The content of this node.
+ */
+ function content($node)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the attributes of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return array $attrs An array mapping attribute names to
+ * values.
+ */
+ function attributes($node)
+ {
+ // Not implemented.
+ }
+}
+
+/**
+ * This concrete implementation of Auth_Yadis_XMLParser implements
+ * the appropriate API for the 'domxml' extension which is typically
+ * packaged with PHP 4. This class will be used whenever the 'domxml'
+ * extension is detected. See the Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_domxml extends Auth_Yadis_XMLParser {
+ function Auth_Yadis_domxml()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = @domxml_open_mem($xml_string, DOMXML_LOAD_PARSING,
+ $this->errors);
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ $this->xpath = $this->doc->xpath_new_context();
+
+ return true;
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return xpath_register_ns($this->xpath, $prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->xpath_eval($xpath, $node);
+ } else {
+ $result = @$this->xpath->xpath_eval($xpath);
+ }
+
+ if (!$result) {
+ $n = array();
+ return $n;
+ }
+
+ if (!$result->nodeset) {
+ $n = array();
+ return $n;
+ }
+
+ return $result->nodeset;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->get_content();
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes();
+ $result = array();
+
+ if ($arr) {
+ foreach ($arr as $attrnode) {
+ $result[$attrnode->name] = $attrnode->value;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+/**
+ * This concrete implementation of Auth_Yadis_XMLParser implements
+ * the appropriate API for the 'dom' extension which is typically
+ * packaged with PHP 5. This class will be used whenever the 'dom'
+ * extension is detected. See the Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_dom extends Auth_Yadis_XMLParser {
+ function Auth_Yadis_dom()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = new DOMDocument;
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ if (!@$this->doc->loadXML($xml_string)) {
+ return false;
+ }
+
+ $this->xpath = new DOMXPath($this->doc);
+
+ if ($this->xpath) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return $this->xpath->registerNamespace($prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->query($xpath, $node);
+ } else {
+ $result = @$this->xpath->query($xpath);
+ }
+
+ $n = array();
+
+ if (!$result) {
+ return $n;
+ }
+
+ for ($i = 0; $i < $result->length; $i++) {
+ $n[] = $result->item($i);
+ }
+
+ return $n;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->textContent;
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes;
+ $result = array();
+
+ if ($arr) {
+ for ($i = 0; $i < $arr->length; $i++) {
+ $node = $arr->item($i);
+ $result[$node->nodeName] = $node->nodeValue;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+global $__Auth_Yadis_defaultParser;
+$__Auth_Yadis_defaultParser = null;
+
+/**
+ * Set a default parser to override the extension-driven selection of
+ * available parser classes. This is helpful in a test environment or
+ * one in which multiple parsers can be used but one is more
+ * desirable.
+ *
+ * @param Auth_Yadis_XMLParser $parser An instance of a
+ * Auth_Yadis_XMLParser subclass.
+ */
+function Auth_Yadis_setDefaultParser(&$parser)
+{
+ global $__Auth_Yadis_defaultParser;
+ $__Auth_Yadis_defaultParser =& $parser;
+}
+
+function Auth_Yadis_getSupportedExtensions()
+{
+ return array(
+ 'dom' => array('classname' => 'Auth_Yadis_dom',
+ 'libname' => array('dom.so', 'dom.dll')),
+ 'domxml' => array('classname' => 'Auth_Yadis_domxml',
+ 'libname' => array('domxml.so', 'php_domxml.dll')),
+ );
+}
+
+/**
+ * Returns an instance of a Auth_Yadis_XMLParser subclass based on
+ * the availability of PHP extensions for XML parsing. If
+ * Auth_Yadis_setDefaultParser has been called, the parser used in
+ * that call will be returned instead.
+ */
+function &Auth_Yadis_getXMLParser()
+{
+ global $__Auth_Yadis_defaultParser;
+
+ if (isset($__Auth_Yadis_defaultParser)) {
+ return $__Auth_Yadis_defaultParser;
+ }
+
+ $p = null;
+ $classname = null;
+
+ $extensions = Auth_Yadis_getSupportedExtensions();
+
+ // Return a wrapper for the resident implementation, if any.
+ foreach ($extensions as $name => $params) {
+ if (!extension_loaded($name)) {
+ foreach ($params['libname'] as $libname) {
+ if (@dl($libname)) {
+ $classname = $params['classname'];
+ }
+ }
+ } else {
+ $classname = $params['classname'];
+ }
+ if (isset($classname)) {
+ $p = new $classname();
+ return $p;
+ }
+ }
+
+ if (!isset($p)) {
+ trigger_error('No XML parser was found', E_USER_ERROR);
+ } else {
+ Auth_Yadis_setDefaultParser($p);
+ }
+
+ return $p;
+}
+
+?>
diff --git a/Auth/Yadis/XRDS.php b/Auth/Yadis/XRDS.php
new file mode 100644
index 0000000..f14a794
--- /dev/null
+++ b/Auth/Yadis/XRDS.php
@@ -0,0 +1,478 @@
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Auth/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ALL', 101);
+
+/**
+ * This match mode means a given service must match ANY filters (at
+ * least one) passed to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ANY', 102);
+
+/**
+ * The priority value used for service elements with no priority
+ * specified.
+ */
+define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
+
+/**
+ * XRD XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
+
+/**
+ * XRDS XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
+
+function Auth_Yadis_getNSMap()
+{
+ return array('xrds' => Auth_Yadis_XMLNS_XRDS,
+ 'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
+}
+
+/**
+ * @access private
+ */
+function Auth_Yadis_array_scramble($arr)
+{
+ $result = array();
+
+ while (count($arr)) {
+ $index = array_rand($arr, 1);
+ $result[] = $arr[$index];
+ unset($arr[$index]);
+ }
+
+ return $result;
+}
+
+/**
+ * This class represents a <Service> element in an XRDS document.
+ * Objects of this type are returned by
+ * Auth_Yadis_XRDS::services() and
+ * Auth_Yadis_Yadis::services(). Each object corresponds directly
+ * to a <Service> element in the XRDS and supplies a
+ * getElements($name) method which you should use to inspect the
+ * element's contents. See {@link Auth_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Service {
+
+ /**
+ * Creates an empty service object.
+ */
+ function Auth_Yadis_Service()
+ {
+ $this->element = null;
+ $this->parser = null;
+ }
+
+ /**
+ * Return the URIs in the "Type" elements, if any, of this Service
+ * element.
+ *
+ * @return array $type_uris An array of Type URI strings.
+ */
+ function getTypes()
+ {
+ $t = array();
+ foreach ($this->getElements('xrd:Type') as $elem) {
+ $c = $this->parser->content($elem);
+ if ($c) {
+ $t[] = $c;
+ }
+ }
+ return $t;
+ }
+
+ function matchTypes($type_uris)
+ {
+ $result = array();
+
+ foreach ($this->getTypes() as $typ) {
+ if (in_array($typ, $type_uris)) {
+ $result[] = $typ;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return the URIs in the "URI" elements, if any, of this Service
+ * element. The URIs are returned sorted in priority order.
+ *
+ * @return array $uris An array of URI strings.
+ */
+ function getURIs()
+ {
+ $uris = array();
+ $last = array();
+
+ foreach ($this->getElements('xrd:URI') as $elem) {
+ $uri_string = $this->parser->content($elem);
+ $attrs = $this->parser->attributes($elem);
+ if ($attrs &&
+ array_key_exists('priority', $attrs)) {
+ $priority = intval($attrs['priority']);
+ if (!array_key_exists($priority, $uris)) {
+ $uris[$priority] = array();
+ }
+
+ $uris[$priority][] = $uri_string;
+ } else {
+ $last[] = $uri_string;
+ }
+ }
+
+ $keys = array_keys($uris);
+ sort($keys);
+
+ // Rebuild array of URIs.
+ $result = array();
+ foreach ($keys as $k) {
+ $new_uris = Auth_Yadis_array_scramble($uris[$k]);
+ $result = array_merge($result, $new_uris);
+ }
+
+ $result = array_merge($result,
+ Auth_Yadis_array_scramble($last));
+
+ return $result;
+ }
+
+ /**
+ * Returns the "priority" attribute value of this <Service>
+ * element, if the attribute is present. Returns null if not.
+ *
+ * @return mixed $result Null or integer, depending on whether
+ * this Service element has a 'priority' attribute.
+ */
+ function getPriority()
+ {
+ $attributes = $this->parser->attributes($this->element);
+
+ if (array_key_exists('priority', $attributes)) {
+ return intval($attributes['priority']);
+ }
+
+ return null;
+ }
+
+ /**
+ * Used to get XML elements from this object's <Service> element.
+ *
+ * This is what you should use to get all custom information out
+ * of this element. This is used by service filter functions to
+ * determine whether a service element contains specific tags,
+ * etc. NOTE: this only considers elements which are direct
+ * children of the <Service> element for this object.
+ *
+ * @param string $name The name of the element to look for
+ * @return array $list An array of elements with the specified
+ * name which are direct children of the <Service> element. The
+ * nodes returned by this function can be passed to $this->parser
+ * methods (see {@link Auth_Yadis_XMLParser}).
+ */
+ function getElements($name)
+ {
+ return $this->parser->evalXPath($name, $this->element);
+ }
+}
+
+/*
+ * Return the expiration date of this XRD element, or None if no
+ * expiration was specified.
+ *
+ * @param $default The value to use as the expiration if no expiration
+ * was specified in the XRD.
+ */
+function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
+{
+ $expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
+ if ($expires_element === null) {
+ return $default;
+ } else {
+ $expires_string = $expires_element->text;
+
+ // Will raise ValueError if the string is not the expected
+ // format
+ $t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
+
+ if ($t === false) {
+ return false;
+ }
+
+ // [int $hour [, int $minute [, int $second [,
+ // int $month [, int $day [, int $year ]]]]]]
+ return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
+ $t['tm_mon'], $t['tm_day'], $t['tm_year']);
+ }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre> $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Auth_Yadis_XRDS will be returned. Otherwise, null will be
+ * returned. This class is used by the Auth_Yadis_Yadis::discover
+ * method.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XRDS {
+
+ /**
+ * Instantiate a Auth_Yadis_XRDS object. Requires an XPath
+ * instance which has been used to parse a valid XRDS document.
+ */
+ function Auth_Yadis_XRDS(&$xmlParser, &$xrdNodes)
+ {
+ $this->parser =& $xmlParser;
+ $this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
+ $this->allXrdNodes =& $xrdNodes;
+ $this->serviceList = array();
+ $this->_parse();
+ }
+
+ /**
+ * Parse an XML string (XRDS document) and return either a
+ * Auth_Yadis_XRDS object or null, depending on whether the
+ * XRDS XML is valid.
+ *
+ * @param string $xml_string An XRDS XML string.
+ * @return mixed $xrds An instance of Auth_Yadis_XRDS or null,
+ * depending on the validity of $xml_string
+ */
+ function &parseXRDS($xml_string, $extra_ns_map = null)
+ {
+ $_null = null;
+
+ if (!$xml_string) {
+ return $_null;
+ }
+
+ $parser = Auth_Yadis_getXMLParser();
+
+ $ns_map = Auth_Yadis_getNSMap();
+
+ if ($extra_ns_map && is_array($extra_ns_map)) {
+ $ns_map = array_merge($ns_map, $extra_ns_map);
+ }
+
+ if (!($parser && $parser->init($xml_string, $ns_map))) {
+ return $_null;
+ }
+
+ // Try to get root element.
+ $root = $parser->evalXPath('/xrds:XRDS[1]');
+ if (!$root) {
+ return $_null;
+ }
+
+ if (is_array($root)) {
+ $root = $root[0];
+ }
+
+ $attrs = $parser->attributes($root);
+
+ if (array_key_exists('xmlns:xrd', $attrs) &&
+ $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+ return $_null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+ return $_null;
+ }
+
+ // Get the last XRD node.
+ $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+ if (!$xrd_nodes) {
+ return $_null;
+ }
+
+ $xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
+ return $xrds;
+ }
+
+ /**
+ * @access private
+ */
+ function _addService($priority, $service)
+ {
+ $priority = intval($priority);
+
+ if (!array_key_exists($priority, $this->serviceList)) {
+ $this->serviceList[$priority] = array();
+ }
+
+ $this->serviceList[$priority][] = $service;
+ }
+
+ /**
+ * Creates the service list using nodes from the XRDS XML
+ * document.
+ *
+ * @access private
+ */
+ function _parse()
+ {
+ $this->serviceList = array();
+
+ $services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
+
+ foreach ($services as $node) {
+ $s =& new Auth_Yadis_Service();
+ $s->element = $node;
+ $s->parser =& $this->parser;
+
+ $priority = $s->getPriority();
+
+ if ($priority === null) {
+ $priority = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ $this->_addService($priority, $s);
+ }
+ }
+
+ /**
+ * Returns a list of service objects which correspond to <Service>
+ * elements in the XRDS XML document for this object.
+ *
+ * Optionally, an array of filter callbacks may be given to limit
+ * the list of returned service objects. Furthermore, the default
+ * mode is to return all service objects which match ANY of the
+ * specified filters, but $filter_mode may be
+ * SERVICES_YADIS_MATCH_ALL if you want to be sure that the
+ * returned services match all the given filters. See {@link
+ * Auth_Yadis_Yadis} for detailed usage information on filter
+ * functions.
+ *
+ * @param mixed $filters An array of callbacks to filter the
+ * returned services, or null if all services are to be returned.
+ * @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
+ * SERVICES_YADIS_MATCH_ANY, depending on whether the returned
+ * services should match ALL or ANY of the specified filters,
+ * respectively.
+ * @return mixed $services An array of {@link
+ * Auth_Yadis_Service} objects if $filter_mode is a valid
+ * mode; null if $filter_mode is an invalid mode (i.e., not
+ * SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
+ */
+ function services($filters = null,
+ $filter_mode = SERVICES_YADIS_MATCH_ANY)
+ {
+
+ $pri_keys = array_keys($this->serviceList);
+ sort($pri_keys, SORT_NUMERIC);
+
+ // If no filters are specified, return the entire service
+ // list, ordered by priority.
+ if (!$filters ||
+ (!is_array($filters))) {
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $this->serviceList[$pri]);
+ }
+
+ return $result;
+ }
+
+ // If a bad filter mode is specified, return null.
+ if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
+ SERVICES_YADIS_MATCH_ALL))) {
+ return null;
+ }
+
+ // Otherwise, use the callbacks in the filter list to
+ // determine which services are returned.
+ $filtered = array();
+
+ foreach ($pri_keys as $priority_value) {
+ $service_obj_list = $this->serviceList[$priority_value];
+
+ foreach ($service_obj_list as $service) {
+
+ $matches = 0;
+
+ foreach ($filters as $filter) {
+ if (call_user_func_array($filter, array($service))) {
+ $matches++;
+
+ if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+
+ $filtered[$pri][] = $service;
+ break;
+ }
+ }
+ }
+
+ if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
+ ($matches == count($filters))) {
+
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+ $filtered[$pri][] = $service;
+ }
+ }
+ }
+
+ $pri_keys = array_keys($filtered);
+ sort($pri_keys, SORT_NUMERIC);
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $filtered[$pri]);
+ }
+
+ return $result;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/Yadis/XRI.php b/Auth/Yadis/XRI.php
new file mode 100644
index 0000000..4e34623
--- /dev/null
+++ b/Auth/Yadis/XRI.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * Routines for XRI resolution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/Yadis/Misc.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'Auth/OpenID.php';
+
+function Auth_Yadis_getDefaultProxy()
+{
+ return 'http://xri.net/';
+}
+
+function Auth_Yadis_getXRIAuthorities()
+{
+ return array('!', '=', '@', '+', '$', '(');
+}
+
+function Auth_Yadis_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Auth_Yadis_getUCSChars(),
+ Auth_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('/[%s]/', implode('', $parts));
+}
+
+function Auth_Yadis_getXrefRE()
+{
+ return '/\((.*?)\)/';
+}
+
+function Auth_Yadis_identifierScheme($identifier)
+{
+ if (Auth_Yadis_startswith($identifier, 'xri://') ||
+ ($identifier &&
+ in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
+ return "XRI";
+ } else {
+ return "URI";
+ }
+}
+
+function Auth_Yadis_toIRINormal($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+
+ return Auth_Yadis_escapeForIRI($xri);
+}
+
+function _escape_xref($xref_match)
+{
+ $xref = $xref_match[0];
+ $xref = str_replace('/', '%2F', $xref);
+ $xref = str_replace('?', '%3F', $xref);
+ $xref = str_replace('#', '%23', $xref);
+ return $xref;
+}
+
+function Auth_Yadis_escapeForIRI($xri)
+{
+ $xri = str_replace('%', '%25', $xri);
+ $xri = preg_replace_callback(Auth_Yadis_getXrefRE(),
+ '_escape_xref', $xri);
+ return $xri;
+}
+
+function Auth_Yadis_toURINormal($xri)
+{
+ return Auth_Yadis_iriToURI(Auth_Yadis_toIRINormal($xri));
+}
+
+function Auth_Yadis_iriToURI($iri)
+{
+ if (1) {
+ return $iri;
+ } else {
+ // According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
+ return preg_replace_callback(Auth_Yadis_getEscapeRE(),
+ 'Auth_Yadis_pct_escape_unicode', $iri);
+ }
+}
+
+
+function Auth_Yadis_XRIAppendArgs($url, $args)
+{
+ // Append some arguments to an HTTP query. Yes, this is just like
+ // OpenID's appendArgs, but with special seasoning for XRI
+ // queries.
+
+ if (count($args) == 0) {
+ return $url;
+ }
+
+ // Non-empty array; if it is an array of arrays, use multisort;
+ // otherwise use sort.
+ if (array_key_exists(0, $args) &&
+ is_array($args[0])) {
+ // Do nothing here.
+ } else {
+ $keys = array_keys($args);
+ sort($keys);
+ $new_args = array();
+ foreach ($keys as $key) {
+ $new_args[] = array($key, $args[$key]);
+ }
+ $args = $new_args;
+ }
+
+ // According to XRI Resolution section "QXRI query parameters":
+ //
+ // "If the original QXRI had a null query component (only a
+ // leading question mark), or a query component consisting of
+ // only question marks, one additional leading question mark MUST
+ // be added when adding any XRI resolution parameters."
+ if (strpos(rtrim($url, '?'), '?') !== false) {
+ $sep = '&';
+ } else {
+ $sep = '?';
+ }
+
+ return $url . $sep . Auth_OpenID::httpBuildQuery($args);
+}
+
+function Auth_Yadis_providerIsAuthoritative($providerID, $canonicalID)
+{
+ $lastbang = strrpos($canonicalID, '!');
+ $p = substr($canonicalID, 0, $lastbang);
+ return $p == $providerID;
+}
+
+function Auth_Yadis_rootAuthority($xri)
+{
+ // Return the root authority for an XRI.
+
+ $root = null;
+
+ if (Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = substr($xri, 6);
+ }
+
+ $authority = explode('/', $xri, 2);
+ $authority = $authority[0];
+ if ($authority[0] == '(') {
+ // Cross-reference.
+ // XXX: This is incorrect if someone nests cross-references so
+ // there is another close-paren in there. Hopefully nobody
+ // does that before we have a real xriparse function.
+ // Hopefully nobody does that *ever*.
+ $root = substr($authority, 0, strpos($authority, ')') + 1);
+ } else if (in_array($authority[0], Auth_Yadis_getXRIAuthorities())) {
+ // Other XRI reference.
+ $root = $authority[0];
+ } else {
+ // IRI reference.
+ $_segments = explode("!", $authority);
+ $segments = array();
+ foreach ($_segments as $s) {
+ $segments = array_merge($segments, explode("*", $s));
+ }
+ $root = $segments[0];
+ }
+
+ return Auth_Yadis_XRI($root);
+}
+
+function Auth_Yadis_XRI($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+ return $xri;
+}
+
+function Auth_Yadis_getCanonicalID($iname, $xrds)
+{
+ // Returns false or a canonical ID value.
+
+ // Now nodes are in reverse order.
+ $xrd_list = array_reverse($xrds->allXrdNodes);
+ $parser =& $xrds->parser;
+ $node = $xrd_list[0];
+
+ $canonicalID_nodes = $parser->evalXPath('xrd:CanonicalID', $node);
+
+ if (!$canonicalID_nodes) {
+ return false;
+ }
+
+ $canonicalID = $canonicalID_nodes[0];
+ $canonicalID = Auth_Yadis_XRI($parser->content($canonicalID));
+
+ $childID = $canonicalID;
+
+ for ($i = 1; $i < count($xrd_list); $i++) {
+ $xrd = $xrd_list[$i];
+
+ $parent_sought = substr($childID, 0, strrpos($childID, '!'));
+ $parentCID = $parser->evalXPath('xrd:CanonicalID', $xrd);
+ if (!$parentCID) {
+ return false;
+ }
+ $parentCID = Auth_Yadis_XRI($parser->content($parentCID[0]));
+
+ if (strcasecmp($parent_sought, $parentCID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ $childID = $parent_sought;
+ }
+
+ $root = Auth_Yadis_rootAuthority($iname);
+ if (!Auth_Yadis_providerIsAuthoritative($root, $childID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ return $canonicalID;
+}
+
+?>
diff --git a/Auth/Yadis/XRIRes.php b/Auth/Yadis/XRIRes.php
new file mode 100644
index 0000000..4e8e8d0
--- /dev/null
+++ b/Auth/Yadis/XRIRes.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Code for using a proxy XRI resolver.
+ */
+
+require_once 'Auth/Yadis/XRDS.php';
+require_once 'Auth/Yadis/XRI.php';
+
+class Auth_Yadis_ProxyResolver {
+ function Auth_Yadis_ProxyResolver(&$fetcher, $proxy_url = null)
+ {
+ $this->fetcher =& $fetcher;
+ $this->proxy_url = $proxy_url;
+ if (!$this->proxy_url) {
+ $this->proxy_url = Auth_Yadis_getDefaultProxy();
+ }
+ }
+
+ function queryURL($xri, $service_type = null)
+ {
+ // trim off the xri:// prefix
+ $qxri = substr(Auth_Yadis_toURINormal($xri), 6);
+ $hxri = $this->proxy_url . $qxri;
+ $args = array(
+ '_xrd_r' => 'application/xrds+xml'
+ );
+
+ if ($service_type) {
+ $args['_xrd_t'] = $service_type;
+ } else {
+ // Don't perform service endpoint selection.
+ $args['_xrd_r'] .= ';sep=false';
+ }
+
+ $query = Auth_Yadis_XRIAppendArgs($hxri, $args);
+ return $query;
+ }
+
+ function query($xri, $service_types, $filters = array())
+ {
+ $services = array();
+ $canonicalID = null;
+ foreach ($service_types as $service_type) {
+ $url = $this->queryURL($xri, $service_type);
+ $response = $this->fetcher->get($url);
+ if ($response->status != 200 and $response->status != 206) {
+ continue;
+ }
+ $xrds = Auth_Yadis_XRDS::parseXRDS($response->body);
+ if (!$xrds) {
+ continue;
+ }
+ $canonicalID = Auth_Yadis_getCanonicalID($xri,
+ $xrds);
+
+ if ($canonicalID === false) {
+ return null;
+ }
+
+ $some_services = $xrds->services($filters);
+ $services = array_merge($services, $some_services);
+ // TODO:
+ // * If we do get hits for multiple service_types, we're
+ // almost certainly going to have duplicated service
+ // entries and broken priority ordering.
+ }
+ return array($canonicalID, $services);
+ }
+}
+
+?>
diff --git a/Auth/Yadis/Yadis.php b/Auth/Yadis/Yadis.php
new file mode 100644
index 0000000..d89f77c
--- /dev/null
+++ b/Auth/Yadis/Yadis.php
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * The core PHP Yadis implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Need both fetcher types so we can use the right one based on the
+ * presence or absence of CURL.
+ */
+require_once "Auth/Yadis/PlainHTTPFetcher.php";
+require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
+
+/**
+ * Need this for parsing HTML (looking for META tags).
+ */
+require_once "Auth/Yadis/ParseHTML.php";
+
+/**
+ * Need this to parse the XRDS document during Yadis discovery.
+ */
+require_once "Auth/Yadis/XRDS.php";
+
+/**
+ * XRDS (yadis) content type
+ */
+define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
+
+/**
+ * Yadis header
+ */
+define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
+
+/**
+ * Contains the result of performing Yadis discovery on a URI.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_DiscoveryResult {
+
+ // The URI that was passed to the fetcher
+ var $request_uri = null;
+
+ // The result of following redirects from the request_uri
+ var $normalized_uri = null;
+
+ // The URI from which the response text was returned (set to
+ // None if there was no XRDS document found)
+ var $xrds_uri = null;
+
+ var $xrds = null;
+
+ // The content-type returned with the response_text
+ var $content_type = null;
+
+ // The document returned from the xrds_uri
+ var $response_text = null;
+
+ // Did the discovery fail miserably?
+ var $failed = false;
+
+ function Auth_Yadis_DiscoveryResult($request_uri)
+ {
+ // Initialize the state of the object
+ // sets all attributes to None except the request_uri
+ $this->request_uri = $request_uri;
+ }
+
+ function fail()
+ {
+ $this->failed = true;
+ }
+
+ function isFailure()
+ {
+ return $this->failed;
+ }
+
+ /**
+ * Returns the list of service objects as described by the XRDS
+ * document, if this yadis object represents a successful Yadis
+ * discovery.
+ *
+ * @return array $services An array of {@link Auth_Yadis_Service}
+ * objects
+ */
+ function services()
+ {
+ if ($this->xrds) {
+ return $this->xrds->services();
+ }
+
+ return null;
+ }
+
+ function usedYadisLocation()
+ {
+ // Was the Yadis protocol's indirection used?
+ return $this->normalized_uri != $this->xrds_uri;
+ }
+
+ function isXRDS()
+ {
+ // Is the response text supposed to be an XRDS document?
+ return ($this->usedYadisLocation() ||
+ $this->content_type == Auth_Yadis_CONTENT_TYPE);
+ }
+}
+
+/**
+ *
+ * Perform the Yadis protocol on the input URL and return an iterable
+ * of resulting endpoint objects.
+ *
+ * input_url: The URL on which to perform the Yadis protocol
+ *
+ * @return: The normalized identity URL and an iterable of endpoint
+ * objects generated by the filter function.
+ *
+ * xrds_parse_func: a callback which will take (uri, xrds_text) and
+ * return an array of service endpoint objects or null. Usually
+ * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
+ *
+ * discover_func: if not null, a callback which should take (uri) and
+ * return an Auth_Yadis_Yadis object or null.
+ */
+function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
+ $discover_func=null, $fetcher=null)
+{
+ if ($discover_func === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $yadis_result = call_user_func_array($discover_func,
+ array($input_url, $fetcher));
+
+ if ($yadis_result === null) {
+ return array($input_url, array());
+ }
+
+ $endpoints = call_user_func_array($xrds_parse_func,
+ array($yadis_result->normalized_uri,
+ $yadis_result->response_text));
+
+ if ($endpoints === null) {
+ $endpoints = array();
+ }
+
+ return array($yadis_result->normalized_uri, $endpoints);
+}
+
+/**
+ * This is the core of the PHP Yadis library. This is the only class
+ * a user needs to use to perform Yadis discovery. This class
+ * performs the discovery AND stores the result of the discovery.
+ *
+ * First, require this library into your program source:
+ *
+ * <pre> require_once "Auth/Yadis/Yadis.php";</pre>
+ *
+ * To perform Yadis discovery, first call the "discover" method
+ * statically with a URI parameter:
+ *
+ * <pre> $http_response = array();
+ * $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ * $yadis_object = Auth_Yadis_Yadis::discover($uri,
+ * $http_response, $fetcher);</pre>
+ *
+ * If the discovery succeeds, $yadis_object will be an instance of
+ * {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS
+ * document found during discovery should have service descriptions,
+ * which can be accessed by calling
+ *
+ * <pre> $service_list = $yadis_object->services();</pre>
+ *
+ * which returns an array of objects which describe each service.
+ * These objects are instances of Auth_Yadis_Service. Each object
+ * describes exactly one whole Service element, complete with all of
+ * its Types and URIs (no expansion is performed). The common use
+ * case for using the service objects returned by services() is to
+ * write one or more filter functions and pass those to services():
+ *
+ * <pre> $service_list = $yadis_object->services(
+ * array("filterByURI",
+ * "filterByExtension"));</pre>
+ *
+ * The filter functions (whose names appear in the array passed to
+ * services()) take the following form:
+ *
+ * <pre> function myFilter(&$service) {
+ * // Query $service object here. Return true if the service
+ * // matches your query; false if not.
+ * }</pre>
+ *
+ * This is an example of a filter which uses a regular expression to
+ * match the content of URI tags (note that the Auth_Yadis_Service
+ * class provides a getURIs() method which you should use instead of
+ * this contrived example):
+ *
+ * <pre>
+ * function URIMatcher(&$service) {
+ * foreach ($service->getElements('xrd:URI') as $uri) {
+ * if (preg_match("/some_pattern/",
+ * $service->parser->content($uri))) {
+ * return true;
+ * }
+ * }
+ * return false;
+ * }</pre>
+ *
+ * The filter functions you pass will be called for each service
+ * object to determine which ones match the criteria your filters
+ * specify. The default behavior is that if a given service object
+ * matches ANY of the filters specified in the services() call, it
+ * will be returned. You can specify that a given service object will
+ * be returned ONLY if it matches ALL specified filters by changing
+ * the match mode of services():
+ *
+ * <pre> $yadis_object->services(array("filter1", "filter2"),
+ * SERVICES_YADIS_MATCH_ALL);</pre>
+ *
+ * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
+ * SERVICES_YADIS_MATCH_ANY}.
+ *
+ * Services described in an XRDS should have a library which you'll
+ * probably be using. Those libraries are responsible for defining
+ * filters that can be used with the "services()" call. If you need
+ * to write your own filter, see the documentation for {@link
+ * Auth_Yadis_Service}.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Yadis {
+
+ /**
+ * Returns an HTTP fetcher object. If the CURL extension is
+ * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
+ * is returned. If not, an instance of
+ * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
+ *
+ * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
+ * return a {@link Auth_Yadis_PlainHTTPFetcher}.
+ */
+ function getHTTPFetcher($timeout = 20)
+ {
+ if (Auth_Yadis_Yadis::curlPresent() &&
+ (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
+ $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
+ } else {
+ $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
+ }
+ return $fetcher;
+ }
+
+ function curlPresent()
+ {
+ return function_exists('curl_init');
+ }
+
+ /**
+ * @access private
+ */
+ function _getHeader($header_list, $names)
+ {
+ foreach ($header_list as $name => $value) {
+ foreach ($names as $n) {
+ if (strtolower($name) == strtolower($n)) {
+ return $value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _getContentType($content_type_header)
+ {
+ if ($content_type_header) {
+ $parts = explode(";", $content_type_header);
+ return strtolower($parts[0]);
+ }
+ }
+
+ /**
+ * This should be called statically and will build a Yadis
+ * instance if the discovery process succeeds. This implements
+ * Yadis discovery as specified in the Yadis specification.
+ *
+ * @param string $uri The URI on which to perform Yadis discovery.
+ *
+ * @param array $http_response An array reference where the HTTP
+ * response object will be stored (see {@link
+ * Auth_Yadis_HTTPResponse}.
+ *
+ * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
+ * Auth_Yadis_HTTPFetcher subclass.
+ *
+ * @param array $extra_ns_map An array which maps namespace names
+ * to namespace URIs to be used when parsing the Yadis XRDS
+ * document.
+ *
+ * @param integer $timeout An optional fetcher timeout, in seconds.
+ *
+ * @return mixed $obj Either null or an instance of
+ * Auth_Yadis_Yadis, depending on whether the discovery
+ * succeeded.
+ */
+ function discover($uri, &$fetcher,
+ $extra_ns_map = null, $timeout = 20)
+ {
+ $result = new Auth_Yadis_DiscoveryResult($uri);
+
+ $request_uri = $uri;
+ $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
+ ', text/html; q=0.3, application/xhtml+xml; q=0.5');
+
+ if ($fetcher === null) {
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
+ }
+
+ $response = $fetcher->get($uri, $headers);
+
+ if (!$response || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->normalized_uri = $response->final_url;
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+
+ if ($result->content_type &&
+ (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
+ Auth_Yadis_CONTENT_TYPE)) {
+ $result->xrds_uri = $result->normalized_uri;
+ } else {
+ $yadis_location = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array(Auth_Yadis_HEADER_NAME));
+
+ if (!$yadis_location) {
+ $parser = new Auth_Yadis_ParseHTML();
+ $yadis_location = $parser->getHTTPEquiv($response->body);
+ }
+
+ if ($yadis_location) {
+ $result->xrds_uri = $yadis_location;
+
+ $response = $fetcher->get($yadis_location);
+
+ if ((!$response) || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+ }
+ }
+
+ $result->response_text = $response->body;
+ return $result;
+ }
+}
+
+?>
diff --git a/CHANGES-2.1.0 b/CHANGES-2.1.0
new file mode 100644
index 0000000..5e68fb9
--- /dev/null
+++ b/CHANGES-2.1.0
@@ -0,0 +1,51 @@
+* API Changes
+ * AX::FetchResponse::fromSuccessResponse - return null when AX
+ response arguments are absent
+ * Alter AX fromOpenIDRequest() to take Auth_OpenID_AuthRequest
+ object instead of Auth_OpenID_Message object so that it matches
+ its counterpart methods in SREG and PAPE extensions.
+ * PAPE (Provider Authentication Policy Extension) module
+ * Updated extension for specification draft 2
+ * Auth_OpenID_PAPE_Request::fromSuccessResponse returns None if
+ PAPE response arguments were not signed
+ * Added functions to generate request/response HTML forms with
+ auto-submission javascript
+ * Consumer (relying party) API:
+ Auth_OpenID_AuthRequest::htmlMarkup
+ * Server API: Auth_OpenID_OpenIDResponse::toHTML
+
+* New Features
+ * Added examples/discover.php, an OpenID service discovery tool
+ * Add optional form_tag_attrs argument to
+ Auth_OpenID_ServerResponse::toFormMarkup for setting arbitrary
+ FORM element attributes
+ * Fetchers now only read/request first megabyte of response
+
+* Bug Fixes
+ * NOT NULL constraints were added to SQLStore tables where
+ appropriate
+ * Yadis discovery now properly falls back to HTML-based discovery if
+ it fails to get an XRDS document
+ * Auth_OpenID_Decoder now behaves correctly when given a protocol
+ message with an invalid OpenID namespace or a missing OpenID mode
+ * Auth_OpenID_OpenIDResponse::toFormMarkup: Use return_to from the
+ request, not the response fields (Not all responses (i.e. cancel,
+ setup_needed) include a return_to field.)
+ * normalize return_to URL before performing return_to verification
+ * Auth_OpenID_Consumer::_verifyDiscoveryResults: fall back to OpenID
+ 1.0 type if 1.1 endpoint cannot be found
+ * Auth_Yadis_ParanoidHTTPFetcher now works correctly with both array
+ and non-array CURL versions
+ * Clarified licensing language in all source files
+ * OpenID 1 association requests no longer explicitly set
+ no-encryption session type
+ * Auth_OpenID_ServiceEndpoint::getDisplayIdentifier no longer
+ includes a fragment, if present, in display identifiers
+ * check_authentication requests: copy entire response, not just
+ signed fields. Fixes missing namespace in check_authentication
+ requests
+ * Yadis discovery now includes application/xhtml+xml and qualities
+ in the Accept header
+ * Normalize URLs correctly with URINorm.php
+ * Auth_OpenID_MySQLStore: Use ENGINE instead of TYPE when creating
+ tables
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..fb604f6
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,85 @@
+What's New in PHP OpenID 2.0
+============================
+
+This library implements both the OpenID 1 and OpenID 2 protocols. The
+API changes in this version of the library are minimal and mostly
+backwards-compatible with current RP and Server implementations. If
+you're already using this library for OpenID 1, only a few small
+changes (see Summary below) will be needed once you upgrade.
+
+The big news here is compatibility with version 2.0 of the OpenID
+protocol. Highlights include:
+
+ * Simple Registration support in a new module Auth/OpenID/SReg.php.
+ (Those previously using
+ Auth_OpenID_SuccessResponse::extensionResponse() are advised to
+ look here and at the example consumer and server for detailed usage
+ information.)
+ * OpenID provider-driven identifier selection.
+ * "Negotiators" allow you to define which association types to use.
+ * Improved examples/detect.php script (bugs fixed)
+ * Improved layout of example consumer (see examples/consumer)
+ * An improved HTML parser implementation
+ * Library is now immune to being included inside functions and
+ methods
+ * Fixes to avoid multibyte overloading problems
+
+If you've written your own custom store or code that interacts
+directly with it, you'll need to review the change notes for
+Auth_OpenID_Interface in Auth/OpenID/Interface.php.
+
+
+Upgrading from earlier versions of this library
+-----------------------------------------------
+
+One of the additions to the OpenID protocol was a specified nonce
+format for one-way nonces. As a result, the nonce table in the
+SQL-driven stores has changed. You'll need to run the Python script
+contrib/upgrade-store-1.1-to-2.0 to upgrade your store, or you'll
+encounter errors about the wrong number of columns in the oid_nonces
+table. To run the script, you'll need a python module supporting your
+database type: pysqlite2, psycopg, or MySQLdb.
+
+If you cannot run the Python script, you can re-create your store by
+dropping the tables in the store and calling createTables() on the
+store object.
+
+Consumers should now pass the consumer return_to URL to
+Auth_OpenID_Consumer::complete() to defend against return_to URL
+tampering. This has REPLACED the old parameter, $query. $query is
+now a second optional parameter. It is STRONGLY RECOMMENDED that you
+never override $query, since the OpenID library uses its own logic to
+sidestep PHP's broken request-processing code.
+
+
+Summary of API Changes
+----------------------
+
+ - Auth_OpenID::fixArgs is now no longer necessary, and
+Auth_OpenID_Consumer::complete and Auth_OpenID_Server::decodeRequest
+no longer take query argument arrays. *You should no longer pass any
+parameters to these methods.*
+
+ - Auth_OpenID_SuccessResponse::extensionResponse() is no longer the
+preferred way to extract extension response parameters from the OpenID
+response. Instead, see the Auth/OpenID/SReg.php module and the
+example consumer and server for detailed usage information on
+constructing Simple Registration requests and inspecting responses.
+extensionResponse() is still valid, but now takes a second parameter
+(bool) indicating whether extension args should be signed.
+
+ - The Auth_OpenID_Server's response answer() method now takes
+additional parameters to support provider-driven identifier selection.
+See the example server and the documentation for
+Auth_OpenID_CheckIDRequest::answer.
+
+ - Auth_OpenID_Consumer::complete() now takes two args:
+
+ - $return_to, a required string that is the return URL passed to
+ Auth_OpenID_AuthRequest::redirectURL()
+
+ - $query, an optional array (or null if absent) denoting the query
+ parameters of the OpenID response. If null, the response data
+ will be extracted from the PHP request environment. Library
+ users SHOULD NOT ever pass anything for $query unless they're
+ testing the library. \ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..aab17c0
--- /dev/null
+++ b/README
@@ -0,0 +1,139 @@
+
+PHP OpenID
+----------
+
+This is the PHP OpenID library by JanRain, Inc. You can visit our
+website for more information about this package and other OpenID
+implementations and tools:
+
+ http://www.openidenabled.com/
+
+GETTING STARTED
+===============
+
+First, run the 'examples/detect.php' script either from the command
+line or via the web. It will generate a report of any system
+configuration changes necessary to run the library.
+
+INSTALLATION
+============
+
+You will need PHP 4.3.0 or greater to use this library. We have
+tested the library Linux on PHP 4.3.0, 4.4.1, 5.0.5, and 5.1.1. We
+have tested the library on Windows XP on PHP 4.4.4.
+
+Follow these steps:
+
+1. Install dependencies.
+
+ - Enable either the GMP extension or Bcmath extension. (GMP is
+ STRONGLY recommended because it's MUCH faster!) This is
+ required.
+
+ - Enable the CURL extension.
+
+ - If you plan to use SQLite, PostgreSQL, or MySQL to store OpenID
+ data, you'll need PEAR DB. You can install this by running this
+ as root:
+
+ # pear install DB
+
+ You'll also need to install and enable the appropriate PHP
+ database extension. Alternatively, you can store OpenID data on
+ the filesystem instead of using a relational database. Nothing
+ special is required for using the filesystem method.
+
+ - Install either the DOM or domxml PHP XML processing extension,
+ but not both (they are incompatible).
+
+2. Copy the Auth/ directory into your PHP include path.
+
+TESTING YOUR SETUP
+==================
+
+You can use the example code to test your setup. To run the example
+consumer or server, follow the instructions in the examples/README
+file.
+
+USING THE API
+=============
+
+The best way to get started using the API is to take a look at the
+example consumer and server in the examples/ directory. See the
+examples/README file for more details.
+
+TROUBLESHOOTING
+===============
+
+* If you're unable to use an OpenID URL with the library, you may want
+to try using the discover tool (examples/discover.php). This tool
+will perform OpenID discovery on the identifier and give a list of
+discovered OpenID services and their types.
+
+* On some systems, PHP basedir restrictions prevent web servers from
+opening a source of randomness, such as /dev/urandom. If your PHP
+OpenID library has trouble getting a satisfactory source of
+randomness, check your Apache and PHP configurations to be sure that
+the randomness source is in the list of allowed paths for the
+"open_basedir" option.
+
+* In some cases, bugs in the GMP math library will result in signature
+validation errors when using this library. Since GMP is preferred
+over bcmath (for performance), you will have to define
+Auth_OpenID_BUGGY_GMP in your application *before* importing any of
+the library code:
+
+ define('Auth_OpenID_BUGGY_GMP', true);
+
+* Not all PHP installations support SSL. You can find out if yours
+supports SSL by reading the "HTTP Fetching" section of the output of
+"examples/detect.php." If your installation does not support SSL,
+then https:// identity URLs and server URLs will not be supported by
+the library. An attempt to use such an identity URL will be
+equivalent to using an invalid OpenID. To enable SSL support,
+recompile PHP with OpenSSL support or install the appropriate OpenSSL
+module for your platform. If you are using CURL, CURL will need to be
+built with OpenSSL support.
+
+GETTING HELP
+============
+
+If you have any questions, recommendations, or patches, please tell
+us! Subscribe to our OpenID development discussion list at
+
+ http://lists.openidenabled.com/mailman/listinfo/dev
+
+DOCUMENTATION
+=============
+
+You can view the HTML library documentation in the doc/ directory.
+You can also find it on the web at
+
+ http://www.openidenabled.com/resources/docs/openid/php/
+
+This package's documentation is in PhpDoc format. To generate the
+documentation, install phpdoc and run the admin/makedoc.sh script.
+Phpdoc lives at:
+
+ http://www.phpdoc.org/
+
+CONTRIBUTING
+============
+
+If you have a bugfix or feature you'd like to contribute, don't
+hesitate to send it to us. Post your patch to the development list at
+
+ http://lists.openidenabled.com/mailman/listinfo/dev
+
+For more detailed information on how to contribute, see
+
+ http://openidenabled.com/contribute/
+
+To run the test suite included with this package, install PHPUnit 1.x
+and run
+
+ php admin/texttest.php
+
+PHPUnit 1.x can be found at
+
+ http://pear.phpunit.de/get/
diff --git a/README.Debian b/README.Debian
new file mode 100644
index 0000000..1928268
--- /dev/null
+++ b/README.Debian
@@ -0,0 +1,8 @@
+Development Environment Setup
+=============================
+
+Janrain note: You'll need to run these commands to generate
+documentation for this project:
+
+ apt-get install php4-pear
+ pear install PhpDocumentor
diff --git a/Tests/Auth/OpenID/AX.php b/Tests/Auth/OpenID/AX.php
new file mode 100644
index 0000000..1b46945
--- /dev/null
+++ b/Tests/Auth/OpenID/AX.php
@@ -0,0 +1,794 @@
+<?php
+
+/*
+ * Tests for the attribute exchange extension module
+ */
+
+require_once "PHPUnit.php";
+require_once "Auth/OpenID/AX.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Consumer.php";
+require_once "Auth/OpenID/Server.php";
+
+class BogusAXMessage extends Auth_OpenID_AX_Message {
+ var $mode = 'bogus';
+
+ function getExtensionArgs()
+ {
+ return $this->_newArgs();
+ }
+}
+
+class AXMessageTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->bax = new BogusAXMessage();
+ }
+
+ function test_checkMode()
+ {
+ $result = $this->bax->_checkMode(array());
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+
+ $result = $this->bax->_checkMode(array('mode' => 'fetch_request'));
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+
+ // does not raise an exception when the mode is right
+ $result = $this->bax->_checkMode(array('mode' => $this->bax->mode));
+ $this->assertTrue($result === true);
+ }
+
+ /*
+ * _newArgs generates something that has the correct mode
+ */
+ function test_checkMode_newArgs()
+ {
+ $result = $this->bax->_checkMode($this->bax->_newArgs());
+ $this->assertTrue($result === true);
+ }
+}
+
+class AttrInfoTest extends PHPUnit_TestCase {
+ function test_construct()
+ {
+ $type_uri = 'a uri';
+ $ainfo = Auth_OpenID_AX_AttrInfo::make($type_uri);
+
+ $this->assertEquals($type_uri, $ainfo->type_uri);
+ $this->assertEquals(1, $ainfo->count);
+ $this->assertFalse($ainfo->required);
+ $this->assertTrue($ainfo->alias === null);
+ }
+}
+
+class ToTypeURIsTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->aliases = new Auth_OpenID_NamespaceMap();
+ }
+
+ function test_empty()
+ {
+ foreach (array(null, '') as $empty) {
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases, $empty);
+ $this->assertEquals(array(), $uris);
+ }
+ }
+
+ function test_undefined()
+ {
+ $result = Auth_OpenID_AX_toTypeURIs($this->aliases,
+ 'http://janrain.com/');
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_one()
+ {
+ $uri = 'http://janrain.com/';
+ $alias = 'openid_hackers';
+ $this->aliases->addAlias($uri, $alias);
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases, $alias);
+ $this->assertEquals(array($uri), $uris);
+ }
+
+ function test_two()
+ {
+ $uri1 = 'http://janrain.com/';
+ $alias1 = 'openid_hackers';
+ $this->aliases->addAlias($uri1, $alias1);
+
+ $uri2 = 'http://jyte.com/';
+ $alias2 = 'openid_hack';
+ $this->aliases->addAlias($uri2, $alias2);
+
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases,
+ implode(',', array($alias1, $alias2)));
+ $this->assertEquals(array($uri1, $uri2), $uris);
+ }
+}
+
+class ParseAXValuesTest extends PHPUnit_TestCase {
+ function failUnlessAXKeyError($ax_args)
+ {
+ $msg = new Auth_OpenID_AX_KeyValueMessage();
+ $result = $msg->parseExtensionArgs($ax_args);
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ $this->assertTrue($result->message);
+ }
+
+ function failUnlessAXValues($ax_args, $expected_args)
+ {
+ $msg = new Auth_OpenID_AX_KeyValueMessage();
+ $msg->parseExtensionArgs($ax_args);
+ $this->assertEquals($expected_args, $msg->data);
+ }
+
+ function test_emptyIsValid()
+ {
+ $this->failUnlessAXValues(array(), array());
+ }
+
+ function test_invalidAlias()
+ {
+ $types = array(
+ 'Auth_OpenID_AX_KeyValueMessage',
+ 'Auth_OpenID_AX_FetchRequest'
+ );
+
+ $inputs = array(
+ array('type.a.b' => 'urn:foo',
+ 'count.a.b' => '1'),
+ array('type.a,b' => 'urn:foo',
+ 'count.a,b' => '1'),
+ );
+
+ foreach ($types as $typ) {
+ foreach ($inputs as $input) {
+ $msg = new $typ();
+ $result = $msg->parseExtensionArgs($input);
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+ }
+ }
+
+ function test_missingValueForAliasExplodes()
+ {
+ $this->failUnlessAXKeyError(array('type.foo' => 'urn:foo'));
+ }
+
+ function test_countPresentButNotValue()
+ {
+ $this->failUnlessAXKeyError(array('type.foo' => 'urn:foo',
+ 'count.foo' => '1'));
+ }
+
+ function test_invalidCountValue()
+ {
+ $msg = new Auth_OpenID_AX_FetchRequest();
+
+ $result = $msg->parseExtensionArgs(
+ array('type.foo' => 'urn:foo',
+ 'count.foo' => 'bogus'));
+
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_requestUnlimitedValues()
+ {
+ $msg = new Auth_OpenID_AX_FetchRequest();
+
+ $result = $msg->parseExtensionArgs(
+ array('mode' => 'fetch_request',
+ 'required' => 'foo',
+ 'type.foo' => 'urn:foo',
+ 'count.foo' => Auth_OpenID_AX_UNLIMITED_VALUES));
+
+ $attrs = $msg->iterAttrs();
+ $foo = $attrs[0];
+
+ $this->assertTrue($foo->count == Auth_OpenID_AX_UNLIMITED_VALUES);
+ $this->assertTrue($foo->wantsUnlimitedValues());
+ }
+
+ function test_longAlias()
+ {
+ // Spec minimum length is 32 characters. This is a silly test
+ // for this library, but it's here for completeness.
+ $alias = str_repeat('x', Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH);
+
+ $msg = new Auth_OpenID_AX_KeyValueMessage();
+ $result = $msg->parseExtensionArgs(
+ array('type.' . $alias => 'urn:foo',
+ 'count.' . $alias => '1',
+ 'value.'.$alias.'.1' => 'first')
+ );
+ $this->assertFalse(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_countPresentAndIsZero()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'count.foo' => '0',
+ ), array('urn:foo' => array()));
+ }
+
+ function test_singletonEmpty()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ ), array('urn:foo' => array()));
+ }
+
+ function test_doubleAlias()
+ {
+ $this->failUnlessAXKeyError(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ 'type.bar' => 'urn:foo',
+ 'value.bar' => '',
+ ));
+ }
+
+ function test_doubleSingleton()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ 'type.bar' => 'urn:bar',
+ 'value.bar' => '',
+ ), array('urn:foo' => array(), 'urn:bar' => array()));
+ }
+
+ function test_singletonValue()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => 'Westfall',
+ ), array('urn:foo' => array('Westfall')));
+ }
+}
+
+class FetchRequestTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_FetchRequest();
+ $this->type_a = 'http://janrain.example.com/a';
+ $this->alias_a = 'a';
+ }
+
+ function test_mode()
+ {
+ $this->assertEquals($this->msg->mode, 'fetch_request');
+ }
+
+ function test_construct()
+ {
+ $this->assertEquals(array(), $this->msg->requested_attributes);
+ $this->assertEquals(null, $this->msg->update_url);
+
+ $msg = new Auth_OpenID_AX_FetchRequest('hailstorm');
+ $this->assertEquals(array(), $msg->requested_attributes);
+ $this->assertEquals('hailstorm', $msg->update_url);
+ }
+
+ function test_add()
+ {
+ $uri = 'mud://puddle';
+
+ // Not yet added:
+ $this->assertFalse(in_array($uri, $this->msg->iterTypes()));
+
+ $attr = Auth_OpenID_AX_AttrInfo::make($uri);
+ $this->msg->add($attr);
+
+ // Present after adding
+ $this->assertTrue(in_array($uri, $this->msg->iterTypes()));
+ }
+
+ function test_addTwice()
+ {
+ $uri = 'lightning://storm';
+
+ $attr = Auth_OpenID_AX_AttrInfo::make($uri);
+ $this->msg->add($attr);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->add($attr)));
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $expected_args = array(
+ 'mode' =>'fetch_request',
+ );
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs());
+ }
+
+ function test_getExtensionArgs_noAlias()
+ {
+ $attr = Auth_OpenID_AX_AttrInfo::make('type://of.transportation');
+
+ $this->msg->add($attr);
+ $ax_args = $this->msg->getExtensionArgs();
+ $found = false;
+ $alias = null;
+
+ foreach ($ax_args as $k => $v) {
+ if (($v == $attr->type_uri) && (strpos($k, 'type.') === 0)) {
+ $alias = substr($k, 5);
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->fail("Didn't find the type definition");
+ return;
+ }
+
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $alias => $attr->type_uri,
+ 'if_available' => $alias));
+ }
+
+ function test_getExtensionArgs_alias_if_available()
+ {
+ $attr = Auth_OpenID_AX_AttrInfo::make(
+ 'type://of.transportation', 1, false,
+ 'transport');
+
+ $this->msg->add($attr);
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $attr->alias => $attr->type_uri,
+ 'if_available' => $attr->alias));
+ }
+
+ function test_getExtensionArgs_alias_req()
+ {
+ $attr = Auth_OpenID_AX_AttrInfo::make(
+ 'type://of.transportation',
+ 1, true, 'transport');
+
+ $this->msg->add($attr);
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $attr->alias => $attr->type_uri,
+ 'required' => $attr->alias));
+ }
+
+ /*
+ * Make sure that getExtensionArgs has the expected result
+ *
+ * This method will fill in the mode.
+ */
+ function failUnlessExtensionArgs($expected_args)
+ {
+ $expected_args['mode'] = $this->msg->mode;
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs());
+ }
+
+ function test_isIterable()
+ {
+ $this->assertEquals(array(), $this->msg->iterAttrs());
+ $this->assertEquals(array(), $this->msg->iterTypes());
+ }
+
+ function test_getRequiredAttrs_empty()
+ {
+ $this->assertEquals(array(), $this->msg->getRequiredAttrs());
+ }
+
+ function test_parseExtensionArgs_extraType()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a);
+
+ $this->assertTrue(Auth_OpenID_AX::isError(
+ $this->msg->parseExtensionArgs($extension_args)));
+ }
+
+ function test_parseExtensionArgs()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals(array($this->type_a), $this->msg->iterTypes());
+ $attr_info = Auth_OpenID::arrayGet($this->msg->requested_attributes,
+ $this->type_a);
+ $this->assertTrue($attr_info);
+ $this->assertFalse($attr_info->required);
+ $this->assertEquals($this->type_a, $attr_info->type_uri);
+ $this->assertEquals($this->alias_a, $attr_info->alias);
+ $this->assertEquals(array($attr_info),
+ $this->msg->iterAttrs());
+ }
+
+ function test_extensionArgs_idempotent()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args, $this->msg->getExtensionArgs());
+
+ $attr = $this->msg->requested_attributes[$this->type_a];
+ $this->assertFalse($attr->required);
+ }
+
+ function test_extensionArgs_idempotent_count_required()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '2',
+ 'required' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args, $this->msg->getExtensionArgs());
+
+ $attr = $this->msg->requested_attributes[$this->type_a];
+ $this->assertTrue($attr->required);
+ }
+
+ function test_extensionArgs_count1()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '1',
+ 'if_available' => $this->alias_a);
+
+ $extension_args_norm = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args_norm, $this->msg->getExtensionArgs());
+ }
+
+ function test_openidNoRealm()
+ {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'checkid_setup',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.update_url' => 'http://different.site/path',
+ 'ax.mode' => 'fetch_request',
+ ));
+ $openid_req = new Auth_OpenID_Request();
+ $openid_req->message =& $openid_req_msg;
+ $result = Auth_OpenID_AX_FetchRequest::fromOpenIDRequest(
+ $openid_req);
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_openidUpdateURLVerificationError()
+ {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'checkid_setup',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'realm' => 'http://example.com/realm',
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.update_url' => 'http://different.site/path',
+ 'ax.mode' => 'fetch_request',
+ ));
+ $openid_req = new Auth_OpenID_Request();
+ $openid_req->message =& $openid_req_msg;
+ $result = Auth_OpenID_AX_FetchRequest::fromOpenIDRequest($openid_req);
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_openidUpdateURLVerificationSuccess()
+ {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'checkid_setup',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'realm' => 'http://example.com/realm',
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.update_url' => 'http://example.com/realm/update_path',
+ 'ax.mode' => 'fetch_request',
+ ));
+ $openid_req = new Auth_OpenID_Request();
+ $openid_req->message =& $openid_req_msg;
+ $fr = Auth_OpenID_AX_FetchRequest::fromOpenIDRequest($openid_req);
+ $this->assertFalse(Auth_OpenID_AX::isError($fr));
+ }
+
+ function test_openidUpdateURLVerificationSuccessReturnTo()
+ {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'checkid_setup',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'return_to' => 'http://example.com/realm',
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.update_url' => 'http://example.com/realm/update_path',
+ 'ax.mode' => 'fetch_request',
+ ));
+ $openid_req = new Auth_OpenID_Request();
+ $openid_req->message =& $openid_req_msg;
+ $fr = Auth_OpenID_AX_FetchRequest::fromOpenIDRequest($openid_req);
+ $this->assertFalse(Auth_OpenID_AX::isError($fr));
+ }
+}
+
+class FauxEndpoint {
+ function FauxEndpoint() {
+ $this->claimed_id = 'http://some.url/';
+ }
+}
+
+class FetchResponseTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_FetchResponse();
+ $this->value_a = 'monkeys';
+ $this->type_a = 'http://phone.home/';
+ $this->alias_a = 'robocop';
+ $this->request_update_url = 'http://update.bogus/';
+ }
+
+ function test_construct()
+ {
+ $this->assertTrue($this->msg->update_url === null);
+ $this->assertEquals(array(), $this->msg->data);
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ );
+ $req = null;
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_empty_request()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ );
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_empty_request_some()
+ {
+ $uri = 'http://not.found/';
+ $alias = 'ext0';
+
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ 'type.' . $alias => $uri,
+ 'count.' . $alias => '0'
+ );
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $req->add(Auth_OpenID_AX_AttrInfo::make('http://not.found/'));
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_updateUrlInResponse()
+ {
+ $uri = 'http://not.found/';
+ $alias = 'ext0';
+
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ 'update_url' => $this->request_update_url,
+ 'type.' . $alias => $uri,
+ 'count.' . $alias => '0'
+ );
+ $req = new Auth_OpenID_AX_FetchRequest($this->request_update_url);
+ $req->add(Auth_OpenID_AX_AttrInfo::make($uri));
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_some_request()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'value.' . $this->alias_a . '.1' => $this->value_a,
+ 'count.' . $this->alias_a => '1'
+ );
+
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $req->add(Auth_OpenID_AX_AttrInfo::make($this->type_a, 1, false, $this->alias_a));
+ $this->msg->addValue($this->type_a, $this->value_a);
+
+ $result = $this->msg->getExtensionArgs($req);
+ $this->assertEquals($expected_args, $result);
+ }
+
+ function test_getExtensionArgs_some_not_request()
+ {
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->msg->addValue($this->type_a, $this->value_a);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->getExtensionArgs($req)));
+ }
+
+ function test_getSingle_success()
+ {
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->msg->addValue($this->type_a, $this->value_a);
+ $this->assertEquals($this->value_a, $this->msg->getSingle($this->type_a));
+ }
+
+ function test_getSingle_none()
+ {
+ $this->assertEquals(null, $this->msg->getSingle($this->type_a));
+ }
+
+ function test_getSingle_extra()
+ {
+ $data = array('x', 'y');
+ $this->msg->setValues($this->type_a, $data);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->getSingle($this->type_a)));
+ }
+
+ function test_get()
+ {
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->get($this->type_a)));
+ }
+
+ function test_fromSuccessResponseWithoutExtension()
+ {
+ $args = array(
+ 'mode' => 'id_res',
+ 'ns' => Auth_OpenID_OPENID2_NS
+ );
+ $sf = array();
+ foreach (array_keys($args) as $k) {
+ array_push($sf, $k);
+ }
+ $msg = Auth_OpenID_Message::fromOpenIDArgs($args);
+ $e = new FauxEndpoint();
+ $resp = new Auth_OpenID_SuccessResponse($e, $msg, $sf);
+ $ax_resp = Auth_OpenID_AX_FetchResponse::fromSuccessResponse($resp);
+ $this->assertTrue($ax_resp === null);
+ }
+
+ function test_fromSuccessResponseWithoutData()
+ {
+ $args = array(
+ 'mode' => 'id_res',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.mode' => 'fetch_response',
+ );
+ $sf = array();
+ foreach (array_keys($args) as $k) {
+ array_push($sf, $k);
+ }
+ $msg = Auth_OpenID_Message::fromOpenIDArgs($args);
+ $e = new FauxEndpoint();
+ $resp = new Auth_OpenID_SuccessResponse($e, $msg, $sf);
+ $ax_resp = Auth_OpenID_AX_FetchResponse::fromSuccessResponse($resp);
+ $this->assertTrue($ax_resp === null);
+ }
+
+ function test_fromSuccessResponse()
+ {
+ $name = "ziggy";
+ $value = "stardust";
+ $uri = "http://david.bowie.name/";
+ $args = array(
+ 'mode' => 'id_res',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'ns.ax' => Auth_OpenID_AX_NS_URI,
+ 'ax.mode' => 'fetch_response',
+ 'ax.update_url' => 'http://example.com/realm/update_path',
+ 'ax.type.'.$name => $uri,
+ 'ax.count.'.$name => '1',
+ 'ax.value.'.$name.'.1' => $value,
+ );
+ $sf = array();
+ foreach (array_keys($args) as $k) {
+ array_push($sf, $k);
+ }
+ $msg = Auth_OpenID_Message::fromOpenIDArgs($args);
+ $e = new FauxEndpoint();
+ $resp = new Auth_OpenID_SuccessResponse($e, $msg, $sf);
+ $ax_resp = Auth_OpenID_AX_FetchResponse::fromSuccessResponse($resp, false);
+ $this->assertFalse($ax_resp === null);
+ $this->assertTrue(is_a($ax_resp, 'Auth_OpenID_AX_FetchResponse'));
+ $values = $ax_resp->get($uri);
+ $this->assertEquals(array($value), $values);
+ }
+}
+
+class StoreRequestTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_StoreRequest();
+ $this->type_a = 'http://three.count/';
+ $this->alias_a = 'juggling';
+ }
+
+ function test_construct()
+ {
+ $this->assertEquals(array(), $this->msg->data);
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $args = $this->msg->getExtensionArgs();
+ $expected_args = array(
+ 'mode' => 'store_request',
+ );
+ $this->assertEquals($expected_args, $args);
+ }
+
+ function test_getExtensionArgs_nonempty()
+ {
+ $data = array('foo', 'bar');
+ $this->msg->setValues($this->type_a, $data);
+ $aliases = new Auth_OpenID_NamespaceMap();
+ $aliases->addAlias($this->type_a, $this->alias_a);
+ $args = $this->msg->getExtensionArgs($aliases);
+ $expected_args = array(
+ 'mode' => 'store_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '2',
+ sprintf('value.%s.1', $this->alias_a) => 'foo',
+ sprintf('value.%s.2', $this->alias_a) => 'bar',
+ );
+ $this->assertEquals($expected_args, $args);
+ }
+}
+
+class StoreResponseTest extends PHPUnit_TestCase {
+ function test_success()
+ {
+ $msg = new Auth_OpenID_AX_StoreResponse();
+ $this->assertTrue($msg->succeeded());
+ $this->assertFalse($msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_success'),
+ $msg->getExtensionArgs());
+ }
+
+ function test_fail_nomsg()
+ {
+ $msg = new Auth_OpenID_AX_StoreResponse(false);
+ $this->assertFalse($msg->succeeded());
+ $this->assertFalse($msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_failure'),
+ $msg->getExtensionArgs());
+ }
+
+ function test_fail_msg()
+ {
+ $reason = 'no reason, really';
+ $msg = new Auth_OpenID_AX_StoreResponse(false, $reason);
+ $this->assertFalse($msg->succeeded());
+ $this->assertEquals($reason, $msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_failure',
+ 'error' => $reason), $msg->getExtensionArgs());
+ }
+}
+
+class Tests_Auth_OpenID_AX extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_OpenID_AX";
+ }
+
+ function Tests_Auth_OpenID_AX()
+ {
+ $this->addTestSuite('StoreResponseTest');
+ $this->addTestSuite('StoreRequestTest');
+ $this->addTestSuite('FetchResponseTest');
+ $this->addTestSuite('FetchRequestTest');
+ $this->addTestSuite('ParseAXValuesTest');
+ $this->addTestSuite('ToTypeURIsTest');
+ $this->addTestSuite('AttrInfoTest');
+ $this->addTestSuite('AXMessageTest');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Association.php b/Tests/Auth/OpenID/Association.php
new file mode 100644
index 0000000..9fc6e17
--- /dev/null
+++ b/Tests/Auth/OpenID/Association.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Tests for the Association implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/Association.php';
+
+class Tests_Auth_OpenID_Association extends PHPUnit_TestCase {
+ function test_me()
+ {
+ $issued = time();
+ $lifetime = 600;
+ $assoc = new Auth_OpenID_Association('handle', 'secret', $issued,
+ $lifetime, 'HMAC-SHA1');
+ $s = $assoc->serialize();
+ $assoc2 = Auth_OpenID_Association::deserialize(
+ 'Auth_OpenID_Association', $s);
+
+ if ($assoc2 === null) {
+ $this->fail('deserialize returned null');
+ } else {
+ $this->assertTrue($assoc2->equal($assoc));
+ }
+ }
+ function test_me256()
+ {
+ if(!Auth_OpenID_HMACSHA256_SUPPORTED) return;
+ $issued = time();
+ $lifetime = 600;
+ $assoc = new Auth_OpenID_Association('handle', 'secret', $issued,
+ $lifetime, 'HMAC-SHA256');
+ $s = $assoc->serialize();
+ $assoc2 = Auth_OpenID_Association::deserialize(
+ 'Auth_OpenID_Association', $s);
+
+ if ($assoc2 === null) {
+ $this->fail('deserialize returned null');
+ } else {
+ $this->assertTrue($assoc2->equal($assoc));
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/AssociationResponse.php b/Tests/Auth/OpenID/AssociationResponse.php
new file mode 100644
index 0000000..da80d15
--- /dev/null
+++ b/Tests/Auth/OpenID/AssociationResponse.php
@@ -0,0 +1,379 @@
+<?php
+
+require_once "PHPUnit.php";
+require_once "Tests/Auth/OpenID/TestUtil.php";
+require_once "Tests/Auth/OpenID/MemStore.php";
+
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Server.php";
+require_once "Auth/OpenID/Consumer.php";
+require_once "Auth/OpenID/Association.php";
+
+// Some values we can use for convenience (see mkAssocResponse)
+global $association_response_values;
+$association_response_values = array(
+ 'expires_in' => '1000',
+ 'assoc_handle' => 'a handle',
+ 'assoc_type' => 'a type',
+ 'session_type' => 'a session type',
+ 'ns' => Auth_OpenID_OPENID2_NS
+ );
+
+/**
+ * Build an association response message that contains the specified
+ * subset of keys. The values come from association_response_values.
+ *
+ * This is useful for testing for missing keys and other times that we
+ * don't care what the values are.
+ */
+function mkAssocResponse($keys)
+{
+ global $association_response_values;
+
+ $args = array();
+
+ foreach ($keys as $key) {
+ $args[$key] = $association_response_values[$key];
+ }
+
+ return Auth_OpenID_Message::fromOpenIDArgs($args);
+}
+
+class Tests_Auth_OpenID_AssociationResponse extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ }
+
+ function failUnlessProtocolError($thing)
+ {
+ $this->assertTrue(Auth_OpenID::isFailure($thing));
+ }
+
+ function _run($keys)
+ {
+ $msg = mkAssocResponse($keys);
+ $dumb = null;
+ $this->assertTrue(Auth_OpenID::isFailure($this->consumer->_extractAssociation($msg, $dumb)));
+ }
+}
+
+/**
+ * Test for returning an error upon missing fields in association
+ * responses for OpenID 2
+ */
+class TestExtractAssociationMissingFieldsOpenID2 extends Tests_Auth_OpenID_AssociationResponse {
+
+ function test_noFields_openid2()
+ {
+ $this->_run(array('ns'));
+ }
+
+ function test_missingExpires_openid2()
+ {
+ $this->_run(array('assoc_handle', 'assoc_type', 'session_type', 'ns'));
+ }
+
+ function test_missingHandle_openid2()
+ {
+ $this->_run(array('expires_in', 'assoc_type', 'session_type', 'ns'));
+ }
+
+ function test_missingAssocType_openid2()
+ {
+ $this->_run(array('expires_in', 'assoc_handle', 'session_type', 'ns'));
+ }
+
+ function test_missingSessionType_openid2()
+ {
+ $this->_run(array('expires_in', 'assoc_handle', 'assoc_type', 'ns'));
+ }
+}
+
+/**
+ * Test for returning an error upon missing fields in association
+ * responses for OpenID 2
+ */
+class TestExtractAssociationMissingFieldsOpenID1 extends Tests_Auth_OpenID_AssociationResponse {
+ function test_noFields_openid1()
+ {
+ $this->_run(array());
+ }
+
+ function test_missingExpires_openid1()
+ {
+ $this->_run(array('assoc_handle', 'assoc_type'));
+ }
+
+ function test_missingHandle_openid1()
+ {
+ $this->_run(array('expires_in', 'assoc_type'));
+ }
+
+ function test_missingAssocType_openid1()
+ {
+ $this->_run(array('expires_in', 'assoc_handle'));
+ }
+}
+
+class DummyAssocationSession {
+ function DummyAssocationSession($session_type, $allowed_assoc_types=array())
+ {
+ $this->session_type = $session_type;
+ $this->allowed_assoc_types = $allowed_assoc_types;
+ }
+}
+
+class ExtractAssociationSessionTypeMismatch extends Tests_Auth_OpenID_AssociationResponse {
+ function _run($requested_session_type, $response_session_type, $openid1=false)
+ {
+ global $association_response_values;
+
+ $assoc_session = new DummyAssocationSession($requested_session_type);
+ $keys = array_keys($association_response_values);
+ if ($openid1) {
+ if (in_array('ns', $keys)) {
+ unset($keys[array_search('ns', $keys)]);
+ }
+ }
+
+ $msg = mkAssocResponse($keys);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type',
+ $response_session_type);
+ $this->assertTrue(
+ $this->consumer->_extractAssociation($msg, $assoc_session) === null);
+ }
+
+ function test_typeMismatchNoEncBlank_openid2()
+ {
+ $this->_run('no-encryption', '');
+ }
+
+ function test_typeMismatchDHSHA1NoEnc_openid2()
+ {
+ $this->_run('DH-SHA1', 'no-encryption');
+ }
+
+ function test_typeMismatchDHSHA256NoEnc_openid2()
+ {
+ $this->_run('DH-SHA256', 'no-encryption');
+ }
+
+ function test_typeMismatchNoEncDHSHA1_openid2()
+ {
+ $this->_run('no-encryption', 'DH-SHA1');
+ }
+
+ function test_typeMismatchDHSHA1NoEnc_openid1()
+ {
+ $this->_run('DH-SHA1', 'DH-SHA256', true);
+ }
+
+ function test_typeMismatchDHSHA256NoEnc_openid1()
+ {
+ $this->_run('DH-SHA256', 'DH-SHA1', true);
+ }
+
+ function test_typeMismatchNoEncDHSHA1_openid1()
+ {
+ $this->_run('no-encryption', 'DH-SHA1', true);
+ }
+}
+
+class TestOpenID1AssociationResponseSessionType extends Tests_Auth_OpenID_AssociationResponse {
+ function _run($expected_session_type, $session_type_value)
+ {
+ // Create a Message with just 'session_type' in it, since
+ // that's all this function will use. 'session_type' may be
+ // absent if it's set to None.
+ $args = array();
+ if ($session_type_value !== null) {
+ $args['session_type'] = $session_type_value;
+ }
+ $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+ $this->assertTrue($message->isOpenID1());
+
+ $actual_session_type = $this->consumer->_getOpenID1SessionType($message);
+ $error_message = sprintf('Returned sesion type parameter %s was expected ' .
+ 'to yield session type %s, but yielded %s',
+ $session_type_value, $expected_session_type,
+ $actual_session_type);
+ $this->assertEquals(
+ $expected_session_type,
+ $actual_session_type,
+ $error_message);
+ }
+
+ function test_none()
+ {
+ $this->_run('no-encryption', null);
+ }
+
+ function test_empty()
+ {
+ $this->_run('no-encryption', '');
+ }
+
+ function test_explicitNoEncryption()
+ {
+ $this->_run('no-encryption', 'no-encryption');
+ }
+
+ function test_dhSHA1()
+ {
+ $this->_run('DH-SHA1', 'DH-SHA1');
+ }
+
+ // DH-SHA256 is not a valid session type for OpenID1, but this
+ // function does not test that. This is mostly just to make sure
+ // that it will pass-through stuff that is not explicitly handled,
+ // so it will get handled the same way as it is handled for OpenID
+ // 2
+ function test_dhSHA256()
+ {
+ $this->_run('DH-SHA256', 'DH-SHA256');
+ }
+}
+
+class DummyAssociationSession {
+ var $secret = "shh! don't tell!";
+ var $extract_secret_called = false;
+ var $session_type = null;
+ var $allowed_assoc_types = null;
+
+ function extractSecret($message)
+ {
+ $this->extract_secret_called = true;
+ return $this->secret;
+ }
+}
+
+class TestInvalidFields extends Tests_Auth_OpenID_AssociationResponse {
+ function setUp()
+ {
+ parent::setUp();
+ $this->session_type = 'testing-session';
+
+ // This must something that works for Association.fromExpiresIn
+ $this->assoc_type = 'HMAC-SHA1';
+
+ $this->assoc_handle = 'testing-assoc-handle';
+
+ // These arguments should all be valid
+ $this->assoc_response = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'expires_in' => '1000',
+ 'assoc_handle' => $this->assoc_handle,
+ 'assoc_type' => $this->assoc_type,
+ 'session_type' => $this->session_type,
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ ));
+
+ $this->assoc_session = new DummyAssociationSession();
+
+ // Make the session for the response's session type
+ $this->assoc_session->session_type = $this->session_type;
+ $this->assoc_session->allowed_assoc_types = array($this->assoc_type);
+ }
+
+ function test_worksWithGoodFields()
+ {
+ // Handle a full successful association response
+ $assoc = $this->consumer->_extractAssociation(
+ $this->assoc_response, $this->assoc_session);
+ $this->assertTrue($this->assoc_session->extract_secret_called);
+ $this->assertEquals($this->assoc_session->secret, $assoc->secret);
+ $this->assertEquals(1000, $assoc->lifetime);
+ $this->assertEquals($this->assoc_handle, $assoc->handle);
+ $this->assertEquals($this->assoc_type, $assoc->assoc_type);
+ }
+
+ function test_badAssocType()
+ {
+ // Make sure that the assoc type in the response is not valid
+ // for the given session.
+ $this->assoc_session->allowed_assoc_types = array();
+ $this->assertTrue(
+ $this->consumer->_extractAssociation($this->assoc_response,
+ $this->assoc_session) === null);
+ }
+
+ function test_badExpiresIn()
+ {
+ // Invalid value for expires_in should cause failure
+ $this->assoc_response->setArg(Auth_OpenID_OPENID_NS, 'expires_in', 'forever');
+ $assoc = $this->consumer->_extractAssociation($this->assoc_response,
+ $this->assoc_session);
+ $this->assertTrue(Auth_OpenID::isFailure($assoc));
+ }
+}
+
+class TestExtractAssociationDiffieHellman extends Tests_Auth_OpenID_AssociationResponse {
+ var $secret = 'xxxxxxxxxxxxxxxxxxxx';
+
+ function _setUpDH()
+ {
+ list($sess, $message) = $this->consumer->_createAssociateRequest(
+ $this->endpoint, 'HMAC-SHA1', 'DH-SHA1');
+
+ // XXX: this is testing _createAssociateRequest
+ $this->assertEquals($this->endpoint->compatibilityMode(),
+ $message->isOpenID1());
+
+ $server_sess = Auth_OpenID_DiffieHellmanSHA1ServerSession::fromMessage($message);
+ $server_resp = $server_sess->answer($this->secret);
+ $server_resp['assoc_type'] = 'HMAC-SHA1';
+ $server_resp['assoc_handle'] = 'handle';
+ $server_resp['expires_in'] = '1000';
+ $server_resp['session_type'] = 'DH-SHA1';
+ return array($sess, Auth_OpenID_Message::fromOpenIDArgs($server_resp));
+ }
+
+ function test_success()
+ {
+ list($sess, $server_resp) = $this->_setUpDH();
+ $ret = $this->consumer->_extractAssociation($server_resp, $sess);
+ $this->assertTrue($ret !== null);
+ $this->assertEquals($ret->assoc_type, 'HMAC-SHA1');
+ $this->assertEquals($ret->secret, $this->secret);
+ $this->assertEquals($ret->handle, 'handle');
+ $this->assertEquals($ret->lifetime, 1000);
+ }
+
+ function test_openid2success()
+ {
+ // Use openid 2 type in endpoint so _setUpDH checks
+ // compatibility mode state properly
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_2_0,
+ Auth_OpenID_TYPE_1_1);
+ $this->test_success();
+ }
+
+ /**
+ * Can't run this test because the base64 decoder is broken.
+ */
+ /*
+ function test_badDHValues()
+ {
+ list($sess, $server_resp) = $this->_setUpDH();
+ $server_resp->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key', "\x00\x00\x00");
+ $this->assertTrue($this->consumer->_extractAssociation($server_resp, $sess) === null);
+ }
+ */
+}
+
+global $Tests_Auth_OpenID_AssociationResponse_other;
+$Tests_Auth_OpenID_AssociationResponse_other = array(
+ new TestInvalidFields(),
+ new TestOpenID1AssociationResponseSessionType(),
+ new ExtractAssociationSessionTypeMismatch(),
+ new TestExtractAssociationMissingFieldsOpenID1(),
+ new TestExtractAssociationMissingFieldsOpenID2()
+ );
+
+if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $Tests_Auth_OpenID_AssociationResponse_other[] = new TestExtractAssociationDiffieHellman();
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/AuthRequest.php b/Tests/Auth/OpenID/AuthRequest.php
new file mode 100644
index 0000000..63e4d4e
--- /dev/null
+++ b/Tests/Auth/OpenID/AuthRequest.php
@@ -0,0 +1,302 @@
+<?php
+
+require_once 'PHPUnit.php';
+
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+
+require_once 'Auth/OpenID/Association.php';
+require_once 'Auth/OpenID/Consumer.php';
+
+class AuthRequest_DummyEndpoint {
+ var $preferred_namespace = null;
+ var $local_id = null;
+ var $server_url = null;
+ var $is_op_identifier = false;
+
+ function preferredNamespace()
+ {
+ return $this->preferred_namespace;
+ }
+
+ function getLocalID()
+ {
+ return $this->local_id;
+ }
+
+ function isOPIdentifier()
+ {
+ return $this->is_op_identifier;
+ }
+}
+
+class AuthRequest_DummyAssoc {
+ var $handle = "assoc-handle";
+}
+
+/**
+ * Base for AuthRequest tests for OpenID 1 and 2.
+ */
+class TestAuthRequestMixin extends OpenIDTestMixin {
+
+ var $preferred_namespace = null;
+ var $immediate = false;
+ var $expected_mode = 'checkid_setup';
+
+ function setUp()
+ {
+ $this->endpoint = new AuthRequest_DummyEndpoint();
+ $this->endpoint->local_id = 'http://server.unittest/joe';
+ $this->endpoint->claimed_id = 'http://joe.vanity.example/';
+ $this->endpoint->server_url = 'http://server.unittest/';
+ $this->endpoint->preferred_namespace = $this->preferred_namespace;
+ $this->realm = 'http://example/';
+ $this->return_to = 'http://example/return/';
+ $this->assoc = new AuthRequest_DummyAssoc();
+ $this->authreq = new Auth_OpenID_AuthRequest($this->endpoint, $this->assoc);
+ }
+
+ function failUnlessAnonymous($msg)
+ {
+ foreach (array('claimed_id', 'identity') as $key) {
+ $this->failIfOpenIDKeyExists($msg, $key);
+ }
+ }
+
+ function failUnlessHasRequiredFields($msg)
+ {
+ $this->assertEquals($this->preferred_namespace,
+ $this->authreq->message->getOpenIDNamespace());
+
+ $this->assertEquals($this->preferred_namespace,
+ $msg->getOpenIDNamespace());
+
+ $this->failUnlessOpenIDValueEquals($msg, 'mode',
+ $this->expected_mode);
+
+ // Implement these in subclasses because they depend on
+ // protocol differences!
+ $this->failUnlessHasRealm($msg);
+ $this->failUnlessIdentifiersPresent($msg);
+ }
+
+ // TESTS
+
+ function test_checkNoAssocHandle()
+ {
+ $this->authreq->assoc = null;
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+
+ $this->failIfOpenIDKeyExists($msg, 'assoc_handle');
+ }
+
+ function test_checkWithAssocHandle()
+ {
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+
+ $this->failUnlessOpenIDValueEquals($msg, 'assoc_handle',
+ $this->assoc->handle);
+ }
+
+ function test_addExtensionArg()
+ {
+ $this->authreq->addExtensionArg('bag:', 'color', 'brown');
+ $this->authreq->addExtensionArg('bag:', 'material', 'paper');
+ $this->assertTrue($this->authreq->message->namespaces->contains('bag:'));
+ $this->assertEquals($this->authreq->message->getArgs('bag:'),
+ array('color' => 'brown',
+ 'material' => 'paper'));
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+
+ // XXX: this depends on the way that Message assigns
+ // namespaces. Really it doesn't care that it has alias "0",
+ // but that is tested anyway
+ $post_args = $msg->toPostArgs();
+ $this->assertEquals('brown', $post_args['openid.ext0.color']);
+ $this->assertEquals('paper', $post_args['openid.ext0.material']);
+ }
+
+ function test_standard()
+ {
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+
+ $this->failUnlessHasIdentifiers(
+ $msg, $this->endpoint->local_id,
+ $this->endpoint->claimed_id);
+ }
+}
+
+class TestAuthRequestOpenID2 extends TestAuthRequestMixin {
+ var $preferred_namespace = Auth_OpenID_OPENID2_NS;
+
+ function failUnlessHasRealm($msg)
+ {
+ // check presence of proper realm key and absence of the wrong
+ // one.
+ $this->failUnlessOpenIDValueEquals($msg, 'realm', $this->realm);
+ $this->failIfOpenIDKeyExists($msg, 'trust_root');
+ }
+
+ function failUnlessIdentifiersPresent($msg)
+ {
+ $identity_present = $msg->hasKey(Auth_OpenID_OPENID_NS, 'identity');
+ $claimed_present = $msg->hasKey(Auth_OpenID_OPENID_NS, 'claimed_id');
+
+ $this->assertEquals($claimed_present, $identity_present);
+ }
+
+ function failUnlessHasIdentifiers($msg, $op_specific_id, $claimed_id)
+ {
+ $this->failUnlessOpenIDValueEquals($msg, 'identity', $op_specific_id);
+ $this->failUnlessOpenIDValueEquals($msg, 'claimed_id', $claimed_id);
+ }
+
+ // TESTS
+
+ function test_markup_checkidImmediate()
+ {
+ $result = $this->authreq->formMarkup($this->realm,
+ null, true);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_markup_returnToArgs()
+ {
+ $this->authreq->return_to_args = array('extra' => 'args');
+ $result = $this->authreq->formMarkup($this->realm,
+ null, false);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_setAnonymousWorksForOpenID2()
+ {
+ // OpenID AuthRequests should be able to set 'anonymous' to true.
+ $this->assertTrue($this->authreq->message->isOpenID2());
+ $this->assertTrue($this->authreq->setAnonymous(true));
+ $this->assertTrue($this->authreq->setAnonymous(false));
+ }
+
+ function test_userAnonymousIgnoresIdentfier()
+ {
+ $this->authreq->setAnonymous(true);
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+ $this->failUnlessHasRequiredFields($msg);
+ $this->failUnlessAnonymous($msg);
+ }
+
+ function test_opAnonymousIgnoresIdentifier()
+ {
+ $this->endpoint->is_op_identifier = true;
+ $this->authreq->setAnonymous(true);
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+ $this->failUnlessHasRequiredFields($msg);
+ $this->failUnlessAnonymous($msg);
+ }
+
+ function test_opIdentifierSendsIdentifierSelect()
+ {
+ $this->endpoint->is_op_identifier = true;
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+ $this->failUnlessHasRequiredFields($msg);
+ $this->failUnlessHasIdentifiers($msg,
+ Auth_OpenID_IDENTIFIER_SELECT,
+ Auth_OpenID_IDENTIFIER_SELECT);
+ }
+}
+
+class TestAuthRequestOpenID1 extends TestAuthRequestMixin {
+ var $preferred_namespace = Auth_OpenID_OPENID1_NS;
+
+ function setUpEndpoint()
+ {
+ parent::setUpEndpoint();
+ $this->endpoint->preferred_namespace = Auth_OpenID_OPENID1_NS;
+ }
+
+ function failUnlessHasIdentifiers($msg, $op_specific_id, $claimed_id)
+ {
+ // Make sure claimed_is is *absent* in request.
+ $this->failUnlessOpenIDValueEquals($msg, 'identity', $op_specific_id);
+ $this->failIfOpenIDKeyExists($msg, 'claimed_id');
+ }
+
+ function failUnlessIdentifiersPresent($msg)
+ {
+ $this->failIfOpenIDKeyExists($msg, 'claimed_id');
+ $this->assertTrue($msg->hasKey(Auth_OpenID_OPENID_NS, 'identity'));
+ }
+
+ function failUnlessHasRealm($msg)
+ {
+ // check presence of proper realm key and absence of the wrong
+ // one.
+ $this->failUnlessOpenIDValueEquals($msg, 'trust_root', $this->realm);
+ $this->failIfOpenIDKeyExists($msg, 'realm');
+ }
+
+ // TESTS
+
+ function test_markup_missingReturnTo()
+ {
+ $result = $this->authreq->formMarkup($this->realm,
+ null, false);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_setAnonymousFailsForOpenID1()
+ {
+ // OpenID 1 requests MUST NOT be able to set anonymous to True
+ $this->assertTrue($this->authreq->message->isOpenID1());
+ $this->assertFalse($this->authreq->setAnonymous(true));
+ $this->assertTrue($this->authreq->setAnonymous(false));
+ }
+
+ function test_identifierSelect()
+ {
+ // Identfier select SHOULD NOT be sent, but this pathway is in
+ // here in case some special discovery stuff is done to
+ // trigger it with OpenID 1. If it is triggered, it will send
+ // identifier_select just like OpenID 2.
+ $this->endpoint->is_op_identifier = true;
+ $msg = $this->authreq->getMessage($this->realm, $this->return_to,
+ $this->immediate);
+ $this->failUnlessHasRequiredFields($msg);
+ $this->assertEquals(Auth_OpenID_IDENTIFIER_SELECT,
+ $msg->getArg(Auth_OpenID_OPENID1_NS,
+ 'identity'));
+ }
+}
+
+class TestAuthRequestOpenID1Immediate extends TestAuthRequestOpenID1 {
+ var $immediate = true;
+ var $expected_mode = 'checkid_immediate';
+}
+
+class TestAuthRequestOpenID2Immediate extends TestAuthRequestOpenID2 {
+ var $immediate = true;
+ var $expected_mode = 'checkid_immediate';
+}
+
+class Tests_Auth_OpenID_AuthRequest extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_OpenID_AuthRequest";
+ }
+
+ function Tests_Auth_OpenID_AuthRequest()
+ {
+ $this->addTestSuite('TestAuthRequestOpenID1');
+ $this->addTestSuite('TestAuthRequestOpenID1Immediate');
+ $this->addTestSuite('TestAuthRequestOpenID2');
+ $this->addTestSuite('TestAuthRequestOpenID2Immediate');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/BigMath.php b/Tests/Auth/OpenID/BigMath.php
new file mode 100644
index 0000000..2a51871
--- /dev/null
+++ b/Tests/Auth/OpenID/BigMath.php
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * Tests for the BigMath functions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/BigMath.php';
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+
+class Tests_Auth_OpenID_BinLongConvertRnd extends PHPUnit_TestCase {
+ var $lib;
+ var $max;
+
+ function Tests_Auth_OpenID_BinLongConvertRnd(&$lib, $max)
+ {
+ $this->lib =& $lib;
+ $this->max = $max;
+ }
+
+ function runTest()
+ {
+ $n = $this->lib->init(0);
+ foreach (range(0, 9) as $i) {
+ $rnd = $this->lib->rand($this->max);
+ $n = $this->lib->add($n, $rnd);
+ }
+ $s = $this->lib->longToBinary($n);
+ $this->assertTrue(is_string($s));
+ $n_prime = $this->lib->binaryToLong($s);
+ $this->assertEquals($this->lib->cmp($n, $n_prime), 0);
+ }
+}
+
+class Tests_Auth_OpenID_BinLongConvert extends PHPUnit_TestCase {
+ var $lib;
+ var $bin;
+ var $lng;
+
+ function Tests_Auth_OpenID_BinLongConvert(&$lib, $bin, $lng)
+ {
+ $this->lib =& $lib;
+ $this->bin = $bin;
+ $this->lng = $lng;
+ }
+
+ function runTest()
+ {
+ $n_prime = $this->lib->binaryToLong($this->bin);
+ $s_prime = $this->lib->longToBinary($this->lng);
+ $this->assertEquals($this->lib->cmp($this->lng, $n_prime), 0);
+ $this->assertTrue($this->bin == $s_prime);
+ }
+}
+
+class Tests_Auth_OpenID_Base64ToLong extends PHPUnit_TestCase {
+ var $num;
+ var $b64;
+ var $lib;
+
+ function Tests_Auth_OpenID_Base64ToLong(&$lib, $b64, $num)
+ {
+ $this->lib = $lib;
+ $this->b64 = $b64;
+ $this->num = $num;
+ }
+
+ function runTest()
+ {
+ $actual = $this->lib->base64ToLong($this->b64);
+ $this->assertTrue($this->lib->cmp($this->num, $actual) == 0);
+ }
+}
+
+class Tests_Auth_OpenID_LongToBase64 extends Tests_Auth_OpenID_Base64ToLong {
+ function Tests_Auth_OpenID_LongToBase64(&$lib, $b64, $num)
+ {
+ $this->lib = $lib;
+ $this->b64 = $b64;
+ $this->num = $num;
+ }
+
+ function runTest()
+ {
+ $actual = $this->lib->longToBase64($this->num);
+ $this->assertEquals($this->b64, $actual);
+ }
+}
+
+class Tests_Auth_OpenID_Rand extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Rand(&$lib)
+ {
+ $this->lib =& $lib;
+ }
+
+ function runTest()
+ {
+ $stop = $this->lib->pow(2, 128);
+ $a = $this->lib->rand($stop);
+ $b = $this->lib->rand($stop);
+
+ $this->assertFalse($this->lib->cmp($b, $a) == 0, "Same: $a $b");
+
+ $n = $this->lib->init(Tests_Auth_OpenID_maxint());
+ $n = $this->lib->add($n, 1);
+
+ // Make sure that we can generate random numbers that are
+ // larger than platform int size
+ $result = $this->lib->rand($n);
+
+ // What can we say about the result?
+ }
+}
+
+/**
+ * Computes the maximum integer value for this PHP installation.
+ *
+ * @return int $max_int_value The maximum integer value for this
+ * PHP installation
+ */
+function Tests_Auth_OpenID_maxint()
+{
+ /* assumes largest integer is of form 2^n - 1 */
+ $to_test = pow(2, 16);
+ while (1) {
+ $last = $to_test;
+ $to_test = 2 * $to_test;
+ if (($to_test < $last) || (!is_int($to_test))) {
+ return($last + ($last - 1));
+ }
+ }
+}
+
+
+class Tests_Auth_OpenID_BigMath extends PHPUnit_TestSuite {
+ function _parseBase64Data()
+ {
+ $lines = Tests_Auth_OpenID_readlines('n2b64');
+
+ $data = array();
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if (!$line) {
+ continue;
+ }
+ list($b64, $ascii) = explode(' ', $line);
+ $data[$b64] = $ascii;
+ }
+ return $data;
+ }
+
+ function _addB64Tests()
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $count = defined('Tests_Auth_OpenID_thorough') ? -1 : 2;
+ $data = $this->_parseBase64Data();
+ foreach ($data as $b64 => $num_s) {
+ // Only test the first few unless thorough is defined
+ if (strlen($num_s) > 5) {
+ if ($count == 0) {
+ break;
+ } else {
+ $count -= 1;
+ }
+ }
+ $num = $lib->init($num_s);
+ $test = new Tests_Auth_OpenID_Base64ToLong($lib, $b64, $num);
+ $test->setName("B64->Long $num_s");
+ $this->addTest($test);
+
+ $test = new Tests_Auth_OpenID_LongToBase64($lib, $b64, $num);
+ $test->setName("Long->B64 $num_s");
+ $this->addTest($test);
+ }
+ }
+
+ function _addBinLongTests()
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $max = Tests_Auth_OpenID_maxint();
+ $upper = defined('Tests_Auth_OpenID_thorough') ? 499 : 3;
+
+ foreach (range(0, $upper) as $iteration) {
+ $test = new Tests_Auth_OpenID_BinLongConvertRnd($lib, $max);
+ $test->setName("BinLongConvertRnd " . strval($iteration));
+ $this->addTest($test);
+ }
+
+ $cases = array(
+ array("\x00", 0),
+ array("\x01", 1),
+ array("\x7F", 127),
+ array("\x00\x80", 128),
+ array("\x00\x81", 129),
+ array("\x00\xFF", 255),
+ array("\x00\x80\x00", 32768),
+ array("OpenID is cool",
+ "1611215304203901150134421257416556")
+ );
+
+ foreach ($cases as $case) {
+ list($bin, $lng_m) = $case;
+ $lng = $lib->init($lng_m);
+ $test = new Tests_Auth_OpenID_BinLongConvert($lib, $bin, $lng);
+ $test->setName('BinLongConvert ' . bin2hex($bin));
+ $this->addTest($test);
+ }
+
+ }
+
+ function Tests_Auth_OpenID_BigMath($name)
+ {
+ $this->setName($name);
+
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $this->addTestSuite('Tests_Auth_OpenID_BigInt');
+ $this->_addB64Tests();
+ $this->_addBinLongTests();
+ $test = new Tests_Auth_OpenID_Rand(Auth_OpenID_getMathLib());
+ $test->setName('Big number rand');
+ $this->addTest($test);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Consumer.php b/Tests/Auth/OpenID/Consumer.php
new file mode 100644
index 0000000..a762a9d
--- /dev/null
+++ b/Tests/Auth/OpenID/Consumer.php
@@ -0,0 +1,2532 @@
+<?php
+
+/**
+ * Tests for the OpenID consumer.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+@session_start();
+
+require_once 'Auth/OpenID/CryptUtil.php';
+require_once 'Auth/Yadis/HTTPFetcher.php';
+require_once 'Auth/OpenID/DiffieHellman.php';
+require_once 'Auth/OpenID/FileStore.php';
+require_once 'Auth/OpenID/KVForm.php';
+require_once 'Auth/OpenID/Consumer.php';
+require_once 'Auth/OpenID/Server.php';
+require_once 'Auth/OpenID/Nonce.php';
+require_once 'Auth/OpenID/SReg.php';
+require_once 'Auth/OpenID/Message.php';
+require_once 'Tests/Auth/OpenID/MemStore.php';
+require_once 'PHPUnit.php';
+
+/*
+ * Convenience function to create a SuccessResponse with the given
+ * arguments, all signed.
+ */
+function mkSuccess($endpoint, $q)
+{
+ $signed_list = array();
+ foreach (array_keys($q) as $k) {
+ $signed_list[] = 'openid.' . $k;
+ }
+ return new Auth_OpenID_SuccessResponse($endpoint,
+ Auth_OpenID_Message::fromOpenIDArgs($q),
+ $signed_list);
+}
+
+class FastConsumerSession extends Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+ function FastConsumerSession($dh = null)
+ {
+ if ($dh === null) {
+ $dh = new Auth_OpenID_DiffieHellman(100389557, 2);
+ }
+
+ $this->dh = $dh;
+ }
+}
+
+function setConsumerSession(&$con)
+{
+ $con->session_types = array('DH-SHA1' => 'FastConsumerSession');
+}
+
+global $_Auth_OpenID_assocs;
+$_Auth_OpenID_assocs = array(
+ array('another 20-byte key.', 'Snarky'),
+ array(str_repeat("\x00", 20), 'Zeros'),
+ );
+
+function Auth_OpenID_parse($qs)
+{
+ $result = array();
+ $parts = explode("&", $qs);
+ foreach ($parts as $pair) {
+ list($key, $value) = explode("=", $pair, 2);
+ assert(!array_key_exists($key, $result));
+ $result[$key] = urldecode($value);
+ }
+ return $result;
+}
+
+function Auth_OpenID_associate($qs, $assoc_secret, $assoc_handle)
+{
+ $query_data = Auth_OpenID_parse($qs);
+
+ assert($query_data['openid.mode'] == 'associate');
+ assert($query_data['openid.assoc_type'] == 'HMAC-SHA1');
+
+ $reply_dict = array(
+ 'assoc_type' => 'HMAC-SHA1',
+ 'assoc_handle' => $assoc_handle,
+ 'expires_in' => '600',
+ );
+
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ assert(count($query_data) == 2);
+ $message = Auth_OpenID_Message::fromPostArgs($query_data);
+ $session = Auth_OpenID_PlainTextServerSession::fromMessage($message);
+ } else {
+ assert((count($query_data) == 6) || (count($query_data) == 4));
+ assert($query_data['openid.mode'] == 'associate');
+ assert($query_data['openid.session_type'] == 'DH-SHA1');
+
+ $message = Auth_OpenID_Message::fromPostArgs($query_data);
+
+ $session = Auth_OpenID_DiffieHellmanSHA1ServerSession::fromMessage($message);
+ $reply_dict['session_type'] = 'DH-SHA1';
+
+ }
+
+ $reply_dict = array_merge($reply_dict, $session->answer($assoc_secret));
+ return Auth_OpenID_KVForm::fromArray($reply_dict);
+}
+
+class Auth_OpenID_TestFetcher extends Auth_Yadis_HTTPFetcher {
+ function Auth_OpenID_TestFetcher($user_url, $user_page,
+ $assoc_secret, $assoc_handle)
+ {
+ $this->get_responses = array($user_url =>
+ new Auth_Yadis_HTTPResponse($user_url,
+ 200,
+ array(),
+ $user_page));
+ $this->assoc_secret = $assoc_secret;
+ $this->assoc_handle = $assoc_handle;
+ $this->num_assocs = 0;
+ }
+
+ function response($url, $body)
+ {
+ if ($body === null) {
+ return new Auth_Yadis_HTTPResponse($url, 404, array(), 'Not found');
+ } else {
+ return new Auth_Yadis_HTTPResponse($url, 200, array(), $body);
+ }
+ }
+
+ function get($url)
+ {
+ if (array_key_exists($url, $this->get_responses)) {
+ return $this->get_responses[$url];
+ } else {
+ return $this->response($url, null);
+ }
+ }
+
+ function _checkAuth($url, $body)
+ {
+ $query_data = Auth_OpenID_parse($body);
+ $expected = array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.signed' => 'assoc_handle,sig,signed',
+ 'openid.sig' => 'fake',
+ 'openid.assoc_handle' => $this->assoc_handle,
+ );
+
+ if ($query_data == $expected) {
+ return new Auth_Yadis_HTTPResponse($url, 200, array(), "is_valid:true\n");
+ } else {
+ return new Auth_Yadis_HTTPResponse($url, 400, array(),
+ "error:bad check_authentication query\n");
+ }
+ }
+
+ function post($url, $body)
+ {
+ if (strpos($body, 'openid.mode=associate') !== false) {
+ $response = Auth_OpenID_associate($body, $this->assoc_secret,
+ $this->assoc_handle);
+ $this->num_assocs++;
+ return $this->response($url, $response);
+ } elseif (strpos($body, 'openid.mode=check_authentication') !== false) {
+ return $this->_checkAuth($url, $body);
+ }
+
+ return $this->response($url, null);
+ }
+}
+
+global $_Auth_OpenID_user_page_pat;
+$_Auth_OpenID_user_page_pat = "<html>
+ <head>
+ <title>A user page</title>
+ %s
+ </head>
+ <body>
+ blah blah
+ </body>
+</html>";
+
+global $_Auth_OpenID_server_url;
+$_Auth_OpenID_server_url = "http://server.example.com/";
+
+global $_Auth_OpenID_consumer_url;
+$_Auth_OpenID_consumer_url = "http://consumer.example.com/";
+
+class Tests_Auth_OpenID_Consumer extends PHPUnit_TestCase {
+
+ function _run(&$consumer, $user_url, $mode, $delegate_url,
+ &$fetcher, &$store, $immediate)
+ {
+ global $_Auth_OpenID_consumer_url,
+ $_Auth_OpenID_server_url;
+
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ setConsumerSession($consumer);
+ }
+
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->claimed_id = $user_url;
+ $endpoint->server_url = $_Auth_OpenID_server_url;
+ $endpoint->local_id = $delegate_url;
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+
+ $result = $consumer->begin($endpoint);
+
+ $return_to = $_Auth_OpenID_consumer_url;
+ $trust_root = $_Auth_OpenID_consumer_url;
+ $redirect_url = $result->redirectURL($trust_root, $return_to,
+ $immediate);
+
+ $parsed = parse_url($redirect_url);
+ $qs = $parsed['query'];
+ $q = Auth_OpenID_parse($qs);
+ $new_return_to = $q['openid.return_to'];
+ unset($q['openid.return_to']);
+
+ $expected = array(
+ 'openid.mode' => $mode,
+ 'openid.identity' => $delegate_url,
+ 'openid.trust_root' => $trust_root,
+ );
+
+ if ($consumer->_use_assocs) {
+ $expected['openid.assoc_handle'] = $fetcher->assoc_handle;
+ }
+
+ $this->assertEquals($expected, $q);
+ $this->assertEquals(0, strpos($redirect_url, $_Auth_OpenID_server_url));
+ $this->assertEquals(0, strpos($new_return_to, $return_to));
+
+ $parsed = parse_url($new_return_to);
+ $query = Auth_OpenID_parse($parsed['query']);
+
+ $query = array_merge($query, array(
+ 'openid.mode'=> 'id_res',
+ 'openid.return_to'=> $new_return_to,
+ 'openid.identity'=> $delegate_url,
+ 'openid.assoc_handle'=> $fetcher->assoc_handle,
+ ));
+
+ if (!$consumer->_use_assocs) {
+ $query['openid.signed'] =
+ 'assoc_handle,mode,signed,identity';
+ $query['openid.assoc_handle'] = $fetcher->assoc_handle;
+ $query['openid.sig'] = 'fake';
+ }
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ if ($consumer->_use_assocs) {
+ $assoc = $store->getAssociation($_Auth_OpenID_server_url,
+ $fetcher->assoc_handle);
+ $message = $assoc->signMessage($message);
+ }
+
+ $result = $consumer->complete($message, $result->endpoint, $new_return_to);
+
+ $this->assertEquals(Auth_OpenID_SUCCESS, $result->status);
+ $this->assertEquals($result->identity_url, $user_url);
+ }
+
+ function _test_success($user_url, $delegate_url, $links, $immediate = false)
+ {
+ global $_Auth_OpenID_filestore_base_dir,
+ $_Auth_OpenID_server_url,
+ $_Auth_OpenID_user_page_pat,
+ $_Auth_OpenID_assocs;
+
+ $store = new Tests_Auth_OpenID_MemStore();
+
+ if ($immediate) {
+ $mode = 'checkid_immediate';
+ } else {
+ $mode = 'checkid_setup';
+ }
+
+ $user_page = sprintf($_Auth_OpenID_user_page_pat, $links);
+ $fetcher = new Auth_OpenID_TestFetcher($user_url, $user_page,
+ $_Auth_OpenID_assocs[0][0],
+ $_Auth_OpenID_assocs[0][1]);
+
+ $consumer = new Auth_OpenID_GenericConsumer($store);
+ $consumer->fetcher =& $fetcher;
+
+ $expected_num_assocs = 0;
+ $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store, $immediate);
+
+ if ($consumer->_use_assocs) {
+ $expected_num_assocs += 1;
+ }
+
+ $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
+
+ // Test that doing it again uses the existing association
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store, $immediate);
+
+ $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
+
+ // Another association is created if we remove the existing one
+ $store->removeAssociation($_Auth_OpenID_server_url,
+ $fetcher->assoc_handle);
+
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store, $immediate);
+
+ if ($consumer->_use_assocs) {
+ $expected_num_assocs += 1;
+ }
+
+ $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
+
+ // Test that doing it again uses the existing association
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store, $immediate);
+
+ $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
+ }
+
+ function test_success()
+ {
+ global $_Auth_OpenID_server_url;
+
+ $user_url = 'http://www.example.com/user.html';
+ $links = sprintf('<link rel="openid.server" href="%s" />',
+ $_Auth_OpenID_server_url);
+
+ $delegate_url = 'http://consumer.example.com/user';
+ $delegate_links = sprintf('<link rel="openid.server" href="%s" />'.
+ '<link rel="openid.delegate" href="%s" />',
+ $_Auth_OpenID_server_url, $delegate_url);
+
+ $this->_test_success($user_url, $user_url, $links);
+ $this->_test_success($user_url, $user_url, $links, true);
+ $this->_test_success($user_url, $delegate_url, $delegate_links);
+ $this->_test_success($user_url, $delegate_url, $delegate_links, true);
+ }
+}
+
+class ConfigurableConsumer extends Auth_OpenID_GenericConsumer {
+ var $return_to_check_disabled = false;
+
+ function disableReturnToChecking() {
+ $this->return_to_check_disabled = true;
+ }
+
+ function complete($message, $endpoint, $return_to) {
+ if ($this->return_to_check_disabled) {
+ $return_to = null;
+ }
+
+ return parent::complete($message, $endpoint, $return_to);
+ }
+
+ function _checkReturnTo($unused, $unused2) {
+ if ($this->return_to_check_disabled) {
+ return true;
+ } else {
+ return parent::_checkReturnTo($unused, $unused2);
+ }
+ }
+}
+
+class _TestIdRes extends PHPUnit_TestCase {
+ var $consumer_class = 'ConfigurableConsumer';
+
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $cl = $this->consumer_class;
+ $this->consumer = new $cl($this->store);
+ $this->return_to = "http://some.host/path";
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+
+ $this->server_id = "sirod";
+ $this->server_url = "serlie";
+ $this->consumer_id = "consu";
+
+ $this->endpoint->claimed_id = $this->consumer_id;
+ $this->endpoint->server_url = $this->server_url;
+ $this->endpoint->local_id = $this->server_id;
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+ }
+}
+
+class Tests_Auth_OpenID_Consumer_TestSetupNeeded extends _TestIdRes {
+ function failUnlessSetupNeeded($expected_setup_url, $message)
+ {
+ if ($this->consumer._checkSetupNeeded($message)) {
+ $this->assertEquals($expected_setup_url,
+ $message->getArg(Auth_OpenID_OPENID_NS,
+ 'user_setup_url'));
+ } else {
+ $this->fail("Expected to find an immediate-mode response");
+ }
+ }
+
+ function test_setupNeededOpenID1()
+ {
+ // The minimum conditions necessary to trigger Setup Needed
+ $setup_url = 'http://unittest/setup-here';
+ $message = Auth_OpenID_Message::fromPostArgs(array(
+ 'opaenid.mode' => 'id_res',
+ 'openid.user_setup_url' => $setup_url
+ ));
+ $this->assertTrue($message->isOpenID1());
+ $this->failUnlessSetupNeeded($setup_url, $message);
+ }
+
+ function test_setupNeededOpenID1_extra()
+ {
+ // Extra stuff along with setup_url still trigger Setup Needed
+ $setup_url = 'http://unittest/setup-here';
+ $message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.mode' => 'id_res',
+ 'openid.user_setup_url' => $setup_url,
+ 'openid.identity' => 'bogus'
+ ));
+ $this->assertTrue($message->isOpenID1());
+ $this->failUnlessSetupNeeded($setup_url, $message);
+ }
+
+ function test_noSetupNeededOpenID1()
+ {
+ // When the user_setup_url is missing on an OpenID 1 message,
+ // we assume that it's not a cancel response to
+ // checkid_immediate
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array('mode' => 'id_res'));
+ $this->assertTrue($message->isOpenID1());
+
+ // No SetupNeededError raised
+ $this->consumer->_checkSetupNeeded($message);
+ }
+
+ function test_setupNeededOpenID2()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'setup_needed',
+ 'ns' => Auth_OpenID_OPENID2_NS
+ ));
+ $this->assertTrue($message->isOpenID2());
+ $response = $this->consumer->complete($message, null, null);
+ $this->assertEquals('setup_needed', $response->status);
+ $this->assertEquals(null, $response->setup_url);
+ }
+
+ function test_setupNeededDoesntWorkForOpenID1()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'setup_needed'));
+
+ $this->assertFalse($this->consumer._checkSetupNeeded($message));
+
+ $response = $this->consumer->complete($message, null, null);
+ $this->assertEquals('failure', $response->status);
+ $this->assertTrue(strpos($response->message, 'Invalid openid.mode') === 0);
+ }
+
+ function test_noSetupNeededOpenID2()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'id_res',
+ 'game' => 'puerto_rico',
+ 'ns' => Auth_OpenID_OPENID2_NS
+ ));
+ $this->assertTrue($message->isOpenID2());
+
+ $this->assertFalse($this->consumer._checkSetupNeeded($message));
+ }
+}
+
+class IdResCheckForFieldsTest extends _TestIdRes {
+ function setUp() {
+ # Argh.
+ $v = null;
+ $this->consumer = new Auth_OpenID_GenericConsumer($v);
+ }
+
+ function successTest($openid_args, $signed_list) {
+ $message = Auth_OpenID_Message::fromOpenIDArgs($openid_args);
+ $message->setArg(Auth_OpenID_OPENID_NS, 'signed', implode(',', $signed_list));
+ $result = $this->consumer->_idResCheckForFields($message);
+ $this->assertFalse(Auth_OpenID::isFailure($result));
+ }
+
+ function test_openid1Success() {
+ $this->successTest(
+ array('return_to' =>'return',
+ 'assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'identity' =>'someone',
+ ),
+ array('return_to', 'identity'));
+ }
+
+ function test_openid2Success() {
+ $this->successTest(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'return_to' =>'return',
+ 'assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'op_endpoint' =>'my favourite server',
+ 'response_nonce' =>'use only once',
+ ),
+ array('return_to', 'response_nonce', 'assoc_handle'));
+ }
+
+ function test_openid2Success_identifiers() {
+ $this->successTest(
+ array('ns' =>Auth_OpenID_OPENID2_NS,
+ 'return_to' =>'return',
+ 'assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'claimed_id' =>'i claim to be me',
+ 'identity' =>'my server knows me as me',
+ 'op_endpoint' =>'my favourite server',
+ 'response_nonce' =>'use only once',
+ ),
+ array('return_to', 'response_nonce', 'identity',
+ 'claimed_id', 'assoc_handle'));
+ }
+
+ function failureTest($openid_args, $signed_list) {
+ $message = Auth_OpenID_Message::fromOpenIDArgs($openid_args);
+ $result = $this->consumer->_idResCheckForFields($message);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ $this->assertTrue(strpos($result->message, 'Missing required') === 0);
+ }
+
+ function test_openid1Missing_returnToSig() {
+ $this->failureTest(
+ array('return_to' =>'return',
+ 'assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'identity' =>'someone',
+ ),
+ array('identity'));
+ }
+
+ function test_openid1Missing_identitySig() {
+ $this->failureTest(
+ array('return_to' =>'return',
+ 'assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'identity' =>'someone',
+ ),
+ array('return_to'));
+ }
+
+ function test_openid1MissingReturnTo() {
+ $this->failureTest(
+ array('assoc_handle' =>'assoc handle',
+ 'sig' =>'a signature',
+ 'identity' =>'someone',
+ ),
+ array('return_to', 'identity'));
+ }
+
+ function test_openid1MissingAssocHandle() {
+ $this->failureTest(
+ array('return_to' =>'return',
+ 'sig' =>'a signature',
+ 'identity' =>'someone',
+ ),
+ array('return_to', 'identity'));
+ }
+}
+
+define('E_CHECK_AUTH_HAPPENED', 'checkauth occurred');
+define('E_MOCK_FETCHER_EXCEPTION', 'mock fetcher exception');
+define('E_ASSERTION_ERROR', 'assertion error');
+
+class _CheckAuthDetectingConsumer extends ConfigurableConsumer {
+ function _verifyDiscoveryResults($message, $endpoint)
+ {
+ return $endpoint;
+ }
+
+ function _idResCheckNonce($message, $endpoint)
+ {
+ return true;
+ }
+
+ function _checkAuth($query, $server_url)
+ {
+ __raiseError(E_CHECK_AUTH_HAPPENED);
+ }
+}
+
+global $GOODSIG;
+$GOODSIG = "[A Good Signature]";
+
+class GoodAssociation {
+ var $expiresIn = 3600;
+ var $handle = "-blah-";
+
+ function getExpiresIn()
+ {
+ return $this->expiresIn;
+ }
+
+ function checkMessageSignature($message)
+ {
+ global $GOODSIG;
+ return $message->getArg(Auth_OpenID_OPENID_NS, 'sig') == $GOODSIG;
+ }
+}
+
+class GoodAssocStore extends Tests_Auth_OpenID_MemStore {
+ function getAssociation($server_url, $handle = null)
+ {
+ return new GoodAssociation();
+ }
+}
+
+class TestIdResCheckSignature extends _TestIdRes {
+ function setUp()
+ {
+ global $GOODSIG;
+
+ parent::setUp();
+ $this->assoc = new GoodAssociation();
+ $this->assoc->handle = "{not_dumb}";
+ $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);
+
+ $this->message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.mode'=> 'id_res',
+ 'openid.identity'=> '=example',
+ 'openid.sig'=> $GOODSIG,
+ 'openid.assoc_handle'=> $this->assoc->handle,
+ 'openid.signed'=> 'mode,identity,assoc_handle,signed',
+ 'frobboz'=> 'banzit'));
+ }
+
+ function test_sign()
+ {
+ // assoc_handle to assoc with good sig
+ $this->consumer->_idResCheckSignature($this->message,
+ $this->endpoint->server_url);
+ }
+
+ function test_signFailsWithBadSig()
+ {
+ $this->message.setArg(Auth_OpenID_OPENID_NS, 'sig', 'BAD SIGNATURE');
+ $result = $this->consumer->_idResCheckSignature($this->message, $this->endpoint->server_url);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+}
+
+class StatelessConsumer1 extends ConfigurableConsumer {
+ function _processCheckAuthResponse($response, $server_url)
+ {
+ return true;
+ }
+
+ function _makeKVPost($args, $server_url)
+ {
+ return array();
+ }
+}
+
+class Tests_Auth_OpenID_Stateless1 extends _TestIdRes {
+ var $consumer_class = "StatelessConsumer1";
+
+ function setUp()
+ {
+ global $GOODSIG;
+
+ parent::setUp();
+ $this->assoc = new GoodAssociation();
+ $this->assoc->handle = "{not_dumb}";
+ $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);
+
+ $this->message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.mode'=> 'id_res',
+ 'openid.identity'=> '=example',
+ 'openid.sig'=> $GOODSIG,
+ 'openid.assoc_handle'=> $this->assoc->handle,
+ 'openid.signed'=> 'mode,identity,assoc_handle,signed',
+ 'frobboz'=> 'banzit'));
+ }
+
+ function test_stateless()
+ {
+ // assoc_handle missing assoc, consumer._checkAuth returns
+ // goodthings
+ $this->message->setArg(Auth_OpenID_OPENID_NS, "assoc_handle", "dumbHandle");
+ $this->consumer->_idResCheckSignature($this->message,
+ $this->endpoint->server_url);
+ }
+}
+
+class StatelessConsumer2 extends ConfigurableConsumer {
+ function _checkAuth($_, $__)
+ {
+ return false;
+ }
+}
+
+class Tests_Auth_OpenID_Stateless2 extends _TestIdRes {
+ var $consumer_class = "StatelessConsumer2";
+
+ function setUp()
+ {
+ global $GOODSIG;
+
+ parent::setUp();
+ $this->assoc = new GoodAssociation();
+ $this->assoc->handle = "{not_dumb}";
+ $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);
+
+ $this->message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.mode'=> 'id_res',
+ 'openid.identity'=> '=example',
+ 'openid.sig'=> $GOODSIG,
+ 'openid.assoc_handle'=> $this->assoc->handle,
+ 'openid.signed'=> 'mode,identity,assoc_handle,signed',
+ 'frobboz'=> 'banzit'));
+ }
+
+ function test_statelessRaisesError()
+ {
+ // assoc_handle missing assoc, consumer._checkAuth returns
+ // goodthings
+ $this->message->setArg(Auth_OpenID_OPENID_NS, "assoc_handle",
+ "dumbHandle");
+ $result = $this->consumer->_idResCheckSignature($this->message,
+ $this->endpoint->server_url);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+}
+
+class Tests_Auth_OpenID_Consumer_CheckNonceTest extends _TestIdRes {
+ function setUp()
+ {
+ parent::setUp();
+ $this->consumer->openid1_nonce_query_arg_name = 'nonce';
+ }
+
+ function test_openid1Success()
+ {
+ // use consumer-generated nonce
+ $nonce_value = Auth_OpenID_mkNonce();
+ $this->return_to = sprintf('http://rt.unittest/?nonce=%s',
+ $nonce_value);
+ $this->response = Auth_OpenID_Message::fromOpenIDArgs(
+ array('return_to' => $this->return_to));
+ $this->response->setArg(Auth_OpenID_BARE_NS, 'nonce', $nonce_value);
+
+ $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
+ $this->assertFalse(Auth_OpenID::isFailure($result));
+ }
+
+ function test_openid1Missing()
+ {
+ // use consumer-generated nonce
+ $this->response = Auth_OpenID_Message::fromOpenIDArgs(array());
+ $n = $this->consumer->_idResGetNonceOpenID1($this->response, $this->endpoint);
+ $this->assertTrue($n === null);
+ }
+
+ function test_consumerNonceOpenID2()
+ {
+ // OpenID 2 does not use consumer-generated nonce
+ $this->return_to = sprintf('http://rt.unittest/?nonce=%s',
+ Auth_OpenID_mkNonce());
+ $this->response = Auth_OpenID_Message::fromOpenIDArgs(
+ array('return_to' => $this->return_to,
+ 'ns' => Auth_OpenID_OPENID2_NS));
+ $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_serverNonce()
+ {
+ // use server-generated nonce
+ $this->response = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'response_nonce' => Auth_OpenID_mkNonce()));
+ $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
+ }
+
+ function test_serverNonceOpenID1()
+ {
+ // OpenID 1 does not use server-generated nonce
+ $this->response = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID1_NS,
+ 'return_to'=> 'http://return.to/',
+ 'response_nonce'=> Auth_OpenID_mkNonce()));
+ $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_badNonce()
+ {
+ // remove the nonce from the store
+ $nonce = Auth_OpenID_mkNonce();
+ list($timestamp, $salt) = Auth_OpenID_splitNonce($nonce);
+
+ $this->store->useNonce($this->server_url, $timestamp, $salt);
+
+ $response = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'response_nonce' => $nonce,
+ 'ns' => Auth_OpenID_OPENID2_NS
+ ));
+
+ $result = $this->consumer->_idResCheckNonce($response,
+ $this->endpoint);
+
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_tamperedNonce()
+ {
+ // Malformed nonce
+ $query = array('response_nonce' => 'malformed',
+ 'ns' => Auth_OpenID_OPENID2_NS);
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $result = $this->consumer->_idResCheckNonce($message,
+ $this->endpoint);
+
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_missingNonce()
+ {
+ // no nonce parameter on the return_to
+ $query = array('openid.return_to' => $this->return_to);
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $result = $this->consumer->_idResCheckNonce($message,
+ $this->endpoint);
+
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+}
+
+class Tests_Auth_OpenID_Consumer_TestCheckAuthTriggered extends _TestIdRes {
+ var $consumer_class = '_CheckAuthDetectingConsumer';
+
+ function _doIdRes($message, $endpoint, $return_to)
+ {
+ return $this->consumer->_doIdRes($message, $endpoint, $return_to);
+ }
+
+ function test_checkAuthTriggered()
+ {
+ $query = array('openid.return_to' => $this->return_to,
+ 'openid.identity' => $this->server_id,
+ 'openid.assoc_handle' =>'not_found',
+ 'openid.sig' => 'bogus',
+ 'openid.signed' => 'identity,return_to');
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->disableReturnToChecking();
+
+ $result = $this->_doIdRes($message, $this->endpoint, null);
+
+ $error = __getError();
+
+ if ($error === null) {
+ $this->fail('_checkAuth did not happen.');
+ }
+ }
+
+ function test_checkAuthTriggeredWithAssoc()
+ {
+ // Store an association for this server that does not match
+ // the handle that is in the query
+ $issued = time();
+ $lifetime = 1000;
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secret', $issued, $lifetime, 'HMAC-SHA1');
+ $this->store->storeAssociation($this->server_url, $assoc);
+
+ $query = array(
+ 'openid.return_to' => $this->return_to,
+ 'openid.identity' => $this->server_id,
+ 'openid.assoc_handle' => 'not_found',
+ 'openid.sig' => 'bogus',
+ 'openid.signed' => 'return_to,identity');
+
+ $this->consumer->disableReturnToChecking();
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $result = $this->_doIdRes($message, $this->endpoint, null);
+ $error = __getError();
+
+ if ($error === null) {
+ $this->fail('_checkAuth did not happen.');
+ }
+ }
+
+ function test_expiredAssoc()
+ {
+ // Store an expired association for the server with the handle
+ // that is in the query
+ $issued = time() - 10;
+ $lifetime = 0;
+ $handle = 'handle';
+ $assoc = new Auth_OpenID_Association(
+ $handle, 'secret', $issued, $lifetime, 'HMAC-SHA1');
+ $this->assertTrue($assoc->getExpiresIn() <= 0);
+ $this->store->storeAssociation($this->server_url, $assoc);
+
+ $query = array(
+ 'openid.return_to' => $this->return_to,
+ 'openid.identity' => $this->server_id,
+ 'openid.sig' => 'bogus',
+ 'openid.signed' => 'identity,return_to',
+ 'openid.assoc_handle' => $handle);
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->disableReturnToChecking();
+
+ $info = $this->_doIdRes($message, $this->endpoint, null);
+
+ $this->assertEquals('failure', $info->status);
+
+ $this->assertTrue(strpos($info->message, 'expired') !== false);
+ }
+
+ function test_newerAssoc()
+ {
+ // Store an expired association for the server with the handle
+ // that is in the query
+ $lifetime = 1000;
+
+ $good_issued = time() - 10;
+ $good_handle = 'handle';
+ $good_assoc = new Auth_OpenID_Association(
+ $good_handle, 'secret', $good_issued, $lifetime, 'HMAC-SHA1');
+ $this->store->storeAssociation($this->server_url, $good_assoc);
+
+ $bad_issued = time() - 5;
+ $bad_handle = 'handle2';
+ $bad_assoc = new Auth_OpenID_Association(
+ $bad_handle, 'secret', $bad_issued, $lifetime, 'HMAC-SHA1');
+ $this->store->storeAssociation($this->server_url, $bad_assoc);
+
+ $query = array(
+ 'openid.return_to' => $this->return_to,
+ 'openid.identity' => $this->server_id,
+ 'openid.assoc_handle' => $good_handle);
+
+ $this->consumer->disableReturnToChecking();
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $message = $good_assoc->signMessage($message);
+
+ $info = $this->_doIdRes($message, $this->endpoint, null);
+
+ $this->assertEquals($info->status, 'success');
+ $this->assertEquals($this->consumer_id, $info->identity_url);
+ }
+}
+
+class _MockFetcher {
+ function _MockFetcher($response = null)
+ {
+ // response is (code, url, body)
+ $this->response = $response;
+ $this->fetches = array();
+ }
+
+ function post($url, $body)
+ {
+ $this->fetches[] = array($url, $body, array());
+ return $this->response;
+ }
+
+ function get($url)
+ {
+ $this->fetches[] = array($url, null, array());
+ return $this->response;
+ }
+}
+
+class Tests_Auth_OpenID_Complete extends _TestIdRes {
+ function test_cancel()
+ {
+ $query = array('openid.mode' => 'cancel');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->disableReturnToChecking();
+
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_CANCEL);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ }
+
+ function test_cancel_with_return_to() {
+ $message = Auth_OpenID_Message::fromPostArgs(array('openid.mode' => 'cancel'));
+ $r = $this->consumer->complete($message, $this->endpoint, $this->return_to);
+ $this->assertEquals($r->status, Auth_OpenID_CANCEL);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ }
+
+ function test_errorWithNoOptionalKeys()
+ {
+ $msg = 'an error message';
+ $contact = 'some contact info here';
+ $message = Auth_OpenID_Message::fromPostArgs(array('openid.mode'=> 'error',
+ 'openid.error'=> $msg,
+ 'openid.contact'=> $contact));
+
+ $this->consumer->disableReturnToChecking();
+
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ $this->assertTrue($r->contact == $contact);
+ $this->assertTrue($r->reference === null);
+ $this->assertEquals($r->message, $msg);
+ }
+
+ function test_errorWithOptionalKeys()
+ {
+ $msg = 'an error message';
+ $contact = 'me';
+ $reference = 'support ticket';
+ $message = Auth_OpenID_Message::fromPostArgs(array('openid.mode'=> 'error',
+ 'openid.error'=> $msg, 'openid.reference'=> $reference,
+ 'openid.contact'=> $contact, 'openid.ns'=> Auth_OpenID_OPENID2_NS
+ ));
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ $this->assertTrue($r->contact == $contact);
+ $this->assertTrue($r->reference == $reference);
+ $this->assertEquals($r->message, $msg);
+ }
+
+ function test_error()
+ {
+ $msg = 'an error message';
+ $query = array('openid.mode' =>'error',
+ 'openid.error' => $msg);
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->disableReturnToChecking();
+
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ $this->assertEquals($r->message, $msg);
+ }
+
+ function test_noMode()
+ {
+ $query = array();
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
+ }
+
+ function test_idResMissingField()
+ {
+ $query = array('openid.mode' => 'id_res');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertTrue(Auth_openID::isFailure($r));
+ }
+}
+
+class _VerifiedError extends Auth_OpenID_FailureResponse {
+}
+
+class Consumer_idResURLMismatch extends ConfigurableConsumer {
+ function _discoverAndVerify($to_match)
+ {
+ return new _VerifiedError(null, 'verified error');
+ }
+}
+
+class Tests_idResURLMismatch extends _TestIdRes {
+ var $consumer_class = 'Consumer_idResURLMismatch';
+
+ function test_idResURLMismatch()
+ {
+ $query = array('openid.mode' => 'id_res',
+ 'openid.return_to' => 'return_to (just anything)',
+ 'openid.identity' => 'something wrong (not this->consumer_id)',
+ 'openid.assoc_handle' => 'does not matter',
+ 'openid.signed' => 'identity,return_to',
+ 'openid.sig' => 'bogus');
+
+ $this->consumer->disableReturnToChecking();
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertTrue(is_a($r, '_VerifiedError'));
+ }
+}
+
+class SetupNeededConsumer extends Auth_OpenID_GenericConsumer {
+ function _checkSetupNeeded($message)
+ {
+ return true;
+ }
+}
+
+class Tests_Auth_OpenID_SetupNeeded extends _TestIdRes {
+ function test_setupNeededIdRes()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array('mode'=> 'id_res'));
+ $response = $this->consumer->complete($message, null, null);
+ $this->assertEquals(Auth_OpenID_SETUP_NEEDED,
+ $response->status);
+ }
+}
+
+class TempConsumer extends ConfigurableConsumer {
+ function _verifyDiscoveryResults($message, $endpoint)
+ {
+ return $endpoint;
+ }
+}
+
+class TestCompleteMissingSig extends PHPUnit_TestCase {
+
+ function setUp()
+ {
+ global $GOODSIG;
+
+ $this->store = new GoodAssocStore();
+ $this->consumer = new ConfigurableConsumer($this->store);
+ $this->server_url = "http://idp.unittest/";
+
+ $claimed_id = 'bogus.claimed';
+
+ $this->message = Auth_OpenID_Message::fromOpenIDArgs(
+ array('mode'=> 'id_res',
+ 'return_to'=> 'return_to (just anything)',
+ 'identity'=> $claimed_id,
+ 'assoc_handle'=> 'does not matter',
+ 'sig'=> $GOODSIG,
+ 'response_nonce'=> Auth_OpenID_mkNonce(),
+ 'signed'=> 'identity,return_to,response_nonce,assoc_handle,claimed_id',
+ 'claimed_id'=> $claimed_id,
+ 'op_endpoint'=> $this->server_url,
+ 'ns' => Auth_OpenID_OPENID2_NS));
+
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->server_url = $this->server_url;
+ $this->endpoint->claimed_id = $claimed_id;
+ $this->consumer->disableReturnToChecking();
+ }
+
+ function test_idResMissingNoSigs()
+ {
+ $c = new TempConsumer($this->store);
+ $c->disableReturnToChecking();
+ $r = $c->complete($this->message, $this->endpoint, null);
+ $this->failUnlessSuccess($r);
+ }
+
+ function test_idResNoIdentity()
+ {
+ $this->message->delArg(Auth_OpenID_OPENID_NS, 'identity');
+ $this->message->delArg(Auth_OpenID_OPENID_NS, 'claimed_id');
+ $this->endpoint->claimed_id = null;
+ $this->message->setArg(Auth_OpenID_OPENID_NS,
+ 'signed', 'return_to,response_nonce,assoc_handle');
+ $r = $this->consumer->complete($this->message, $this->endpoint, null);
+ $this->failUnlessSuccess($r);
+ }
+
+ function test_idResMissingIdentitySig()
+ {
+ $this->message->setArg(Auth_OpenID_OPENID_NS,
+ 'signed',
+ 'return_to,response_nonce,assoc_handle,claimed_id');
+ $r = $this->consumer->complete($this->message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ }
+
+ function test_idResMissingReturnToSig()
+ {
+ $this->message->setArg(Auth_OpenID_OPENID_NS,
+ 'signed',
+ 'identity,response_nonce,assoc_handle,claimed_id');
+ $r = $this->consumer->complete($this->message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ }
+
+ function test_idResMissingAssocHandleSig()
+ {
+ $this->message->setArg(Auth_OpenID_OPENID_NS, 'signed',
+ 'identity,response_nonce,return_to,claimed_id');
+ $r = $this->consumer->complete($this->message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ }
+
+ function test_idResMissingClaimedIDSig()
+ {
+ $this->message->setArg(Auth_OpenID_OPENID_NS, 'signed',
+ 'identity,response_nonce,return_to,assoc_handle');
+ $r = $this->consumer->complete($this->message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ }
+
+ function failUnlessSuccess($response)
+ {
+ if ($response->status != Auth_OpenID_SUCCESS) {
+ $this->fail(sprintf("Non-successful response: %s", $response->status));
+ }
+ }
+}
+
+class TestReturnToArgs extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $store = null;
+ $this->consumer = new Auth_OpenID_GenericConsumer($store);
+ }
+
+ function test_returnToArgsUnexpectedArg()
+ {
+ $query = array(
+ 'openid.mode' => 'id_res',
+ 'openid.return_to' => 'http://example.com/',
+ 'foo' => 'bar');
+
+ // no return value, success is assumed if there are no
+ // exceptions.
+ $this->assertTrue(Auth_OpenID::isFailure(
+ $this->consumer->_verifyReturnToArgs($query)));
+ }
+
+ function test_returnToArgsOkay()
+ {
+ $query = array(
+ 'openid.mode'=> 'id_res',
+ 'openid.return_to'=> 'http://example.com/?foo=bar',
+ 'foo'=> 'bar'
+ );
+ // no return value, success is assumed if there are no exceptions.
+ $result = $this->consumer->_verifyReturnToArgs($query);
+
+ $this->assertFalse(Auth_OpenID::isFailure($result));
+ $this->assertTrue($result);
+ }
+
+ function test_returnToMismatch()
+ {
+ $query = array(
+ 'openid.mode' => 'id_res',
+ 'openid.return_to' => 'http://example.com/?foo=bar');
+
+ // fail, query has no key 'foo'.
+ $result = $this->consumer->_verifyReturnToArgs($query);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+
+ $query['foo'] = 'baz';
+ // fail, values for 'foo' do not match.
+ $result = $this->consumer->_verifyReturnToArgs($query);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_noReturnTo()
+ {
+ $query = array('openid.mode'=> 'id_res');
+ $result = $this->consumer->_verifyReturnToArgs($query);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_completeBadReturnTo()
+ {
+ // Test GenericConsumer.complete()'s handling of bad return_to
+ // values.
+ $return_to = "http://some.url/path?foo=bar";
+
+ // Scheme, authority, and path differences are checked by
+ // GenericConsumer._checkReturnTo. Query args checked by
+ // GenericConsumer._verifyReturnToArgs.
+ $bad_return_tos = array(
+ // Scheme only
+ "https://some.url/path?foo=bar",
+ // Authority only
+ "http://some.url.invalid/path?foo=bar",
+ // Path only
+ "http://some.url/path_extra?foo=bar",
+ // Query args differ
+ "http://some.url/path?foo=bar2",
+ "http://some.url/path?foo2=bar"
+ );
+
+ $m = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $m->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
+ $m->setArg(Auth_OpenID_BARE_NS, 'foo', 'bar');
+ $endpoint = null;
+
+ foreach ($bad_return_tos as $bad) {
+ $m->setArg(Auth_OpenID_OPENID_NS, 'return_to', $bad);
+ $this->assertFalse($this->consumer->_checkReturnTo($m, $return_to));
+ }
+ }
+
+ function test_completeGoodReturnTo()
+ {
+ // Test GenericConsumer.complete()'s handling of good
+ // return_to values.
+ $return_to = "http://some.url/path";
+
+ $good_return_tos = array(
+ array($return_to,
+ array()),
+ array($return_to . "?another=arg",
+ array(array(
+ array(Auth_OpenID_BARE_NS, 'another'),
+ 'arg'))),
+ array($return_to . "?another=arg#fragment",
+ array(array(
+ array(Auth_OpenID_BARE_NS, 'another'),
+ 'arg'))),
+ array("HTTP://some.url/path",array()),
+ array("http://some.URL/path",array()),
+ array("http://some.url:80/path",array()),
+ array("http://some.url/./path",array())
+ );
+
+ $endpoint = null;
+
+ foreach ($good_return_tos as $pair) {
+ list($good_return_to, $extra) = $pair;
+ $m = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $m->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
+
+ for ($i = 0; $i < count($extra); $i++) {
+ list($ckey, $value) = $extra[$i];
+ $ns = $ckey[0];
+ $key = $ckey[1];
+ $m->setArg($ns, $key, $value);
+ }
+
+ $m->setArg(Auth_OpenID_OPENID_NS, 'return_to', $good_return_to);
+ $result = $this->consumer->complete($m, $endpoint, $return_to);
+ $this->assertTrue(is_a($result, 'Auth_OpenID_CancelResponse'));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckAuthResponse extends _TestIdRes {
+ function _createAssoc()
+ {
+ $issued = time();
+ $lifetime = 1000;
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secret', $issued, $lifetime, 'HMAC-SHA1');
+ $store =& $this->consumer->store;
+ $store->storeAssociation($this->server_url, $assoc);
+ $assoc2 = $store->getAssociation($this->server_url);
+ $this->assertEquals($assoc, $assoc2);
+ }
+
+ function test_goodResponse()
+ {
+ // successful response to check_authentication
+ $response = array('is_valid' => 'true');
+ $message = Auth_OpenID_Message::fromOpenIDArgs($response);
+ $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
+ $this->assertTrue($r);
+ }
+
+ function test_missingAnswer()
+ {
+ // check_authentication returns false when the server sends no
+ // answer
+ $response = array();
+ $message = Auth_OpenID_Message::fromPostArgs($response);
+ $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
+ $this->assertFalse($r);
+ }
+
+ function test_badResponse()
+ {
+ // check_authentication returns false when is_valid is false
+ $response = array('is_valid' => 'false');
+ $message = Auth_OpenID_Message::fromOpenIDArgs($response);
+
+ $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
+ $this->assertFalse($r);
+ }
+
+ function test_badResponseInvalidate()
+ {
+ // Make sure that the handle is invalidated when is_valid is
+ // false
+ $this->_createAssoc();
+ $response = array('is_valid' => 'false',
+ 'invalidate_handle' => 'handle');
+
+ $message = Auth_OpenID_Message::fromOpenIDArgs($response);
+
+ $r = $this->consumer->_processCheckAuthResponse($message,
+ $this->server_url);
+ $this->assertFalse($r);
+ $this->assertTrue(
+ $this->consumer->store->getAssociation($this->server_url) === null);
+ }
+
+ function test_invalidateMissing()
+ {
+ // invalidate_handle with a handle that is not present
+ $response = array('is_valid' => 'true',
+ 'invalidate_handle' => 'missing');
+
+ $message = Auth_OpenID_Message::fromOpenIDArgs($response);
+
+ $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
+ $this->assertTrue($r);
+ }
+
+ function test_invalidatePresent()
+ {
+ // invalidate_handle with a handle that exists"""
+ $this->_createAssoc();
+ $response = array('is_valid' => 'true',
+ 'invalidate_handle' => 'handle');
+
+ $message = Auth_OpenID_Message::fromOpenIDArgs($response);
+
+ $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
+ $this->assertTrue($r);
+ $this->assertTrue(
+ $this->consumer->store->getAssociation($this->server_url) === null);
+ }
+}
+
+class _IdResFetchFailingConsumer extends Auth_OpenID_GenericConsumer {
+ var $message = 'fetch failed';
+
+ function _doIdRes($message, $endpoint)
+ {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ $this->message);
+ }
+}
+
+class Tests_Auth_OpenID_FetchErrorInIdRes extends _TestIdRes {
+ var $consumer_class = '_IdResFetchFailingConsumer';
+
+ function test_idResFailure()
+ {
+ $query = array('openid.mode' => 'id_res');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $r = $this->consumer->complete($message, $this->endpoint, null);
+ $this->assertEquals($r->status, Auth_OpenID_FAILURE);
+ $this->assertEquals($r->identity_url, $this->consumer_id);
+ $this->assertEquals($this->consumer->message, $r->message);
+ }
+}
+
+class _ExceptionRaisingMockFetcher {
+ function get($url)
+ {
+ __raiseError(E_MOCK_FETCHER_EXCEPTION);
+ }
+
+ function post($url, $body)
+ {
+ __raiseError(E_MOCK_FETCHER_EXCEPTION);
+
+ return new Auth_Yadis_HTTPResponse($url, 400,
+ array(), '');
+ }
+}
+
+class _BadArgCheckingConsumer extends Auth_OpenID_GenericConsumer {
+ function _makeKVPost($message, $tmp)
+ {
+ $args = $message->toPostArgs();
+
+ if ($args != array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.signed' => 'foo')) {
+ __raiseError(E_ASSERTION_ERROR);
+ }
+ return null;
+ }
+}
+
+class Tests_Auth_OpenID_Consumer_TestCheckAuth extends _TestIdRes {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
+ $this->fetcher = new _MockFetcher();
+ $this->consumer->fetcher =& $this->fetcher;
+ }
+
+ function test_checkauth_error()
+ {
+ global $_Auth_OpenID_server_url;
+ $this->fetcher->response = new Auth_Yadis_HTTPResponse("http://some_url",
+ 404,
+ array(),
+ "blah:blah\n");
+ $query = array('openid.signed' => 'stuff, things');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $r = $this->consumer->_checkAuth($message, $_Auth_OpenID_server_url);
+ if ($r !== false) {
+ $this->fail("Expected _checkAuth result to be false");
+ }
+ }
+
+ function test_bad_args()
+ {
+ $query = array('openid.signed' => 'foo',
+ 'closid.foo' => 'something');
+
+ $consumer = new _BadArgCheckingConsumer($this->store);
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->assertFalse($consumer->_checkAuth($message, 'does://not.matter'));
+ }
+
+ function test_signedList()
+ {
+ $signed = 'identity,mode,ns.sreg,sreg.email';
+ $query = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode'=> 'id_res',
+ 'sig'=> 'rabbits',
+ 'identity'=> '=example',
+ 'assoc_handle'=> 'munchkins',
+ 'ns.sreg' => 'urn:sreg',
+ 'sreg.email' => 'bogus@example.com',
+ 'signed'=> $signed,
+ 'foo'=> 'bar'));
+
+ $args = $this->consumer->_createCheckAuthRequest($query);
+ $this->assertTrue($args->isOpenID1());
+ $signed_list = explode(',',$signed);
+ foreach ($signed_list as $k) {
+ $this->assertTrue($args->getAliasedArg($k));
+ }
+ }
+
+
+ function test_112()
+ {
+ $args = array('openid.assoc_handle' => 'fa1f5ff0-cde4-11dc-a183-3714bfd55ca8',
+ 'openid.claimed_id' => 'http://binkley.lan/user/test01',
+ 'openid.identity' => 'http://test01.binkley.lan/',
+ 'openid.mode' => 'id_res',
+ 'openid.ns' => 'http://specs.openid.net/auth/2.0',
+ 'openid.ns.pape' => 'http://specs.openid.net/extensions/pape/1.0',
+ 'openid.op_endpoint' => 'http://binkley.lan/server',
+ 'openid.pape.auth_policies' => 'none',
+ 'openid.pape.auth_time' => '2008-01-28T20 =>42 =>36Z',
+ 'openid.pape.nist_auth_level' => '0',
+ 'openid.response_nonce' => '2008-01-28T21 =>07 =>04Z99Q=',
+ 'openid.return_to' => 'http://binkley.lan =>8001/process?janrain_nonce=2008-01-28T21%3A07%3A02Z0tMIKx',
+ 'openid.sig' => 'YJlWH4U6SroB1HoPkmEKx9AyGGg=',
+ 'openid.signed' => 'assoc_handle,identity,response_nonce,return_to,claimed_id,op_endpoint,pape.auth_time,ns.pape,pape.nist_auth_level,pape.auth_policies'
+ );
+ $this->assertEquals(Auth_OpenID_OPENID2_NS, $args['openid.ns']);
+ $incoming = Auth_OpenID_Message::fromPostArgs($args);
+ $this->assertTrue($incoming->isOpenID2());
+ $car = $this->consumer->_createCheckAuthRequest($incoming);
+ $expected_args = $args;
+ $expected_args['openid.mode'] = 'check_authentication';
+ $expected = Auth_OpenID_Message::fromPostArgs($expected_args);
+ $this->assertTrue($expected->isOpenID2());
+ $this->assertEquals($expected, $car);
+ $this->assertEquals($expected_args, $car->toPostArgs());
+ }
+}
+
+class Tests_Auth_OpenID_Consumer_TestFetchAssoc extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->fetcher = new _MockFetcher();
+ $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
+ $this->consumer->fetcher =& $this->fetcher;
+ }
+
+ function test_kvpost_error()
+ {
+ $this->fetcher->response = new Auth_Yadis_HTTPResponse("http://some_url",
+ 404,
+ array(),
+ "blah:blah\n");
+ $query = array('openid.mode' => 'associate');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $r = $this->consumer->_makeKVPost($message,
+ "http://server_url");
+ if ($r !== null) {
+ $this->fail("Expected _makeKVPost result to be null");
+ }
+ }
+
+ function test_error_404()
+ {
+ // 404 from a kv post raises HTTPFetchingError
+ $this->fetcher->response = new Auth_Yadis_HTTPResponse(
+ "http://some_url", 404, array('Hea'=> 'der'), 'blah:blah\n');
+
+ $result = $this->consumer->_makeKVPost(
+ Auth_OpenID_Message::fromPostArgs(array('mode'=>'associate')),
+ "http://server_url");
+
+ $this->assertTrue($result === null);
+ }
+
+ function test_error_exception()
+ {
+ $this->consumer->fetcher = new _ExceptionRaisingMockFetcher();
+
+ $query = array('openid.mode' => 'associate');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->_makeKVPost($message,
+ "http://server_url");
+
+ if (__getError() !== E_MOCK_FETCHER_EXCEPTION) {
+ $this->fail("Expected ExceptionRaisingMockFetcher to " .
+ "raise E_MOCK_FETCHER_EXCEPTION");
+ }
+
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->server_url = 'some://url';
+
+ // exception fetching returns no association
+ $this->assertEquals($this->consumer->_getAssociation($endpoint),
+ null);
+
+ $query = array('openid.signed' => '');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $this->consumer->_checkAuth($message,
+ 'some://url');
+
+ if (__getError() !== E_MOCK_FETCHER_EXCEPTION) {
+ $this->fail("Expected ExceptionRaisingMockFetcher to " .
+ "raise E_MOCK_FETCHER_EXCEPTION (_checkAuth)");
+ }
+ }
+}
+
+class Tests_Auth_OpenID_AuthRequestHTMLMarkup extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->claimed_id = 'identity_url';
+
+ $this->request = new Auth_OpenID_AuthRequest($this->endpoint, null);
+ }
+
+ function test_htmlMarkup()
+ {
+ $html = $this->request->htmlMarkup('http://realm.com/',
+ 'http://realm.com/return_to');
+ $this->assertTrue(substr($html,"<html>") !== false);
+ $this->assertTrue(substr($html,"</html>") !== false);
+ $this->assertTrue(substr($html,"<body onload") !== false);
+ $this->assertTrue(substr($html,"</body>") !== false);
+ $this->assertTrue(substr($html,"<form") !== false);
+ $this->assertTrue(substr($html,"</form>") !== false);
+ }
+}
+
+class Tests_Auth_OpenID_SuccessResponse extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->claimed_id = 'identity_url';
+ }
+
+ function test_extensionResponse()
+ {
+ $uri = "http://bogus.unittest/1.0";
+
+ $query = array(
+ 'openid.ns.unittest' => $uri,
+ 'openid.unittest.one' => '1',
+ 'openid.unittest.two' =>'2',
+ 'openid.sreg.nickname' => 'j3h',
+ 'openid.return_to' => 'return_to');
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $message);
+
+ $utargs = $resp->extensionResponse($uri, false);
+ $this->assertEquals($utargs, array('one' => '1', 'two' => '2'));
+ $sregargs = $resp->extensionResponse(Auth_OpenID_SREG_NS_URI, false);
+ $this->assertEquals($sregargs, array('nickname' => 'j3h'));
+ }
+
+ function test_extensionResponseSigned()
+ {
+ $args = array(
+ 'ns.sreg' => 'urn:sreg',
+ 'ns.unittest' => 'urn:unittest',
+ 'unittest.one' => '1',
+ 'unittest.two' => '2',
+ 'sreg.nickname' => 'j3h',
+ 'sreg.dob' => 'yesterday',
+ 'return_to' => 'return_to',
+ 'signed' => 'sreg.nickname,unittest.one,sreg.dob');
+
+ $signed_list = array('openid.sreg.nickname',
+ 'openid.unittest.one',
+ 'openid.sreg.dob',
+ 'openid.ns.sreg');
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs($args);
+ $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $msg, $signed_list);
+
+ // All args in this NS are signed, so expect all.
+ $sregargs = $resp->extensionResponse('urn:sreg', true);
+ $this->assertEquals($sregargs,
+ array('nickname' => 'j3h',
+ 'dob' => 'yesterday'));
+
+ // Not all args in this NS are signed, so expect null when
+ // asking for them.
+ $utargs = $resp->extensionResponse('urn:unittest', true);
+ $this->assertEquals($utargs, null);
+ }
+
+ function test_noReturnTo()
+ {
+ $message = Auth_OpenID_Message::fromPostArgs(array());
+ $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $message);
+ $this->assertTrue($resp->getReturnTo() === null);
+ }
+
+ function test_returnTo()
+ {
+ $query = array('openid.return_to' => 'return_to');
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ $resp = new Auth_OpenID_SuccessResponse($this->endpoint,
+ $message, array('openid.return_to'));
+
+ $this->assertEquals($resp->getReturnTo(), 'return_to');
+ }
+}
+
+class _StubConsumer {
+ function _StubConsumer()
+ {
+ $this->assoc = null;
+ $this->response = null;
+ $this->endpoint = null;
+ $this->fetcher = new _MockFetcher();
+ }
+
+ function begin($service)
+ {
+ $auth_req = new Auth_OpenID_AuthRequest($service, $this->assoc);
+ $this->endpoint = $service;
+ return $auth_req;
+ }
+
+ function complete($message, $endpoint, $return_to)
+ {
+ return $this->response;
+ }
+}
+
+class Tests_Auth_OpenID_DiscoFailure extends PHPUnit_TestCase {
+ var $consumerClass = null;
+
+ function setUp()
+ {
+ foreach ($_SESSION as $k => $v) {
+ unset($_SESSION[$k]);
+ }
+
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->claimed_id = 'http://identity.url/';
+ $this->endpoint->claimed_id = $this->claimed_id;
+ $this->store = null;
+ $this->session = new Auth_Yadis_PHPSession();
+ $cls = $this->consumerClass;
+ $this->consumer =& new $cls($this->store, &$this->session);
+ $this->consumer->consumer =& new _StubConsumer();
+ $this->discovery =& new Auth_Yadis_Discovery(&$this->session,
+ $this->claimed_id,
+ $this->consumer->session_key_prefix);
+ }
+}
+
+class Consumer_completeEmptySession extends Auth_OpenID_GenericConsumer {
+ var $test_case = null;
+ var $text = "failed complete";
+
+ function complete($message, $endpoint, $return_to)
+ {
+ $this->test_case->assertTrue($endpoint === null);
+ return new Auth_OpenID_FailureResponse($endpoint, $this->text);
+ }
+}
+
+class Tests_Auth_OpenID_ConsumerTest2 extends PHPUnit_TestCase {
+ function setUp()
+ {
+ foreach ($_SESSION as $k => $v) {
+ unset($_SESSION[$k]);
+ }
+
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->claimed_id = 'http://identity.url/';
+ $this->endpoint->claimed_id = $this->claimed_id;
+ $this->store = null;
+ $this->session = new Auth_Yadis_PHPSession();
+ $this->consumer =& new Auth_OpenID_Consumer($this->store, $this->session);
+ $this->consumer->consumer =& new _StubConsumer();
+ $this->discovery =& new Auth_Yadis_Discovery(&$this->session,
+ $this->claimed_id,
+ $this->consumer->session_key_prefix);
+ }
+
+ function test_beginWithoutDiscovery()
+ {
+ // Does this really test anything non-trivial?
+ $result = $this->consumer->beginWithoutDiscovery($this->endpoint);
+
+ // The result is an auth request
+ $this->assertTrue(strtolower(get_class($result)) ==
+ 'auth_openid_authrequest');
+
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+
+ // Side-effect of calling beginWithoutDiscovery is setting the
+ // session value to the endpoint attribute of the result
+ $this->assertTrue(
+ $loader->fromSession(
+ $this->session->get($this->consumer->_token_key)) ==
+ $result->endpoint);
+
+ // The endpoint that we passed in is the endpoint on the
+ // auth_request
+ $this->assertTrue($result->endpoint == $this->endpoint);
+ }
+
+ function test_completeEmptySession()
+ {
+ $this->consumer->consumer = new Consumer_completeEmptySession($this->store);
+ $this->consumer->consumer->test_case =& $this;
+
+ $response = $this->consumer->complete(null);
+ $this->assertTrue(Auth_OpenID::isFailure($response));
+ $this->assertEquals($this->consumer->consumer->text, $response->message);
+ $this->assertTrue($response->identity_url === null);
+ }
+
+ function _doResp($auth_req, $exp_resp)
+ {
+ // complete a transaction, using the expected response from
+ // the generic consumer.
+ $this->consumer->consumer->response = $exp_resp;
+
+ // endpoint is stored in the session
+ // $this->assertTrue($this->session->data);
+ $this->assertTrue($_SESSION);
+ $resp = $this->consumer->complete(null);
+
+ // All responses should have the same identity URL, and the
+ // session should be cleaned out
+ if ($this->endpoint->claimed_id != Auth_OpenID_IDENTIFIER_SELECT) {
+ $this->assertTrue($resp->identity_url == $this->claimed_id);
+ }
+
+ $this->assertFalse(in_array($this->consumer->_token_key,
+ $_SESSION)); // this->session->data));
+
+ // Expected status response
+ $this->assertEquals($resp->status, $exp_resp->status);
+
+ return $resp;
+ }
+
+ function _doRespNoDisco($exp_resp)
+ {
+ // Set up a transaction without discovery
+ $auth_req = $this->consumer->beginWithoutDiscovery($this->endpoint);
+ $resp = $this->_doResp($auth_req, $exp_resp);
+ // There should be nothing left in the session once we have
+ // completed.
+ $this->assertFalse($this->session->contents());
+ return $resp;
+ }
+
+ /*
+ * Be sure that the session gets cleaned up when the response is
+ * successful and has a different URL than the one in the request.
+ */
+ function test_successDifferentURL()
+ {
+ // Set up a request endpoint describing an IDP URL
+ $this->identity_url = 'http://idp.url/';
+ $this->endpoint->claimed_id = $this->endpoint->local_id = Auth_OpenID_IDENTIFIER_SELECT;
+
+ // Use a response endpoint with a different URL (asserted by
+ // the IDP)
+ $resp_endpoint = new Auth_OpenID_ServiceEndpoint();
+ $resp_endpoint->claimed_id = "http://user.url/";
+
+ $resp = $this->_doRespDisco(
+ true,
+ mkSuccess($resp_endpoint, array()));
+ $this->assertTrue($this->discovery->getManager(true) === null);
+ }
+
+ function test_noDiscoCompleteSuccessWithToken()
+ {
+ $message = Auth_OpenID_Message::fromPostArgs(array());
+ $this->_doRespNoDisco(new Auth_OpenID_SuccessResponse($this->endpoint,
+ $message));
+ }
+
+ function test_noDiscoCompleteCancelWithToken()
+ {
+ $this->_doRespNoDisco(new Auth_OpenID_CancelResponse($this->endpoint));
+ }
+
+ function test_noDiscoCompleteFailure()
+ {
+ $msg = 'failed!';
+ $resp = $this->_doRespNoDisco(new Auth_OpenID_FailureResponse($this->endpoint, $msg));
+ $this->assertTrue($resp->message == $msg);
+ }
+
+ function test_noDiscoCompleteSetupNeeded()
+ {
+ $setup_url = 'http://setup.url/';
+ $resp = $this->_doRespNoDisco(
+ new Auth_OpenID_SetupNeededResponse($this->endpoint, $setup_url));
+ $this->assertTrue($resp->setup_url == $setup_url);
+ }
+
+ // To test that discovery is cleaned up, we need to initialize a
+ // Yadis manager, and have it put its values in the session.
+ function _doRespDisco($is_clean, $exp_resp)
+ {
+ // Set up and execute a transaction, with discovery
+ $this->discovery->createManager(array($this->endpoint),
+ $this->claimed_id);
+ $auth_req = $this->consumer->begin($this->claimed_id);
+ $resp = $this->_doResp($auth_req, $exp_resp);
+
+ $manager = $this->discovery->getManager();
+ if ($is_clean) {
+ $this->assertTrue($this->discovery->getManager() === null);
+ } else {
+ $this->assertFalse($this->discovery->getManager() === null);
+ }
+
+ return $resp;
+ }
+
+ // Cancel and success DO clean up the discovery process
+ function test_completeSuccess()
+ {
+ $message = Auth_OpenID_Message::fromPostArgs(array());
+ $this->_doRespDisco(true,
+ new Auth_OpenID_SuccessResponse($this->endpoint,
+ $message));
+ }
+
+ function test_completeCancel()
+ {
+ $this->_doRespDisco(true,
+ new Auth_OpenID_CancelResponse($this->endpoint));
+ }
+
+ // Failure and setup_needed don't clean up the discovery process
+ function test_completeFailure()
+ {
+ $msg = 'failed!';
+ $resp = $this->_doRespDisco(false,
+ new Auth_OpenID_FailureResponse($this->endpoint, $msg));
+ $this->assertTrue($resp->message == $msg);
+ }
+
+ function test_completeSetupNeeded()
+ {
+ $setup_url = 'http://setup.url/';
+ $resp = $this->_doRespDisco(false,
+ new Auth_OpenID_SetupNeededResponse($this->endpoint, $setup_url));
+ $this->assertTrue($resp->status == Auth_OpenID_SETUP_NEEDED);
+ $this->assertTrue($resp->setup_url == $setup_url);
+ }
+
+ function test_begin()
+ {
+ $this->discovery->createManager(array($this->endpoint),
+ $this->claimed_id);
+ // Should not raise an exception
+ $auth_req = $this->consumer->begin($this->claimed_id);
+ $this->assertTrue(strtolower(get_class($auth_req)) === 'auth_openid_authrequest');
+ $this->assertTrue($auth_req->endpoint == $this->endpoint);
+ $this->assertTrue($auth_req->endpoint == $this->consumer->consumer->endpoint);
+ $this->assertTrue($auth_req->assoc == $this->consumer->consumer->assoc);
+ }
+}
+
+class IDPDrivenTest_Consumer1 extends ConfigurableConsumer {
+ var $iverified = array();
+ var $endpoint = null;
+ var $failure_cb = null;
+ var $check_endpoint = null;
+
+ function _idResCheckNonce($message, $endpoint)
+ {
+ return true;
+ }
+
+ function _verifyDiscoveryResults($identifier, $endpoint)
+ {
+ call_user_func($this->failure_cb,
+ $endpoint === $this->check_endpoint);
+ $this->iverified[] = $this->endpoint;
+ return $this->endpoint;
+ }
+}
+
+class IDPDrivenTest_Consumer2 extends ConfigurableConsumer {
+ function verifyDiscoveryResults($identifier, $endp)
+ {
+ return new Auth_OpenID_FailureResponse(null,
+ "Bogus");
+ }
+}
+
+class IDPDrivenTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new GoodAssocStore();
+ $this->consumer = new ConfigurableConsumer($this->store);
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->server_url = "http://idp.unittest/";
+ }
+
+ function test_idpDrivenBegin()
+ {
+ // Testing here that the token-handling doesn't explode...
+ $this->assertTrue($this->consumer->begin($this->endpoint) !== null);
+ }
+
+ function test_idpDrivenComplete()
+ {
+ global $GOODSIG;
+
+ $this->consumer = new IDPDrivenTest_Consumer1($this->store);
+ $this->consumer->failure_cb = array(&$this, "assertTrue");
+ $this->consumer->check_endpoint =& $this->endpoint;
+
+ $identifier = '=directed_identifier';
+ $message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.identity'=> '=directed_identifier',
+ 'openid.return_to'=> 'x',
+ 'openid.assoc_handle'=> 'z',
+ 'openid.signed'=> 'identity,return_to',
+ 'openid.sig'=> $GOODSIG));
+
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->claimed_id = $identifier;
+ $endpoint->server_url = $this->endpoint->server_url;
+ $endpoint->local_id = $identifier;
+
+ $this->consumer->disableReturnToChecking();
+
+ $this->consumer->endpoint =& $endpoint;
+ $response = $this->consumer->_doIdRes($message, $this->endpoint, null);
+
+ $this->failUnlessSuccess($response);
+
+ $this->assertEquals($response->identity_url, "=directed_identifier");
+
+ // assert that discovery attempt happens and returns good
+ $this->assertEquals($this->consumer->iverified, array($endpoint));
+ }
+
+ function test_idpDrivenCompleteFraud()
+ {
+ global $GOODSIG;
+
+ $this->consumer = new IDPDrivenTest_Consumer2($this->store);
+
+ // crap with an identifier that doesn't match discovery info
+ $message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.identity'=> '=directed_identifier',
+ 'openid.return_to'=> 'x',
+ 'openid.assoc_handle'=> 'z',
+ 'openid.signed'=> 'identity,return_to',
+ 'openid.sig'=> $GOODSIG));
+
+ $this->consumer->disableReturnToChecking();
+
+ $result = $this->consumer->_doIdRes($message, $this->endpoint, null);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function failUnlessSuccess($response)
+ {
+ if ($response->status != Auth_OpenID_SUCCESS) {
+ $this->fail("Non-successful response (status is ".$response->status.")");
+ }
+ }
+}
+
+global $__test_otherServer_text;
+$__test_otherServer_text = "__test_otherServer";
+class TestDiscoveryVerification_test_otherServer extends Auth_OpenID_GenericConsumer {
+ function _discoverAndVerify($to_match)
+ {
+ global $__test_otherServer_text;
+ return new Auth_OpenID_FailureResponse(null, $__test_otherServer_text);
+ }
+}
+
+class TestDiscoveryVerification extends PHPUnit_TestCase {
+ var $services = array();
+
+ function discoveryFunc($identifier)
+ {
+ return array($identifier, $this->services);
+ }
+
+ function setUp()
+ {
+ $this->store = new GoodAssocStore();
+ $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
+ $this->consumer->discoverMethod = array($this,
+ 'discoveryFunc');
+
+ $this->identifier = "http://idp.unittest/1337";
+ $this->server_url = "http://endpoint.unittest/";
+
+ $this->message = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.ns'=> Auth_OpenID_OPENID2_NS,
+ 'openid.identity'=> $this->identifier,
+ 'openid.claimed_id'=> $this->identifier,
+ 'openid.op_endpoint'=> $this->server_url));
+
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->server_url = $this->server_url;
+ }
+
+ function test_theGoodStuff()
+ {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $endpoint->claimed_id = $this->identifier;
+ $endpoint->server_url = $this->server_url;
+ $endpoint->local_id = $this->identifier;
+ $this->services = array($endpoint);
+ $r = $this->consumer->_verifyDiscoveryResults($this->message, $endpoint);
+
+ $this->assertEquals($r, $endpoint);
+ }
+
+ function test_otherServer()
+ {
+ global $__test_otherServer_text;
+
+ // setup
+ $this->consumer = new TestDiscoveryVerification_test_otherServer($this->store);
+ $this->consumer->discoverMethod = array($this,
+ 'discoveryFunc');
+
+ // a set of things without the stuff
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $endpoint->claimed_id = $this->identifier;
+ $endpoint->server_url = "http://the-MOON.unittest/";
+ $endpoint->local_id = $this->identifier;
+ $this->services = array($endpoint);
+
+ $result = $this->consumer->_verifyDiscoveryResults(
+ $this->message, $endpoint);
+
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ $this->assertTrue(strpos($result->message, $__test_otherServer_text) !== false);
+ }
+
+ function test_foreignDelegate()
+ {
+ global $__test_otherServer_text;
+
+ // setup
+ $this->consumer = new TestDiscoveryVerification_test_otherServer($this->store);
+ $this->consumer->discoverMethod = array($this,
+ 'discoveryFunc');
+
+ // a set of things with the server stuff but other delegate
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $endpoint->claimed_id = $this->identifier;
+ $endpoint->server_url = $this->server_url;
+ $endpoint->local_id = "http://unittest/juan-carlos";
+
+ $result = $this->consumer->_verifyDiscoveryResults(
+ $this->message, $endpoint);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ $this->assertTrue(strpos($result->message, $__test_otherServer_text) !== false);
+ }
+
+ function test_nothingDiscovered()
+ {
+ // a set of no things.
+ $this->services = array();
+ $result = $this->consumer->_verifyDiscoveryResults(
+ $this->message, $this->endpoint);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+}
+
+class DummyEndpoint {
+ var $use_compatibility = false;
+
+ function compatibilityMode()
+ {
+ return $this->use_compatibility;
+ }
+}
+
+class TestCreateAssociationRequest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->endpoint = new DummyEndpoint();
+ $s = null;
+ $this->consumer = new Auth_OpenID_GenericConsumer($s);
+ $this->assoc_type = 'HMAC-SHA1';
+ }
+
+ function test_noEncryptionSendsType()
+ {
+ $session_type = 'no-encryption';
+ list($session, $args) = $this->consumer->_createAssociateRequest(
+ $this->endpoint, $this->assoc_type, $session_type);
+
+ $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));
+
+ $expected = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'session_type'=>$session_type,
+ 'mode'=>'associate',
+ 'assoc_type'=>$this->assoc_type));
+
+ $this->assertEquals($expected->toPostArgs(),
+ $args->toPostArgs());
+ }
+
+ function test_noEncryptionSendsTypeHMACSHA256()
+ {
+ if (!Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $this->pass();
+ return;
+ }
+
+ $session_type = 'no-encryption';
+ $this->assoc_type = 'HMAC-SHA256';
+
+ list($session, $args) = $this->consumer->_createAssociateRequest(
+ $this->endpoint, $this->assoc_type, $session_type);
+
+ $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));
+
+ $expected = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'session_type'=>$session_type,
+ 'mode'=>'associate',
+ 'assoc_type'=>$this->assoc_type));
+
+ $this->assertEquals($expected->toPostArgs(),
+ $args->toPostArgs());
+
+ $response = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'session_type'=>$session_type,
+ 'assoc_type'=>$this->assoc_type,
+ 'expires_in' => '10000000000',
+ 'mac_key' => 'ZM9v',
+ 'assoc_handle' => 'turnme'
+ )
+ );
+
+ $assoc = $this->consumer->_extractAssociation($response, $session);
+
+ $this->assertTrue($assoc !== null);
+ $this->assertTrue(is_a($assoc, 'Auth_OpenID_Association'));
+ $this->assertTrue($assoc->assoc_type = $this->assoc_type);
+ $this->assertTrue($assoc->session_type = $session_type);
+ }
+
+ function test_noEncryptionCompatibility()
+ {
+ $this->endpoint->use_compatibility = true;
+ $session_type = 'no-encryption';
+ list($session, $args) = $this->consumer->_createAssociateRequest(
+ $this->endpoint, $this->assoc_type, $session_type);
+
+ $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));
+ $this->assertEquals(Auth_OpenID_Message::fromOpenIDArgs(array('mode'=>'associate',
+ 'assoc_type'=>$this->assoc_type)),
+ $args);
+ }
+
+ function test_dhSHA1Compatibility()
+ {
+ // Set the consumer's session type to a fast session since we
+ // need it here.
+ setConsumerSession($this->consumer);
+
+ $this->endpoint->use_compatibility = true;
+ $session_type = 'DH-SHA1';
+ list($session, $args) = $this->consumer->_createAssociateRequest(
+ $this->endpoint, $this->assoc_type, $session_type);
+
+ $this->assertTrue(is_a($session,
+ 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession'));
+
+ // This is a random base-64 value, so just check that it's
+ // present.
+ $this->assertTrue($args->hasKey(Auth_OpenID_OPENID1_NS, 'dh_consumer_public'));
+ $args->delArg(Auth_OpenID_OPENID1_NS, 'dh_consumer_public');
+
+ // OK, session_type is set here and not for no-encryption
+ // compatibility
+ $expected = Auth_OpenID_Message::fromOpenIDArgs(array('mode'=>'associate',
+ 'session_type'=>'DH-SHA1',
+ 'assoc_type'=>$this->assoc_type,
+ 'dh_modulus'=> 'BfvStQ==',
+ 'dh_gen'=> 'Ag=='));
+
+ $this->assertEquals($expected->toPostArgs(),
+ $args->toPostArgs());
+ }
+}
+
+class TestDiffieHellmanResponseParameters extends PHPUnit_TestCase {
+ var $session_cls = null;
+ var $message_namespace = null;
+
+ function setUp()
+ {
+ // Pre-compute DH with small prime so tests run quickly.
+ $this->server_dh = new Auth_OpenID_DiffieHellman(100389557, 2);
+ $this->consumer_dh = new Auth_OpenID_DiffieHellman(100389557, 2);
+
+ $lib =& Auth_OpenID_getMathLib();
+
+ $cls = $this->session_cls;
+ $this->consumer_session = new $cls($this->consumer_dh);
+
+ // base64(btwoc(g ^ xb mod p))
+ $this->dh_server_public = $lib->longToBase64($this->server_dh->public);
+
+ $this->secret = Auth_OpenID_CryptUtil::randomString(
+ $this->consumer_session->secret_size);
+
+ $this->enc_mac_key = base64_encode(
+ $this->server_dh->xorSecret($this->consumer_dh->public,
+ $this->secret,
+ $this->consumer_session->hash_func));
+
+ $this->msg = new Auth_OpenID_Message($this->message_namespace);
+ }
+
+ function testExtractSecret()
+ {
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
+ $this->dh_server_public);
+
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
+ $this->enc_mac_key);
+
+ $extracted = $this->consumer_session->extractSecret($this->msg);
+ $this->assertEquals($extracted, $this->secret);
+ }
+
+ function testAbsentServerPublic()
+ {
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
+ $this->enc_mac_key);
+
+ $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
+ }
+
+ function testAbsentMacKey()
+ {
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
+ $this->dh_server_public);
+
+ $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
+ }
+
+ /*
+ function testInvalidBase64Public()
+ {
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
+ 'n o t b a s e 6 4.');
+
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
+ $this->enc_mac_key);
+
+ $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
+ }
+
+ function testInvalidBase64MacKey()
+ {
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
+ $this->dh_server_public);
+
+ $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
+ 'n o t base 64');
+
+ $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
+ }
+ */
+}
+
+class TestOpenID1SHA1 extends TestDiffieHellmanResponseParameters {
+ var $session_cls = 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession';
+ var $message_namespace = Auth_OpenID_OPENID1_NS;
+}
+
+class TestOpenID2SHA1 extends TestDiffieHellmanResponseParameters {
+ var $session_cls = 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession';
+ var $message_namespace = Auth_OpenID_OPENID2_NS;
+}
+
+if (!defined('Auth_OpenID_NO_MATH_SUPPORT') &&
+ Auth_OpenID_SHA256_SUPPORTED) {
+ class TestOpenID2SHA256 extends TestDiffieHellmanResponseParameters {
+ var $session_cls = 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession';
+ var $message_namespace = Auth_OpenID_OPENID2_NS;
+ }
+}
+
+class Tests_Auth_OpenID_KVPost extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->server_url = 'http://unittest/bogus';
+ }
+
+ function test_200()
+ {
+ $response = new Auth_Yadis_HTTPResponse();
+ $response->status = 200;
+ $response->body = "foo:bar\nbaz:quux\n";
+ $r = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);
+ $expected_msg = Auth_OpenID_Message::fromOpenIDArgs(array('foo' => 'bar', 'baz' => 'quux'));
+ $this->assertEquals($expected_msg, $r);
+ }
+
+ function test_400()
+ {
+ $response = new Auth_Yadis_HTTPResponse();
+ $response->status = 400;
+ $response->body = "error:bonk\nerror_code:7\n";
+ $result = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerErrorContainer'));
+ $this->assertEquals($result->error_text, 'bonk');
+ $this->assertEquals($result->error_code, '7');
+ }
+
+ function test_500()
+ {
+ // 500 as an example of any non-200, non-400 code.
+ $response = new Auth_Yadis_HTTPResponse();
+ $response->status = 500;
+ $response->body = "foo:bar\nbaz:quux\n";
+ $result = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);
+ $this->assertTrue($result === null);
+ }
+}
+
+// Add other test cases to be run.
+global $Tests_Auth_OpenID_Consumer_other;
+$Tests_Auth_OpenID_Consumer_other = array(
+ // new Tests_Auth_OpenID_Consumer_TestSetupNeeded(),
+ new Tests_Auth_OpenID_AuthRequestHTMLMarkup(),
+ new Tests_Auth_OpenID_Consumer_TestCheckAuth(),
+ new Tests_Auth_OpenID_Consumer_TestCheckAuthTriggered(),
+ new Tests_Auth_OpenID_Consumer_TestFetchAssoc(),
+ new Tests_Auth_OpenID_Consumer_CheckNonceTest(),
+ new Tests_Auth_OpenID_Complete(),
+ new Tests_Auth_OpenID_SuccessResponse(),
+ new Tests_Auth_OpenID_CheckAuthResponse(),
+ new Tests_Auth_OpenID_FetchErrorInIdRes(),
+ new Tests_Auth_OpenID_ConsumerTest2(),
+ new Tests_Auth_OpenID_Stateless1(),
+ new Tests_Auth_OpenID_Stateless2(),
+ new TestCompleteMissingSig(),
+ new TestReturnToArgs(),
+ new IDPDrivenTest(),
+ new TestDiscoveryVerification(),
+ new Tests_Auth_OpenID_KVPost(),
+ new Tests_idResURLMismatch(),
+ new IdResCheckForFieldsTest(),
+ );
+
+if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $Tests_Auth_OpenID_Consumer_other[] = new TestCreateAssociationRequest();
+ $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID1SHA1();
+ $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID2SHA1();
+}
+
+if (!defined('Auth_OpenID_NO_MATH_SUPPORT') &&
+ Auth_OpenID_SHA256_SUPPORTED) {
+ $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID2SHA256();
+}
+
+?>
diff --git a/Tests/Auth/OpenID/CryptUtil.php b/Tests/Auth/OpenID/CryptUtil.php
new file mode 100644
index 0000000..f158c1c
--- /dev/null
+++ b/Tests/Auth/OpenID/CryptUtil.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Tests for the CryptUtil functions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/CryptUtil.php';
+
+class Tests_Auth_OpenID_CryptUtil extends PHPUnit_TestCase {
+ function test_length()
+ {
+ $cases = array(1, 10, 255);
+ foreach ($cases as $length) {
+ $data = Auth_OpenID_CryptUtil::getBytes($length);
+ $this->assertEquals(Auth_OpenID::bytes($data), $length);
+ }
+ }
+
+ function test_different()
+ {
+ $num_iterations = 100;
+ $data_length = 20;
+
+ $data = Auth_OpenID_CryptUtil::getBytes($num_iterations);
+ for ($i = 0; $i < $num_iterations; $i++) {
+ $last = $data;
+ $data = Auth_OpenID_CryptUtil::getBytes($data_length);
+ $this->assertFalse($data == $last);
+ }
+ }
+
+ function test_cryptrand()
+ {
+ // It's possible, but HIGHLY unlikely that a correct
+ // implementation will fail by returning the same number twice
+
+ $s = Auth_OpenID_CryptUtil::getBytes(32);
+ $t = Auth_OpenID_CryptUtil::getBytes(32);
+ $this->assertEquals(Auth_OpenID::bytes($s), 32);
+ $this->assertEquals(Auth_OpenID::bytes($t), 32);
+ $this->assertFalse($s == $t);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/DiffieHellman.php b/Tests/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 0000000..080d909
--- /dev/null
+++ b/Tests/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Tests for the Diffie-Hellman key exchange implementation in the
+ * OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/DiffieHellman.php';
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+
+class Tests_Auth_OpenID_DiffieHellman_CheckCases extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_CheckCases($cases, $n)
+ {
+ $this->cases = $cases;
+ $this->n = $n;
+ }
+
+ function runTest()
+ {
+ $this->assertEquals($this->n, count($this->cases));
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman_Private extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_Private($name, $input, $expected)
+ {
+ $this->setName("$name");
+ $this->input = $input;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $dh = new Auth_OpenID_DiffieHellman(null, null, $this->input);
+ $this->assertEquals($lib->cmp($this->expected, $dh->getPublicKey()), 0);
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman_Exch extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_Exch($name, $p1, $p2, $shared)
+ {
+ $this->setName("$name");
+ $this->p1 = $p1;
+ $this->p2 = $p2;
+ $this->shared = $shared;
+ }
+
+ function runTest()
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $shared = $lib->init($this->shared);
+ $dh1 = new Auth_OpenID_DiffieHellman(null, null, $this->p1);
+ $dh2 = new Auth_OpenID_DiffieHellman(null, null, $this->p2);
+ $sh1 = $dh1->getSharedSecret($dh2->getPublicKey());
+ $sh2 = $dh2->getSharedSecret($dh1->getPublicKey());
+ $this->assertEquals($lib->cmp($shared, $sh1), 0);
+ $this->assertEquals($lib->cmp($shared, $sh2), 0);
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman extends PHPUnit_TestSuite {
+ function _readPrivateTestCases()
+ {
+ $lines = Tests_Auth_OpenID_readlines('dhpriv');
+ $cases = array();
+ foreach ($lines as $line) {
+ $case = array();
+ if (!preg_match('/^(\d+) (\d+)\n$/', $line, $case)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($case);
+ if ($c != 3) {
+ trigger_error("Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ }
+
+ array_shift($case);
+ $cases[] = $case;
+ }
+
+ return $cases;
+ }
+
+ function _readExchTestCases()
+ {
+ $lines = Tests_Auth_OpenID_readlines('dhexch');
+ $cases = array();
+ foreach ($lines as $line) {
+ $case = array();
+ if (!preg_match('/^(\d+) (\d+) (\d+)\n$/', $line, $case)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($case);
+ if ($c != 4) {
+ trigger_error("Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ }
+
+ array_shift($case);
+ $cases[] = $case;
+ }
+ return $cases;
+ }
+
+ function Tests_Auth_OpenID_DiffieHellman($name)
+ {
+ $this->setName($name);
+
+ $priv_cases = Tests_Auth_OpenID_DiffieHellman::_readPrivateTestCases();
+ $sanity = new Tests_Auth_OpenID_DiffieHellman_CheckCases(
+ $priv_cases, 29);
+ $sanity->setName('Check parsing of priv test data');
+ $this->addTest($sanity);
+
+ $exch_cases = Tests_Auth_OpenID_DiffieHellman::_readExchTestCases();
+ $sanity = new Tests_Auth_OpenID_DiffieHellman_CheckCases(
+ $exch_cases, 25);
+ $sanity->setName('Check parsing of exch test data');
+ $this->addTest($sanity);
+
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ if (defined('Tests_Auth_OpenID_thorough')) {
+ $npriv = count($priv_cases);
+ $nexch = count($exch_cases);
+ } else {
+ $npriv = 1;
+ $nexch = 3;
+ }
+
+ for ($i = 0; $i < $npriv; $i++) {
+ list($input, $expected) = $priv_cases[$i];
+ $one = new Tests_Auth_OpenID_DiffieHellman_Private(
+ "DHPriv $i", $input, $expected);
+ $this->addTest($one);
+ }
+
+ for ($i = 0; $i < $nexch; $i++) {
+ $case = $exch_cases[$i];
+ $one = new Tests_Auth_OpenID_DiffieHellman_Exch(
+ $i, $case[0], $case[1], $case[2]);
+ $this->addTest($one);
+ }
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/Discover_OpenID.php b/Tests/Auth/OpenID/Discover_OpenID.php
new file mode 100644
index 0000000..a00873f
--- /dev/null
+++ b/Tests/Auth/OpenID/Discover_OpenID.php
@@ -0,0 +1,783 @@
+<?php
+
+require_once 'PHPUnit.php';
+require_once 'TestUtil.php';
+
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/Discover.php';
+require_once 'Auth/Yadis/Manager.php';
+require_once 'Auth/Yadis/Misc.php';
+require_once 'Auth/Yadis/XRI.php';
+
+/**
+ * Tests for the core of the PHP Yadis library discovery logic.
+ */
+
+class _SimpleMockFetcher {
+ function _SimpleMockFetcher($responses)
+ {
+ $this->responses = $responses;
+ }
+
+ function get($url)
+ {
+ $response = array_pop($this->responses);
+ assert($response[1] == $url);
+ return $response;
+ }
+}
+
+class Tests_Auth_OpenID_ServiceEndpoint extends PHPUnit_TestCase {
+ function setUp() {
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ }
+
+ function test_getDisplayIdentifier_noFragment() {
+ $urls = array("http://foo.bar.com/something",
+ "http://foo.bar.com/something?else=what&nothing=0",
+ "https://smoker.myopenid.com/"
+ );
+
+ foreach ($urls as $url) {
+ $this->endpoint->claimed_id = $url;
+ $this->assertEquals($url, $this->endpoint->getDisplayIdentifier());
+ }
+ }
+
+ function test_getDisplayIdentifier_withFragment() {
+ $urls = array("http://foo.bar.com/something#fragged",
+ "http://foo.bar.com/something?else=what&nothing=0#ow",
+ "https://smoker.myopenid.com/#myentirelife"
+ );
+
+ foreach ($urls as $url) {
+ $this->endpoint->claimed_id = $url;
+ $split = explode('#', $url);
+ $this->assertEquals($split[0],
+ $this->endpoint->getDisplayIdentifier());
+ }
+ }
+}
+
+class Tests_Auth_OpenID_DiscoveryFailure extends PHPUnit_TestCase {
+
+ function Tests_Auth_OpenID_DiscoveryFailure($responses)
+ {
+ // Response is ($code, $url, $body).
+ $this->cases = array(
+ array(null, 'http://network.error/', ''),
+ array(404, 'http://not.found/', ''),
+ array(400, 'http://bad.request/', ''),
+ array(500, 'http://server.error/', ''),
+ array(200, 'http://header.found/', 200,
+ array('x-xrds-location' => 'http://xrds.missing/')),
+ array(404, 'http://xrds.missing/', ''));
+
+ $this->url = $responses[0]->final_url;
+ $this->responses = $responses;
+ $this->fetcher = new _SimpleMockFetcher($this->responses);
+ }
+
+ function runTest()
+ {
+ foreach ($this->cases as $case) {
+ list($status, $url, $body) = $case;
+ $expected_status = $status;
+
+ $result = Auth_OpenID_discover($this->url, $this->fetcher);
+ list($id_url, $svclist) = $result;
+
+ $this->assertEquals($svclist, array());
+ }
+ }
+}
+
+### Tests for raising/catching exceptions from the fetcher through the
+### discover function
+
+class _ErrorRaisingFetcher {
+ // Just raise an exception when fetch is called
+
+ function _ErrorRaisingFetcher($thing_to_raise)
+ {
+ $this->thing_to_raise = $thing_to_raise;
+ }
+
+ function post($body = null)
+ {
+ __raiseError($this->thing_to_raise);
+ }
+
+ function get($url)
+ {
+ __raiseError($this->thing_to_raise);
+ }
+}
+
+define('E_AUTH_OPENID_EXCEPTION', 'e_exception');
+define('E_AUTH_OPENID_DIDFETCH', 'e_didfetch');
+define('E_AUTH_OPENID_VALUE_ERROR', 'e_valueerror');
+define('E_AUTH_OPENID_RUNTIME_ERROR', 'e_runtimeerror');
+define('E_AUTH_OPENID_OI', 'e_oi');
+
+class Tests_Auth_OpenID_Discover_FetchException extends PHPUnit_TestCase {
+ // Make sure exceptions get passed through discover function from
+ // fetcher.
+
+ function Tests_Auth_OpenID_Discover_FetchException($exc)
+ {
+ $this->cases = array(E_AUTH_OPENID_EXCEPTION,
+ E_AUTH_OPENID_DIDFETCH,
+ E_AUTH_OPENID_VALUE_ERROR,
+ E_AUTH_OPENID_RUNTIME_ERROR,
+ E_AUTH_OPENID_OI);
+ }
+
+ function runTest()
+ {
+ foreach ($this->cases as $thing_to_raise) {
+ $fetcher = ErrorRaisingFetcher($thing_to_raise);
+ Auth_OpenID_discover('http://doesnt.matter/', $fetcher);
+ $exc = __getError();
+
+ if ($exc !== $thing_to_raise) {
+ $this->fail('FetchException expected %s to be raised',
+ $thing_to_raise);
+ }
+ }
+ }
+}
+
+
+// Tests for openid.consumer.discover.discover
+
+class _DiscoveryMockFetcher extends Auth_Yadis_HTTPFetcher {
+ function _DiscoveryMockFetcher(&$documents)
+ {
+ $this->redirect = null;
+ $this->documents = &$documents;
+ $this->fetchlog = array();
+ }
+
+ function supportsSSL()
+ {
+ return true;
+ }
+
+ function post($url, $body = null, $headers = null)
+ {
+ return $this->get($url, $headers, $body);
+ }
+
+ function get($url, $headers = null, $body = null)
+ {
+ $this->fetchlog[] = array($url, $body, $headers);
+
+ if ($this->redirect) {
+ $final_url = $this->redirect;
+ } else {
+ $final_url = $url;
+ }
+
+ if (array_key_exists($url, $this->documents)) {
+ list($ctype, $body) = $this->documents[$url];
+ $status = 200;
+ } else {
+ $status = 404;
+ $ctype = 'text/plain';
+ $body = '';
+ }
+
+ return new Auth_Yadis_HTTPResponse($final_url, $status,
+ array('content-type' => $ctype), $body);
+ }
+}
+
+class _DiscoveryBase extends PHPUnit_TestCase {
+ var $id_url = "http://someuser.unittest/";
+ var $fetcherClass = '_DiscoveryMockFetcher';
+
+ function _checkService($s,
+ $server_url,
+ $claimed_id=null,
+ $local_id=null,
+ $canonical_id=null,
+ $types=null,
+ $used_yadis=false,
+ $display_identifier=null)
+ {
+ $this->assertEquals($server_url, $s->server_url);
+ if ($types == array('2.0 OP')) {
+ $this->assertFalse($claimed_id);
+ $this->assertFalse($local_id);
+ $this->assertFalse($s->claimed_id);
+ $this->assertFalse($s->local_id);
+ $this->assertFalse($s->getLocalID());
+ $this->assertFalse($s->compatibilityMode());
+ $this->assertTrue($s->isOPIdentifier());
+ $this->assertEquals($s->preferredNamespace(),
+ Auth_OpenID_OPENID2_NS);
+ } else {
+ $this->assertEquals($claimed_id, $s->claimed_id);
+ $this->assertEquals($local_id, $s->getLocalID());
+ }
+
+ if ($used_yadis) {
+ $this->assertTrue($s->used_yadis, "Expected to use Yadis");
+ } else {
+ $this->assertFalse($s->used_yadis,
+ "Expected to use old-style discovery");
+ }
+
+ $openid_types = array(
+ '1.1' => Auth_OpenID_TYPE_1_1,
+ '1.0' => Auth_OpenID_TYPE_1_0,
+ '2.0' => Auth_OpenID_TYPE_2_0,
+ '2.0 OP' => Auth_OpenID_TYPE_2_0_IDP);
+
+ $type_uris = array();
+ foreach ($types as $t) {
+ $type_uris[] = $openid_types[$t];
+ }
+
+ $this->assertEquals($type_uris, $s->type_uris);
+ $this->assertEquals($canonical_id, $s->canonicalID);
+
+ if ($s->canonicalID) {
+ $this->assertTrue($s->getDisplayIdentifier() != $claimed_id);
+ $this->assertTrue($s->getDisplayIdentifier() !== null);
+ $this->assertEquals($display_identifier, $s->getDisplayIdentifier());
+ $this->assertEquals($s->claimed_id, $s->canonicalID);
+ }
+
+ $this->assertEquals($s->display_identifier ? $s->display_identifier : $s->claimed_id,
+ $s->getDisplayIdentifier());
+ }
+
+ function setUp()
+ {
+ $cls = $this->fetcherClass;
+ // D is for Dumb.
+ $d = array();
+ $this->fetcher = new $cls($d);
+ }
+}
+
+class Tests_Auth_OpenID_Discover_OpenID extends _DiscoveryBase {
+ function _discover($content_type, $data,
+ $expected_services, $expected_id=null)
+ {
+ if ($expected_id === null) {
+ $expected_id = $this->id_url;
+ }
+
+ $this->fetcher->documents[$this->id_url] = array($content_type, $data);
+ list($id_url, $services) = Auth_OpenID_discover($this->id_url,
+ $this->fetcher);
+ $this->assertEquals($expected_services, count($services));
+ $this->assertEquals($expected_id, $id_url);
+ return $services;
+ }
+
+ function test_404()
+ {
+ list($url, $services) = Auth_OpenID_discover($this->id_url . '/404',
+ $this->fetcher);
+ $this->assertTrue($services == array());
+ }
+
+ function test_noOpenID()
+ {
+ $services = $this->_discover('text/plain',
+ "junk",
+ 0);
+
+ $services = $this->_discover(
+ 'text/html',
+ Tests_Auth_OpenID_readdata('test_discover_openid_no_delegate.html'),
+ 1);
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ $this->id_url,
+ null,
+ array('1.1'),
+ false);
+ }
+
+ function test_html1()
+ {
+ $services = $this->_discover('text/html',
+ Tests_Auth_OpenID_readdata('test_discover_openid.html'),
+ 1);
+
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('1.1'),
+ false,
+ $this->id_url);
+ }
+
+ /*
+ * Ensure that the Claimed Identifier does not have a fragment if
+ * one is supplied in the User Input.
+ */
+ function test_html1Fragment()
+ {
+ $data = Tests_Auth_OpenID_readdata('openid.html');
+ $content_type = 'text/html';
+ $expected_services = 1;
+
+ $this->fetcher->documents[$this->id_url] = array($content_type, $data);
+ $expected_id = $this->id_url;
+ $this->id_url = $this->id_url . '#fragment';
+ list($id_url, $services) = Auth_OpenID_discover($this->id_url, $this->fetcher);
+ $this->assertEquals($expected_services, count($services));
+ $this->assertEquals($expected_id, $id_url);
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ $expected_id,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('1.1'),
+ false,
+ $this->id_url);
+ }
+
+ function test_html2()
+ {
+ $services = $this->_discover('text/html',
+ Tests_Auth_OpenID_readdata('test_discover_openid2.html'),
+ 1);
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('2.0'),
+ false,
+ $this->id_url);
+ }
+
+ function test_html1And2()
+ {
+ $services = $this->_discover('text/html',
+ Tests_Auth_OpenID_readdata('test_discover_openid_1_and_2.html'),
+ 2);
+
+ $types = array('2.0', '1.1');
+
+ for ($i = 0; $i < count($types); $i++) {
+ $t = $types[$i];
+ $s = $services[$i];
+
+ $this->_checkService(
+ $s,
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array($t),
+ false,
+ $this->id_url);
+ }
+ }
+
+ function test_yadisEmpty()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_0entries.xml'),
+ 0);
+ }
+
+ function test_htmlEmptyYadis()
+ {
+ // HTML document has discovery information, but points to an
+ // empty Yadis document.
+
+ // The XRDS document pointed to by "openid_and_yadis.html"
+ $this->fetcher->documents[$this->id_url . 'xrds'] =
+ array('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_0entries.xml'));
+
+ $services = $this->_discover('text/html',
+ Tests_Auth_OpenID_readdata('test_discover_openid_and_yadis.html'),
+ 1);
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('1.1'),
+ false,
+ $this->id_url);
+ }
+
+ function test_yadis1NoDelegate()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_no_delegate.xml'),
+ 1);
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ $this->id_url,
+ null,
+ array('1.0'),
+ true,
+ $this->id_url);
+ }
+
+ function test_yadis2NoLocalID()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid2_xrds_no_local_id.xml'),
+ 1);
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ $this->id_url,
+ null,
+ array('2.0'),
+ true,
+ $this->id_url);
+ }
+
+ function test_yadis2()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid2_xrds.xml'),
+ 1);
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('2.0'),
+ true,
+ $this->id_url);
+ }
+
+ function test_yadis2OP()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_idp.xml'),
+ 1);
+
+ $this->_checkService($services[0],
+ "http://www.myopenid.com/server",
+ null,
+ null,
+ null,
+ array('2.0 OP'),
+ true,
+ $this->id_url);
+ }
+
+ function test_yadis2OPDelegate()
+ {
+ // The delegate tag isn't meaningful for OP entries.
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_idp_delegate.xml'),
+ 1);
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ null, null, null,
+ array('2.0 OP'),
+ true,
+ $this->id_url);
+ }
+
+ function test_yadis2BadLocalID()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_yadis_2_bad_local_id.xml'),
+ 0);
+ }
+
+ function test_yadis1And2()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid_1_and_2_xrds.xml'),
+ 1);
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ $this->id_url,
+ 'http://smoker.myopenid.com/',
+ null,
+ array('2.0', '1.1'),
+ true);
+ }
+
+ function test_yadis1And2BadLocalID()
+ {
+ $services = $this->_discover('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid_1_and_2_xrds_bad_delegate.xml'),
+ 0);
+ }
+}
+
+class _MockFetcherForXRIProxy extends Auth_Yadis_HTTPFetcher {
+
+ function _MockFetcherForXRIProxy($documents)
+ {
+ $this->documents = $documents;
+ $this->fetchlog = array();
+ }
+
+ function get($url, $headers=null)
+ {
+ return $this->fetch($url, $headers);
+ }
+
+ function post($url, $body)
+ {
+ return $this->fetch($url, $body);
+ }
+
+ function fetch($url, $body=null, $headers=null)
+ {
+ $this->fetchlog[] = array($url, $body, $headers);
+
+ $u = parse_url($url);
+ $proxy_host = $u['host'];
+ $xri = $u['path'];
+ $query = Auth_OpenID::arrayGet($u, 'query');
+
+ if ((!$headers) && (!$query)) {
+ trigger_error('Error in mock XRI fetcher: no headers or query');
+ }
+
+ if (Auth_Yadis_startswith($xri, '/')) {
+ $xri = substr($xri, 1);
+ }
+
+ if (array_key_exists($xri, $this->documents)) {
+ list($ctype, $body) = $this->documents[$xri];
+ $status = 200;
+ } else {
+ $status = 404;
+ $ctype = 'text/plain';
+ $body = '';
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $status,
+ array('content-type' => $ctype),
+ $body);
+ }
+}
+
+class TestXRIDiscovery extends _DiscoveryBase {
+ var $fetcherClass = '_MockFetcherForXRIProxy';
+
+ function setUp() {
+ parent::setUp();
+
+ $this->fetcher->documents = array('=smoker' => array('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('yadis_2entries_delegate.xml')),
+ '=smoker*bad' => array('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('yadis_another_delegate.xml')));
+ }
+
+ function test_xri() {
+ list($user_xri, $services) = Auth_OpenID_discoverXRI('=smoker');
+
+ $this->_checkService(
+ $services[0],
+ "http://www.myopenid.com/server",
+ Auth_Yadis_XRI("=!1000"),
+ 'http://smoker.myopenid.com/',
+ Auth_Yadis_XRI("=!1000"),
+ array('1.0'),
+ true,
+ '=smoker');
+
+ $this->_checkService(
+ $services[1],
+ "http://www.livejournal.com/openid/server.bml",
+ Auth_Yadis_XRI("=!1000"),
+ 'http://frank.livejournal.com/',
+ Auth_Yadis_XRI("=!1000"),
+ array('1.0'),
+ true,
+ '=smoker');
+ }
+
+ function test_xriNoCanonicalID() {
+ list($user_xri, $services) = Auth_OpenID_discoverXRI('=smoker*bad');
+ $this->assertFalse($services);
+ }
+
+ function test_useCanonicalID() {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->claimed_id = Auth_Yadis_XRI("=!1000");
+ $endpoint->canonicalID = Auth_Yadis_XRI("=!1000");
+ $htis->assertEquals($endpoint->getLocalID(), Auth_Yadis_XRI("=!1000"));
+ }
+}
+
+class Tests_Auth_OpenID_DiscoverSession {
+ function Tests_Auth_OpenID_DiscoverSession()
+ {
+ $this->data = array();
+ }
+
+ function set($name, $value)
+ {
+ $this->data[$name] = $value;
+ }
+
+ function get($name, $default=null)
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ function del($name)
+ {
+ unset($this->data[$name]);
+ }
+}
+
+global $__Tests_BOGUS_SERVICE;
+$__Tests_BOGUS_SERVICE = new Auth_OpenID_ServiceEndpoint();
+$__Tests_BOGUS_SERVICE->claimed_id = "=really.bogus.endpoint";
+
+function __serviceCheck_discover_cb($url, $fetcher)
+{
+ global $__Tests_BOGUS_SERVICE;
+ return array($url, array($__Tests_BOGUS_SERVICE));
+}
+
+class _FetcherWithSSL extends _DiscoveryMockFetcher {
+ function supportsSSL()
+ {
+ return true;
+ }
+}
+
+class _FetcherWithoutSSL extends _DiscoveryMockFetcher {
+ function supportsSSL()
+ {
+ return false;
+ }
+}
+
+class _NonFetcher extends _DiscoveryMockFetcher {
+ var $used = false;
+
+ function _NonFetcher()
+ {
+ $a = array();
+ parent::_DiscoveryMockFetcher($a);
+ }
+
+ function supportsSSL()
+ {
+ return false;
+ }
+
+ function get($url, $headers)
+ {
+ $this->used = true;
+ }
+}
+
+class Tests_Auth_OpenID_SSLSupport extends PHPUnit_TestCase {
+ function test_discoverDropSSL()
+ {
+ // In the absence of SSL support, the discovery process should
+ // drop endpoints whose server URLs are HTTPS.
+ $id_url = 'http://bogus/';
+
+ $d = array(
+ $id_url => array('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid_ssl.xml'))
+ );
+
+ $f =& new _FetcherWithoutSSL($d);
+
+ $result = Auth_OpenID_discover($id_url, $f);
+
+ list($url, $services) = $result;
+
+ $this->assertTrue($url == $id_url);
+ $this->assertTrue(count($services) == 1);
+
+ $e = $services[0];
+ $this->assertTrue($e->server_url == 'http://nossl.vroom.unittest/server');
+ }
+
+ function test_discoverRetainSSL()
+ {
+ // In the presence of SSL support, the discovery process
+ // should NOT drop endpoints whose server URLs are HTTPS.
+
+ // In the absence of SSL support, the discovery process should
+ // drop endpoints whose server URLs are HTTPS.
+ $id_url = 'http://bogus/';
+
+ $d = array(
+ $id_url => array('application/xrds+xml',
+ Tests_Auth_OpenID_readdata('test_discover_openid_ssl.xml'))
+ );
+
+ $f =& new _FetcherWithSSL($d);
+
+ $result = Auth_OpenID_discover($id_url, $f);
+
+ list($url, $services) = $result;
+
+ $this->assertTrue($url == $id_url);
+ $this->assertTrue(count($services) == 2);
+
+ $e = $services[0];
+ $this->assertTrue($e->server_url == 'http://nossl.vroom.unittest/server');
+
+ $e = $services[1];
+ $this->assertTrue($e->server_url == 'https://ssl.vroom.unittest/server');
+ }
+
+ function test_discoverSSL()
+ {
+ // The consumer code should not attempt to perform discovery
+ // on an HTTPS identity URL in the absence of SSL support.
+
+ $id_url = 'https://unsupported/';
+
+ $f =& new _NonFetcher();
+
+ $result = Auth_OpenID_discover($id_url, $f);
+
+ $this->assertTrue($result == array($id_url, array()));
+ $this->assertFalse($f->used);
+ }
+}
+
+global $Tests_Auth_OpenID_Discover_OpenID_other;
+$Tests_Auth_OpenID_Discover_OpenID_other = array(
+ new Tests_Auth_OpenID_SSLSupport()
+ );
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Extension.php b/Tests/Auth/OpenID/Extension.php
new file mode 100644
index 0000000..477110d
--- /dev/null
+++ b/Tests/Auth/OpenID/Extension.php
@@ -0,0 +1,46 @@
+<?php
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/Message.php';
+require_once 'Auth/OpenID/Extension.php';
+
+class _ExtensionTest_DummyExtension extends Auth_OpenID_Extension {
+ var $ns_uri = 'http://an.extension/';
+ var $ns_alias = 'dummy';
+
+ function getExtensionArgs()
+ {
+ return array();
+ }
+}
+
+class Tests_Auth_OpenID_Extension extends PHPUnit_TestCase {
+ function test_OpenID1()
+ {
+ $oid1_msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $ext = new _ExtensionTest_DummyExtension();
+ $ext->toMessage($oid1_msg);
+ $namespaces = $oid1_msg->namespaces;
+
+ $this->assertTrue($namespaces->isImplicit($ext->ns_uri));
+ $this->assertEquals($ext->ns_uri,
+ $namespaces->getNamespaceURI($ext->ns_alias));
+ $this->assertEquals($ext->ns_alias,
+ $namespaces->getAlias($ext->ns_uri));
+ }
+
+ function test_OpenID2()
+ {
+ $oid2_msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $ext = new _ExtensionTest_DummyExtension();
+ $ext->toMessage($oid2_msg);
+ $namespaces = $oid2_msg->namespaces;
+ $this->assertFalse($namespaces->isImplicit($ext->ns_uri));
+ $this->assertEquals($ext->ns_uri,
+ $namespaces->getNamespaceURI($ext->ns_alias));
+ $this->assertEquals($ext->ns_alias,
+ $namespaces->getAlias($ext->ns_uri));
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/HMAC.php b/Tests/Auth/OpenID/HMAC.php
new file mode 100644
index 0000000..874892a
--- /dev/null
+++ b/Tests/Auth/OpenID/HMAC.php
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * Tests for the HMAC-SHA1 utility functions used by the OpenID
+ * library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/HMAC.php';
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+
+class Tests_Auth_OpenID_HMAC_TestCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_HMAC_TestCase(
+ $name, $key, $data, $expected, $hmac_func)
+ {
+
+ $this->setName($name);
+ $this->key = $key;
+ $this->data = $data;
+ $this->expected = $expected;
+ $this->hmac_func = $hmac_func;
+ }
+
+ function runTest()
+ {
+ $actual = call_user_func($this->hmac_func, $this->key, $this->data);
+ $this->assertEquals(bin2hex($this->expected), bin2hex($actual));
+ }
+}
+
+class Tests_Auth_OpenID_HMAC extends PHPUnit_TestSuite {
+ function _strConvert($s)
+ {
+ $repeat_pat = '/^0x([a-f0-9]{2}) repeated (\d+) times$/';
+ if (preg_match($repeat_pat, $s, $match)) {
+ $c = chr(hexdec($match[1]));
+ $n = $match[2];
+ $data = '';
+ for ($i = 0; $i < $n; $i++) {
+ $data .= $c;
+ }
+ } elseif (substr($s, 0, 2) == "0x") {
+ $data = pack('H*', substr($s, 2, strlen($s) - 1));
+ } elseif (preg_match('/^"(.*)"$/', $s, $match)) {
+ $data = $match[1];
+ } else {
+ trigger_error("Bad data format: $s", E_USER_ERROR);
+ }
+ return $data;
+ }
+
+ function _readTestCases($test_file_name, $digest_len)
+ {
+ $lines = Tests_Auth_OpenID_readlines($test_file_name);
+ $cases = array();
+ $case = array();
+ foreach ($lines as $line) {
+ if ($line{0} == "#") {
+ continue;
+ }
+
+ // Blank line separates test cases
+ if ($line == "\n") {
+ $cases[] = $case;
+ $case = array();
+ } else {
+ $match = array();
+ $pat = '/^([a-z0-9_-]+) =\s+(.*?)\n$/';
+ if (!preg_match($pat, $line, $match)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($match);
+ if ($c != 3) {
+ trigger_error(
+ "Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ return false;
+ }
+
+ $key = $match[1];
+ $value = $match[2];
+ $case[$key] = $value;
+ }
+ }
+
+ if (count($case)) {
+ $cases[] = $case;
+ }
+
+ $final = array();
+
+ // Normalize strings and check data integrity
+ foreach ($cases as $case) {
+ $clean = array();
+ $clean["key"] =
+ Tests_Auth_OpenID_HMAC::_strConvert($case["key"]);
+ if (defined(@$case["key_len"])) {
+ if (Auth_OpenID::bytes($clean["key"]) != $case["key_len"]) {
+ trigger_error("Bad key length", E_USER_ERROR);
+ }
+ }
+
+ $clean["data"] =
+ Tests_Auth_OpenID_HMAC::_strConvert($case["data"]);
+ if (defined(@$case["data_len"])) {
+ if (Auth_OpenID::bytes($clean["data"]) != $case["data_len"]) {
+ trigger_error("Bad data length", E_USER_ERROR);
+ }
+ }
+
+ $clean["digest"] =
+ Tests_Auth_OpenID_HMAC::_strConvert($case["digest"]);
+ if (Auth_OpenID::bytes($clean["digest"]) != $digest_len) {
+ $l = Auth_OpenID::bytes($clean["digest"]);
+ trigger_error("Bad digest length: $l", E_USER_ERROR);
+ }
+
+ $clean['test_case'] = $case['test_case'];
+
+ $final[] = $clean;
+ }
+ return $final;
+ }
+
+ function Tests_Auth_OpenID_HMAC($name)
+ {
+ $this->setName($name);
+ $hash_test_defs = array(array(
+ 'Auth_OpenID_HMACSHA1', 'hmac-sha1.txt', 20));
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $hash_test_defs[] =
+ array('Auth_OpenID_HMACSHA256', 'hmac-sha256.txt', 32);
+ }
+ foreach ($hash_test_defs as $params) {
+ list($hash_func, $filename, $hash_len) = $params;
+ $cases = $this->_readTestCases($filename, $hash_len);
+ foreach ($cases as $case) {
+ $test = new Tests_Auth_OpenID_HMAC_TestCase(
+ $case['test_case'],
+ $case['key'],
+ $case['data'],
+ $case['digest'],
+ $hash_func);
+
+ $digest = $case['digest'];
+ $this->_addTestByValue($test);
+ }
+ }
+ }
+
+ function _addTestByValue($test) {
+ $this->addTest($test);
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/KVForm.php b/Tests/Auth/OpenID/KVForm.php
new file mode 100644
index 0000000..eb7162a
--- /dev/null
+++ b/Tests/Auth/OpenID/KVForm.php
@@ -0,0 +1,261 @@
+<?php
+
+/**
+ * Tests for the KVForm module.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/KVForm.php';
+
+global $_Tests_Auth_OpenID_kverrors;
+$_Tests_Auth_OpenID_kverrors = null;
+
+/**
+ * Keep a list of the logged errors
+ */
+function Tests_Auth_OpenID_kvHandleError($errno, $errmsg)
+{
+ global $_Tests_Auth_OpenID_kverrors;
+ $_Tests_Auth_OpenID_kverrors[] = $errmsg;
+}
+
+
+class Tests_Auth_OpenID_KVForm_TestCase extends PHPUnit_TestCase {
+ var $errs;
+
+ function runTest()
+ {
+ // Re-set the number of logged errors
+ global $_Tests_Auth_OpenID_kverrors;
+ $_Tests_Auth_OpenID_kverrors = array();
+
+ set_error_handler("Tests_Auth_OpenID_kvHandleError");
+
+ $this->_runTest();
+
+ // Check to make sure we have the expected number of logged errors
+ //$this->assertEquals($this->errs, count($_Tests_Auth_OpenID_kverrors));
+
+ restore_error_handler();
+ }
+
+ function _runTest()
+ {
+ trigger_error('Must be overridden', E_USER_ERROR);
+ }
+}
+
+class Tests_Auth_OpenID_KVForm_TestCase_Parse
+extends Tests_Auth_OpenID_KVForm_TestCase {
+ function Tests_Auth_OpenID_KVForm_TestCase_Parse(
+ $arr, $str, $lossy, $errs)
+ {
+
+ $this->arr = $arr;
+ $this->str = $str;
+ $this->lossy = $lossy;
+ $this->errs = $errs;
+ }
+
+ function _runTest()
+ {
+ // Do one parse, after which arrayToKV and kvToArray should be
+ // inverses.
+ $parsed1 = Auth_OpenID_KVForm::toArray($this->str);
+ $serial1 = Auth_OpenID_KVForm::fromArray($this->arr);
+
+ if ($this->lossy == "neither" || $this->lossy == "str") {
+ $this->assertEquals($this->arr, $parsed1, "str was lossy");
+ }
+
+ if ($this->lossy == "neither" || $this->lossy == "arr") {
+ $this->assertEquals($this->str, $serial1, "array was lossy");
+ }
+
+ $parsed2 = Auth_OpenID_KVForm::toArray($serial1);
+ $serial2 = Auth_OpenID_KVForm::fromArray($parsed1);
+
+ // Round-trip both
+ $parsed3 = Auth_OpenID_KVForm::toArray($serial2);
+ $serial3 = Auth_OpenID_KVForm::fromArray($parsed2);
+
+ $this->assertEquals($serial2, $serial3, "serialized forms differ");
+
+ // Check to make sure that they're inverses.
+ $this->assertEquals($parsed2, $parsed3, "parsed forms differ");
+
+ }
+}
+
+class Tests_Auth_OpenID_KVForm_TestCase_Null
+extends Tests_Auth_OpenID_KVForm_TestCase {
+ function Tests_Auth_OpenID_KVForm_TestCase_Null($arr, $errs)
+ {
+ $this->arr = $arr;
+ $this->errs = $errs;
+ }
+
+ function _runTest()
+ {
+ $serialized = Auth_OpenID_KVForm::fromArray($this->arr);
+ $this->assertTrue($serialized === null,
+ 'serialization unexpectedly succeeded');
+ }
+}
+
+class Tests_Auth_OpenID_KVForm extends PHPUnit_TestSuite {
+ function Tests_Auth_OpenID_KVForm($name)
+ {
+ $this->setName($name);
+ $testdata_list = array(
+ array("name" => "simple",
+ "str" => "college:harvey mudd\n",
+ "arr" => array("college" => "harvey mudd"),
+ ),
+ array("name" => "empty",
+ "str" => "",
+ "arr" => array(),
+ ),
+ array("name" => "empty (just newline)",
+ "str" => "\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "empty (double newline)",
+ "str" => "\n\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 2,
+ ),
+ array("name" => "empty (no colon)",
+ "str" => "East is least\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "two keys",
+ "str" => "city:claremont\nstate:CA\n",
+ "arr" => array('city' => 'claremont',
+ 'state' => 'CA'),
+ ),
+ array("name" => "real life",
+ "str" => "is_valid:true\ninvalidate_handle:" .
+ "{HMAC-SHA1:2398410938412093}\n",
+ "arr" => array('is_valid' => 'true',
+ 'invalidate_handle' =>
+ '{HMAC-SHA1:2398410938412093}'),
+ ),
+ array("name" => "empty key and value",
+ "str" => ":\n",
+ "arr" => array(''=>''),
+ ),
+ array("name" => "empty key, not value",
+ "str" => ":missing key\n",
+ "arr" => array(''=>'missing key'),
+ ),
+ array("name" => "whitespace at front of key",
+ "str" => " street:foothill blvd\n",
+ "arr" => array('street'=>'foothill blvd'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "whitespace at front of value",
+ "str" => "major: computer science\n",
+ "arr" => array('major'=>'computer science'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "whitespace around key and value",
+ "str" => " dorm : east \n",
+ "arr" => array('dorm'=>'east'),
+ "lossy" => "str",
+ "errors" => 2,
+ ),
+ array("name" => "missing trailing newline",
+ "str" => "e^(i*pi)+1:0",
+ "arr" => array('e^(i*pi)+1'=>'0'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "missing trailing newline (two key)",
+ "str" => "east:west\nnorth:south",
+ "arr" => array('east'=>'west',
+ 'north'=>'south'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "colon in key",
+ "arr" => array("k:k" => 'v'),
+ "errors" => 1,
+ ),
+ array("name" => "newline in key",
+ "arr" => array("k\nk" => 'v'),
+ "errors" => 1,
+ ),
+ array("name" => "newline in value",
+ "arr" => array('k' => "v\nv"),
+ "errors" => 1,
+ ),
+ array("name" => "array whitespace",
+ "arr" => array(" k " => "v"),
+ "lossy" => "both",
+ "str" => " k :v\n",
+ "errors" => 2,
+ ),
+ array("name" => "array ordering 1",
+ "arr" => array("a" => "x",
+ "b" => "x",
+ "c" => "x"),
+ "str" => "a:x\nb:x\nc:x\n",
+ ),
+ array("name" => "array ordering 2",
+ "arr" => array("a" => "x",
+ "c" => "x",
+ "b" => "x"),
+ "str" => "a:x\nc:x\nb:x\n",
+ ),
+ );
+
+ foreach ($testdata_list as $testdata) {
+ if (isset($testdata['str'])) {
+ $str = $testdata['str'];
+ } else {
+ $str = null;
+ }
+
+ $arr = $testdata["arr"];
+
+ if (isset($testdata['errors'])) {
+ $errs = $testdata["errors"];
+ } else {
+ $errs = 0;
+ }
+
+ if (is_null($str)) {
+ $test = new Tests_Auth_OpenID_KVForm_TestCase_Null($arr, $errs);
+ } else {
+ if (isset($testdata['lossy'])) {
+ $lossy = $testdata["lossy"];
+ } else {
+ $lossy = 'neither';
+ }
+ $test = new Tests_Auth_OpenID_KVForm_TestCase(
+ $arr, $str, $lossy, $errs);
+ }
+ $test->setName($testdata["name"]);
+ $this->addTest($test);
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/MemStore.php b/Tests/Auth/OpenID/MemStore.php
new file mode 100644
index 0000000..a45aae9
--- /dev/null
+++ b/Tests/Auth/OpenID/MemStore.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * In-memory OpenID store implementation for testing only
+ */
+require_once "Auth/OpenID/Interface.php";
+require_once 'Auth/OpenID/Nonce.php';
+
+class ServerAssocs {
+ function ServerAssocs()
+ {
+ $this->assocs = array();
+ }
+
+ function set($assoc)
+ {
+ $this->assocs[$assoc->handle] = $assoc;
+ }
+
+ function get($handle)
+ {
+ return Auth_OpenID::arrayGet($this->assocs, $handle);
+ }
+
+ function remove($handle)
+ {
+ if (array_key_exists($handle, $this->assocs)) {
+ unset($this->assocs[$handle]);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * Returns association with the oldest issued date.
+ *
+ * or null if there are no associations.
+ */
+ function best()
+ {
+ $best = null;
+ foreach ($this->assocs as $handle => $assoc) {
+ if (($best === null) || ($best->issued < $assoc->issued)) {
+ $best = $assoc;
+ }
+ }
+ return $best;
+ }
+
+ /*
+ * Remove expired associations.
+ *
+ * @return (removed associations, remaining associations)
+ */
+ function cleanup()
+ {
+ $remove = array();
+ foreach ($this->assocs as $handle => $assoc) {
+ if ($assoc->getExpiresIn() == 0) {
+ $remove[] = $handle;
+ }
+ }
+
+ foreach ($remove as $handle) {
+ unset($this->assocs[$handle]);
+ }
+
+ return array(count($remove), count($this->assocs));
+ }
+}
+
+/*
+ * In-process memory store.
+ *
+ * Use for single long-running processes. No persistence supplied.
+ */
+class Tests_Auth_OpenID_MemStore extends Auth_OpenID_OpenIDStore {
+ function Tests_Auth_OpenID_MemStore()
+ {
+ $this->server_assocs = array();
+ $this->nonces = array();
+ }
+
+ function &_getServerAssocs($server_url)
+ {
+ if (!array_key_exists($server_url, $this->server_assocs)) {
+ $this->server_assocs[$server_url] =& new ServerAssocs();
+ }
+
+ return $this->server_assocs[$server_url];
+ }
+
+ function storeAssociation($server_url, $assoc)
+ {
+ $assocs =& $this->_getServerAssocs($server_url);
+ $assocs->set($assoc);
+ }
+
+ function getAssociation($server_url, $handle=null)
+ {
+ $assocs =& $this->_getServerAssocs($server_url);
+ if ($handle === null) {
+ return $assocs->best();
+ } else {
+ return $assocs->get($handle);
+ }
+ }
+
+ function removeAssociation($server_url, $handle)
+ {
+ $assocs =& $this->_getServerAssocs($server_url);
+ return $assocs->remove($handle);
+ }
+
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
+ return false;
+ }
+
+ $anonce = array($server_url, intval($timestamp), $salt);
+
+ if (in_array($anonce, $this->nonces)) {
+ return false;
+ } else {
+ array_push($this->nonces, $anonce);
+ return true;
+ }
+ }
+
+ function cleanupNonces()
+ {
+ global $Auth_OpenID_SKEW;
+
+ $now = time();
+ $expired = array();
+ foreach ($this->nonces as $anonce) {
+ if (abs($anonce[1] - $now) > $Auth_OpenID_SKEW) {
+ // removing items while iterating over the set could
+ // be bad.
+ $expired[] = $anonce;
+ }
+ }
+
+ foreach ($expired as $anonce) {
+ unset($this->nonces[array_search($anonce, $this->nonces)]);
+ }
+
+ return count($expired);
+ }
+
+ function cleanupAssociations()
+ {
+ $remove_urls = array();
+ $removed_assocs = 0;
+ foreach ($this->server_assocs as $server_url => $assocs) {
+ list($removed, $remaining) = $assocs->cleanup();
+ $removed_assocs += $removed;
+ if (!$remaining) {
+ $remove_urls[] = $server_url;
+ }
+ }
+
+ // Remove entries from server_assocs that had none remaining.
+ foreach ($remove_urls as $server_url) {
+ unset($this->server_assocs[$server_url]);
+ }
+
+ return $removed_assocs;
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/Message.php b/Tests/Auth/OpenID/Message.php
new file mode 100644
index 0000000..7f86848
--- /dev/null
+++ b/Tests/Auth/OpenID/Message.php
@@ -0,0 +1,1252 @@
+<?php
+
+/**
+ * Unit tests for the Auth_OpenID_Message implementation.
+ */
+
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID.php";
+require_once "PHPUnit.php";
+
+class MessageTest extends PHPUnit_TestCase {
+ function _argTest($ns, $key, $expected = null)
+ {
+ $a_default = 'a bogus default value';
+
+ $this->assertEquals($this->msg->getArg($ns, $key), $expected);
+ if ($expected === null) {
+ $this->assertEquals(
+ $this->msg->getArg($ns, $key, $a_default), $a_default);
+ $result = $this->msg->getArg($ns, $key, Auth_OpenID_NO_DEFAULT);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ } else {
+ $this->assertEquals(
+ $this->msg->getArg($ns, $key, $a_default), $expected);
+ $this->assertEquals(
+ $this->msg->getArg($ns, $key, Auth_OpenID_NO_DEFAULT),
+ $expected);
+ }
+ }
+}
+
+class Tests_Auth_OpenID_EmptyMessage extends MessageTest {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_Message();
+ }
+
+ function test_toPostArgs()
+ {
+ $this->assertEquals($this->msg->toPostArgs(), array());
+ }
+
+ function test_toArgs()
+ {
+ $this->assertEquals($this->msg->toArgs(), array());
+ }
+
+ function test_toKVForm()
+ {
+ $this->assertEquals($this->msg->toKVForm(), '');
+ }
+
+ function test_toURLEncoded()
+ {
+ $this->assertEquals($this->msg->toURLEncoded(), '');
+ }
+
+ function test_toURL()
+ {
+ $base_url = 'http://base.url/';
+ $this->assertEquals($this->msg->toURL($base_url), $base_url);
+ }
+
+ function test_getOpenID()
+ {
+ $this->assertEquals($this->msg->getOpenIDNamespace(), null);
+ }
+
+ function test_getKeyOpenID()
+ {
+ $key = $this->msg->getKey(Auth_OpenID_OPENID_NS, 'foo');
+ $this->assertTrue(Auth_OpenID::isFailure($key));
+
+ $this->msg->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, false);
+ $key = $this->msg->getKey(Auth_OpenID_OPENID_NS, 'foo');
+ $this->assertEquals('openid.foo', $key);
+ }
+
+ function test_getKeyBARE()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_BARE_NS, 'foo'), 'foo');
+ }
+
+ function test_getKeyNS1()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_OPENID1_NS, 'foo'), null);
+ }
+
+ function test_getKeyNS2()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_OPENID2_NS, 'foo'), null);
+ }
+
+ function test_getKeyNS3()
+ {
+ $this->assertEquals($this->msg->getKey('urn:nothing-significant', 'foo'),
+ null);
+ }
+
+ function test_hasKey()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID_NS, 'foo'), false);
+ }
+
+ function test_hasKeyBARE()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_BARE_NS, 'foo'), false);
+ }
+
+ function test_hasKeyNS1()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID1_NS, 'foo'), false);
+ }
+
+ function test_hasKeyNS2()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID2_NS, 'foo'), false);
+ }
+
+ function test_hasKeyNS3()
+ {
+ $this->assertEquals($this->msg->hasKey('urn:nothing-significant', 'foo'),
+ false);
+ }
+
+ function test_getArg()
+ {
+ $result = $this->msg->getArg(Auth_OpenID_OPENID_NS, 'foo');
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_getArgs()
+ {
+ $result = $this->msg->getArgs(Auth_OpenID_OPENID_NS);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function test_getArgsBARE()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_BARE_NS), array());
+ }
+
+ function test_getArgsNS1()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID1_NS), array());
+ }
+
+ function test_getArgsNS2()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID2_NS), array());
+ }
+
+ function test_getArgsNS3()
+ {
+ $this->assertEquals($this->msg->getArgs('urn:nothing-significant'), array());
+ }
+
+ function test_updateArgs()
+ {
+ $result= $this->msg->updateArgs(Auth_OpenID_OPENID_NS,
+ array('does not' => 'matter'));
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function _test_updateArgsNS($ns)
+ {
+ $update_args = array(
+ 'Camper van Beethoven' => 'David Lowery',
+ 'Magnolia Electric Co.' => 'Jason Molina');
+
+ $this->assertEquals($this->msg->getArgs($ns), array());
+ $this->msg->updateArgs($ns, $update_args);
+ $this->assertEquals($this->msg->getArgs($ns), $update_args);
+ }
+
+ function test_updateArgsBARE()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_updateArgsNS1()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_updateArgsNS2()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_updateArgsNS3()
+ {
+ $this->_test_updateArgsNS('urn:nothing-significant');
+ }
+
+ function test_setArg()
+ {
+ $result = $this->msg->setArg(Auth_OpenID_OPENID_NS,
+ 'does not', 'matter');
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function _test_setArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $value = 'David Lowery';
+ $this->assertEquals($this->msg->getArg($ns, $key), null);
+ $this->msg->setArg($ns, $key, $value);
+ $this->assertEquals($this->msg->getArg($ns, $key), $value);
+ }
+
+ function test_setArgBARE()
+ {
+ $this->_test_setArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_setArgNS1()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_setArgNS2()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_setArgNS3()
+ {
+ $this->_test_setArgNS('urn:nothing-significant');
+ }
+
+ function test_delArg()
+ {
+ $result = $this->msg->delArg(Auth_OpenID_OPENID_NS, 'does not');
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+
+ function _test_delArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $this->assertEquals($this->msg->delArg($ns, $key), false);
+ }
+
+ function test_delArgBARE()
+ {
+ $this->_test_delArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_delArgNS1()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_delArgNS2()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_delArgNS3()
+ {
+ $this->_test_delArgNS('urn:nothing-significant');
+ }
+
+ function test_isOpenID1()
+ {
+ $this->assertFalse($this->msg->isOpenID1());
+ }
+
+ function test_isOpenID2()
+ {
+ $this->assertFalse($this->msg->isOpenID2());
+ }
+
+ function test_args()
+ {
+ $this->_argTest(Auth_OpenID_BARE_NS, 'foo');
+ $this->_argTest(Auth_OpenID_OPENID1_NS, 'foo');
+ $this->_argTest(Auth_OpenID_OPENID2_NS, 'foo');
+ $this->_argTest('urn:nothing-significant', 'foo');
+ }
+}
+
+class Tests_Auth_OpenID_OpenID1Message extends MessageTest {
+ function setUp()
+ {
+ $this->msg = Auth_OpenID_Message::fromPostArgs(array('openid.mode' => 'error',
+ 'openid.error' => 'unit test'));
+ }
+
+ function test_toPostArgs()
+ {
+ $this->assertEquals($this->msg->toPostArgs(),
+ array('openid.mode' => 'error',
+ 'openid.error' => 'unit test'));
+ }
+
+ function test_toArgs()
+ {
+ $this->assertEquals($this->msg->toArgs(),
+ array('mode' => 'error',
+ 'error' => 'unit test'));
+ }
+
+ function test_toKVForm()
+ {
+ $this->assertEquals($this->msg->toKVForm(),
+ "error:unit test\nmode:error\n");
+ }
+
+ function test_toURLEncoded()
+ {
+ $this->assertEquals($this->msg->toURLEncoded(),
+ 'openid.error=unit+test&openid.mode=error');
+ }
+
+ function test_toURL()
+ {
+ $base_url = 'http://base.url/';
+ $actual = $this->msg->toURL($base_url);
+ $actual_base = substr($actual, 0, strlen($base_url));
+ $this->assertEquals($actual_base, $base_url);
+ $this->assertEquals($actual[strlen($base_url)], '?');
+ $query = substr($actual, strlen($base_url) + 1);
+
+ $parsed = Auth_OpenID::parse_str($query);
+
+ $this->assertEquals($parsed, array('openid.mode' => 'error',
+ 'openid.error' => 'unit test'));
+ }
+
+ function test_getOpenID()
+ {
+ $this->assertEquals($this->msg->getOpenIDNamespace(),
+ Auth_OpenID_OPENID1_NS);
+ $this->assertTrue($this->msg->namespaces->isImplicit(Auth_OpenID_OPENID1_NS));
+ }
+
+ function test_getKeyOpenID()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_OPENID_NS, 'mode'),
+ 'openid.mode');
+ }
+
+ function test_getKeyBARE()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_BARE_NS, 'mode'), 'mode');
+ }
+
+ function test_getKeyNS1()
+ {
+ $this->assertEquals(
+ $this->msg->getKey(Auth_OpenID_OPENID1_NS, 'mode'), 'openid.mode');
+ }
+
+ function test_getKeyNS2()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_OPENID2_NS, 'mode'), null);
+ }
+
+ function test_getKeyNS3()
+ {
+ $this->assertEquals(
+ $this->msg->getKey('urn:nothing-significant', 'mode'), null);
+ }
+
+ function test_hasKey()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID_NS, 'mode'), true);
+ }
+
+ function test_hasKeyBARE()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_BARE_NS, 'mode'), false);
+ }
+
+ function test_hasKeyNS1()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID1_NS, 'mode'), true);
+ }
+
+ function test_hasKeyNS2()
+ {
+ $this->assertEquals(
+ $this->msg->hasKey(Auth_OpenID_OPENID2_NS, 'mode'), false);
+ }
+
+ function test_hasKeyNS3()
+ {
+ $this->assertEquals(
+ $this->msg->hasKey('urn:nothing-significant', 'mode'), false);
+ }
+
+ function test_getArgs()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID_NS),
+ array('mode' => 'error',
+ 'error' => 'unit test'));
+ }
+
+ function test_getArgsBARE()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_BARE_NS), array());
+ }
+
+ function test_getArgsNS1()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID1_NS),
+ array('mode' => 'error',
+ 'error' => 'unit test'));
+ }
+
+ function test_getArgsNS2()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID2_NS), array());
+ }
+
+ function test_getArgsNS3()
+ {
+ $this->assertEquals($this->msg->getArgs('urn:nothing-significant'), array());
+ }
+
+ function _test_updateArgsNS($ns, $before=null)
+ {
+ if ($before === null) {
+ $before = array();
+ }
+
+ $update_args = array(
+ 'Camper van Beethoven' => 'David Lowery',
+ 'Magnolia Electric Co.' => 'Jason Molina');
+
+ $this->assertEquals($this->msg->getArgs($ns), $before);
+ $this->msg->updateArgs($ns, $update_args);
+ $after = $before;
+ $after = array_merge($after, $update_args);
+ $this->assertEquals($this->msg->getArgs($ns), $after);
+ }
+
+ function test_updateArgs()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID_NS,
+ array('mode' => 'error', 'error' => 'unit test'));
+ }
+
+ function test_updateArgsBARE()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_updateArgsNS1()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID1_NS,
+ array('mode' => 'error', 'error' => 'unit test'));
+ }
+
+ function test_updateArgsNS2()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_updateArgsNS3()
+ {
+ $this->_test_updateArgsNS('urn:nothing-significant');
+ }
+
+ function _test_setArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $value = 'David Lowery';
+ $this->assertEquals($this->msg->getArg($ns, $key), null);
+ $this->msg->setArg($ns, $key, $value);
+ $this->assertEquals($this->msg->getArg($ns, $key), $value);
+ }
+
+ function test_setArg()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID_NS);
+ }
+
+ function test_setArgBARE()
+ {
+ $this->_test_setArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_setArgNS1()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_setArgNS2()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_setArgNS3()
+ {
+ $this->_test_setArgNS('urn:nothing-significant');
+ }
+
+ function _test_delArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $value = 'David Lowery';
+
+ $this->assertEquals($this->msg->delArg($ns, $key), false);
+ $this->msg->setArg($ns, $key, $value);
+ $this->assertEquals($this->msg->getArg($ns, $key), $value);
+ $this->msg->delArg($ns, $key);
+ $this->assertEquals($this->msg->getArg($ns, $key), null);
+ }
+
+ function test_delArg()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID_NS);
+ }
+
+ function test_delArgBARE()
+ {
+ $this->_test_delArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_delArgNS1()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_delArgNS2()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_delArgNS3()
+ {
+ $this->_test_delArgNS('urn:nothing-significant');
+ }
+
+ function test_isOpenID1()
+ {
+ $this->assertTrue($this->msg->isOpenID1());
+ }
+
+ function test_isOpenID2()
+ {
+ $this->assertFalse($this->msg->isOpenID2());
+ }
+
+ function test_args()
+ {
+ $this->_argTest(Auth_OpenID_BARE_NS, 'mode');
+ $this->_argTest(Auth_OpenID_OPENID_NS, 'mode', 'error');
+ $this->_argTest(Auth_OpenID_OPENID1_NS, 'mode', 'error');
+ $this->_argTest(Auth_OpenID_OPENID2_NS, 'mode');
+ $this->_argTest('urn:nothing-significant', 'mode');
+ }
+}
+
+class Tests_Auth_OpenID_OpenID1ExplicitMessage extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = Auth_OpenID_Message::fromPostArgs(array('openid.mode' => 'error',
+ 'openid.error' => 'unit test',
+ 'openid.ns' => Auth_OpenID_OPENID1_NS));
+ }
+
+ function test_isOpenID1()
+ {
+ $this->assertTrue($this->msg->isOpenID1());
+ $this->assertFalse(
+ $this->msg->namespaces->isImplicit(Auth_OpenID_OPENID1_NS));
+ }
+
+ function test_isOpenID2()
+ {
+ $this->assertFalse($this->msg->isOpenID2());
+ }
+
+ function test_toPostArgs()
+ {
+ $this->assertEquals($this->msg->toPostArgs(),
+ array('openid.mode' => 'error',
+ 'openid.error' => 'unit test',
+ 'openid.ns' => Auth_OpenID_OPENID1_NS));
+ }
+
+ function test_toArgs()
+ {
+ $this->assertEquals($this->msg->toArgs(),
+ array('mode' => 'error',
+ 'error' => 'unit test',
+ 'ns' => Auth_OpenID_OPENID1_NS));
+ }
+
+ function test_toKVForm()
+ {
+ $this->assertEquals($this->msg->toKVForm(),
+ "error:unit test\nmode:error\nns:".
+ Auth_OpenID_OPENID1_NS."\n");
+ }
+
+ function test_toURLEncoded()
+ {
+ $this->assertEquals($this->msg->toURLEncoded(),
+ 'openid.error=unit+test&openid.mode=error&openid.ns=http%3A%2F%2Fopenid.net%2Fsignon%2F1.0');
+ }
+
+ function test_toURL()
+ {
+ $base_url = 'http://base.url/';
+ $actual = $this->msg->toURL($base_url);
+ $actual_base = substr($actual, 0, strlen($base_url));
+ $this->assertEquals($actual_base, $base_url);
+ $this->assertEquals($actual[strlen($base_url)], '?');
+ $query = substr($actual, strlen($base_url) + 1);
+
+ $parsed = Auth_OpenID::parse_str($query);
+
+ $this->assertEquals($parsed, array('openid.mode' => 'error',
+ 'openid.error' => 'unit test',
+ 'openid.ns' => Auth_OpenID_OPENID1_NS));
+ }
+}
+
+class Tests_Auth_OpenID_OpenID2Message extends MessageTest {
+ function setUp()
+ {
+ $this->msg = Auth_OpenID_Message::fromPostArgs(array('openid.mode' => 'error',
+ 'openid.error' => 'unit test',
+ 'openid.ns' => Auth_OpenID_OPENID2_NS));
+ $this->msg->setArg(Auth_OpenID_BARE_NS, "xey", "value");
+ }
+
+ function test_toPostArgs()
+ {
+ $this->assertEquals($this->msg->toPostArgs(),
+ array('openid.mode' => 'error',
+ 'openid.error' => 'unit test',
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'xey' => 'value'));
+ }
+
+ function test_toArgs()
+ {
+ // This method can't tolerate BARE_NS.
+ $this->msg->delArg(Auth_OpenID_BARE_NS, "xey");
+ $this->assertEquals($this->msg->toArgs(),
+ array('mode' => 'error',
+ 'error' => 'unit test',
+ 'ns' => Auth_OpenID_OPENID2_NS));
+ }
+
+ function test_toKVForm()
+ {
+ // Can't tolerate BARE_NS in kvform
+ $this->msg->delArg(Auth_OpenID_BARE_NS, "xey");
+ $this->assertEquals($this->msg->toKVForm(),
+ sprintf("error:unit test\nmode:error\nns:%s\n",
+ Auth_OpenID_OPENID2_NS));
+ }
+
+ function _test_urlencoded($s)
+ {
+ $expected = 'openid.error=unit+test&openid.mode=error&' .
+ 'openid.ns=%s&xey=value';
+
+ $expected = sprintf($expected, urlencode(Auth_OpenID_OPENID2_NS));
+ $this->assertEquals($s, $expected);
+ }
+
+ function test_toURLEncoded()
+ {
+ $this->_test_urlencoded($this->msg->toURLEncoded());
+ }
+
+ function test_toURL()
+ {
+ $base_url = 'http://base.url/';
+ $actual = $this->msg->toURL($base_url);
+ $actual_base = substr($actual, 0, strlen($base_url));
+
+ $this->assertEquals($actual_base, $base_url);
+ $this->assertEquals($actual[strlen($base_url)], '?');
+ $query = substr($actual, strlen($base_url) + 1);
+ $this->_test_urlencoded($query);
+ }
+
+ function test_getOpenID()
+ {
+ $this->assertEquals($this->msg->getOpenIDNamespace(),
+ Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_getKeyOpenID()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_OPENID_NS, 'mode'),
+ 'openid.mode');
+ }
+
+ function test_getKeyBARE()
+ {
+ $this->assertEquals($this->msg->getKey(Auth_OpenID_BARE_NS, 'mode'), 'mode');
+ }
+
+ function test_getKeyNS1()
+ {
+ $this->assertEquals(
+ $this->msg->getKey(Auth_OpenID_OPENID1_NS, 'mode'), null);
+ }
+
+ function test_getKeyNS2()
+ {
+ $this->assertEquals(
+ $this->msg->getKey(Auth_OpenID_OPENID2_NS, 'mode'), 'openid.mode');
+ }
+
+ function test_getKeyNS3()
+ {
+ $this->assertEquals(
+ $this->msg->getKey('urn:nothing-significant', 'mode'), null);
+ }
+
+ function test_hasKeyOpenID()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_OPENID_NS, 'mode'), true);
+ }
+
+ function test_hasKeyBARE()
+ {
+ $this->assertEquals($this->msg->hasKey(Auth_OpenID_BARE_NS, 'mode'), false);
+ }
+
+ function test_hasKeyNS1()
+ {
+ $this->assertEquals(
+ $this->msg->hasKey(Auth_OpenID_OPENID1_NS, 'mode'), false);
+ }
+
+ function test_hasKeyNS2()
+ {
+ $this->assertEquals(
+ $this->msg->hasKey(Auth_OpenID_OPENID2_NS, 'mode'), true);
+ }
+
+ function test_hasKeyNS3()
+ {
+ $this->assertEquals(
+ $this->msg->hasKey('urn:nothing-significant', 'mode'), false);
+ }
+
+ function test_getArgsOpenID()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID_NS),
+ array('mode' => 'error',
+ 'error' => 'unit test'));
+ }
+
+ function test_getArgsBARE()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_BARE_NS),
+ array('xey' => 'value'));
+ }
+
+ function test_getArgsNS1()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID1_NS), array());
+ }
+
+ function test_getArgsNS2()
+ {
+ $this->assertEquals($this->msg->getArgs(Auth_OpenID_OPENID2_NS),
+ array('mode' => 'error',
+ 'error' => 'unit test'));
+ }
+
+ function test_getArgsNS3()
+ {
+ $this->assertEquals($this->msg->getArgs('urn:nothing-significant'), array());
+ }
+
+ function _test_updateArgsNS($ns, $before=null)
+ {
+ if ($before === null) {
+ $before = array();
+ }
+
+ $update_args = array(
+ 'Camper van Beethoven' => 'David Lowery',
+ 'Magnolia Electric Co.' => 'Jason Molina');
+
+ $this->assertEquals($this->msg->getArgs($ns), $before);
+ $this->msg->updateArgs($ns, $update_args);
+ $after = $before;
+ $after = array_merge($after, $update_args);
+ $this->assertEquals($this->msg->getArgs($ns), $after);
+ }
+
+ function test_updateArgsOpenID()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID_NS,
+ array('mode' => 'error', 'error' => 'unit test'));
+ }
+
+ function test_updateArgsBARE()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_BARE_NS,
+ array('xey' => 'value'));
+ }
+
+ function test_updateArgsNS1()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_updateArgsNS2()
+ {
+ $this->_test_updateArgsNS(Auth_OpenID_OPENID2_NS,
+ array('mode' => 'error', 'error' => 'unit test'));
+ }
+
+ function test_updateArgsNS3()
+ {
+ $this->_test_updateArgsNS('urn:nothing-significant');
+ }
+
+ function _test_setArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $value = 'David Lowery';
+ $this->assertEquals($this->msg->getArg($ns, $key), null);
+ $this->msg->setArg($ns, $key, $value);
+ $this->assertEquals($this->msg->getArg($ns, $key), $value);
+ }
+
+ function test_setArgOpenID()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID_NS);
+ }
+
+ function test_setArgBARE()
+ {
+ $this->_test_setArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_setArgNS1()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_setArgNS2()
+ {
+ $this->_test_setArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_setArgNS3()
+ {
+ $this->_test_setArgNS('urn:nothing-significant');
+ }
+
+ function test_badAlias()
+ {
+ // Make sure dotted aliases and OpenID protocol fields are not
+ // allowed as namespace aliases.
+
+ global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
+
+ $all = array_merge($Auth_OpenID_OPENID_PROTOCOL_FIELDS, array('dotted.alias'));
+
+ foreach ($all as $f) {
+ $args = array(sprintf('openid.ns.%s', $f) => 'blah',
+ sprintf('openid.%s.foo', $f) => 'test');
+
+ // .fromPostArgs covers .fromPostArgs, .fromOpenIDArgs,
+ // ._fromOpenIDArgs, and .fromOpenIDArgs (since it calls
+ // .fromPostArgs). Python code raises AssertionError, but
+ // we usually return null for bad things in PHP.
+ $this->assertEquals($this->msg->fromPostArgs($args), null);
+ }
+ }
+
+ function _test_delArgNS($ns)
+ {
+ $key = 'Camper van Beethoven';
+ $value = 'David Lowery';
+
+ $this->assertEquals($this->msg->delArg($ns, $key), false);
+ $this->msg->setArg($ns, $key, $value);
+ $this->assertEquals($this->msg->getArg($ns, $key), $value);
+ $this->msg->delArg($ns, $key);
+ $this->assertEquals($this->msg->getArg($ns, $key), null);
+ }
+
+ function test_delArgOpenID()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID_NS);
+ }
+
+ function test_delArgBARE()
+ {
+ $this->_test_delArgNS(Auth_OpenID_BARE_NS);
+ }
+
+ function test_delArgNS1()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID1_NS);
+ }
+
+ function test_delArgNS2()
+ {
+ $this->_test_delArgNS(Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_delArgNS3()
+ {
+ $this->_test_delArgNS('urn:nothing-significant');
+ }
+
+ function test_overwriteExtensionArg()
+ {
+ $ns = 'urn:unittest_extension';
+ $key = 'mykey';
+ $value_1 = 'value_1';
+ $value_2 = 'value_2';
+
+ $this->msg->setArg($ns, $key, $value_1);
+ $this->assertTrue($this->msg->getArg($ns, $key) == $value_1);
+ $this->msg->setArg($ns, $key, $value_2);
+ $this->assertTrue($this->msg->getArg($ns, $key) == $value_2);
+ }
+
+ function test_argList()
+ {
+ $this->assertEquals($this->msg->fromPostArgs(array('arg' => array(1, 2, 3))),
+ null);
+ }
+
+ function test_isOpenID1()
+ {
+ $this->assertFalse($this->msg->isOpenID1());
+ }
+
+ function test_isOpenID2()
+ {
+ $this->assertTrue($this->msg->isOpenID2());
+ }
+
+ function test_args()
+ {
+ $this->_argTest(Auth_OpenID_BARE_NS, 'mode');
+ $this->_argTest(Auth_OpenID_OPENID_NS, 'mode', 'error');
+ $this->_argTest(Auth_OpenID_OPENID1_NS, 'mode');
+ $this->_argTest(Auth_OpenID_OPENID2_NS, 'mode', 'error');
+ $this->_argTest('urn:nothing-significant', 'mode');
+ }
+}
+
+class Tests_Auth_OpenID_GeneralMessageTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->postargs = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => 'http://bogus.example.invalid:port/',
+ 'openid.assoc_handle' => 'FLUB',
+ 'openid.return_to' => 'Neverland');
+
+ $this->action_url = 'scheme://host:port/path?query';
+
+ $this->form_tag_attrs = array(
+ 'company' => 'janrain',
+ 'class' => 'fancyCSS');
+
+ $this->submit_text = 'GO!';
+
+ // Expected data regardless of input
+
+ $this->required_form_attrs = array(
+ 'accept-charset' => 'UTF-8',
+ 'enctype' => 'application/x-www-form-urlencoded',
+ 'method' => 'post');
+ }
+
+ function _checkForm($html, $message_, $action_url,
+ $form_tag_attrs, $submit_text)
+ {
+ $parser =& Auth_Yadis_getXMLParser();
+
+ // Parse HTML source
+ $this->assertTrue($parser->init($html, array()));
+
+ // Get root element
+ $form = $parser->evalXPath('/form[1]');
+ $this->assertTrue(count($form) == 1);
+ $form = $form[0];
+
+ // Check required form attributes
+ $form_attrs = $parser->attributes($form);
+ foreach ($this->required_form_attrs as $k => $v) {
+ $this->assertTrue($form_attrs[$k] == $v);
+ }
+
+ // Check extra form attributes
+ foreach ($form_tag_attrs as $k => $v) {
+ // Skip attributes that already passed the required
+ // attribute check, since they should be ignored by the
+ // form generation code.
+ if (in_array($k, array_keys($this->required_form_attrs))) {
+ continue;
+ }
+
+ $this->assertTrue($form_attrs[$k] == $v,
+ "Form attr $k is ".$form_attrs[$k]." (expected $v)");
+ }
+
+ // Check hidden fields against post args
+ $hiddens = array();
+ $input_elements = $parser->evalXPath('input', $form);
+ foreach ($input_elements as $e) {
+ $attrs = $parser->attributes($e);
+ if (strtoupper($attrs['type']) == 'HIDDEN') {
+ $hiddens[] = $e;
+ }
+ }
+
+ // For each post arg, make sure there is a hidden with that
+ // value. Make sure there are no other hiddens.
+ $postargs = $message_->toPostArgs();
+ foreach ($postargs as $name => $value) {
+ $found = false;
+
+ foreach ($hiddens as $e) {
+ $attrs = $parser->attributes($e);
+ if ($attrs['name'] == $name) {
+ $this->assertTrue($attrs['value'] == $value);
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->fail("Post arg $name not found in form");
+ }
+ }
+
+ $keys = array_keys($postargs);
+ foreach ($hiddens as $e) {
+ $attrs = $parser->attributes($e);
+ $this->assertTrue(in_array($attrs['name'], $keys));
+ }
+
+ // Check action URL
+ $this->assertTrue($form_attrs['action'] == $action_url);
+
+ // Check submit text
+ $submits = array();
+ foreach ($input_elements as $e) {
+ $attrs = $parser->attributes($e);
+ if (strtoupper($attrs['type']) == 'SUBMIT') {
+ $submits[] = $e;
+ }
+ }
+
+ $this->assertTrue(count($submits) == 1);
+
+ $attrs = $parser->attributes($submits[0]);
+ $this->assertTrue($attrs['value'] == $submit_text);
+ }
+
+ function test_toFormMarkup()
+ {
+ $m = Auth_OpenID_Message::fromPostArgs($this->postargs);
+ $html = $m->toFormMarkup($this->action_url, $this->form_tag_attrs,
+ $this->submit_text);
+ $this->_checkForm($html, $m, $this->action_url,
+ $this->form_tag_attrs, $this->submit_text);
+ }
+
+ function test_overrideMethod()
+ {
+ // Be sure that caller cannot change form method to GET.
+ $m = Auth_OpenID_Message::fromPostArgs($this->postargs);
+
+ $tag_attrs = $this->form_tag_attrs;
+ $tag_attrs['method'] = 'GET';
+
+ $html = $m->toFormMarkup($this->action_url, $this->form_tag_attrs,
+ $this->submit_text);
+ $this->_checkForm($html, $m, $this->action_url,
+ $this->form_tag_attrs, $this->submit_text);
+ }
+
+ function test_overrideRequired()
+ {
+ // Be sure that caller CANNOT change the form charset for
+ // encoding type.
+ $m = Auth_OpenID_Message::fromPostArgs($this->postargs);
+
+ $tag_attrs = $this->form_tag_attrs;
+ $tag_attrs['accept-charset'] = 'UCS4';
+ $tag_attrs['enctype'] = 'invalid/x-broken';
+
+ $html = $m->toFormMarkup($this->action_url, $tag_attrs,
+ $this->submit_text);
+ $this->_checkForm($html, $m, $this->action_url,
+ $tag_attrs, $this->submit_text);
+ }
+
+ function test_setOpenIDNamespace_invalid()
+ {
+ $m = new Auth_OpenID_Message();
+ $invalid_things = array(
+ // Empty string is not okay here.
+ '',
+ // Good guess! But wrong.
+ 'http://openid.net/signon/2.0',
+ // What?
+ 'http://specs%\\\r2Eopenid.net/auth/2.0',
+ // Too much escapings!
+ 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0',
+ // This is a Type URI, not a openid.ns value.
+ 'http://specs.openid.net/auth/2.0/signon',
+ );
+
+ foreach ($invalid_things as $x) {
+ $this->assertTrue($m->setOpenIDNamespace($x, true) === false);
+ }
+ }
+
+ function test_isOpenID1()
+ {
+ $v1_namespaces = array(
+ // Yes, there are two of them.
+ 'http://openid.net/signon/1.1',
+ 'http://openid.net/signon/1.0',
+ );
+
+ foreach ($v1_namespaces as $ns) {
+ $m = new Auth_OpenID_Message($ns);
+ $this->assertTrue($m->isOpenID1(),
+ "$ns not recognized as OpenID 1");
+ $this->assertEquals($ns, $m->getOpenIDNamespace());
+ $this->assertTrue($m->namespaces->isImplicit($ns));
+ }
+ }
+
+ function test_isOpenID2()
+ {
+ $ns = 'http://specs.openid.net/auth/2.0';
+ $m = new Auth_OpenID_Message($ns);
+ $this->assertTrue($m->isOpenID2());
+ $this->assertFalse(
+ $m->namespaces->isImplicit(Auth_OpenID_NULL_NAMESPACE));
+ $this->assertEquals($ns, $m->getOpenIDNamespace());
+ }
+
+ function test_setOpenIDNamespace_explicit()
+ {
+ $m = new Auth_OpenID_Message();
+ $m->setOpenIDNamespace(Auth_OpenID_THE_OTHER_OPENID1_NS, false);
+ $this->assertFalse($m->namespaces->isImplicit(
+ Auth_OpenID_THE_OTHER_OPENID1_NS));
+ }
+
+ function test_setOpenIDNamespace_implicit()
+ {
+ $m = new Auth_OpenID_Message();
+ $m->setOpenIDNamespace(Auth_OpenID_THE_OTHER_OPENID1_NS, true);
+ $this->assertTrue(
+ $m->namespaces->isImplicit(Auth_OpenID_THE_OTHER_OPENID1_NS));
+ }
+
+
+ function test_explicitOpenID11NSSerialzation()
+ {
+ $m = new Auth_OpenID_Message();
+ $m->setOpenIDNamespace(Auth_OpenID_THE_OTHER_OPENID1_NS, false);
+
+ $post_args = $m->toPostArgs();
+ $this->assertEquals($post_args,
+ array('openid.ns' =>
+ Auth_OpenID_THE_OTHER_OPENID1_NS));
+ }
+
+ function test_fromPostArgs_ns11()
+ {
+ // An example of the stuff that some Drupal installations send us,
+ // which includes openid.ns but is 1.1.
+ $query = array(
+ 'openid.assoc_handle' => '',
+ 'openid.claimed_id' => 'http://foobar.invalid/',
+ 'openid.identity' => 'http://foobar.myopenid.com',
+ 'openid.mode' => 'checkid_setup',
+ 'openid.ns' => 'http://openid.net/signon/1.1',
+ 'openid.ns.sreg' => 'http://openid.net/extensions/sreg/1.1',
+ 'openid.return_to' => 'http://drupal.invalid/return_to',
+ 'openid.sreg.required' => 'nickname,email',
+ 'openid.trust_root' => 'http://drupal.invalid',
+ );
+ $m = Auth_OpenID_Message::fromPostArgs($query);
+ $this->assertTrue($m->isOpenID1());
+ }
+}
+
+class Tests_Auth_OpenID_NamespaceMap extends PHPUnit_TestCase {
+ function test_onealias()
+ {
+ $nsm = new Auth_OpenID_NamespaceMap();
+ $uri = 'http://example.com/foo';
+ $alias = "foo";
+ $nsm->addAlias($uri, $alias);
+ $this->assertTrue($nsm->getNamespaceURI($alias) == $uri);
+ $this->assertTrue($nsm->getAlias($uri) == $alias);
+ }
+
+ function test_iteration()
+ {
+ $nsm = new Auth_OpenID_NamespaceMap();
+ $uripat = 'http://example.com/foo%d';
+
+ $nsm->add(sprintf($uripat, 0));
+
+ for ($n = 1; $n < 23; $n++) {
+ $this->assertTrue($nsm->contains(sprintf($uripat, $n - 1)));
+ $this->assertTrue($nsm->isDefined(sprintf($uripat, $n - 1)));
+ $nsm->add(sprintf($uripat, $n));
+ }
+
+ foreach ($nsm->iteritems() as $pair) {
+ list($uri, $alias) = $pair;
+ $this->assertTrue('ext'.substr($uri, 22) == $alias);
+ }
+
+ $it = $nsm->iterAliases();
+ $this->assertTrue(count($it) == 23);
+
+ $it = $nsm->iterNamespaceURIs();
+ $this->assertTrue(count($it) == 23);
+ }
+}
+
+class Tests_Auth_OpenID_Message extends PHPUnit_TestCase {
+}
+
+global $Tests_Auth_OpenID_Message_other;
+$Tests_Auth_OpenID_Message_other = array(
+ new Tests_Auth_OpenID_EmptyMessage(),
+ new Tests_Auth_OpenID_OpenID1Message(),
+ new Tests_Auth_OpenID_OpenID2Message(),
+ new Tests_Auth_OpenID_NamespaceMap(),
+ new Tests_Auth_OpenID_OpenID1ExplicitMessage(),
+ new Tests_Auth_OpenID_GeneralMessageTest()
+ );
+
+?>
diff --git a/Tests/Auth/OpenID/Negotiation.php b/Tests/Auth/OpenID/Negotiation.php
new file mode 100644
index 0000000..c5ad873
--- /dev/null
+++ b/Tests/Auth/OpenID/Negotiation.php
@@ -0,0 +1,348 @@
+<?php
+
+require_once "PHPUnit.php";
+require_once "Tests/Auth/OpenID/TestUtil.php";
+require_once "Tests/Auth/OpenID/MemStore.php";
+
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Consumer.php";
+
+/**
+ * A consumer whose _requestAssocation will return predefined results
+ * instead of trying to actually perform association requests.
+ */
+class ErrorRaisingConsumer extends Auth_OpenID_GenericConsumer {
+ // The list of objects to be returned by successive calls to
+ // _requestAssocation. Each call will pop the first element from
+ // this list and return it to _negotiateAssociation. If the
+ // element is a Message object, it will be wrapped in a
+ // ServerErrorContainer exception. Otherwise it will be returned
+ // as-is.
+ var $return_messages = array();
+
+ function _requestAssociation($endpoint, $assoc_type, $session_type)
+ {
+ $m = array_pop($this->return_messages);
+ if (is_a($m, 'Auth_OpenID_Message')) {
+ return Auth_OpenID_ServerErrorContainer::fromMessage($m);
+ } else if (Auth_OpenID::isFailure($m)) {
+ return $m;
+ } else {
+ return $m;
+ }
+ }
+}
+
+/**
+ * Test the session type negotiation behavior of an OpenID 2 consumer.
+ */
+class TestOpenID2SessionNegotiation extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $dumb = null;
+ $this->consumer = new ErrorRaisingConsumer($dumb);
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $this->endpoint->server_url = 'bogus';
+ }
+
+ /**
+ * Test the case where the response to an associate request is a
+ * server error or is otherwise undecipherable.
+ */
+ function testBadResponse()
+ {
+ $this->consumer->return_messages = array(
+ new Auth_OpenID_Message($this->endpoint->preferredNamespace()));
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ /**
+ * Test the case where the response to an associate request is a
+ * a failure response object.
+ */
+ function testBadResponseWithFailure()
+ {
+ $this->consumer->return_messages = array(
+ new Auth_OpenID_FailureResponse($this->endpoint));
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ /**
+ * Test the case where the association type (assoc_type) returned
+ * in an unsupported-type response is absent.
+ */
+ function testEmptyAssocType()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', null);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'new-session-type');
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Unsupported association type',
+ // 'Server responded with unsupported association ' +
+ // 'session but did not supply a fallback.')
+ }
+
+ /**
+ * Test the case where the session type (session_type) returned in
+ * an unsupported-type response is absent.
+ */
+ function testEmptySessionType()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'new-assoc-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', null);
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Unsupported association type',
+ // 'Server responded with unsupported association ' +
+ // 'session but did not supply a fallback.')
+ }
+
+ /**
+ * Test the case where an unsupported-type response specifies a
+ * preferred (assoc_type, session_type) combination that is not
+ * allowed by the consumer's SessionNegotiator.
+ */
+ function testNotAllowed()
+ {
+ $allowed_types = array();
+
+ $negotiator = new Auth_OpenID_SessionNegotiator($allowed_types);
+ $this->consumer->negotiator = $negotiator;
+
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'not-allowed');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'not-allowed');
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Unsupported association type',
+ // 'Server sent unsupported session/association type:')
+ }
+
+ /**
+ * Test the case where an unsupported-type response triggers a
+ * retry to get an association with the new preferred type.
+ */
+ function testUnsupportedWithRetry()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'HMAC-SHA1');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'DH-SHA1');
+
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secret', 'issued', 10000, 'HMAC-SHA1');
+
+ $this->consumer->return_messages = array($msg, $assoc);
+ $this->assertTrue($this->consumer->_negotiateAssociation($this->endpoint) === $assoc);
+
+ // $this->failUnlessLogMatches('Unsupported association type');
+ }
+
+ /**
+ * Test the case where an unsupported-typ response triggers a
+ * retry, but the retry fails and None is returned instead.
+ */
+ function testUnsupportedWithRetryAndFail()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'HMAC-SHA1');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'DH-SHA1');
+
+ $this->consumer->return_messages = array($msg,
+ new Auth_OpenID_Message($this->endpoint->preferredNamespace()));
+
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Unsupported association type',
+ // 'Server %s refused' % ($this->endpoint.server_url))
+ }
+
+ /**
+ * Test the valid case, wherein an association is returned on the
+ * first attempt to get one.
+ */
+ function testValid()
+ {
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secret', 'issued', 10000, 'HMAC-SHA1');
+
+ $this->consumer->return_messages = array($assoc);
+ $this->assertTrue($this->consumer->_negotiateAssociation($this->endpoint) === $assoc);
+ // $this->failUnlessLogEmpty()
+ }
+}
+
+/**
+ * Tests for the OpenID 1 consumer association session behavior. See
+ * the docs for TestOpenID2SessionNegotiation. Notice that this class
+ * is not a subclass of the OpenID 2 tests. Instead, it uses many of
+ * the same inputs but inspects the log messages logged with
+ * oidutil.log. See the calls to $this->failUnlessLogMatches. Some
+ * of these tests pass openid2-style messages to the openid 1
+ * association processing logic to be sure it ignores the extra data.
+ */
+class TestOpenID1SessionNegotiation extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $dumb = null;
+ $this->consumer = new ErrorRaisingConsumer($dumb);
+
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+ $this->endpoint->type_uris = array(Auth_OpenID_OPENID1_NS);
+ $this->endpoint->server_url = 'bogus';
+ }
+
+ function testBadResponse()
+ {
+ $this->consumer->return_messages =
+ array(new Auth_OpenID_Message($this->endpoint->preferredNamespace()));
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ function testEmptyAssocType()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', null);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'new-session-type');
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ function testEmptySessionType()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'new-assoc-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', null);
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Server error when requesting an association');
+ }
+
+ function testNotAllowed()
+ {
+ $allowed_types = array();
+
+ $negotiator = new Auth_OpenID_SessionNegotiator($allowed_types);
+ $this->consumer->negotiator = $negotiator;
+
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'not-allowed');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'not-allowed');
+
+ $this->consumer->return_messages = array($msg);
+ $this->assertEquals($this->consumer->_negotiateAssociation($this->endpoint), null);
+
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ function testUnsupportedWithRetry()
+ {
+ $msg = new Auth_OpenID_Message($this->endpoint->preferredNamespace());
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error', 'Unsupported type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'HMAC-SHA1');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'session_type', 'DH-SHA1');
+
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secretxx', 'issued', 10000, 'HMAC-SHA1');
+
+ $this->consumer->return_messages = array($assoc, $msg);
+
+ $result = $this->consumer->_negotiateAssociation($this->endpoint);
+ $this->assertTrue($result === null);
+
+ // $this->failUnlessLogMatches('Server error when requesting an association')
+ }
+
+ function testValid()
+ {
+ $assoc = new Auth_OpenID_Association(
+ 'handle', 'secret', 'issued', 10000, 'HMAC-SHA1');
+
+ $this->consumer->return_messages = array($assoc);
+ $this->assertTrue($this->consumer->_negotiateAssociation($this->endpoint) === $assoc);
+ // $this->failUnlessLogEmpty()
+ }
+}
+
+class TestNegotiatorBehaviors extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->allowed_types = array(
+ array('HMAC-SHA1', 'no-encryption'),
+ array('HMAC-SHA256', 'no-encryption')
+ );
+
+ $this->n = new Auth_OpenID_SessionNegotiator($this->allowed_types);
+ }
+
+ function testAddAllowedTypeNoSessionTypes()
+ {
+ $this->assertFalse($this->n->addAllowedType('invalid'));
+ }
+
+ function testAddAllowedTypeBadSessionType()
+ {
+ $this->assertFalse($this->n->addAllowedType('assoc1', 'invalid'));
+ }
+
+ function testAddAllowedTypeContents()
+ {
+ $assoc_type = 'HMAC-SHA1';
+ $this->assertTrue($this->n->addAllowedType($assoc_type));
+
+ foreach (Auth_OpenID_getSessionTypes($assoc_type) as $typ) {
+ $this->assertTrue(in_array(array($assoc_type, $typ),
+ $this->n->allowed_types));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_Negotiation extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return 'Tests_Auth_OpenID_Negotiation';
+ }
+
+ function Tests_Auth_OpenID_Negotiation()
+ {
+ $this->addTestSuite('TestNegotiatorBehaviors');
+ $this->addTestSuite('TestOpenID1SessionNegotiation');
+ $this->addTestSuite('TestOpenID2SessionNegotiation');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Nonce.php b/Tests/Auth/OpenID/Nonce.php
new file mode 100644
index 0000000..1936735
--- /dev/null
+++ b/Tests/Auth/OpenID/Nonce.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * Tests for the Nonce implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2006 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/Nonce.php';
+
+define('Tests_Auth_OpenID_nonce_re',
+ '/\A\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/');
+
+class Tests_Auth_OpenID_Nonce extends PHPUnit_TestSuite {
+ function Tests_Auth_OpenID_Nonce()
+ {
+ $this->addTestSuite('Tests_Auth_OpenID_NonceTests');
+ $this->makeSplitTests();
+ $this->makeCheckTimestampTests();
+ $this->setName('Tests_Auth_OpenID_Nonce');
+ }
+
+ function makeSplitTests()
+ {
+ $cases = array(
+ '',
+ '1970-01-01T00:00:00+1:00',
+ '1969-01-01T00:00:00Z',
+ '1970-00-01T00:00:00Z',
+ '1970.01-01T00:00:00Z',
+ 'Thu Sep 7 13:29:31 PDT 2006',
+ 'monkeys',
+ );
+
+ foreach ($cases as $nonce_str) {
+ $this->_mkSplitTest($nonce_str);
+ }
+ }
+
+ function _mkSplitTest($nonce_str)
+ {
+ $test = new Tests_Auth_OpenID_Nonce_BadSplitCase($nonce_str);
+ $test->setName('BadNonceSplit ' . var_export($nonce_str, true));
+ $this->addTest($test);
+ }
+
+ function makeCheckTimestampTests()
+ {
+ $cases = array(
+ // exact, no allowed skew
+ array('1970-01-01T00:00:00Z', 0, 0, true),
+
+ // exact, large skew
+ array('1970-01-01T00:00:00Z', 1000, 0, true),
+
+ // no allowed skew, one second old
+ array('1970-01-01T00:00:00Z', 0, 1, false),
+
+ // many seconds old, outside of skew
+ array('1970-01-01T00:00:00Z', 10, 50, false),
+
+ // one second old, one second skew allowed
+ array('1970-01-01T00:00:00Z', 1, 1, true),
+
+ // One second in the future, one second skew allowed
+ array('1970-01-01T00:00:02Z', 1, 1, true),
+
+ // two seconds in the future, one second skew allowed
+ array('1970-01-01T00:00:02Z', 1, 0, false),
+
+ // malformed nonce string
+ array('monkeys', 0, 0, false)
+ );
+
+ foreach ($cases as $case) {
+ $this->_mkCheckTest($case);
+ }
+ }
+
+ function _mkCheckTest($case)
+ {
+ list($nonce_str, $skew, $now, $expected) = $case;
+ $test = new Tests_Auth_OpenID_Nonce_TimestampCase(
+ $nonce_str, $skew, $now, $expected);
+ $test->setName('CheckTimestamp ' . var_export($nonce_str, true));
+ $this->addTest($test);
+ }
+}
+
+class Tests_Auth_OpenID_Nonce_TimestampCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Nonce_TimestampCase(
+ $nonce_str, $skew, $now, $expected)
+ {
+ $this->nonce_string = $nonce_str;
+ $this->allowed_skew = $skew;
+ $this->now = $now;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $actual = Auth_OpenID_checkTimestamp($this->nonce_string,
+ $this->allowed_skew,
+ $this->now);
+ $this->assertEquals($this->expected, $actual);
+ }
+}
+
+class Tests_Auth_OpenID_NonceTests extends PHPUnit_TestCase {
+ function test_mkNonce()
+ {
+ $nonce_str = Auth_OpenID_mkNonce();
+ $this->assertTrue(preg_match(Tests_Auth_OpenID_nonce_re, $nonce_str));
+ }
+
+ function test_mkNonce_when()
+ {
+ $nonce_str = Auth_OpenID_mkNonce(0);
+ $this->assertTrue(preg_match(Tests_Auth_OpenID_nonce_re, $nonce_str));
+ $tpart = substr($nonce_str, 0, 20);
+ $this->assertEquals('1970-01-01T00:00:00Z', $tpart);
+ }
+
+ function test_splitNonce()
+ {
+ $s = '1970-01-01T00:00:00Z';
+ $expected_t = 0;
+ $expected_salt = '';
+ list($actual_t, $actual_salt) = Auth_OpenID_splitNonce($s);
+ $this->assertEquals($expected_t, $actual_t);
+ $this->assertEquals($expected_salt, $actual_salt);
+ }
+
+
+ function test_mkSplit()
+ {
+ $t = 42;;
+ $nonce_str = Auth_OpenID_mkNonce($t);
+ $this->assertTrue(preg_match(Tests_Auth_OpenID_nonce_re, $nonce_str));
+ list($et, $salt) = Auth_OpenID_splitNonce($nonce_str);
+ $this->assertEquals(6, strlen($salt));
+ $this->assertEquals($et, $t);
+ }
+}
+
+class Tests_Auth_OpenID_Nonce_BadSplitCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Nonce_BadSplitCase($nonce_str)
+ {
+ $this->nonce_str = $nonce_str;
+ }
+
+ function runTest()
+ {
+ $result = Auth_OpenID_splitNonce($this->nonce_str);
+ $this->assertNull($result);
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/OpenID_Yadis.php b/Tests/Auth/OpenID/OpenID_Yadis.php
new file mode 100644
index 0000000..02981c0
--- /dev/null
+++ b/Tests/Auth/OpenID/OpenID_Yadis.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * Tests for the combination of Yadis discovery and the OpenID
+ * protocol.
+ */
+
+require_once "PHPUnit.php";
+require_once "Auth/Yadis/XRDS.php";
+require_once "Auth/OpenID/Discover.php";
+
+global $__XRDS_BOILERPLATE;
+$__XRDS_BOILERPLATE = '<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0">
+ <XRD>
+%s
+ </XRD>
+</xrds:XRDS>
+';
+
+// Different sets of server URLs for use in the URI tag
+global $__server_url_options;
+$__server_url_options = array(
+ array(), // This case should not generate an endpoint object
+ array('http://server.url/'),
+ array('https://server.url/'),
+ array('https://server.url/', 'http://server.url/'),
+ array('https://server.url/',
+ 'http://server.url/',
+ 'http://example.server.url/'),
+ );
+
+// A couple of example extension type URIs. These are not at all
+// official, but are just here for testing.
+global $__ext_types;
+$__ext_types = array(
+ 'http://janrain.com/extension/blah',
+ 'http://openid.net/sreg/1.0');
+
+// All valid combinations of Type tags that should produce an OpenID
+// endpoint
+global $__openid_types;
+$__openid_types = array(
+ Auth_OpenID_TYPE_1_0,
+ Auth_OpenID_TYPE_1_1);
+
+$temp = array();
+foreach (__subsets($__ext_types) as $exts) {
+ foreach (__subsets($__openid_types) as $ts) {
+ if ($ts) {
+ $temp[] = array_merge($exts, $ts);
+ }
+ }
+}
+
+global $__type_uri_options;
+$__type_uri_options = $temp;
+
+// Range of valid Delegate tag values for generating test data
+global $__delegate_options;
+$__delegate_options = array(
+ null,
+ 'http://vanity.domain/',
+ 'https://somewhere/yadis/');
+
+$temp = array();
+foreach ($__delegate_options as $delegate) {
+ foreach ($__type_uri_options as $type_uris) {
+ foreach ($__server_url_options as $uris) {
+ $temp[] = array($uris, $type_uris, $delegate);
+ }
+ }
+}
+
+// All combinations of valid URIs, Type URIs and Delegate tags
+global $__data;
+$__data = $temp;
+
+function _mkXRDS($services_str)
+{
+ global $__XRDS_BOILERPLATE;
+ return sprintf($__XRDS_BOILERPLATE, $services_str);
+}
+
+function _mkService($uris = null, $type_uris = null,
+ $delegate = null, $dent = ' ')
+{
+ $chunks = array($dent, "<Service>\n");
+ $dent2 = $dent . ' ';
+ if ($type_uris) {
+ foreach ($type_uris as $type_uri) {
+ $chunks = array_merge($chunks,
+ array($dent2 . '<Type>',
+ $type_uri, "</Type>\n"));
+ }
+ }
+
+ if ($uris) {
+ foreach ($uris as $uri) {
+ if (is_array($uri)) {
+ list($uri, $prio) = $uri;
+ } else {
+ $prio = null;
+ }
+ $chunks = array_merge($chunks, array($dent2, '<URI'));
+ if ($prio !== null) {
+ $chunks = array_merge($chunks, array(' priority="', strval($prio), '"'));
+ }
+ $chunks = array_merge($chunks, array('>', $uri, "</URI>\n"));
+ }
+ }
+
+ if ($delegate) {
+ $chunks = array_merge($chunks,
+ array($dent2, '<openid:Delegate>',
+ $delegate, "</openid:Delegate>\n"));
+ }
+
+ $chunks = array_merge($chunks, array($dent, "</Service>\n"));
+
+ return implode("", $chunks);
+}
+
+// Used for generating test data
+function __subsets($list)
+{
+ // Generate all non-empty sublists of a list
+ $subsets_list = array(array());
+ foreach ($list as $elem) {
+
+ $temp = array();
+ foreach ($subsets_list as $t) {
+ $temp[] = array_merge(array($elem), $t);
+ }
+
+ $subsets_list = array_merge($subsets_list, $temp);
+ }
+
+ return $subsets_list;
+}
+
+class Tests_Auth_OpenID_Tester extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Tester($uris, $type_uris, $delegate)
+ {
+ $this->uris = $uris;
+ $this->type_uris = $type_uris;
+ $this->local_id = $delegate;
+ parent::PHPUnit_TestCase();
+ }
+
+ function setUp()
+ {
+ $this->yadis_url = 'http://unit.test/';
+
+ // Create an XRDS document to parse
+ $services = _mkService($this->uris,
+ $this->type_uris,
+ $this->local_id);
+ $this->xrds = _mkXRDS($services);
+ }
+
+ function runTest()
+ {
+ // Parse into endpoint objects that we will check
+ $xrds_object = Auth_Yadis_XRDS::parseXRDS($this->xrds);
+
+ $endpoints = array();
+
+ if ($xrds_object) {
+ $endpoints = $xrds_object->services(array('filter_MatchesAnyOpenIDType'));
+ $endpoints = Auth_OpenID_makeOpenIDEndpoints($this->yadis_url, $endpoints);
+ }
+
+ // make sure there are the same number of endpoints as
+ // URIs. This assumes that the type_uris contains at least one
+ // OpenID type.
+ $this->assertEquals(count($this->uris), count($endpoints),
+ "URI <-> Endpoint count");
+
+ // So that we can check equality on the endpoint types
+ $type_uris = $this->type_uris;
+ sort($type_uris);
+
+
+ $seen_uris = array();
+ foreach ($endpoints as $endpoint) {
+ $seen_uris[] = $endpoint->server_url;
+
+ // All endpoints will have same yadis_url
+ $this->assertEquals($this->yadis_url, $endpoint->claimed_id);
+
+ // and delegate
+ $this->assertEquals($this->local_id, $endpoint->local_id);
+
+ // and types
+ $actual_types = $endpoint->type_uris;
+ sort($actual_types);
+ $this->assertEquals($actual_types, $type_uris);
+ }
+
+ // So that they will compare equal, because we don't care what
+ // order they are in
+ sort($seen_uris);
+ $uris = $this->uris;
+ sort($uris);
+
+ // Make sure we saw all URIs, and saw each one once
+ $this->assertEquals($uris, $seen_uris);
+ }
+}
+
+class Tests_Auth_OpenID_OpenID_Yadis extends PHPUnit_TestSuite {
+ function Tests_Auth_OpenID_OpenID_Yadis()
+ {
+ global $__data;
+ foreach ($__data as $case) {
+ $this->addTest(new Tests_Auth_OpenID_Tester($case[0], $case[1], $case[2]));
+ }
+ }
+
+ function getName()
+ {
+ return 'Tests_Auth_OpenID_OpenID_Yadis';
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/PAPE.php b/Tests/Auth/OpenID/PAPE.php
new file mode 100644
index 0000000..d53fb04
--- /dev/null
+++ b/Tests/Auth/OpenID/PAPE.php
@@ -0,0 +1,246 @@
+<?php
+
+require_once "PHPUnit.php";
+
+require_once "Auth/OpenID/PAPE.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Server.php";
+
+class PapeRequestTestCase extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->req = new Auth_OpenID_PAPE_Request();
+ }
+
+ function test_construct()
+ {
+ $this->assertEquals(array(), $this->req->preferred_auth_policies);
+ $this->assertEquals(null, $this->req->max_auth_age);
+ $this->assertEquals('pape', $this->req->ns_alias);
+
+ $req2 = new Auth_OpenID_PAPE_Request(array(PAPE_AUTH_MULTI_FACTOR), 1000);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $req2->preferred_auth_policies);
+ $this->assertEquals(1000, $req2->max_auth_age);
+ }
+
+ function test_add_policy_uri()
+ {
+ $this->assertEquals(array(), $this->req->preferred_auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $this->req->preferred_auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $this->req->preferred_auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_PHISHING_RESISTANT);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT),
+ $this->req->preferred_auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT),
+ $this->req->preferred_auth_policies);
+ }
+
+ function test_getExtensionArgs() {
+ $this->assertEquals(array('preferred_auth_policies' => ''), $this->req->getExtensionArgs());
+ $this->req->addPolicyURI('http://uri');
+ $this->assertEquals(array('preferred_auth_policies' => 'http://uri'), $this->req->getExtensionArgs());
+ $this->req->addPolicyURI('http://zig');
+ $this->assertEquals(array('preferred_auth_policies' => 'http://uri http://zig'), $this->req->getExtensionArgs());
+ $this->req->max_auth_age = 789;
+ $this->assertEquals(array('preferred_auth_policies' => 'http://uri http://zig', 'max_auth_age' => '789'), $this->req->getExtensionArgs());
+ }
+
+ function test_parseExtensionArgs() {
+ $args = array('preferred_auth_policies' => 'http://foo http://bar',
+ 'max_auth_age' => '9');
+ $this->req->parseExtensionArgs($args);
+ $this->assertEquals(9, $this->req->max_auth_age);
+ $this->assertEquals(array('http://foo','http://bar'), $this->req->preferred_auth_policies);
+ }
+
+ function test_parseExtensionArgs_empty() {
+ $this->req->parseExtensionArgs(array());
+ $this->assertEquals(null, $this->req->max_auth_age);
+ $this->assertEquals(array(), $this->req->preferred_auth_policies);
+ }
+
+ function test_fromOpenIDRequest() {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'checkid_setup',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'ns.pape' => Auth_OpenID_PAPE_NS_URI,
+ 'pape.preferred_auth_policies' => implode(' ', array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT)),
+ 'pape.max_auth_age' => '5476'
+ ));
+ $oid_req = new Auth_OpenID_Request();
+ $oid_req->message = $openid_req_msg;
+ $req = Auth_OpenID_PAPE_Request::fromOpenIDRequest($oid_req);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT), $req->preferred_auth_policies);
+ $this->assertEquals(5476, $req->max_auth_age);
+ }
+
+ function test_fromOpenIDRequest_no_pape() {
+ $message = new Auth_OpenID_Message();
+ $openid_req = new Auth_OpenID_Request();
+ $openid_req->message = $message;
+ $pape_req = Auth_OpenID_PAPE_Request::fromOpenIDRequest($openid_req);
+ $this->assertTrue($pape_req === null);
+ }
+
+ function test_preferred_types() {
+ $this->req->addPolicyURI(PAPE_AUTH_PHISHING_RESISTANT);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $pt = $this->req->preferredTypes(array(PAPE_AUTH_MULTI_FACTOR,
+ PAPE_AUTH_MULTI_FACTOR_PHYSICAL));
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $pt);
+ }
+}
+
+class PAPE_DummySuccessResponse {
+ function PAPE_DummySuccessResponse($message, $signed_stuff)
+ {
+ $this->message = $message;
+ $this->signed_stuff = $signed_stuff;
+ }
+
+ function getSignedNS($ns_uri)
+ {
+ return $this->signed_stuff;
+ }
+}
+
+class PapeResponseTestCase extends PHPUnit_TestCase {
+ function setUp() {
+ $this->req = new Auth_OpenID_PAPE_Response();
+ }
+
+ function test_construct() {
+ $this->assertEquals(array(), $this->req->auth_policies);
+ $this->assertEquals(null, $this->req->auth_time);
+ $this->assertEquals('pape', $this->req->ns_alias);
+ $this->assertEquals(null, $this->req->nist_auth_level);
+
+ $req2 = new Auth_OpenID_PAPE_Response(array(PAPE_AUTH_MULTI_FACTOR),
+ '2001-01-01T04:05:23Z',
+ 3);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $req2->auth_policies);
+ $this->assertEquals('2001-01-01T04:05:23Z', $req2->auth_time);
+ $this->assertEquals(3, $req2->nist_auth_level);
+ }
+
+ function test_add_policy_uri() {
+ $this->assertEquals(array(), $this->req->auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $this->req->auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR), $this->req->auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_PHISHING_RESISTANT);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT), $this->req->auth_policies);
+ $this->req->addPolicyURI(PAPE_AUTH_MULTI_FACTOR);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT), $this->req->auth_policies);
+ }
+
+ function test_getExtensionArgs() {
+ $this->assertEquals(array('auth_policies' => 'none'), $this->req->getExtensionArgs());
+ $this->req->addPolicyURI('http://uri');
+ $this->assertEquals(array('auth_policies' => 'http://uri'), $this->req->getExtensionArgs());
+ $this->req->addPolicyURI('http://zig');
+ $this->assertEquals(array('auth_policies' => 'http://uri http://zig'), $this->req->getExtensionArgs());
+ $this->req->auth_time = '2008-03-02T12:34:56Z';
+ $this->assertEquals(array('auth_policies' => 'http://uri http://zig', 'auth_time' => '2008-03-02T12:34:56Z'), $this->req->getExtensionArgs());
+ $this->req->nist_auth_level = 3;
+ $this->assertEquals(array('auth_policies' => 'http://uri http://zig', 'auth_time' => '2008-03-02T12:34:56Z', 'nist_auth_level' => '3'), $this->req->getExtensionArgs());
+ }
+
+ function test_getExtensionArgs_error_auth_age() {
+ $this->req->auth_time = "foo2008-03-02T12:34:56Z";
+ $this->assertEquals(false, $this->req->getExtensionArgs());
+ $this->req->auth_time = "2008-03-02T12:34:56Zbar";
+ $this->assertEquals(false, $this->req->getExtensionArgs());
+ }
+
+ function test_getExtensionArgs_error_nist_auth_level() {
+ $this->req->nist_auth_level = "high as a kite";
+ $this->assertEquals(false, $this->req->getExtensionArgs());
+ $this->req->nist_auth_level = 5;
+ $this->assertEquals(false, $this->req->getExtensionArgs());
+ $this->req->nist_auth_level = -1;
+ $this->assertEquals(false, $this->req->getExtensionArgs());
+ }
+
+ function test_parseExtensionArgs() {
+ $args = array('auth_policies' => 'http://foo http://bar',
+ 'auth_time' => '2008-03-02T12:34:56Z');
+ $this->req->parseExtensionArgs($args);
+ $this->assertEquals('2008-03-02T12:34:56Z', $this->req->auth_time);
+ $this->assertEquals(array('http://foo','http://bar'), $this->req->auth_policies);
+ }
+
+ function test_parseExtensionArgs_empty() {
+ $this->req->parseExtensionArgs(array());
+ $this->assertEquals(null, $this->req->auth_time);
+ $this->assertEquals(array(), $this->req->auth_policies);
+ }
+
+ function test_parseExtensionArgs_strict_bogus1() {
+ $args = array('auth_policies' => 'http://foo http://bar',
+ 'auth_time' => 'yesterday');
+ $this->assertEquals(false, $this->req->parseExtensionArgs($args, true));
+ }
+
+ function test_parseExtensionArgs_strict_bogus2() {
+ $args = array('auth_policies' => 'http://foo http://bar',
+ 'auth_time' => '63',
+ 'nist_auth_level' => 'some');
+ $this->assertEquals(false, $this->req->parseExtensionArgs($args, true));
+ }
+
+ function test_parseExtensionArgs_strict_good() {
+ $args = array('auth_policies' => 'http://foo http://bar',
+ 'auth_time' => '2008-03-02T12:34:56Z',
+ 'nist_auth_level' => '0');
+ $this->req->parseExtensionArgs($args, true);
+ $this->assertEquals(array('http://foo','http://bar'), $this->req->auth_policies);
+ $this->assertEquals('2008-03-02T12:34:56Z', $this->req->auth_time);
+ $this->assertEquals(0, $this->req->nist_auth_level);
+ }
+
+ function test_parseExtensionArgs_nostrict_bogus() {
+ $args = array('auth_policies' => 'http://foo http://bar',
+ 'auth_time' => 'the other day',
+ 'nist_auth_level' => 'some');
+ $this->req->parseExtensionArgs($args);
+ $this->assertEquals(array('http://foo','http://bar'), $this->req->auth_policies);
+ $this->assertEquals(null, $this->req->auth_time);
+ $this->assertEquals(null, $this->req->nist_auth_level);
+ }
+
+ function test_fromSuccessResponse() {
+ $openid_req_msg = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'id_res',
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'ns.pape' => Auth_OpenID_PAPE_NS_URI,
+ 'auth_policies' => implode(' ', array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT)),
+ 'auth_time' => '2008-03-02T12:34:56Z'
+ ));
+ $signed_stuff = array(
+ 'auth_policies' => implode(' ', array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT)),
+ 'auth_time' => '2008-03-02T12:34:56Z'
+ );
+ $oid_req = new PAPE_DummySuccessResponse($openid_req_msg, $signed_stuff);
+ $req = Auth_OpenID_PAPE_Response::fromSuccessResponse($oid_req);
+ $this->assertEquals(array(PAPE_AUTH_MULTI_FACTOR, PAPE_AUTH_PHISHING_RESISTANT), $req->auth_policies);
+ $this->assertEquals('2008-03-02T12:34:56Z', $req->auth_time);
+ }
+}
+
+class Tests_Auth_OpenID_PAPE extends PHPUnit_TestSuite {
+ function getName() {
+ return "Tests_Auth_OpenID_PAPE";
+ }
+
+ function Tests_Auth_OpenID_PAPE() {
+ $this->addTestSuite('PapeRequestTestCase');
+ $this->addTestSuite('PapeResponseTestCase');
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/Parse.php b/Tests/Auth/OpenID/Parse.php
new file mode 100644
index 0000000..9a3547c
--- /dev/null
+++ b/Tests/Auth/OpenID/Parse.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * Tests for the Consumer parsing functions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+require_once 'Auth/OpenID/Parse.php';
+require_once 'PHPUnit.php';
+
+class Tests_Auth_OpenID_Link extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Link($case)
+ {
+ list($desc, $markup, $links, $case_text) = $case;
+ $this->desc = $desc;
+ $this->markup = $markup;
+ $this->expected_links = $links;
+ $this->case_text = $case_text;
+ $this->parser = new Auth_OpenID_Parse();
+ }
+
+ function getName()
+ {
+ return $this->desc;
+ }
+
+ function runTest()
+ {
+ $parsed = $this->parser->parseLinkAttrs($this->markup);
+ $i = 0;
+
+ foreach ($this->expected_links as $expected) {
+ list($is_optional_link, $expected_link) = $expected;
+ if ($is_optional_link &&
+ ($i >= count($parsed))) {
+ continue;
+ }
+
+ if (count($parsed) <= $i) {
+ $i++;
+ continue;
+ }
+
+ $act_link = $parsed[$i];
+
+ $increment = true;
+ foreach ($expected_link as $attr => $data) {
+ list($is_optional_attr, $value) = $data;
+
+ if ($is_optional_attr) {
+ $actual_value = null;
+ if (array_key_exists($attr, $act_link)) {
+ $actual_value = $act_link[$attr];
+ } else {
+ continue;
+ }
+ } else {
+ $actual_value = $act_link[$attr];
+ }
+
+ if ($is_optional_link &&
+ ($value != $actual_value)) {
+ $increment = false;
+ break;
+ }
+
+ $this->assertEquals($value, $actual_value);
+ }
+
+ if ($increment) {
+ $i++;
+ }
+ }
+
+ $this->assertEquals($i, count($parsed));
+ }
+}
+
+class NumTestCases extends PHPUnit_TestCase {
+ function NumTestCases($test_cases, $num_tests)
+ {
+ $this->test_cases = $test_cases;
+ $this->num_tests = $num_tests;
+ }
+
+ function runTest()
+ {
+ $this->assertEquals(count($this->test_cases),
+ $this->num_tests);
+ }
+}
+
+class Tests_Auth_OpenID_Parse extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_OpenID_Parse";
+ }
+
+ function _parseCheck($cond, $where)
+ {
+ if (!$cond) {
+ trigger_error('Parse error in ' . $where, E_USER_ERROR);
+ }
+ }
+
+ function parseLink($line)
+ {
+ $parts = explode(" ", $line);
+ $optional = intval($parts[0] == 'Link*:');
+ $this->_parseCheck($optional || ($parts[0] == 'Link:'), __FUNCTION__);
+
+ $attrs = array();
+ foreach (array_slice($parts, 1) as $attr) {
+ list($k, $v) = explode("=", $attr, 2);
+ if ($k[strlen($k) - 1] == '*') {
+ $attr_optional = 1;
+ $k = substr($k, 0, strlen($k) - 1);
+ } else {
+ $attr_optional = 0;
+ }
+
+ $attrs[$k] = array($attr_optional, $v);
+ }
+
+ return array($optional, $attrs);
+ }
+
+ function parseCase($s)
+ {
+ list($header, $markup) = explode("\n\n", $s, 2);
+ $lines = explode("\n", $header);
+ $name = array_shift($lines);
+ $this->_parseCheck(strpos($name, 'Name: ') == 0, __FUNCTION__);
+ $desc = substr($name, 6);
+ $parsed = array();
+ foreach ($lines as $line) {
+ $parsed[] = $this->parseLink($line);
+ }
+
+ return array($desc, $markup, $parsed);
+ }
+
+ function parseTests($s)
+ {
+ $tests = array();
+
+ $cases = explode("\n\n\n", $s);
+ $header = array_shift($cases);
+ list($tests_line, $unused) = explode("\n", $header, 2);
+ list($k, $v) = explode(": ", $tests_line);
+ $this->_parseCheck(('Num Tests' == $k), __FUNCTION__);
+ $num_tests = intval($v);
+
+ foreach (array_slice($cases, 0, count($cases) - 1) as $case) {
+ list($desc, $markup, $links) = $this->parseCase($case);
+ $tests[] = array($desc, $markup, $links, $case);
+ }
+
+ return array($num_tests, $tests);
+ }
+
+ function Tests_Auth_OpenID_Parse()
+ {
+ $test_data = Tests_Auth_OpenID_readdata('linkparse.txt');
+
+ list($num_tests, $test_cases) = $this->parseTests($test_data);
+
+ $this->addTest(new NumTestCases($test_cases, $num_tests));
+
+ foreach ($test_cases as $case) {
+ $this->addTest(new Tests_Auth_OpenID_Link($case));
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/RPVerify.php b/Tests/Auth/OpenID/RPVerify.php
new file mode 100644
index 0000000..59c8b1a
--- /dev/null
+++ b/Tests/Auth/OpenID/RPVerify.php
@@ -0,0 +1,299 @@
+<?php
+
+/*
+ * Unit tests for verification of return_to URLs for a realm.
+ */
+
+require_once 'Auth/OpenID/Discover.php';
+require_once 'Auth/OpenID/TrustRoot.php';
+
+require_once 'Auth/Yadis/Yadis.php';
+
+require_once 'PHPUnit.php';
+
+// Because "null" cannot be passed by reference.
+$NULL_FETCHER = null;
+
+/*
+ * Tests for building the discovery URL from a realm and a return_to
+ * URL
+ */
+class Tests_Auth_OpenID_BuildDiscoveryURL extends PHPUnit_TestCase {
+ /*
+ * Build a discovery URL out of the realm and a return_to and make
+ * sure that it matches the expected discovery URL
+ */
+ function failUnlessDiscoURL($realm, $expected_discovery_url)
+ {
+ $actual_discovery_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm);
+ $this->assertEquals($expected_discovery_url, $actual_discovery_url);
+ }
+
+ /*
+ * There is no wildcard and the realm is the same as the return_to
+ * URL
+ */
+ function test_trivial()
+ {
+ $this->failUnlessDiscoURL('http://example.com/foo',
+ 'http://example.com/foo');
+ }
+
+ /*
+ * There is a wildcard
+ */
+ function test_wildcard()
+ {
+ $this->failUnlessDiscoURL('http://*.example.com/foo',
+ 'http://www.example.com/foo');
+ }
+}
+
+class _MockDiscover {
+ function _MockDiscover(&$data) {
+ $this->data =& $data;
+ }
+
+ function mockDiscover($uri, $fetcher, $discover_function=null)
+ {
+ $result = new Auth_Yadis_DiscoveryResult($uri);
+ $result->response_text = $this->data;
+ $result->normalized_uri = $uri;
+ return $result;
+ }
+}
+
+class Tests_Auth_OpenID_ExtractReturnToURLs extends PHPUnit_TestCase {
+ var $disco_url = 'http://example.com/';
+
+ function failUnlessXRDSHasReturnURLs($data, $expected_return_urls)
+ {
+ $discover_object = new _MockDiscover($data);
+ $actual_return_urls = Auth_OpenID_getAllowedReturnURLs($this->disco_url, $NULL_FETCHER, array(&$discover_object, 'mockDiscover'));
+
+ $this->assertEquals($expected_return_urls, $actual_return_urls);
+ }
+
+ function failUnlessDiscoveryFailure($text)
+ {
+ $discover_object = new _MockDiscover($text);
+ $this->assertFalse(Auth_OpenID_getAllowedReturnURLs($this->disco_url, $NULL_FETCHER, array(&$discover_object, 'mockDiscover')));
+ }
+
+ function test_empty()
+ {
+ $this->failUnlessDiscoveryFailure('');
+ }
+
+ function test_badXML()
+ {
+ $this->failUnlessDiscoveryFailure('>');
+ }
+
+ function test_noEntries()
+ {
+ $this->failUnlessXRDSHasReturnURLs('<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ </XRD>
+</xrds:XRDS>
+', array());
+ }
+
+ function test_noReturnToEntries()
+ {
+ $this->failUnlessXRDSHasReturnURLs('<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/server</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+', array());
+ }
+
+ function test_oneEntry()
+ {
+ $this->failUnlessXRDSHasReturnURLs('<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service>
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>http://rp.example.com/return</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+', array('http://rp.example.com/return'));
+ }
+
+ function test_twoEntries()
+ {
+ $this->failUnlessXRDSHasReturnURLs('<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="0">
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>http://rp.example.com/return</URI>
+ </Service>
+ <Service priority="1">
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>http://other.rp.example.com/return</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+', array('http://rp.example.com/return',
+ 'http://other.rp.example.com/return'));
+ }
+
+ function test_twoEntries_withOther()
+ {
+ $this->failUnlessXRDSHasReturnURLs('<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="0">
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>http://rp.example.com/return</URI>
+ </Service>
+ <Service priority="1">
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>http://other.rp.example.com/return</URI>
+ </Service>
+ <Service priority="0">
+ <Type>http://example.com/LOLCATS</Type>
+ <URI>http://example.com/invisible+uri</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+', array('http://rp.example.com/return',
+ 'http://other.rp.example.com/return'));
+ }
+}
+
+class Tests_Auth_OpenID_ReturnToMatches extends PHPUnit_TestCase {
+ function test_noEntries()
+ {
+ $this->assertFalse(Auth_OpenID_returnToMatches(array(), 'anything'));
+ }
+
+ function test_exactMatch()
+ {
+ $r = 'http://example.com/return.to';
+ $this->assertTrue(Auth_OpenID_returnToMatches(array($r), $r));
+ }
+
+ function test_garbageMatch()
+ {
+ $r = 'http://example.com/return.to';
+ $this->assertTrue(Auth_OpenID_returnToMatches(
+ array('This is not a URL at all. In fact, it has characters, ' .
+ 'like "<" that are not allowed in URLs', $r), $r));
+ }
+
+ function test_descendant()
+ {
+ $r = 'http://example.com/return.to';
+ $this->assertTrue(Auth_OpenID_returnToMatches(array($r),
+ 'http://example.com/return.to/user:joe'));
+ }
+
+ function test_wildcard()
+ {
+ $this->assertFalse(Auth_OpenID_returnToMatches(
+ array('http://*.example.com/return.to'),
+ 'http://example.com/return.to'));
+ }
+
+ function test_noMatch()
+ {
+ $r = 'http://example.com/return.to';
+ $this->assertFalse(Auth_OpenID_returnToMatches(array($r),
+ 'http://example.com/xss_exploit'));
+ }
+}
+
+class Verifier {
+ function Verifier(&$test_case, $return_to)
+ {
+ $this->tc =& $test_case;
+ $this->return_to = $return_to;
+ }
+
+ function verify($disco_url)
+ {
+ $this->tc->assertEquals('http://www.example.com/', $disco_url);
+
+ if ($this->return_to === false) {
+ return false;
+ } else {
+ return array($this->return_to);
+ }
+ }
+}
+
+class Tests_Auth_OpenID_VerifyReturnTo extends PHPUnit_TestCase {
+
+ function test_bogusRealm()
+ {
+ $this->assertFalse(Auth_OpenID_verifyReturnTo('', 'http://example.com/', $NULL_FETCHER));
+ }
+
+ function test_verifyWithDiscoveryCalled()
+ {
+ $realm = 'http://*.example.com/';
+ $return_to = 'http://www.example.com/foo';
+
+ $v = new Verifier($this, $return_to);
+
+ $this->assertTrue(Auth_OpenID_verifyReturnTo($realm, $return_to, $NULL_FETCHER, array(&$v, 'verify')));
+ }
+
+ function test_verifyFailWithDiscoveryCalled()
+ {
+ $realm = 'http://*.example.com/';
+ $return_to = 'http://www.example.com/foo';
+
+ $v = new Verifier($this, 'http://something-else.invalid/');
+
+ $this->assertFalse(Auth_OpenID_verifyReturnTo($realm, $return_to, $NULL_FETCHER, array(&$v, 'verify')));
+ }
+
+ function test_verifyFailIfDiscoveryRedirects()
+ {
+ $realm = 'http://*.example.com/';
+ $return_to = 'http://www.example.com/foo';
+
+ $v = new Verifier($this, false);
+
+ $this->assertFalse(Auth_OpenID_verifyReturnTo($realm, $return_to, $NULL_FETCHER, array(&$v, 'verify')));
+ }
+}
+
+class Tests_Auth_OpenID_RPVerify extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_OpenID_RPVerify";
+ }
+
+ function Tests_Auth_OpenID_RPVerify()
+ {
+ $this->addTestSuite('Tests_Auth_OpenID_VerifyReturnTo');
+ $this->addTestSuite('Tests_Auth_OpenID_ReturnToMatches');
+ $this->addTestSuite('Tests_Auth_OpenID_ExtractReturnToURLs');
+ $this->addTestSuite('Tests_Auth_OpenID_BuildDiscoveryURL');
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/SReg.php b/Tests/Auth/OpenID/SReg.php
new file mode 100644
index 0000000..5142b63
--- /dev/null
+++ b/Tests/Auth/OpenID/SReg.php
@@ -0,0 +1,675 @@
+<?php
+
+/**
+ * SReg.php testing code.
+ */
+
+require_once 'Auth/OpenID/SReg.php';
+require_once 'Auth/OpenID/Message.php';
+require_once 'Auth/OpenID/Server.php';
+
+require_once 'PHPUnit.php';
+
+class SRegURITest extends PHPUnit_TestCase {
+ function test_is11()
+ {
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI_1_1,
+ Auth_OpenID_SREG_NS_URI);
+ }
+}
+
+class CheckFieldNameTest extends PHPUnit_TestCase {
+ function test_goodNamePasses()
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
+ $this->assertTrue(Auth_OpenID_checkFieldName($field_name));
+ }
+ }
+
+ function test_badNameFails()
+ {
+ $this->assertfalse(Auth_OpenID_checkFieldName('INVALID'));
+ }
+
+ function test_badTypeFails()
+ {
+ $this->assertfalse(Auth_OpenID_checkFieldName(null));
+ }
+}
+
+// For supportsSReg test
+class FakeEndpoint {
+ function FakeEndpoint($supported)
+ {
+ $this->supported = $supported;
+ $this->checked_uris = array();
+ }
+
+ function usesExtension($namespace_uri)
+ {
+ $this->checked_uris[] = $namespace_uri;
+ return in_array($namespace_uri, $this->supported);
+ }
+}
+
+class SupportsSRegTest extends PHPUnit_TestCase {
+ function test_unsupported()
+ {
+ $endpoint = new FakeEndpoint(array());
+ $this->assertfalse(Auth_OpenID_supportsSReg($endpoint));
+ $this->assertEquals(array(Auth_OpenID_SREG_NS_URI_1_1,
+ Auth_OpenID_SREG_NS_URI_1_0),
+ $endpoint->checked_uris);
+ }
+
+ function test_supported_1_1()
+ {
+ $endpoint = new FakeEndpoint(array(Auth_OpenID_SREG_NS_URI_1_1));
+ $this->assertTrue(Auth_OpenID_supportsSReg($endpoint));
+ $this->assertEquals(array(Auth_OpenID_SREG_NS_URI_1_1),
+ $endpoint->checked_uris);
+ }
+
+ function test_supported_1_0()
+ {
+ $endpoint = new FakeEndpoint(array(Auth_OpenID_SREG_NS_URI_1_0));
+ $this->assertTrue(Auth_OpenID_supportsSReg($endpoint));
+ $this->assertEquals(array(Auth_OpenID_SREG_NS_URI_1_1,
+ Auth_OpenID_SREG_NS_URI_1_0),
+ $endpoint->checked_uris);
+ }
+}
+
+class FakeMessage {
+ function FakeMessage()
+ {
+ $this->openid1 = false;
+ $this->namespaces = new Auth_OpenID_NamespaceMap();
+ }
+
+ function isOpenID1()
+ {
+ return $this->openid1;
+ }
+}
+
+class GetNSTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new FakeMessage();
+ }
+
+ function test_openID2Empty()
+ {
+ $ns_uri = Auth_OpenID_SRegBase::_getSRegNS($this->msg);
+ $this->assertEquals($this->msg->namespaces->getAlias($ns_uri), 'sreg');
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI, $ns_uri);
+ }
+
+ function test_openID1Empty()
+ {
+ $this->msg->openid1 = true;
+ $ns_uri = Auth_OpenID_SRegBase::_getSRegNS($this->msg);
+ $this->assertEquals($this->msg->namespaces->getAlias($ns_uri), 'sreg');
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI, $ns_uri);
+ }
+
+ function test_openID1Defined_1_0()
+ {
+ $this->msg->openid1 = true;
+ $this->msg->namespaces->add(Auth_OpenID_SREG_NS_URI_1_0);
+ $ns_uri = Auth_OpenID_SRegBase::_getSRegNS($this->msg);
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI_1_0, $ns_uri);
+ }
+
+ function test_openID1Defined_1_0_overrideAlias()
+ {
+ foreach (array(true, false) as $openid_version) {
+ foreach (array(Auth_OpenID_SREG_NS_URI_1_0,
+ Auth_OpenID_SREG_NS_URI_1_1) as $sreg_version) {
+ foreach (array('sreg', 'bogus') as $alias) {
+ $this->setUp();
+
+ $this->msg->openid1 = $openid_version;
+ $this->assertTrue($this->msg->namespaces->addAlias($sreg_version, $alias) !== null);
+ $ns_uri = Auth_OpenID_SRegBase::_getSRegNS($this->msg);
+ $this->assertEquals($this->msg->namespaces->getAlias($ns_uri), $alias);
+ $this->assertEquals($sreg_version, $ns_uri);
+ }
+ }
+ }
+ }
+
+ function test_openID1DefinedBadly()
+ {
+ $this->msg->openid1 = true;
+ $this->msg->namespaces->addAlias('http://invalid/', 'sreg');
+ $this->assertTrue(Auth_OpenID_SRegBase::_getSRegNS($this->msg) === null);
+ }
+
+ function test_openID2DefinedBadly()
+ {
+ $this->msg->openid1 = false;
+ $this->msg->namespaces->addAlias('http://invalid/', 'sreg');
+ $this->assertTrue(Auth_OpenID_SRegBase::_getSRegNS($this->msg) === null);
+ }
+
+ function test_openID2Defined_1_0()
+ {
+ $this->msg->namespaces->add(Auth_OpenID_SREG_NS_URI_1_0);
+ $ns_uri = Auth_OpenID_SRegBase::_getSRegNS($this->msg);
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI_1_0, $ns_uri);
+ }
+
+ function test_openID1_sregNSfromArgs()
+ {
+ $args = array(
+ 'sreg.optional' => 'nickname',
+ 'sreg.required' => 'dob');
+
+ $m = Auth_OpenID_Message::fromOpenIDArgs($args);
+
+ $this->assertTrue($m->getArg(Auth_OpenID_SREG_NS_URI_1_1, 'optional') == 'nickname');
+ $this->assertTrue($m->getArg(Auth_OpenID_SREG_NS_URI_1_1, 'required') == 'dob');
+ }
+}
+
+global $__args_sentinel;
+global $__ns_sentinel;
+$__args_sentinel = 'args_sentinel';
+$__ns_sentinel = 'ns_sentinel';
+
+class SentinelFakeMessage {
+ function SentinelFakeMessage(&$test_case)
+ {
+ $this->test_case =& $test_case;
+ $this->message = new Auth_OpenID_Message();
+ }
+
+ function getArgs($ns_uri)
+ {
+ global $__ns_sentinel, $__args_sentinel;
+ $this->test_case->assertEquals($__ns_sentinel, $ns_uri);
+ return $__args_sentinel;
+ }
+}
+
+// XXX Ugly hack. Thanks, PHP.
+global $__TestingReq_TEST_CASE;
+$__TestingReq_TEST_CASE = "FLUB";
+
+function __setTestCase(&$thing) {
+ global $__TestingReq_TEST_CASE;
+ $__TestingReq_TEST_CASE = $thing;
+}
+
+function &__getTestCase() {
+ global $__TestingReq_TEST_CASE;
+ return $__TestingReq_TEST_CASE;
+}
+
+class TestingReq extends Auth_OpenID_SRegRequest {
+ function fromOpenIDRequest(&$thing, &$test_case)
+ {
+ __setTestCase($test_case);
+ $obj = parent::fromOpenIDRequest($thing, 'TestingReq');
+ return $obj;
+ }
+
+ function _getSRegNS($unused)
+ {
+ global $__ns_sentinel;
+ return $__ns_sentinel;
+ }
+
+ function parseExtensionArgs($args)
+ {
+ global $__args_sentinel;
+ $tc =& __getTestCase();
+ $tc->assertEquals($__args_sentinel, $args);
+ }
+}
+
+class SRegRequestTest extends PHPUnit_TestCase {
+ function test_constructEmpty()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertEquals(array(), $req->optional);
+ $this->assertEquals(array(), $req->required);
+ $this->assertEquals(null, $req->policy_url);
+ $this->assertEquals(Auth_OpenID_SREG_NS_URI, $req->ns_uri);
+ }
+
+ function test_constructFields()
+ {
+ $req = Auth_OpenID_SRegRequest::build(
+ array('nickname'),
+ array('gender'),
+ 'http://policy',
+ 'http://sreg.ns_uri');
+ $this->assertEquals(array('gender'), $req->optional);
+ $this->assertEquals(array('nickname'), $req->required);
+ $this->assertEquals('http://policy', $req->policy_url);
+ $this->assertEquals('http://sreg.ns_uri', $req->ns_uri);
+ }
+
+ function test_constructBadFields()
+ {
+ $this->assertTrue(Auth_OpenID_SRegRequest::build(array('elvis')) === null);
+ }
+
+ function test_fromOpenIDResponse()
+ {
+ $openid_req = new Auth_OpenID_Request();
+
+ $msg = new SentinelFakeMessage($this);
+ $openid_req->message =& $msg;
+
+ $req = TestingReq::fromOpenIDRequest($openid_req, $this);
+ $this->assertTrue(is_a($req, 'TestingReq'));
+ }
+
+ function test_parseExtensionArgs_empty()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array()));
+ }
+
+ function test_parseExtensionArgs_extraIgnored()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('janrain' => 'inc')));
+ }
+
+ function test_parseExtensionArgs_nonStrict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('required' => 'beans')));
+ $this->assertEquals(array(), $req->required);
+ }
+
+ function test_parseExtensionArgs_strict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->parseExtensionArgs(array('required' => 'beans'),
+ true));
+ }
+
+ function test_parseExtensionArgs_policy()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(
+ array('policy_url' => 'http://policy'), true));
+ $this->assertEquals('http://policy', $req->policy_url);
+ }
+
+ function test_parseExtensionArgs_requiredEmpty()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('required' => ''), true));
+ $this->assertEquals(array(), $req->required);
+ }
+
+ function test_parseExtensionArgs_optionalEmpty()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => ''), true));
+ $this->assertEquals(array(), $req->optional);
+ }
+
+ function test_parseExtensionArgs_optionalSingle()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => 'nickname'), true));
+ $this->assertEquals(array('nickname'), $req->optional);
+ }
+
+ function test_parseExtensionArgs_optionalList()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => 'nickname,email'), true));
+ $this->assertEquals(array('nickname','email'), $req->optional);
+ }
+
+ function test_parseExtensionArgs_optionalListBadNonStrict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => 'nickname,email,beer')));
+ $this->assertEquals(array('nickname','email'), $req->optional);
+ }
+
+ function test_parseExtensionArgs_optionalListBadStrict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->parseExtensionArgs(array('optional' => 'nickname,email,beer'),
+ true));
+ }
+
+ function test_parseExtensionArgs_bothNonStrict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => 'nickname',
+ 'required' => 'nickname')));
+ $this->assertEquals(array(), $req->optional);
+ $this->assertEquals(array('nickname'), $req->required);
+ }
+
+ function test_parseExtensionArgs_bothStrict()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->parseExtensionArgs(
+ array('optional' => 'nickname',
+ 'required' => 'nickname'),
+ true));
+ }
+
+ function test_parseExtensionArgs_bothList()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertTrue($req->parseExtensionArgs(array('optional' => 'nickname,email',
+ 'required' => 'country,postcode'),
+ true));
+ $this->assertEquals(array('nickname','email'), $req->optional);
+ $this->assertEquals(array('country','postcode'), $req->required);
+ }
+
+ function test_allRequestedFields()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertEquals(array(), $req->allRequestedFields());
+ $req->requestField('nickname');
+ $this->assertEquals(array('nickname'), $req->allRequestedFields());
+ $req->requestField('gender', true);
+ $requested = $req->allRequestedFields();
+ sort($requested);
+ $this->assertEquals(array('gender', 'nickname'), $requested);
+ }
+
+ function test_wereFieldsRequested()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->wereFieldsRequested());
+ $req->requestField('gender');
+ $this->assertTrue($req->wereFieldsRequested());
+ }
+
+ function test_contains()
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ $req = Auth_OpenID_SRegRequest::build();
+ foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
+ $this->assertFalse($req->contains($field_name));
+ }
+
+ $this->assertFalse($req->contains('something else'));
+
+ $req->requestField('nickname');
+ foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
+ if ($field_name == 'nickname') {
+ $this->assertTrue($req->contains($field_name));
+ } else {
+ $this->assertFalse($req->contains($field_name));
+ }
+ }
+ }
+
+ function test_requestField_bogus()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->requestField('something else'));
+ $this->assertFalse($req->requestField('something else', true));
+ }
+
+ function test_requestField()
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ // Add all of the fields, one at a time
+ $req = Auth_OpenID_SRegRequest::build();
+ $fields = array_keys($Auth_OpenID_sreg_data_fields);
+ foreach ($fields as $field_name) {
+ $req->requestField($field_name);
+ }
+
+ $this->assertEquals($fields, $req->optional);
+ $this->assertEquals(array(), $req->required);
+
+ // By default, adding the same fields over again has no effect
+ foreach ($fields as $field_name) {
+ $req->requestField($field_name);
+ }
+
+ $this->assertEquals($fields, $req->optional);
+ $this->assertEquals(array(), $req->required);
+
+ // Requesting a field as required overrides requesting it as
+ // optional
+ $expected = $fields;
+ $overridden = array_pop($expected);
+
+ $this->assertTrue($req->requestField($overridden, true));
+
+ $this->assertEquals($expected, $req->optional);
+ $this->assertEquals(array($overridden), $req->required);
+
+ // Requesting a field as required overrides requesting it as
+ // optional
+ foreach ($fields as $field_name) {
+ $this->assertTrue($req->requestField($field_name, true));
+ }
+
+ $this->assertEquals(array(), $req->optional);
+ foreach ($fields as $f) {
+ $this->assertTrue(in_array($f, $req->required));
+ }
+
+ // Requesting it as optional does not downgrade it to optional
+ foreach ($fields as $field_name) {
+ $req->requestField($field_name);
+ }
+
+ $this->assertEquals(array(), $req->optional);
+
+ foreach ($fields as $f) {
+ $this->assertTrue(in_array($f, $req->required));
+ }
+ }
+
+ function test_requestFields_type()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertFalse($req->requestFields('nickname'));
+ }
+
+ function test_requestFields()
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ // Add all of the fields
+ $req = Auth_OpenID_SRegRequest::build();
+
+ $fields = array_keys($Auth_OpenID_sreg_data_fields);
+ $req->requestFields($fields);
+
+ $this->assertEquals($fields, $req->optional);
+ $this->assertEquals(array(), $req->required);
+
+ // By default, adding the same fields over again has no effect
+ $req->requestFields($fields);
+
+ $this->assertEquals($fields, $req->optional);
+ $this->assertEquals(array(), $req->required);
+
+ // Requesting a field as required overrides requesting it as
+ // optional
+ $expected = $fields;
+ $overridden = array_shift($expected);
+ $req->requestFields(array($overridden), true);
+
+ foreach ($expected as $f) {
+ $this->assertTrue(in_array($f, $req->optional));
+ }
+
+ $this->assertEquals(array($overridden), $req->required);
+
+ // Requesting a field as required overrides requesting it as
+ // optional
+ $req->requestFields($fields, true);
+
+ $this->assertEquals(array(), $req->optional);
+ $this->assertEquals($fields, $req->required);
+
+ // Requesting it as optional does not downgrade it to optional
+ $req->requestFields($fields);
+
+ $this->assertEquals(array(), $req->optional);
+ $this->assertEquals($fields, $req->required);
+ }
+
+ function test_getExtensionArgs()
+ {
+ $req = Auth_OpenID_SRegRequest::build();
+ $this->assertEquals(array(), $req->getExtensionArgs());
+
+ $this->assertTrue($req->requestField('nickname'));
+ $this->assertEquals(array('optional' => 'nickname'),
+ $req->getExtensionArgs());
+
+ $this->assertTrue($req->requestField('email'));
+ $this->assertEquals(array('optional' => 'nickname,email'),
+ $req->getExtensionArgs());
+
+ $this->assertTrue($req->requestField('gender', true));
+ $this->assertEquals(array('optional' => 'nickname,email',
+ 'required' => 'gender'),
+ $req->getExtensionArgs());
+
+ $this->assertTrue($req->requestField('postcode', true));
+ $this->assertEquals(array('optional' => 'nickname,email',
+ 'required' => 'gender,postcode'),
+ $req->getExtensionArgs());
+
+ $req->policy_url = 'http://policy.invalid/';
+ $this->assertEquals(array('optional' => 'nickname,email',
+ 'required' => 'gender,postcode',
+ 'policy_url' => 'http://policy.invalid/'),
+ $req->getExtensionArgs());
+ }
+}
+
+class DummySuccessResponse {
+ function DummySuccessResponse($message, $signed_stuff)
+ {
+ $this->message = $message;
+ $this->signed_stuff = $signed_stuff;
+ }
+
+ function getSignedNS($ns_uri)
+ {
+ return $this->signed_stuff;
+ }
+}
+
+class SRegResponseTest extends PHPUnit_TestCase {
+ function test_fromSuccessResponse_signed()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'sreg.nickname' => 'The Mad Stork',
+ ));
+ $success_resp = new DummySuccessResponse($message, array());
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($success_resp);
+ $this->assertTrue(count($sreg_resp->contents()) === 0);
+ }
+
+ function test_fromSuccessResponse_unsigned()
+ {
+ $message = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'sreg.nickname' => 'The Mad Stork',
+ ));
+
+ $success_resp = new DummySuccessResponse($message, array());
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($success_resp,
+ false);
+
+ $this->assertEquals(array('nickname' => 'The Mad Stork'),
+ $sreg_resp->contents());
+ }
+}
+
+class SendFieldsTest extends PHPUnit_TestCase {
+ function _test($uri)
+ {
+ // Create a request message with simple registration fields
+ $sreg_req = Auth_OpenID_SRegRequest::build(array('nickname', 'email'),
+ array('fullname'));
+ $req_msg = new Auth_OpenID_Message($uri);
+ $req_msg->updateArgs(Auth_OpenID_SREG_NS_URI,
+ $sreg_req->getExtensionArgs());
+
+ $req = new Auth_OpenID_Request();
+ $req->message =& $req_msg;
+ $req->namespace = $req_msg->getOpenIDNamespace();
+
+ // -> send checkid_* request
+
+ // Create an empty response message
+ $resp_msg = new Auth_OpenID_Message($uri);
+ $resp = new Auth_OpenID_ServerResponse($req);
+ $resp->fields = $resp_msg;
+
+ $data = array(
+ 'nickname' => 'linusaur',
+ 'postcode' => '12345',
+ 'country' => 'US',
+ 'gender' => 'M',
+ 'fullname' => 'Leonhard Euler',
+ 'email' => 'president@whitehouse.gov',
+ 'dob' => '0000-00-00',
+ 'language' => 'en-us');
+
+ // Put the requested data fields in the response message
+ $sreg_resp = Auth_OpenID_SRegResponse::extractResponse($sreg_req, $data);
+ $resp->addExtension($sreg_resp);
+
+ // <- send id_res response
+
+ // Extract the fields that were sent
+ $sreg_data_resp = $resp->fields->getArgs(Auth_OpenID_SREG_NS_URI);
+ $this->assertEquals(
+ array('nickname' => 'linusaur',
+ 'email' => 'president@whitehouse.gov',
+ 'fullname' => 'Leonhard Euler'),
+ $sreg_data_resp);
+ }
+
+ function test()
+ {
+ foreach (array(Auth_OpenID_OPENID1_NS,
+ Auth_OpenID_OPENID2_NS) as $uri) {
+ $this->_test($uri);
+ }
+ }
+}
+
+class Tests_Auth_OpenID_SReg extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_OpenID_SReg";
+ }
+
+ function Tests_Auth_OpenID_SReg()
+ {
+ $this->addTestSuite('SRegURITest');
+ $this->addTestSuite('CheckFieldNameTest');
+ $this->addTestSuite('SupportsSRegTest');
+ $this->addTestSuite('GetNSTest');
+ $this->addTestSuite('SRegRequestTest');
+ $this->addTestSuite('SRegResponseTest');
+ $this->addTestSuite('SendFieldsTest');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Server.php b/Tests/Auth/OpenID/Server.php
new file mode 100644
index 0000000..79a7593
--- /dev/null
+++ b/Tests/Auth/OpenID/Server.php
@@ -0,0 +1,2464 @@
+<?php
+
+/**
+ * Tests for Auth_OpenID_Server
+ */
+
+require_once "PHPUnit.php";
+require_once "Tests/Auth/OpenID/MemStore.php";
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/Server.php";
+require_once "Auth/OpenID/Consumer.php";
+
+function altModulus()
+{
+ $lib =& Auth_OpenID_getMathLib();
+ static $num = null;
+
+ if (!$num) {
+ $num = $lib->init("1423261515703355186607439952816216983770".
+ "5735494988446894302176757360889904836136".
+ "0422513557553514790045512299468953431585".
+ "3008125488594198571710943663581589034331".
+ "6791551733211386105974742540867014420109".
+ "9811846875730766487278261498262568348338".
+ "4764372005569983660877797099908075182915".
+ "81860338635288400119293970087"
+ );
+ }
+
+ return $num;
+}
+
+global $ALT_GEN;
+$ALT_GEN = 5;
+
+function arrayToString($arr)
+{
+ $s = "Array(";
+
+ $parts = array();
+ foreach ($arr as $k => $v) {
+ if (is_array($v)) {
+ $v = arrayToString($v);
+ }
+ $parts[] = sprintf("%s => %s", $k, $v);
+ }
+
+ $s .= implode(", ", $parts);
+ $s .= ")";
+
+ return $s;
+}
+
+function _Auth_OpenID_NotAuthorized()
+{
+ return false;
+}
+
+class Tests_Auth_OpenID_Test_ServerError extends PHPUnit_TestCase {
+ function test_browserWithReturnTo()
+ {
+ $return_to = "http://rp.unittest/consumer";
+ // will be a ProtocolError raised by Decode or CheckIDRequest.answer
+ $args = array(
+ 'openid.mode' => 'monkeydance',
+ 'openid.identity' => 'http://wagu.unittest/',
+ 'openid.return_to' => $return_to);
+
+ $e = new Auth_OpenID_ServerError(
+ Auth_OpenID_Message::fromPostArgs($args),
+ "plucky");
+
+ $this->assertTrue($e->hasReturnTo());
+ $expected_args = array(
+ 'openid.mode' => 'error',
+ 'openid.error' => 'plucky');
+
+ $encoded = $e->encodeToURL();
+ if (Auth_OpenID_isError($encoded)) {
+ $this->fail($encoded->toString());
+ return;
+ }
+
+ list($rt_base, $_result_args) = explode("?", $e->encodeToURL(), 2);
+ $result_args = Auth_OpenID::getQuery($_result_args);
+
+ $this->assertEquals($result_args, $expected_args);
+ }
+
+ function test_browserWithReturnTo_OpenID2_GET()
+ {
+ $return_to = "http://rp.unittest/consumer";
+ // will be a ProtocolError raised by Decode or
+ // CheckIDRequest.answer
+ $args = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'monkeydance',
+ 'openid.identity' => 'http://wagu.unittest/',
+ 'openid.claimed_id' => 'http://wagu.unittest/',
+ 'openid.return_to' => $return_to));
+
+ $e = new Auth_OpenID_ServerError($args, "plucky");
+ $this->assertTrue($e->hasReturnTo());
+ $expected_args = array('openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'error',
+ 'openid.error' => 'plucky');
+
+ list($rt_base, $result_args_s) = explode('?', $e->encodeToURL(), 2);
+ $result_args = Auth_OpenID::parse_str($result_args_s);
+
+ $this->assertEquals($result_args, $expected_args);
+ }
+
+ function test_browserWithReturnTo_OpenID2_POST()
+ {
+ $return_to = "http://rp.unittest/consumer" . str_repeat('x', Auth_OpenID_OPENID1_URL_LIMIT);
+ // will be a ProtocolError raised by Decode or
+ // CheckIDRequest.answer
+ $args = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'monkeydance',
+ 'openid.identity' => 'http://wagu.unittest/',
+ 'openid.claimed_id' => 'http://wagu.unittest/',
+ 'openid.return_to' => $return_to));
+
+ $e = new Auth_OpenID_ServerError($args, "plucky");
+ $this->assertTrue($e->hasReturnTo());
+ $expected_args = array('openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'error',
+ 'openid.error' => 'plucky');
+
+ $this->assertTrue($e->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM);
+
+ $msg = $e->toMessage();
+
+ $this->assertTrue($e->toFormMarkup() ==
+ $msg->toFormMarkup($args->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
+ }
+
+ function test_browserWithReturnTo_OpenID1_exceeds_limit()
+ {
+ $return_to = "http://rp.unittest/consumer" . str_repeat('x', Auth_OpenID_OPENID1_URL_LIMIT);
+ // will be a ProtocolError raised by Decode or
+ // CheckIDRequest.answer
+ $args = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.mode' => 'monkeydance',
+ 'openid.identity' => 'http://wagu.unittest/',
+ 'openid.return_to' => $return_to));
+
+ $this->assertTrue($args->isOpenID1());
+
+ $e = new Auth_OpenID_ServerError($args, "plucky");
+ $this->assertTrue($e->hasReturnTo());
+ $expected_args = array('openid.mode' => 'error',
+ 'openid.error' => 'plucky');
+
+ $this->assertTrue($e->whichEncoding() == Auth_OpenID_ENCODE_URL);
+
+ list($rt_base, $result_args_s) = explode('?', $e->encodeToURL(), 2);
+ $result_args = Auth_OpenID::parse_str($result_args_s);
+ $this->assertEquals($result_args, $expected_args);
+ }
+
+ function test_noReturnTo()
+ {
+ // will be a ProtocolError raised by Decode or CheckIDRequest.answer
+ $args = array(
+ 'openid.mode' => 'zebradance',
+ 'openid.identity' => 'http://wagu.unittest/');
+
+ $e = new Auth_OpenID_ServerError(
+ Auth_OpenID_Message::fromPostArgs($args),
+ "waffles");
+
+ $this->assertFalse($e->hasReturnTo());
+ $expected = "error:waffles\nmode:error\n";
+ $this->assertEquals($e->encodeToKVForm(), $expected);
+ }
+
+ function test_noMessage()
+ {
+ $e = new Auth_OpenID_ServerError();
+ $this->assertFalse($e->hasReturnTo());
+ $this->assertEquals($e->whichEncoding(), null);
+ $this->assertEquals($e->getReturnTo(), null);
+ }
+}
+
+class Tests_Auth_OpenID_Test_Decode extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->id_url = "http://decoder.am.unittest/";
+ $this->rt_url = "http://rp.unittest/foobot/?qux=zam";
+ $this->tr_url = "http://rp.unittest/";
+ $this->assoc_handle = "{assoc}{handle}";
+
+ $this->claimed_id = 'http://de.legating.de.coder.unittest/';
+ $this->op_endpoint = 'http://endpoint.unittest/encode';
+
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->server = new Auth_OpenID_Server($this->store,
+ $this->op_endpoint);
+ $this->decoder = new Auth_OpenID_Decoder($this->server);
+ }
+
+ function test_none()
+ {
+ $args = array();
+ $r = $this->decoder->decode($args);
+ $this->assertEquals($r, null);
+ }
+
+ function test_irrelevant()
+ {
+ $args = array(
+ 'pony' => 'spotted',
+ 'sreg.mutant_power' => 'decaffinator');
+
+ $r = $this->decoder->decode($args);
+
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_bad()
+ {
+ $args = array(
+ 'openid.mode' => 'twos-compliment',
+ 'openid.pants' => 'zippered');
+
+ // Be sure that decoding the args returns an error.
+ $result = $this->decoder->decode($args);
+
+ $this->assertTrue(Auth_OpenID_isError($result));
+ }
+
+ function test_checkidImmediate()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_immediate',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => $this->tr_url,
+ # should be ignored
+ 'openid.some.extension' => 'junk');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_immediate");
+ $this->assertEquals($r->immediate, true);
+ $this->assertEquals($r->identity, $this->id_url);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ $this->assertEquals($r->assoc_handle, $this->assoc_handle);
+ }
+
+ function test_checkidSetup()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => $this->tr_url);
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_setup");
+ $this->assertEquals($r->immediate, false);
+ $this->assertEquals($r->identity, $this->id_url);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ }
+
+ function test_checkidSetupOpenID2()
+ {
+ $args = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.claimed_id' => $this->claimed_id,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.realm' => $this->tr_url
+ );
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_setup");
+ $this->assertEquals($r->immediate, False);
+ $this->assertEquals($r->identity, $this->id_url);
+ $this->assertEquals($r->claimed_id, $this->claimed_id);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ }
+
+ function test_checkidSetupNoClaimedIDOpenID2()
+ {
+ $args = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.realm' => $this->tr_url
+ );
+
+ $result = $this->decoder->decode($args);
+ $this->assertTrue(is_a($result, "Auth_OpenID_ServerError"));
+ }
+
+ function test_checkidSetupNoIdentityOpenID2()
+ {
+ $args = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.realm' => $this->tr_url);
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckIDRequest'));
+ $this->assertEquals($r->mode, "checkid_setup");
+ $this->assertEquals($r->immediate, false);
+ $this->assertEquals($r->identity, null);
+ $this->assertEquals($r->trust_root, $this->tr_url);
+ $this->assertEquals($r->return_to, $this->rt_url);
+ }
+
+ function test_checkidSetupNoReturnOpenID1()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.trust_root' => $this->tr_url);
+
+ $result = $this->decoder->decode($args);
+ if (!Auth_OpenID_isError($result)) {
+ $this->fail("Expected Auth_OpenID_ServerError");
+ }
+ }
+
+ function test_checkidSetupNoReturnOpenID2()
+ {
+ // Make sure an OpenID 2 request with no return_to can be
+ // decoded, and make sure a response to such a request raises
+ // NoReturnToError.
+ $args = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.claimed_id' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.realm' => $this->tr_url);
+
+ $req = $this->decoder->decode($args);
+
+ $this->assertTrue(is_a($req,
+ 'Auth_OpenID_CheckIDRequest'));
+
+ $this->assertTrue(is_a($req->answer(false), 'Auth_OpenID_NoReturnToError'));
+ $this->assertTrue(is_a($req->encodeToURL('bogus'), 'Auth_OpenID_NoReturnToError'));
+ $this->assertTrue(is_a($req->getCancelURL(), 'Auth_OpenID_NoReturnToError'));
+ }
+
+ function test_checkidSetupRealmRequiredOpenID2()
+ {
+ // Make sure that an OpenID 2 request which lacks return_to
+ // cannot be decoded if it lacks a realm. Spec: This value
+ // (openid.realm) MUST be sent if openid.return_to is omitted.
+
+ $args = array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle);
+
+ $this->assertTrue(is_a($this->decoder->decode($args),
+ 'Auth_OpenID_ServerError'));
+ }
+
+ function test_checkidSetupBadReturn()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => 'not a url');
+
+ $result = $this->decoder->decode($args);;
+ if (Auth_OpenID_isError($result)) {
+ $this->assertTrue($result->message);
+ } else {
+ $this->fail(sprintf("Expected ProtocolError, instead " .
+ "returned with %s", gettype($result)));
+ }
+ }
+
+ function test_checkidSetupUntrustedReturn()
+ {
+ $args = array(
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $this->id_url,
+ 'openid.assoc_handle' => $this->assoc_handle,
+ 'openid.return_to' => $this->rt_url,
+ 'openid.trust_root' => 'http://not-the-return-place.unittest/');
+
+ $result = $this->decoder->decode($args);
+ $this->assertTrue(is_a($result, 'Auth_OpenID_UntrustedReturnURL'));
+ }
+
+ function test_checkAuth()
+ {
+ $args = array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.assoc_handle' => '{dumb}{handle}',
+ 'openid.sig' => 'sigblob',
+ 'openid.signed' => 'foo,bar,mode',
+ 'openid.foo' => 'signedval1',
+ 'openid.bar' => 'signedval2',
+ 'openid.baz' => 'unsigned');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckAuthRequest'));
+ $this->assertEquals($r->mode, 'check_authentication');
+ $this->assertEquals($r->sig, 'sigblob');
+ }
+
+ function test_checkAuthMissingSignature()
+ {
+ $args = array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.assoc_handle' => '{dumb}{handle}',
+ 'openid.signed' => 'foo,bar,mode',
+ 'openid.foo' => 'signedval1',
+ 'openid.bar' => 'signedval2',
+ 'openid.baz' => 'unsigned');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_checkAuthAndInvalidate()
+ {
+ $args = array(
+ 'openid.mode' => 'check_authentication',
+ 'openid.assoc_handle' => '{dumb}{handle}',
+ 'openid.invalidate_handle' => '[[SMART_handle]]',
+ 'openid.sig' => 'sigblob',
+ 'openid.signed' => 'foo,bar,mode',
+ 'openid.foo' => 'signedval1',
+ 'openid.bar' => 'signedval2',
+ 'openid.baz' => 'unsigned');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_CheckAuthRequest'));
+ $this->assertEquals($r->invalidate_handle, '[[SMART_handle]]');
+ }
+
+ function test_associateDH()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ print "(Skipping test_associateDH)";
+ return;
+ }
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "Rzup9265tw==");
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_AssociateRequest'));
+ $this->assertEquals($r->mode, "associate");
+ $this->assertEquals($r->session->session_type, "DH-SHA1");
+ $this->assertEquals($r->assoc_type, "HMAC-SHA1");
+ $this->assertTrue($r->session->consumer_pubkey);
+ }
+
+ function test_associateDHMissingKey()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ print "(Skipping test_associateDHMissingKey)";
+ return;
+ }
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1');
+
+ // Using DH-SHA1 without supplying dh_consumer_public is an error.
+ $result = $this->decoder->decode($args);
+ if (!Auth_OpenID_isError($result)) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
+ }
+
+ /**
+ * XXX: Cannot produce a value to break base64_decode
+ function test_associateDHpubKeyNotB64()
+ {
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "donkeydonkeydonkey");
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+ */
+
+ function test_associateDHModGen()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ print "(Skipping test_associateDHModGen)";
+ return;
+ }
+
+ global $ALT_GEN;
+
+ // test dh with non-default but valid values for dh_modulus
+ // and dh_gen
+ $lib =& Auth_OpenID_getMathLib();
+
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
+ 'openid.dh_modulus' => $lib->longToBase64(altModulus()),
+ 'openid.dh_gen' => $lib->longToBase64($ALT_GEN));
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_AssociateRequest'));
+ $this->assertEquals($r->mode, "associate");
+ $this->assertEquals($r->session->session_type, "DH-SHA1");
+ $this->assertEquals($r->assoc_type, "HMAC-SHA1");
+ $this->assertTrue($lib->cmp($r->session->dh->mod, altModulus()) === 0);
+ $this->assertTrue($lib->cmp($r->session->dh->gen, $ALT_GEN) === 0);
+ $this->assertTrue($r->session->consumer_pubkey);
+ }
+
+ /**
+ * XXX: Can't test invalid base64 values for mod and gen because
+ * PHP's base64 decoder is much too forgiving or just plain
+ * broken.
+ function test_associateDHCorruptModGen()
+ {
+ // test dh with non-default but valid values for dh_modulus
+ // and dh_gen
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
+ 'openid.dh_modulus' => 'pizza',
+ 'openid.dh_gen' => 'gnocchi');
+
+ $r = $this->decoder->decode($args);
+ print_r($r);
+
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+ */
+
+ function test_associateDHMissingModGen()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ print "(Skipping test_associateDHMissingModGen)";
+ return;
+ }
+
+ // test dh with non-default but valid values for dh_modulus
+ // and dh_gen
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
+ 'openid.dh_modulus' => 'pizza');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_associateWeirdSession()
+ {
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'FLCL6',
+ 'openid.dh_consumer_public' => "YQ==\n");
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_associatePlain()
+ {
+ $args = array('openid.mode' => 'associate');
+
+ $r = $this->decoder->decode($args);
+ $this->assertTrue(is_a($r, 'Auth_OpenID_AssociateRequest'));
+ $this->assertEquals($r->mode, "associate");
+ $this->assertEquals($r->session->session_type, "no-encryption");
+ $this->assertEquals($r->assoc_type, "HMAC-SHA1");
+ }
+
+ function test_nomode()
+ {
+ $args = array(
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => "my public keeey");
+
+ $result = $this->decoder->decode($args);
+ if (!Auth_OpenID_isError($result)) {
+ $this->fail(sprintf("Expected Auth_OpenID_Error. Got %s",
+ gettype($result)));
+ }
+ }
+
+ function test_invalidns()
+ {
+ $args = array('openid.ns' => 'Tuesday',
+ 'openid.mode' => 'associate');
+
+ $result = $this->decoder->decode($args);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+
+ // Assert that the ProtocolError does have a Message attached
+ // to it, even though the request wasn't a well-formed Message.
+ $this->assertTrue($result->message);
+
+ // The error message contains the bad openid.ns.
+ $this->assertTrue(strpos($result->text, 'Tuesday') != -1);
+ }
+}
+
+class Tests_Auth_OpenID_Test_Encode extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->encoder = new Auth_OpenID_Encoder();
+ $this->encode = $this->encoder;
+ $this->op_endpoint = 'http://endpoint.unittest/encode';
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->server = new Auth_OpenID_Server($this->store,
+ $this->op_endpoint);
+ }
+
+ function encode($thing) {
+ return $this->encoder->encode($thing);
+ }
+
+ function test_id_res_OpenID2_GET()
+ {
+ /* Check that when an OpenID 2 response does not exceed the
+ OpenID 1 message size, a GET response (i.e., redirect) is
+ issued. */
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/999',
+ 'http://burr.unittest/',
+ false,
+ $this->server->op_endpoint);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'mode' => 'id_res',
+ 'identity' => $request->identity,
+ 'claimed_id' => $request->identity,
+ 'return_to' => $request->return_to));
+
+ $this->assertFalse($response->renderAsForm());
+ $this->assertTrue($response->whichEncoding() == Auth_OpenID_ENCODE_URL);
+ $webresponse = $this->encode($response);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+ }
+
+ function test_id_res_OpenID2_POST()
+ {
+ /* Check that when an OpenID 2 response exceeds the OpenID 1
+ message size, a POST response (i.e., an HTML form) is
+ returned. */
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/999',
+ 'http://burr.unittest/',
+ false,
+ $this->server->op_endpoint);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'ns' => Auth_OpenID_OPENID2_NS,
+ 'mode' => 'id_res',
+ 'identity' => $request->identity,
+ 'claimed_id' => $request->identity,
+ 'return_to' => str_repeat('x', Auth_OpenID_OPENID1_URL_LIMIT)));
+
+ $this->assertTrue($response->renderAsForm());
+ $this->assertTrue(strlen($response->encodeToURL()) > Auth_OpenID_OPENID1_URL_LIMIT);
+ $this->assertTrue($response->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM);
+ $webresponse = $this->encode($response);
+ $this->assertEquals($webresponse->body, $response->toFormMarkup());
+ }
+
+ function test_id_res_OpenID1_exceeds_limit()
+ {
+ /* Check that when an OpenID 1 response exceeds the OpenID 1
+ message size, a GET response is issued. Technically, this
+ shouldn't be permitted by the library, but this test is in
+ place to preserve the status quo for OpenID 1. */
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/999',
+ 'http://burr.unittest/',
+ false,
+ $this->server->op_endpoint);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'id_res',
+ 'identity' => $request->identity,
+ 'return_to' => str_repeat('x', Auth_OpenID_OPENID1_URL_LIMIT)));
+
+ $this->assertFalse($response->renderAsForm());
+ $this->assertTrue(strlen($response->encodeToURL()) > Auth_OpenID_OPENID1_URL_LIMIT);
+ $this->assertTrue($response->whichEncoding() == Auth_OpenID_ENCODE_URL);
+ $webresponse = $this->encode($response);
+ $this->assertEquals($webresponse->headers['location'], $response->encodeToURL());
+ }
+
+ function test_id_res()
+ {
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false,
+ $this->server);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(
+ array(
+ 'mode' => 'id_res',
+ 'identity' => $request->identity,
+ 'return_to' => $request->return_to));
+
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location',
+ $webresponse->headers));
+
+ $location = $webresponse->headers['location'];
+ $this->assertTrue(strpos($location, $request->return_to) === 0);
+ // "%s does not start with %s" % ($location,
+ // $request->return_to));
+
+ $parsed = parse_url($location);
+ $query = array();
+ $query = Auth_OpenID::parse_str($parsed['query']);
+
+ $expected = $response->fields->toPostArgs();
+ $this->assertEquals($query, $expected);
+ }
+
+ function test_cancel()
+ {
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false, null,
+ $this->server);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array('mode' => 'cancel'));
+
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+ }
+
+ function test_cancelToForm()
+ {
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/999',
+ 'http://burr.unittest/',
+ false, null,
+ $this->server);
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array('mode' => 'cancel'));
+
+ $form = $response->toFormMarkup();
+ $pos = strpos($form, 'http://burr.unittest/999');
+ $this->assertTrue($pos !== false, var_export($pos, true));
+ }
+
+ function test_assocReply()
+ {
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $message = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $message->setArg(Auth_OpenID_OPENID2_NS, 'session_type',
+ 'no-encryption');
+ $request = Auth_OpenID_AssociateRequest::fromMessage($message,
+ $this->server);
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(
+ array('assoc_handle' => "every-zig"));
+ $webresponse = $this->encoder->encode($response);
+ $body = "assoc_handle:every-zig\n";
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+ }
+
+ function test_checkauthReply()
+ {
+ $request = new Auth_OpenID_CheckAuthRequest('a_sock_monkey',
+ 'siggggg',
+ array());
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'is_valid' => 'true',
+ 'invalidate_handle' => 'xXxX:xXXx'));
+
+ $body = "invalidate_handle:xXxX:xXXx\nis_valid:true\n";
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+
+ function test_unencodableError()
+ {
+ $args = array('openid.identity' => 'http://limu.unittest/');
+
+ $e = new Auth_OpenID_ServerError(Auth_OpenID_Message::fromPostArgs($args),
+ "wet paint");
+
+ $result = $this->encoder->encode($e);
+ if (!Auth_OpenID_isError($result, 'Auth_OpenID_EncodingError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
+ }
+
+ function test_encodableError()
+ {
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.identity' => 'http://limu.unittest/');
+
+ $body="error:snoot\nmode:error\n";
+ $err = new Auth_OpenID_ServerError(Auth_OpenID_Message::fromPostArgs($args),
+ "snoot");
+
+ $webresponse = $this->encoder->encode($err);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_ERROR);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+}
+
+class Tests_Auth_OpenID_SigningEncode extends PHPUnit_TestCase {
+ function setUp()
+ {
+ // Use filestore here instead of memstore
+ $this->store = new Tests_Auth_OpenID_MemStore();
+
+ $this->op_endpoint = 'http://endpoint.unittest/encode';
+
+ $this->server = new Auth_OpenID_Server($this->store,
+ $this->op_endpoint);
+
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false,
+ null,
+ $this->server);
+
+ $this->response = new Auth_OpenID_ServerResponse($this->request);
+ $this->response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'mode' => 'id_res',
+ 'identity' => $this->request->identity,
+ 'return_to' => $this->request->return_to));
+
+ $this->signatory = new Auth_OpenID_Signatory($this->store);
+ $this->dumb_key = $this->signatory->dumb_key;
+ $this->normal_key = $this->signatory->normal_key;
+
+ $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
+ }
+
+ function test_idres()
+ {
+ $assoc_handle = '{bicycle}{shed}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+ $this->store->storeAssociation($this->normal_key, $assoc);
+ $this->request->assoc_handle = $assoc_handle;
+ $webresponse = $this->encoder->encode($this->response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location',
+ $webresponse->headers));
+
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = Auth_OpenID::getQuery($parsed['query']);
+
+ $this->assertTrue(array_key_exists('openid.sig', $query));
+ $this->assertTrue(array_key_exists('openid.assoc_handle', $query));
+ $this->assertTrue(array_key_exists('openid.signed', $query));
+ }
+
+ function test_idresDumb()
+ {
+ $webresponse = $this->encoder->encode($this->response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = Auth_OpenID::getQuery($parsed['query']);
+
+ $this->assertTrue(array_key_exists('openid.sig', $query));
+ $this->assertTrue(array_key_exists('openid.assoc_handle', $query));
+ $this->assertTrue(array_key_exists('openid.signed', $query));
+ }
+
+ function test_forgotStore()
+ {
+ $this->encoder->signatory = null;
+ $result = $this->encoder->encode($this->response);
+ if (!is_a($result, 'Auth_OpenID_ServerError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
+ }
+
+ function test_cancel()
+ {
+ $request = new Auth_OpenID_CheckIDRequest(
+ 'http://bombom.unittest/',
+ 'http://burr.unittest/',
+ 'http://burr.unittest/999',
+ false,
+ null,
+ $this->server);
+
+ $response = new Auth_OpenID_ServerResponse($request, 'cancel');
+ $webresponse = $this->encoder->encode($response);
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_REDIRECT);
+ $this->assertTrue(array_key_exists('location', $webresponse->headers));
+ $location = $webresponse->headers['location'];
+ $parsed = parse_url($location);
+ $query = Auth_OpenID::getQuery($parsed['query']);
+
+ $this->assertFalse(array_key_exists('openid.sig', $query));
+ }
+
+ function test_assocReply()
+ {
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $message = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $request = Auth_OpenID_AssociateRequest::fromMessage($message,
+ $this->server);
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(
+ array('assoc_handle' => "every-zig"));
+ $webresponse = $this->encoder->encode($response);
+ $body = "assoc_handle:every-zig\n";
+
+ $this->assertEquals($webresponse->code, AUTH_OPENID_HTTP_OK);
+ $this->assertEquals($webresponse->headers, array());
+ $this->assertEquals($webresponse->body, $body);
+ }
+ }
+
+ function test_alreadySigned()
+ {
+ $this->response->fields->setArg(Auth_OpenID_OPENID_NS, 'sig', 'priorSig==');
+ $result = $this->encoder->encode($this->response);
+ if (!is_a($result, 'Auth_OpenID_AlreadySigned')) {
+ $this->fail(sprintf("Expected Auth_OpenID_AlreadySigned " .
+ "instance, got %s", gettype($result)));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckID extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+
+ $this->op_endpoint = 'http://endpoint.unittest/encode';
+
+ $this->server = new Auth_OpenID_Server($this->store,
+ $this->op_endpoint);
+
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bambam.unittest/',
+ 'http://bar.unittest/999',
+ 'http://bar.unittest/',
+ false, null,
+ $this->server);
+
+ $this->request->message = new Auth_OpenID_Message(
+ Auth_OpenID_OPENID2_NS);
+ }
+
+ function test_fromMessageClaimedIDWithoutIdentityOpenID2()
+ {
+ $name = 'https://example.myopenid.com';
+
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to',
+ 'http://invalid:8000/rt');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'claimed_id', $name);
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage(
+ $msg, $this->server);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_fromMessageIdentityWithoutClaimedIDOpenID2()
+ {
+ $name = 'https://example.myopenid.com';
+
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to',
+ 'http://invalid:8000/rt');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'identity', $name);
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage(
+ $msg, $this->server);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_fromMessageWithEmptyTrustRoot()
+ {
+ $return_to = 'http://does.not.matter/';
+ $msg = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.assoc_handle' => '{blah}{blah}{OZivdQ==}',
+ 'openid.claimed_id' => 'http://delegated.invalid/',
+ 'openid.identity' => 'http://op-local.example.com/',
+ 'openid.mode' => 'checkid_setup',
+ 'openid.ns' => 'http://openid.net/signon/1.0',
+ 'openid.return_to' => $return_to,
+ 'openid.trust_root' => ''
+ ));
+ $result = Auth_OpenID_CheckIDRequest::fromMessage(
+ $msg, $this->server);
+ $this->assertEquals($return_to, $result->trust_root);
+ }
+
+ function test_trustRootInvalid()
+ {
+ $this->request->trust_root = "http://foo.unittest/17";
+ $this->request->return_to = "http://foo.unittest/39";
+ $this->assertFalse($this->request->trustRootValid());
+ }
+
+ function test_trustRootValid()
+ {
+ $this->request->trust_root = "http://foo.unittest/";
+ $this->request->return_to = "http://foo.unittest/39";
+ $this->assertTrue($this->request->trustRootValid());
+ }
+
+ function test_malformedTrustRoot()
+ {
+ $this->request->trust_root = "invalid://trust*root/";
+ $this->request->return_to = "http://foo.unittest/39";
+ $sentinel = 'Sentinel';
+ $this->request->message = $sentinel;
+
+ $result = $this->request->trustRootValid();
+ $this->assertTrue(Auth_OpenID_isError($result));
+ $this->assertEquals($result->message, $sentinel);
+ }
+
+ function _verify($trust_root, $return_to, $value)
+ {
+ $this->assertEquals($this->request->trust_root, $trust_root);
+ $this->assertEquals($this->request->return_to, $return_to);
+ return $value;
+ }
+
+ function _verifyTrue($trust_root, $return_to)
+ {
+ return $this->_verify($trust_root, $return_to, true);
+ }
+
+ function _verifyFalse($trust_root, $return_to)
+ {
+ return $this->_verify($trust_root, $return_to, false);
+ }
+
+ /*
+ * Make sure that verifyReturnTo is calling
+ * Auth_OpenID_verifyReturnTo
+ */
+ function test_returnToVerified_callsVerify()
+ {
+ // Ensure that True and False are passed through unchanged
+ $this->request->verifyReturnTo = array(&$this, '_verifyTrue');
+ $this->assertEquals(true, $this->request->returnToVerified());
+
+ $this->request->verifyReturnTo = array(&$this, '_verifyFalse');
+ $this->assertEquals(false, $this->request->returnToVerified());
+ }
+
+ function test_answerToInvalidRoot()
+ {
+ $this->request->trust_root = "http://foo.unittest/17";
+ $this->request->return_to = "http://foo.unittest/39";
+ $result = $this->request->answer(true);
+ if (!is_a($result, 'Auth_OpenID_UntrustedReturnURL')) {
+ $this->fail(sprintf("Expected Auth_OpenID_UntrustedReturnURL, " .
+ "got %s", gettype($result)));
+ }
+ $this->assertTrue($this->request->answer(false));
+ }
+
+ function _expectAnswer($answer, $identity=null, $claimed_id=null)
+ {
+ if (is_a($answer, 'Auth_OpenID_ServerError')) {
+ $this->fail("Got ServerError, expected valid response in ".$this->getName());
+ return;
+ }
+
+ $expected_list = array(
+ array('mode', 'id_res'),
+ array('return_to', $this->request->return_to),
+ array('op_endpoint', $this->op_endpoint));
+
+ if ($identity) {
+ $expected_list[] = array('identity', $identity);
+
+ if ($claimed_id) {
+ $expected_list[] = array('claimed_id', $claimed_id);
+ } else {
+ $expected_list[] = array('claimed_id', $identity);
+ }
+ }
+
+ foreach ($expected_list as $pair) {
+ list($k, $expected) = $pair;
+ $actual = $answer->fields->getArg(Auth_OpenID_OPENID_NS, $k);
+ $this->assertEquals($expected, $actual,
+ "Got wrong value for field '".$k."'");
+ }
+
+ $this->assertTrue($answer->fields->hasKey(Auth_OpenID_OPENID_NS, 'response_nonce'));
+ $this->assertTrue($answer->fields->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS);
+
+ # One for nonce, one for ns
+ $this->assertEquals(count($answer->fields->toPostArgs()),
+ count($expected_list) + 2);
+ }
+
+ function test_answerAllow()
+ {
+ $answer = $this->request->answer(true);
+
+ if (Auth_OpenID_isError($answer)) {
+ $this->fail($answer->toString());
+ return;
+ }
+ $this->assertEquals($answer->request, $this->request);
+ $this->_expectAnswer($answer, $this->request->identity);
+ }
+
+ function test_answerAllowDelegatedIdentity()
+ {
+ $this->request->claimed_id = 'http://delegating.unittest/';
+ $answer = $this->request->answer(true);
+ $this->_expectAnswer($answer, $this->request->identity,
+ $this->request->claimed_id);
+ }
+
+ function test_answerAllowWithoutIdentityReally()
+ {
+ $this->request->identity = null;
+ $answer = $this->request->answer(true);
+ $this->assertEquals($answer->request, $this->request);
+ $this->_expectAnswer($answer);
+ }
+
+ function test_answerAllowAnonymousFail()
+ {
+ $this->request->identity = null;
+ // XXX - Check on this, I think this behavior is legal in
+ // OpenID 2.0?
+ // $this->failUnlessRaises(
+ // ValueError, $this->request->answer, true, identity="=V");
+ $this->assertTrue(is_a($this->request->answer(true, null, "=V"),
+ 'Auth_OpenID_ServerError'));
+ }
+
+ function test_answerAllowWithIdentity()
+ {
+ $this->request->identity = Auth_OpenID_IDENTIFIER_SELECT;
+ $selected_id = 'http://anon.unittest/9861';
+ $answer = $this->request->answer(true, null, $selected_id);
+ $this->_expectAnswer($answer, $selected_id);
+ }
+
+ function test_fromMessageWithoutTrustRoot()
+ {
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);;
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to',
+ 'http://real_trust_root/foo');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'identity', 'george');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'claimed_id', 'george');
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage(
+ $msg, $this->server->op_endpoint);
+
+ $this->assertEquals($result->trust_root,
+ 'http://real_trust_root/foo');
+ }
+
+ function test_fromMessageWithoutTrustRootOrReturnTo()
+ {
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'identity', 'george');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'claimed_id', 'george');
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage(
+ $msg, $this->server);
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_answerAllowNoEndpointOpenID1()
+ {
+ $identity = 'http://bambam.unittest/';
+ $reqmessage = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'identity' => $identity,
+ 'trust_root' => 'http://bar.unittest/',
+ 'return_to' => 'http://bar.unittest/999',
+ ));
+ $this->server->op_endpoint = null;
+ $this->request = Auth_OpenID_CheckIDRequest::fromMessage($reqmessage, $this->server);
+ $answer = $this->request->answer(true);
+
+ $expected_list = array('mode' => 'id_res',
+ 'return_to' => $this->request->return_to,
+ 'identity' => $identity,
+ );
+
+ foreach ($expected_list as $k => $expected) {
+ $actual = $answer->fields->getArg(Auth_OpenID_OPENID_NS, $k);
+ $this->assertEquals($expected, $actual);
+ }
+
+ $this->assertTrue($answer->fields->hasKey(Auth_OpenID_OPENID_NS,
+ 'response_nonce'));
+ $this->assertTrue($answer->fields->getOpenIDNamespace(),
+ Auth_OpenID_OPENID1_NS);
+ $this->assertTrue(
+ $answer->fields->namespaces->isImplicit(Auth_OpenID_OPENID1_NS));
+
+ // One for nonce (OpenID v1 namespace is implicit)
+ $this->assertEquals(count($answer->fields->toPostArgs()),
+ count($expected_list) + 1,
+ var_export($answer->fields->toPostArgs(), true));
+ }
+
+ function test_answerAllowWithDelegatedIdentityOpenID2()
+ {
+ // Answer an IDENTIFIER_SELECT case with a delegated
+ // identifier. claimed_id delegates to selected_id here.
+ $this->request->identity = Auth_OpenID_IDENTIFIER_SELECT;
+ $selected_id = 'http://anon.unittest/9861';
+ $claimed_id = 'http://monkeyhat.unittest/';
+ $answer = $this->request->answer(true, null, $selected_id,
+ $claimed_id);
+ $this->_expectAnswer($answer, $selected_id, $claimed_id);
+ }
+
+ function test_answerAllowWithDelegatedIdentityOpenID1()
+ {
+ // claimed_id parameter doesn't exist in OpenID 1.
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $this->request->message = $msg;
+ // claimed_id delegates to selected_id here.
+ $this->request->identity = Auth_OpenID_IDENTIFIER_SELECT;
+ $selected_id = 'http://anon.unittest/9861';
+ $claimed_id = 'http://monkeyhat.unittest/';
+
+ $result = $this->request->answer(true,
+ null,
+ $selected_id,
+ $claimed_id);
+
+ $this->assertTrue(is_a($result, "Auth_OpenID_ServerError"),
+ var_export($result, true));
+ }
+
+ function test_answerAllowWithAnotherIdentity()
+ {
+ // XXX - Check on this, I think this behavior is legal is
+ // OpenID 2.0?
+ // $this->failUnlessRaises(ValueError, $this->request->answer, true,
+ // identity="http://pebbles.unittest/");
+ $result = $this->request->answer(true, null, "http://pebbles.unittest/");
+ $this->assertTrue(is_a($result, "Auth_OpenID_ServerError"));
+ }
+
+ function test_answerAllowNoIdentityOpenID1()
+ {
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $this->request->message = $msg;
+ $this->request->identity = null;
+ // $this->failUnlessRaises(ValueError, $this->request->answer, true,
+ // identity=null);
+ $result = $this->request->answer(true);
+ $this->assertTrue(is_a($result, "Auth_OpenID_ServerError"));
+ }
+
+ function test_answerAllowForgotEndpoint()
+ {
+ $this->request->server->op_endpoint = null;
+ $result = $this->request->answer(true);
+ $this->assertTrue(is_a($result, "Auth_OpenID_ServerError"));
+ }
+
+ function test_checkIDWithNoIdentityOpenID1()
+ {
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'trust_root', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 'bogus');
+
+ // $this->failUnlessRaises(server->ProtocolError,
+ // server->CheckIDRequest->fromMessage,
+ // msg, $this->server);
+ $result = Auth_OpenID_CheckIDRequest::fromMessage($msg, $this->server);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_trustRootOpenID1()
+ {
+ // Ignore openid.realm in OpenID 1
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'trust_root', 'http://real_trust_root/');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'realm', 'http://fake_trust_root/');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to', 'http://real_trust_root/foo');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'identity', 'george');
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage($msg, $this->server);
+
+ $this->assertTrue($result->trust_root == 'http://real_trust_root/');
+ }
+
+ function test_trustRootOpenID2()
+ {
+ // Ignore openid.trust_root in OpenID 2
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'mode', 'checkid_setup');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'realm', 'http://real_trust_root/');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'trust_root', 'http://fake_trust_root/');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'return_to', 'http://real_trust_root/foo');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 'bogus');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'identity', 'george');
+ $msg->setArg(Auth_OpenID_OPENID_NS, 'claimed_id', 'george');
+
+ $result = Auth_OpenID_CheckIDRequest::fromMessage($msg, $this->server);
+
+ $this->assertTrue($result->trust_root == 'http://real_trust_root/');
+ }
+
+ function test_encodeToURL()
+ {
+ $server_url = 'http://openid-server.unittest/';
+ $result = $this->request->encodeToURL($server_url);
+
+ $this->assertFalse(is_a($result, 'Auth_OpenID_ServerError'));
+
+ // How to check? How about a round-trip test.
+ list($base, $result_args) = explode("?", $result, 2);
+ $args = Auth_OpenID::getQuery($result_args);
+ $message = Auth_OpenID_Message::fromPostArgs($args);
+
+ $rebuilt_request = Auth_OpenID_CheckIDRequest::fromMessage($message,
+ $this->server);
+ // argh, lousy hack
+ $this->assertTrue($rebuilt_request->equals($this->request));
+ }
+
+ function test_answerAllowNoTrustRoot()
+ {
+ $this->request->trust_root = null;
+ $answer = $this->request->answer(true);
+ $this->assertEquals($answer->request, $this->request);
+ $this->_expectAnswer($answer, $this->request->identity);
+ }
+
+ function test_answerImmediateDenyOpenID1()
+ {
+ $msg = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $this->request->message = $msg;
+ $this->request->namespace = $msg->getOpenIDNamespace();
+ $this->request->mode = 'checkid_immediate';
+ $this->request->claimed_id = 'http://claimed-id.test/';
+ $this->request->immediate = true;
+ $server_url = "http://setup-url.unittest/";
+ $answer = $this->request->answer(false, $server_url);
+
+ $this->assertEquals($answer->request, $this->request);
+ $this->assertEquals(count($answer->fields->toPostArgs()), 2);
+ $this->assertEquals($answer->fields->getOpenIDNamespace(),
+ Auth_OpenID_OPENID1_NS);
+ $this->assertTrue(
+ $answer->fields->namespaces->isImplicit(Auth_OpenID_OPENID1_NS));
+ $this->assertEquals($answer->fields->getArg(Auth_OpenID_OPENID_NS, 'mode'),
+ 'id_res');
+
+ $usu = $answer->fields->getArg(Auth_OpenID_OPENID_NS,'user_setup_url');
+ $this->assertTrue(strpos($usu, $server_url) == 0);
+ $expected_substr = 'openid.claimed_id=http%3A%2F%2Fclaimed-id.test%2F';
+ $this->assertTrue(strpos($usu, $expected_substr), $usu);
+ }
+
+ function test_answerImmediateDenyOpenID2()
+ {
+ $this->request->mode = 'checkid_immediate';
+ $this->request->immediate = true;
+ $server_url = "http://setup-url.unittest/";
+ $answer = $this->request->answer(false, $server_url);
+
+ $this->assertEquals($answer->request, $this->request);
+ $this->assertEquals(count($answer->fields->toPostArgs()), 3);
+ $this->assertEquals($answer->fields->getOpenIDNamespace(),
+ Auth_OpenID_OPENID2_NS);
+ $this->assertEquals($answer->fields->getArg(Auth_OpenID_OPENID_NS, 'mode'),
+ 'setup_needed');
+ }
+
+ function test_answerSetupDeny()
+ {
+ $answer = $this->request->answer(false);
+ $this->assertEquals($answer->fields->getArgs(Auth_OpenID_OPENID_NS),
+ array('mode' => 'cancel'));
+ }
+
+ function test_getCancelURL()
+ {
+ $url = $this->request->getCancelURL();
+
+ $parsed = parse_url($url);
+ $query = Auth_OpenID::getQuery($parsed['query']);
+
+ $this->assertEquals(array('openid.mode' => 'cancel',
+ 'openid.ns' => Auth_OpenID_OPENID2_NS),
+ $query);
+ }
+
+ function test_getCancelURLimmed()
+ {
+ $this->request->mode = 'checkid_immediate';
+ $this->request->immediate = true;
+ $result = $this->request->getCancelURL();
+ if (!is_a($result, 'Auth_OpenID_ServerError')) {
+ $this->fail(sprintf("Expected Auth_OpenID_ServerError, got %s",
+ gettype($result)));
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckIDExtension extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->op_endpoint = 'http://endpoint.unittest/ext';
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->server = new Auth_OpenID_Server($this->store, $this->op_endpoint);
+ $this->request = new Auth_OpenID_CheckIDRequest(
+ 'http://bambam.unittest/',
+ 'http://bar.unittest/',
+ 'http://bar.unittest/999',
+ false,
+ null,
+ $this->server);
+
+ $this->response = new Auth_OpenID_ServerResponse($this->request);
+ $this->response->fields->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
+ $this->response->fields->setArg(Auth_OpenID_OPENID_NS, 'blue', 'star');
+ }
+
+ function test_addField()
+ {
+ $namespace = 'something:';
+ $this->response->fields->setArg($namespace, 'bright', 'potato');
+ $this->assertEquals($this->response->fields->getArgs(Auth_OpenID_OPENID_NS),
+ array('blue' => 'star',
+ 'mode' => 'id_res'));
+
+ $this->assertEquals($this->response->fields->getArgs($namespace),
+ array('bright' => 'potato'));
+ }
+
+ function test_addFields()
+ {
+ $namespace = 'mi5:';
+ $args = array('tangy' => 'suspenders',
+ 'bravo' => 'inclusion');
+
+ $this->response->fields->updateArgs($namespace, $args);
+ $this->assertEquals($this->response->fields->getArgs(Auth_OpenID_OPENID_NS),
+ array('blue' => 'star',
+ 'mode' => 'id_res'));
+ $this->assertEquals($this->response->fields->getArgs($namespace), $args);
+ }
+}
+
+class _MockSignatory {
+ var $isValid = true;
+
+ function _MockSignatory($assoc)
+ {
+ $this->assocs = array($assoc);
+ }
+
+ function verify($assoc_handle, $message)
+ {
+ if (!$message->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
+ return false;
+ }
+
+ if (in_array(array(true, $assoc_handle), $this->assocs)) {
+ return $this->isValid;
+ } else {
+ return false;
+ }
+ }
+
+ function getAssociation($assoc_handle, $dumb)
+ {
+ if (in_array(array($dumb, $assoc_handle), $this->assocs)) {
+ // This isn't a valid implementation for many uses of this
+ // function, mind you.
+ return true;
+ } else {
+ return null;
+ }
+ }
+
+ function invalidate($assoc_handle, $dumb)
+ {
+ if (in_array(array($dumb, $assoc_handle), $this->assocs)) {
+ $i = 0;
+ foreach ($this->assocs as $pair) {
+ if ($pair == array($dumb, $assoc_handle)) {
+ unset($this->assocs[$i]);
+ break;
+ }
+ $i++;
+ }
+ }
+ }
+}
+
+class Tests_Auth_OpenID_CheckAuth extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->assoc_handle = 'mooooooooo';
+ $this->message = Auth_OpenID_Message::fromPostArgs(
+ array('openid.sig' => 'signarture',
+ 'one' => 'alpha',
+ 'two' => 'beta'));
+
+ $this->request = new Auth_OpenID_CheckAuthRequest(
+ $this->assoc_handle, $this->message);
+
+ $this->signatory = new _MockSignatory(array(true, $this->assoc_handle));
+ }
+
+ function test_valid()
+ {
+ $this->request->namespace = Auth_OpenID_OPENID1_NS;
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields->getArgs(Auth_OpenID_OPENID1_NS),
+ array('is_valid' => 'true'));
+ $this->assertEquals($r->request, $this->request);
+ }
+
+ function test_invalid()
+ {
+ $this->request->namespace = Auth_OpenID_OPENID1_NS;
+ $this->signatory->isValid = false;
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields->getArgs(Auth_OpenID_OPENID1_NS),
+ array('is_valid' => 'false'));
+ }
+
+ function test_replay()
+ {
+ $this->request->namespace = Auth_OpenID_OPENID1_NS;
+ $r = $this->request->answer($this->signatory);
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields->getArgs(Auth_OpenID_OPENID1_NS),
+ array('is_valid' => 'false'));
+ }
+
+ function test_invalidatehandle()
+ {
+ $this->request->namespace = Auth_OpenID_OPENID1_NS;
+ $this->request->invalidate_handle = "bogusHandle";
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields->getArgs(Auth_OpenID_OPENID1_NS),
+ array('is_valid' => 'true',
+ 'invalidate_handle' => "bogusHandle"));
+ $this->assertEquals($r->request, $this->request);
+ }
+
+ function test_invalidatehandleNo()
+ {
+ $this->request->namespace = Auth_OpenID_OPENID1_NS;
+ $assoc_handle = 'goodhandle';
+ $this->signatory->assocs[] = array(false, 'goodhandle');
+ $this->request->invalidate_handle = $assoc_handle;
+ $r = $this->request->answer($this->signatory);
+ $this->assertEquals($r->fields->getArgs(Auth_OpenID_OPENID1_NS),
+ array('is_valid' => 'true'));
+ }
+}
+
+class Tests_Auth_OpenID_Associate extends PHPUnit_TestCase {
+ // TODO: test DH with non-default values for modulus and gen.
+ // (important to do because we actually had it broken for a
+ // while.)
+
+ function setUp()
+ {
+ $message = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $this->request = Auth_OpenID_AssociateRequest::fromMessage($message);
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->signatory = new Auth_OpenID_Signatory($this->store);
+ }
+
+ function test_dhSHA1()
+ {
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $this->assoc = $this->signatory->createAssociation(false,
+ 'HMAC-SHA1');
+
+ $dh = new Auth_OpenID_DiffieHellman();
+ $ml =& Auth_OpenID_getMathLib();
+
+ $cpub = $dh->public;
+ $session = new Auth_OpenID_DiffieHellmanSHA1ServerSession(
+ new Auth_OpenID_DiffieHellman(),
+ $cpub);
+
+ $this->request = new Auth_OpenID_AssociateRequest($session,
+ 'HMAC-SHA1');
+ $response = $this->request->answer($this->assoc);
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_type"),
+ "HMAC-SHA1");
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->assertFalse(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "mac_key"));
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "session_type"),
+ "DH-SHA1");
+
+ $this->assertTrue(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "enc_mac_key"));
+
+ $this->assertTrue(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "dh_server_public"));
+
+ $enc_key = base64_decode(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "enc_mac_key"));
+
+ $spub = $ml->base64ToLong(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "dh_server_public"));
+
+ $secret = $dh->xorSecret($spub, $enc_key, $session->hash_func);
+
+ $this->assertEquals($secret, $this->assoc->secret);
+ }
+ }
+
+ function test_dhSHA256()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ !Auth_OpenID_SHA256_SUPPORTED) {
+ print "(Skipping test_dhSHA256)";
+ return;
+ }
+
+ $this->assoc = $this->signatory->createAssociation(false,
+ 'HMAC-SHA256');
+ $consumer_dh = new Auth_OpenID_DiffieHellman();
+ $cpub = $consumer_dh->public;
+ $server_dh = new Auth_OpenID_DiffieHellman();
+ $session = new Auth_OpenID_DiffieHellmanSHA256ServerSession($server_dh, $cpub);
+
+ $this->request = new Auth_OpenID_AssociateRequest($session, 'HMAC-SHA256');
+ $response = $this->request->answer($this->assoc);
+
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS, "mac_key"));
+ $this->assertTrue($response->fields->getArg(Auth_OpenID_OPENID_NS, "enc_mac_key"));
+ $this->assertTrue($response->fields->getArg(Auth_OpenID_OPENID_NS, "dh_server_public"));
+
+ $fields = array(
+ 'assoc_type' => 'HMAC-SHA256',
+ 'assoc_handle' => $this->assoc->handle,
+ 'session_type' => 'DH-SHA256',
+ );
+
+ foreach ($fields as $k => $v) {
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, $k), $v);
+ }
+
+ $enc_key = base64_decode(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "enc_mac_key"));
+
+ $lib =& Auth_OpenID_getMathLib();
+ $spub = $lib->base64ToLong($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "dh_server_public"));
+ $secret = $consumer_dh->xorSecret($spub, $enc_key, 'Auth_OpenID_SHA256');
+
+ $s = base64_encode($secret);
+ $assoc_s = base64_encode($this->assoc->secret);
+
+ $this->assertEquals($s, $assoc_s);
+ }
+
+ function test_protoError256()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ !Auth_OpenID_HMACSHA256_SUPPORTED) {
+ print "(Skipping test_protoError256)";
+ return;
+ }
+
+ $s256_session = new Auth_OpenID_DiffieHellmanSHA256ConsumerSession();
+
+ $invalid_s256 = array('openid.assoc_type' => 'HMAC-SHA1',
+ 'openid.session_type' => 'DH-SHA256');
+
+ $invalid_s256 = array_merge($invalid_s256, $s256_session->getRequest());
+
+ $invalid_s256_2 = array('openid.assoc_type' => 'MONKEY-PIRATE',
+ 'openid.session_type' => 'DH-SHA256');
+
+ $invalid_s256_2 = array_merge($invalid_s256_2, $s256_session->getRequest());
+
+ $bad_request_argss = array(
+ $invalid_s256,
+ $invalid_s256_2);
+
+ foreach ($bad_request_argss as $request_args) {
+ $message = Auth_OpenID_Message::fromPostArgs($request_args);
+ $result = Auth_OpenID_Associaterequest::fromMessage($message);
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+ }
+
+ function test_plaintext()
+ {
+ $this->assoc = $this->signatory->createAssociation(false,
+ 'HMAC-SHA1');
+ $response = $this->request->answer($this->assoc);
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_type"),
+ "HMAC-SHA1");
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "expires_in"),
+ sprintf("%d", $this->signatory->SECRET_LIFETIME));
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "mac_key"),
+ base64_encode($this->assoc->secret));
+
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "session_type"));
+
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "enc_mac_key"));
+
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "dh_server_public"));
+ }
+
+ function test_plaintextV2()
+ {
+ // The main difference between this and the v1 test is that
+ // the session_typ is always returned in v2.
+ $args = array('openid.mode' => 'associate',
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.assoc_type' => 'HMAC-SHA1',
+ 'openid.session_type' => 'no-encryption');
+
+ $this->request = Auth_OpenID_AssociateRequest::fromMessage(
+ Auth_OpenID_Message::fromPostArgs($args));
+ $this->assertFalse($this->request->message->isOpenID1());
+
+ $this->assoc = $this->signatory->createAssociation(false,
+ 'HMAC-SHA1');
+ $response = $this->request->answer($this->assoc);
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_type"),
+ "HMAC-SHA1");
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "expires_in"),
+ sprintf("%d", $this->signatory->SECRET_LIFETIME));
+
+ $this->assertEquals(
+ $response->fields->getArg(Auth_OpenID_OPENID_NS, "mac_key"),
+ base64_encode($this->assoc->secret));
+
+ $session_type = $response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "session_type");
+ $this->assertEquals('no-encryption', $session_type);
+
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "enc_mac_key"));
+ $this->assertFalse($response->fields->getArg(Auth_OpenID_OPENID_NS,
+ "dh_server_public"));
+ }
+
+ function test_protoError()
+ {
+ $s1_session = new Auth_OpenID_DiffieHellmanSHA1ConsumerSession();
+
+ $invalid_s1 = array('openid.assoc_type' => 'HMAC-SHA256',
+ 'openid.session_type' => 'DH-SHA1');
+ $invalid_s1 = array_merge($invalid_s1, $s1_session->getRequest());
+
+ $invalid_s1_2 = array('openid.assoc_type' => 'ROBOT-NINJA',
+ 'openid.session_type' => 'DH-SHA1');
+ $invalid_s1_2 = array_merge($invalid_s1_2, $s1_session->getRequest());
+
+ $bad_request_argss = array(array('openid.assoc_type' => 'Wha?'),
+ $invalid_s1,
+ $invalid_s1_2);
+
+ foreach ($bad_request_argss as $request_args) {
+ $message = Auth_OpenID_Message::fromPostArgs($request_args);
+ $result = Auth_OpenID_AssociateRequest::fromMessage($message);
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+ }
+
+ function test_protoErrorFields()
+ {
+ $contact = 'user@example.invalid';
+ $reference = 'Trac ticket number MAX_INT';
+ $error = 'poltergeist';
+
+ $openid1_args = array(
+ 'openid.identitiy' => 'invalid',
+ 'openid.mode' => 'checkid_setup');
+
+ $openid2_args = $openid1_args;
+ $openid2_args = array_merge($openid2_args,
+ array('openid.ns' => Auth_OpenID_OPENID2_NS));
+
+ // Check presence of optional fields in both protocol versions
+
+ $openid1_msg = Auth_OpenID_Message::fromPostArgs($openid1_args);
+ $p = new Auth_OpenID_ServerError($openid1_msg, $error,
+ $reference, $contact);
+ $reply = $p->toMessage();
+
+ $this->assertEquals($reply->getArg(Auth_OpenID_OPENID_NS, 'reference'),
+ $reference);
+ $this->assertEquals($reply->getArg(Auth_OpenID_OPENID_NS, 'contact'),
+ $contact);
+
+ $openid2_msg = Auth_OpenID_Message::fromPostArgs($openid2_args);
+ $p = new Auth_OpenID_ServerError($openid2_msg, $error,
+ $reference, $contact);
+ $reply = $p->toMessage();
+
+ $this->assertEquals($reply->getArg(Auth_OpenID_OPENID_NS, 'reference'),
+ $reference);
+ $this->assertEquals($reply->getArg(Auth_OpenID_OPENID_NS, 'contact'),
+ $contact);
+ }
+
+ function failUnlessExpiresInMatches($msg, $expected_expires_in)
+ {
+ $expires_in_str = $msg->getArg(Auth_OpenID_OPENID_NS, 'expires_in');
+ if ($expires_in_str === null) {
+ $this->fail("Expected expires_in value.");
+ return;
+ }
+
+ $expires_in = intval($expires_in_str);
+
+ // Slop is necessary because the tests can sometimes get run
+ // right on a second boundary
+ $slop = 1; // second
+ $difference = $expected_expires_in - $expires_in;
+
+ $error_message = sprintf('"expires_in" value not within %s of expected: '.
+ 'expected=%s, actual=%s',
+ $slop, $expected_expires_in, $expires_in);
+ $this->assertTrue((0 <= $difference &&
+ $difference <= $slop), $error_message);
+ }
+
+ function test_plaintext256()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ !Auth_OpenID_SHA256_SUPPORTED) {
+ print "(Skipping test_plaintext256)";
+ return;
+ }
+
+ $this->assoc = $this->signatory->createAssociation(false,
+ 'HMAC-SHA256');
+ $response = $this->request->answer($this->assoc);
+ $f = $response->fields;
+
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, "assoc_type"),
+ "HMAC-SHA1");
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, "assoc_handle"),
+ $this->assoc->handle);
+
+ $this->failUnlessExpiresInMatches(
+ $f,
+ $this->signatory->SECRET_LIFETIME);
+
+ $this->assertEquals(
+ $f->getArg(Auth_OpenID_OPENID_NS, "mac_key"),
+ base64_encode($this->assoc->secret));
+ $this->assertFalse($f->hasKey(Auth_OpenID_OPENID_NS, "session_type"));
+ $this->assertFalse($f->hasKey(Auth_OpenID_OPENID_NS, "enc_mac_key"));
+ $this->assertFalse($f->hasKey(Auth_OpenID_OPENID_NS, "dh_server_public"));
+ }
+
+ function test_unsupportedPrefer()
+ {
+ $allowed_assoc = 'COLD-PET-RAT';
+ $allowed_sess = 'FROG-BONES';
+ $message = 'This is a unit test';
+
+ // Set an OpenID 2 message so answerUnsupported doesn't raise
+ // ProtocolError.
+ $this->request->message = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+
+ $response = $this->request->answerUnsupported(
+ $message,
+ $allowed_assoc,
+ $allowed_sess);
+ $f = $response->fields;
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'error_code'),
+ 'unsupported-type');
+
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'assoc_type'),
+ $allowed_assoc);
+
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'error'),
+ $message);
+
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'session_type'),
+ $allowed_sess);
+ }
+
+ function test_unsupported()
+ {
+ $message = 'This is a unit test';
+
+ $this->request->message = new Auth_OpenID_Message(Auth_OpenID_OPENID2_NS);
+
+ $response = $this->request->answerUnsupported($message);
+
+ $f = $response->fields;
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'error_code'),
+ 'unsupported-type');
+
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'assoc_type'), null);
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'error'), $message);
+ $this->assertEquals($f->getArg(Auth_OpenID_OPENID_NS, 'session_type'), null);
+ }
+}
+
+class Counter {
+ function Counter()
+ {
+ $this->count = 0;
+ }
+
+ function inc()
+ {
+ $this->count += 1;
+ }
+}
+
+class Tests_Auth_OpenID_ServerTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $this->server = new Auth_OpenID_Server($this->store);
+ }
+
+ function test_associate()
+ {
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $message = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
+ $request = Auth_OpenID_AssociateRequest::fromMessage($message);
+ $response = $this->server->openid_associate($request);
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS,
+ 'assoc_handle'));
+ }
+ }
+
+ function test_associate2()
+ {
+ // Associate when the server has no allowed association types
+ //
+ // Gives back an error with error_code and no fallback session
+ // or assoc types.
+ $this->server->negotiator->setAllowedTypes(array());
+
+ $msg = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.session_type' => 'no-encryption'));
+
+ $request = Auth_OpenID_AssociateRequest::fromMessage($msg);
+
+ $response = $this->server->openid_associate($request);
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, "error"));
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, "error_code"));
+ $this->assertFalse($response->fields->hasKey(Auth_OpenID_OPENID_NS, "assoc_handle"));
+ $this->assertFalse($response->fields->hasKey(Auth_OpenID_OPENID_NS, "assoc_type"));
+ $this->assertFalse($response->fields->hasKey(Auth_OpenID_OPENID_NS, "session_type"));
+ }
+
+ function test_associate3()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ !Auth_OpenID_HMACSHA256_SUPPORTED) {
+ print "(Skipping test_associate3)";
+ return;
+ }
+
+ // Request an assoc type that is not supported when there are
+ // supported types.
+ //
+ // Should give back an error message with a fallback type.
+ $this->server->negotiator->setAllowedTypes(array(array('HMAC-SHA256', 'DH-SHA256')));
+
+ $msg = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.ns' => Auth_OpenID_OPENID2_NS,
+ 'openid.session_type' => 'no-encryption'));
+
+ $request = Auth_OpenID_AssociateRequest::fromMessage($msg);
+ $response = $this->server->openid_associate($request);
+
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, "error"));
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, "error_code"));
+ $this->assertFalse($response->fields->hasKey(Auth_OpenID_OPENID_NS, "assoc_handle"));
+ $this->assertEquals($response->fields->getArg(Auth_OpenID_OPENID_NS, "assoc_type"),
+ 'HMAC-SHA256');
+ $this->assertEquals($response->fields->getArg(Auth_OpenID_OPENID_NS, "session_type"),
+ 'DH-SHA256');
+ }
+
+ function test_associate4()
+ {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ !Auth_OpenID_HMACSHA256_SUPPORTED) {
+ print "(Skipping test_associate4)";
+ return;
+ }
+
+ $this->assertTrue($this->server->negotiator->setAllowedTypes(
+ array(array('HMAC-SHA256', 'DH-SHA256'))));
+
+ $query = array(
+ 'openid.dh_consumer_public' =>
+ 'ALZgnx8N5Lgd7pCj8K86T/DDMFjJXSss1SKoLmxE72kJTzOtG6I2PaYrHX'.
+ 'xku4jMQWSsGfLJxwCZ6280uYjUST/9NWmuAfcrBfmDHIBc3H8xh6RBnlXJ'.
+ '1WxJY3jHd5k1/ZReyRZOxZTKdF/dnIqwF8ZXUwI6peV0TyS/K1fOfF/s',
+ 'openid.assoc_type' => 'HMAC-SHA256',
+ 'openid.session_type' => 'DH-SHA256');
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $request = Auth_OpenID_AssociateRequest::fromMessage($message);
+ $response = $this->server->openid_associate($request);
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, "assoc_handle"));
+ }
+
+ function test_missingSessionTypeOpenID2()
+ {
+ // Make sure session_type is required in OpenID 2
+ $msg = Auth_OpenID_Message::fromPostArgs(array('openid.ns' => Auth_OpenID_OPENID2_NS));
+
+ $result = Auth_OpenID_AssociateRequest::fromMessage($msg);
+
+ $this->assertTrue(is_a($result, 'Auth_OpenID_ServerError'));
+ }
+
+ function test_checkAuth()
+ {
+ $request = new Auth_OpenID_CheckAuthRequest('arrrrrf',
+ '0x3999', array());
+
+ $response = $this->server->openid_check_authentication($request);
+ $this->assertTrue($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'is_valid'));
+ }
+}
+
+class Tests_Auth_OpenID_Signatory extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->store =& new Tests_Auth_OpenID_MemStore();
+ $this->signatory =& new Auth_OpenID_Signatory($this->store);
+ $this->dumb_key = $this->signatory->dumb_key;
+ $this->normal_key = $this->signatory->normal_key;
+ }
+
+ function test_sign()
+ {
+ $request = new Auth_OpenID_ServerRequest();
+ $request->namespace = Auth_OpenID_OPENID1_NS;
+
+ $assoc_handle = '{assoc}{lookatme}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+ $this->store->storeAssociation($this->normal_key, $assoc);
+ $request->assoc_handle = $assoc_handle;
+ $request->namespace = Auth_OpenID_OPENID1_NS;
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned'));
+
+ $sresponse = $this->signatory->sign($response);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle'),
+ $assoc_handle);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS, 'signed'),
+ 'assoc_handle,azu,bar,foo,signed');
+
+ $this->assertTrue($sresponse->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig'));
+ }
+
+ function test_signDumb()
+ {
+ $request = new Auth_OpenID_ServerRequest();
+ $request->assoc_handle = null;
+ $request->namespace = Auth_OpenID_OPENID1_NS;
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned'));
+
+ $sresponse = $this->signatory->sign($response);
+
+ $assoc_handle = $sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ $this->assertTrue($assoc_handle);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+
+ $this->assertTrue($assoc);
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS, 'signed'),
+ 'assoc_handle,azu,bar,foo,signed');
+ $this->assertTrue($sresponse->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig'));
+ }
+
+ function test_signExpired()
+ {
+ $request = new Auth_OpenID_ServerRequest();
+ $assoc_handle = '{assoc}{lookatme}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(-10, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+ $this->store->storeAssociation($this->normal_key, $assoc);
+ $this->assertTrue($this->store->getAssociation($this->normal_key,
+ $assoc_handle));
+
+ $request->assoc_handle = $assoc_handle;
+ $request->namespace = Auth_OpenID_OPENID1_NS;
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned'));
+
+ $sresponse = $this->signatory->sign($response);
+
+ $new_assoc_handle = $sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+ $this->assertTrue($new_assoc_handle);
+ $this->assertFalse($new_assoc_handle == $assoc_handle);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle'),
+ $assoc_handle);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'signed'),
+ 'assoc_handle,azu,bar,foo,invalidate_handle,signed');
+ $this->assertTrue($sresponse->fields->hasKey(Auth_OpenID_OPENID_NS,
+ 'sig'));
+
+ // make sure the expired association is gone
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $assoc_handle));
+
+ // make sure the new key is a dumb mode association
+ $this->assertTrue($this->store->getAssociation($this->dumb_key,
+ $new_assoc_handle));
+
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $new_assoc_handle));
+ }
+
+ function test_signInvalidHandle()
+ {
+ $request = new Auth_OpenID_ServerRequest();
+ $assoc_handle = '{bogus-assoc}{notvalid}';
+
+ $request->assoc_handle = $assoc_handle;
+ $request->namespace = Auth_OpenID_OPENID1_NS;
+
+ $response = new Auth_OpenID_ServerResponse($request);
+ $response->fields = Auth_OpenID_Message::fromOpenIDArgs(array(
+ 'foo' => 'amsigned',
+ 'bar' => 'notsigned',
+ 'azu' => 'alsosigned'));
+
+ $response->signed = array('foo', 'azu');
+ $sresponse = $this->signatory->sign($response);
+
+ $new_assoc_handle = $sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ $this->assertTrue($new_assoc_handle);
+ $this->assertFalse($new_assoc_handle == $assoc_handle);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle'),
+ $assoc_handle);
+
+ $this->assertEquals($sresponse->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'signed'),
+ 'assoc_handle,azu,bar,foo,invalidate_handle,signed');
+ $this->assertTrue($sresponse->fields->hasKey(Auth_OpenID_OPENID_NS,
+ 'sig'));
+
+ // make sure the new key is a dumb mode association
+ $this->assertTrue($this->store->getAssociation($this->dumb_key,
+ $new_assoc_handle));
+
+ $this->assertFalse($this->store->getAssociation($this->normal_key,
+ $new_assoc_handle));
+ }
+
+ function test_verify()
+ {
+ $assoc_handle = '{vroom}{zoom}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+
+ $signed = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.foo' => 'bar',
+ 'openid.apple' => 'orange',
+ 'openid.assoc_handle' => $assoc_handle,
+ 'openid.signed' => 'apple,assoc_handle,foo,signed',
+ 'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco='));
+
+ $verified = $this->signatory->verify($assoc_handle, $signed);
+ $this->assertTrue($verified);
+ }
+
+ function test_verifyBadSig()
+ {
+ $assoc_handle = '{vroom}{zoom}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+
+ $signed = Auth_OpenID_Message::fromPostArgs(array(
+ 'openid.foo' => 'bar',
+ 'openid.apple' => 'orange',
+ 'openid.assoc_handle' => $assoc_handle,
+ 'openid.signed' => 'apple,assoc_handle,foo,signed',
+ 'openid.sig' => str_rot13('uXoT1qm62/BB09Xbj98TQ8mlBco=')));
+
+ $verified = $this->signatory->verify($assoc_handle, $signed);
+
+ $this->assertFalse($verified);
+ }
+
+ function test_verifyBadHandle()
+ {
+ $assoc_handle = '{vroom}{zoom}';
+ $signed = Auth_OpenID_Message::fromPostArgs(
+ array('foo' => 'bar',
+ 'apple' => 'orange',
+ 'openid.sig' => "Ylu0KcIR7PvNegB/K41KpnRgJl0="));
+
+ $verified = $this->signatory->verify($assoc_handle, $signed);
+ $this->assertFalse($verified);
+ }
+
+ function test_verifyAssocMismatch()
+ {
+ // Attempt to validate sign-all message with a signed-list
+ // assoc.
+ $assoc_handle = '{vroom}{zoom}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ 60, $assoc_handle, 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+
+ $signed = Auth_OpenID_Message::fromPostArgs(array(
+ 'foo' => 'bar',
+ 'apple' => 'orange',
+ 'openid.sig' => "d71xlHtqnq98DonoSgoK/nD+QRM="
+ ));
+
+ $verified = $this->signatory->verify($assoc_handle, $signed);
+ $this->assertFalse($verified);
+ }
+
+ function test_getAssoc()
+ {
+ $assoc_handle = $this->makeAssoc(true);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $this->assertEquals($assoc->handle, $assoc_handle);
+ }
+
+ function test_getAssocExpired()
+ {
+ $assoc_handle = $this->makeAssoc(true, -10);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertFalse($assoc);
+ }
+
+ function test_getAssocInvalid()
+ {
+ $ah = 'no-such-handle';
+ $this->assertEquals(
+ $this->signatory->getAssociation($ah, false), null);
+ }
+
+ function test_getAssocDumbVsNormal()
+ {
+ $assoc_handle = $this->makeAssoc(true);
+ $this->assertEquals(
+ $this->signatory->getAssociation($assoc_handle, false), null);
+ }
+
+ function test_createAssociation()
+ {
+ $assoc = $this->signatory->createAssociation(false);
+ $this->assertTrue($this->signatory->getAssociation($assoc->handle,
+ false));
+ }
+
+ function makeAssoc($dumb, $lifetime = 60)
+ {
+ $assoc_handle = '{bling}';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $lifetime, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation((($dumb) ? $this->dumb_key :
+ $this->normal_key), $assoc);
+ return $assoc_handle;
+ }
+
+ function test_invalidate()
+ {
+ $assoc_handle = '-squash-';
+ $assoc = Auth_OpenID_Association::fromExpiresIn(60, $assoc_handle,
+ 'sekrit', 'HMAC-SHA1');
+
+ $this->store->storeAssociation($this->dumb_key, $assoc);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertTrue($assoc);
+ $this->signatory->invalidate($assoc_handle, true);
+ $assoc = $this->signatory->getAssociation($assoc_handle, true);
+ $this->assertFalse($assoc);
+ }
+}
+
+class Tests_Auth_OpenID_Server extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_OpenID_Server";
+ }
+
+ function Tests_Auth_OpenID_Server()
+ {
+ $this->addTestSuite('Tests_Auth_OpenID_Signatory');
+ $this->addTestSuite('Tests_Auth_OpenID_ServerTest');
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $this->addTestSuite('Tests_Auth_OpenID_Associate');
+ }
+ $this->addTestSuite('Tests_Auth_OpenID_CheckAuth');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckIDExtension');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckAuth');
+ $this->addTestSuite('Tests_Auth_OpenID_SigningEncode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_Encode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_Decode');
+ $this->addTestSuite('Tests_Auth_OpenID_Test_ServerError');
+ $this->addTestSuite('Tests_Auth_OpenID_CheckID');
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/StoreTest.php b/Tests/Auth/OpenID/StoreTest.php
new file mode 100644
index 0000000..b656ba0
--- /dev/null
+++ b/Tests/Auth/OpenID/StoreTest.php
@@ -0,0 +1,705 @@
+<?php
+
+/**
+ * A test script for the OpenIDStore classes.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require classes and functions to run the Store tests.
+ */
+require_once 'Auth/OpenID/Association.php';
+require_once 'Auth/OpenID/CryptUtil.php';
+require_once 'Auth/OpenID/Nonce.php';
+require_once 'Auth/OpenID.php';
+require_once 'PHPUnit.php';
+
+function _Auth_OpenID_mkdtemp()
+{
+ if (strpos(PHP_OS, 'WIN') === 0) {
+ $dir = $_ENV['TMP'];
+ if (!isset($dir)) {
+ $dir = 'C:\Windows\Temp';
+ }
+ } else {
+ $dir = @$_ENV['TMPDIR'];
+ if (!isset($dir)) {
+ $dir = '/tmp';
+ }
+ }
+
+ return Auth_OpenID_FileStore::_mkdtemp($dir);
+}
+
+/**
+ * This is the host where the SQL stores' databases should be created
+ * and destroyed.
+ */
+global $_Auth_OpenID_db_test_host;
+$_Auth_OpenID_db_test_host = 'dbtest';
+
+/**
+ * Generate a sufficently unique database name so many hosts can run
+ * SQL store tests on the server at the same time and not step on each
+ * other.
+ */
+function _Auth_OpenID_getTmpDbName()
+{
+ $hostname = php_uname('n');
+ $hostname = str_replace('.', '_', $hostname);
+ $hostname = str_replace('-', '_', $hostname);
+ $hostname = strtolower($hostname);
+
+ return sprintf("%s_%d_%s_openid_test",
+ $hostname,
+ getmypid(),
+ strval(rand(1, time())));
+}
+
+/**
+ * Superclass that has methods for testing OpenID stores. Subclass this to
+ * test your own store implementation.
+ *
+ * @package OpenID
+ */
+class Tests_Auth_OpenID_Store extends PHPUnit_TestCase {
+
+ /**
+ * Prepares for the SQL store tests.
+ */
+ function setUp()
+ {
+ $this->letters = Auth_OpenID_letters;
+ $this->digits = Auth_OpenID_digits;
+ $this->punct = Auth_OpenID_punct;
+ $this->allowed_nonce = $this->letters . $this->digits;
+ $this->allowed_handle = $this->letters . $this->digits . $this->punct;
+ }
+
+ /**
+ * Generates an association with the specified parameters.
+ */
+ function genAssoc($now, $issued = 0, $lifetime = 600)
+ {
+ $sec = Auth_OpenID_CryptUtil::randomString(20);
+ $hdl = Auth_OpenID_CryptUtil::randomString(128, $this->allowed_handle);
+ return new Auth_OpenID_Association($hdl, $sec, $now + $issued,
+ $lifetime, 'HMAC-SHA1');
+ }
+
+ /**
+ * @access private
+ */
+ function _checkRetrieve(&$store, $url, $handle, $expected, $name = null)
+ {
+ $retrieved_assoc = $store->getAssociation($url, $handle);
+ if ($expected === null) {
+ $this->assertTrue($retrieved_assoc === null);
+ } else {
+ $this->assertTrue($expected->equal($retrieved_assoc), $name);
+ }
+ }
+
+ function _checkRemove(&$store, $url, $handle, $expected, $name = null)
+ {
+ $present = $store->removeAssociation($url, $handle);
+ $this->assertTrue((!$expected && !$present) ||
+ ($expected && $present),
+ $name);
+ }
+
+ /**
+ * Make sure a given store has a minimum of API compliance. Call
+ * this function with an empty store.
+ *
+ * Raises AssertionError if the store does not work as expected.
+ *
+ * OpenIDStore -> NoneType
+ */
+ function _testStore(&$store)
+ {
+ // Association functions
+ $now = time();
+
+ $server_url = 'http://www.myopenid.com/openid';
+
+ $assoc = $this->genAssoc($now);
+
+ $this->_checkRetrieve($store, $server_url, null, null,
+ 'Make sure that a missing association returns no result');
+
+ $store->storeAssociation($server_url, $assoc);
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'Check that after storage, getting returns the same result');
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'more than once');
+
+ $store->storeAssociation($server_url, $assoc);
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'Storing more than once has no ill effect');
+
+ // Removing an association that does not exist returns not present
+ $this->_checkRemove($store, $server_url, $assoc->handle . 'x', false,
+ "Remove nonexistent association (1)");
+
+ // Removing an association that does not exist returns not present
+ $this->_checkRemove($store, $server_url . 'x', $assoc->handle, false,
+ "Remove nonexistent association (2)");
+
+ // Removing an association that is present returns present
+ $this->_checkRemove($store, $server_url, $assoc->handle, true,
+ "Remove existent association");
+
+ // but not present on subsequent calls
+ $this->_checkRemove($store, $server_url, $assoc->handle, false,
+ "Remove nonexistent association after removal");
+
+ // Put assoc back in the store
+ $store->storeAssociation($server_url, $assoc);
+
+ // More recent and expires after assoc
+ $assoc2 = $this->genAssoc($now, $issued = 1);
+ $store->storeAssociation($server_url, $assoc2);
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc2,
+ 'After storing an association with a different handle, but the
+same $server_url, the handle with the later expiration is
+returned.');
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc,
+ 'We can still retrieve the older association');
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, $assoc2,
+ 'Plus we can retrieve the association with the later expiration
+explicitly');
+
+ $assoc3 = $this->genAssoc($now, $issued = 2, $lifetime = 100);
+ $store->storeAssociation($server_url, $assoc3);
+
+ // More recent issued time, so assoc3 is expected.
+ $this->_checkRetrieve($store, $server_url, null, $assoc3, "(1)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
+ $assoc, "(2)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
+ $assoc2, "(3)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
+ $assoc3, "(4)");
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle, true, "(5)");
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc3, "(6)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
+ $assoc, "(7)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
+ null, "(8)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
+ $assoc3, "(9)");
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle,
+ false, "(10)");
+
+ $this->_checkRemove($store, $server_url, $assoc3->handle,
+ true, "(11)");
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc, "(12)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
+ $assoc, "(13)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
+ null, "(14)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
+ null, "(15)");
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle,
+ false, "(16)");
+
+ $this->_checkRemove($store, $server_url, $assoc->handle,
+ true, "(17)");
+
+ $this->_checkRemove($store, $server_url, $assoc3->handle,
+ false, "(18)");
+
+ $this->_checkRetrieve($store, $server_url, null, null, "(19)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
+ null, "(20)");
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
+ null, "(21)");
+
+ $this->_checkRetrieve($store, $server_url,$assoc3->handle,
+ null, "(22)");
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle,
+ false, "(23)");
+
+ $this->_checkRemove($store, $server_url, $assoc->handle,
+ false, "(24)");
+
+ $this->_checkRemove($store, $server_url, $assoc3->handle,
+ false, "(25)");
+
+ // Put associations into store, for two different server URLs
+ $assoc1 = $this->genAssoc($now);
+ $assoc2 = $this->genAssoc($now + 2);
+ $server_url1 = "http://one.example.com/one";
+ $server_url2 = "http://two.localhost.localdomain/two";
+
+ $store->storeAssociation($server_url1, $assoc1);
+ $store->storeAssociation($server_url2, $assoc2);
+
+ // Ask for each one, make sure we get it
+ $this->_checkRetrieve($store, $server_url1, $assoc1->handle,
+ $assoc1, "(26)");
+
+ $this->_checkRetrieve($store, $server_url2, $assoc2->handle,
+ $assoc2, "(27)");
+
+ $store->storeAssociation($server_url1, $assoc1);
+ $store->storeAssociation($server_url2, $assoc2);
+
+ // Ask for each one, make sure we get it
+ $this->_checkRetrieve($store, $server_url1, null,
+ $assoc1, "(28)");
+
+ $this->_checkRetrieve($store, $server_url2, null,
+ $assoc2, "(29)");
+
+ // test expired associations
+ // assoc 1: server 1, valid
+ // assoc 2: server 1, expired
+ // assoc 3: server 2, expired
+ // assoc 4: server 3, valid
+ $assocValid1 = $this->genAssoc($now, -3600, 7200);
+ $assocValid2 = $this->genAssoc($now, -5);
+ $assocExpired1 = $this->genAssoc($now, -7200, 3600);
+ $assocExpired2 = $this->genAssoc($now, -7200, 3600);
+
+ if (!$store->supportsCleanup()) {
+ return;
+ }
+
+ $store->cleanupAssociations();
+ $store->storeAssociation($server_url . '1', $assocValid1);
+ $store->storeAssociation($server_url . '1', $assocExpired1);
+ $store->storeAssociation($server_url . '2', $assocExpired2);
+ $store->storeAssociation($server_url . '3', $assocValid2);
+
+ $cleaned = $store->cleanupAssociations();
+ $this->assertEquals(2, $cleaned);
+ }
+
+ function _checkUseNonce(&$store, $nonce, $expected, $server_url, $msg=null)
+ {
+ list($stamp, $salt) = Auth_OpenID_splitNonce($nonce);
+ $actual = $store->useNonce($server_url, $stamp, $salt);
+ $this->assertEquals(intval($expected), intval($actual), "_checkUseNonce failed: $server_url, $msg");
+ }
+
+ function _testNonce(&$store)
+ {
+ // Nonce functions
+
+ $server_url = 'http://www.myopenid.com/openid';
+
+ foreach (array($server_url, '') as $url) {
+ // Random nonce (not in store)
+ $nonce1 = Auth_OpenID_mkNonce();
+
+ // A nonce is not by default
+ $this->_checkUseNonce($store, $nonce1, true, $url, "blergx");
+
+ // Once stored, cannot be stored again
+ $this->_checkUseNonce($store, $nonce1, false, $url, 2);
+
+ // And using again has the same effect
+ $this->_checkUseNonce($store, $nonce1, false, $url, 3);
+
+ // Nonces from when the universe was an hour old should
+ // not pass these days.
+ $old_nonce = Auth_OpenID_mkNonce(3600);
+ $this->_checkUseNonce($store, $old_nonce, false, $url,
+ "Old nonce ($old_nonce) passed.");
+
+ }
+ }
+
+ function _testNonceCleanup(&$store) {
+ if (!$store->supportsCleanup()) {
+ return;
+ }
+
+ $server_url = 'http://www.myopenid.com/openid';
+
+ $now = time();
+
+ $old_nonce1 = Auth_OpenID_mkNonce($now - 20000);
+ $old_nonce2 = Auth_OpenID_mkNonce($now - 10000);
+ $recent_nonce = Auth_OpenID_mkNonce($now - 600);
+
+ global $Auth_OpenID_SKEW;
+ $orig_skew = $Auth_OpenID_SKEW;
+
+ $Auth_OpenID_SKEW = 0;
+ $store->cleanupNonces();
+ // Set SKEW high so stores will keep our nonces.
+ $Auth_OpenID_SKEW = 100000;
+
+ $params = Auth_OpenID_splitNonce($old_nonce1);
+ array_unshift($params, $server_url);
+ $this->assertTrue(call_user_func_array(array(&$store, 'useNonce'), $params));
+
+ $params = Auth_OpenID_splitNonce($old_nonce2);
+ array_unshift($params, $server_url);
+ $this->assertTrue(call_user_func_array(array(&$store, 'useNonce'), $params));
+
+ $params = Auth_OpenID_splitNonce($recent_nonce);
+ array_unshift($params, $server_url);
+ $this->assertTrue(call_user_func_array(array(&$store, 'useNonce'), $params));
+
+ $Auth_OpenID_SKEW = 3600;
+ $cleaned = $store->cleanupNonces();
+ $this->assertEquals(2, $cleaned); // , "Cleaned %r nonces." % (cleaned,)
+
+ $Auth_OpenID_SKEW = 100000;
+ // A roundabout method of checking that the old nonces were
+ // cleaned is to see if we're allowed to add them again.
+
+ $params = Auth_OpenID_splitNonce($old_nonce1);
+ array_unshift($params, $server_url);
+ $this->assertTrue(call_user_func_array(array(&$store, 'useNonce'), $params));
+ $params = Auth_OpenID_splitNonce($old_nonce2);
+ array_unshift($params, $server_url);
+ $this->assertTrue(call_user_func_array(array(&$store, 'useNonce'), $params));
+
+ // The recent nonce wasn't cleaned, so it should still fail.
+ $params = Auth_OpenID_splitNonce($recent_nonce);
+ array_unshift($params, $server_url);
+ $this->assertFalse(call_user_func_array(array(&$store, 'useNonce'), $params));
+
+ $Auth_OpenID_SKEW = $orig_skew;
+ }
+
+}
+/**
+ * Class that tests all of the stores included with the OpenID library
+ *
+ * @package OpenID
+ */
+class Tests_Auth_OpenID_Included_StoreTest extends Tests_Auth_OpenID_Store {
+ function test_memstore()
+ {
+ require_once 'Tests/Auth/OpenID/MemStore.php';
+ $store = new Tests_Auth_OpenID_MemStore();
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+ }
+
+ function test_filestore()
+ {
+ require_once 'Auth/OpenID/FileStore.php';
+
+ $temp_dir = _Auth_OpenID_mkdtemp();
+
+ if (!$temp_dir) {
+ trigger_error('Could not create temporary directory ' .
+ 'with Auth_OpenID_FileStore::_mkdtemp',
+ E_USER_WARNING);
+ return null;
+ }
+
+ $store = new Auth_OpenID_FileStore($temp_dir);
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+ $store->destroy();
+ }
+
+ function test_postgresqlstore()
+ {
+ // If the postgres extension isn't loaded or loadable, succeed
+ // because we can't run the test.
+ if (!(extension_loaded('pgsql') ||
+ @dl('pgsql.so') ||
+ @dl('php_pgsql.dll'))) {
+ print "(not testing PostGreSQL store)";
+ $this->pass();
+ return;
+ }
+
+ require_once 'Auth/OpenID/PostgreSQLStore.php';
+ require_once 'DB.php';
+
+ global $_Auth_OpenID_db_test_host;
+
+ $temp_db_name = _Auth_OpenID_getTmpDbName();
+
+ $connect_db_name = 'test_master';
+
+ $dsn = array(
+ 'phptype' => 'pgsql',
+ 'username' => 'openid_test',
+ 'password' => '',
+ 'hostspec' => $_Auth_OpenID_db_test_host,
+ 'database' => $connect_db_name
+ );
+
+ $allowed_failures = 5;
+ $result = null;
+ $sleep_time = 1.0;
+ $sql = sprintf("CREATE DATABASE %s", $temp_db_name);
+
+ for ($failures = 0; $failures < $allowed_failures; $failures++) {
+ $template_db =& DB::connect($dsn);
+
+ if (PEAR::isError($template_db)) {
+ $result =& $template_db;
+ } else {
+ // Try to create the test database.
+ $result = $template_db->query($sql);
+
+ $template_db->disconnect();
+ unset($template_db);
+
+ if (!PEAR::isError($result)) {
+ break;
+ }
+ }
+
+ $sleep_time *= ((mt_rand(1, 100) / 100.0) + 1.5);
+ print "Failed to create database $temp_db_name.\n".
+ "Waiting $sleep_time before trying again\n";
+
+ $int_sleep = floor($sleep_time);
+ $frac_sleep = $sleep_time - $int_sleep;
+ sleep($int_sleep);
+ usleep($frac_sleep * 1000000.0);
+ }
+
+ if ($failures == $allowed_failures) {
+ $this->pass("Temporary database creation failed after $failures ".
+ " tries ('$temp_db_name'): " . $result->getMessage());
+ return;
+ }
+
+ // Disconnect from template1 and reconnect to the temporary
+ // testing database.
+ $dsn['database'] = $temp_db_name;
+ $db =& DB::connect($dsn);
+
+ if (PEAR::isError($db)) {
+ $this->fail("Temporary database connection failed " .
+ " ('$temp_db_name'): " . $db->getMessage());
+ return;
+ }
+
+ $store =& new Auth_OpenID_PostgreSQLStore($db);
+
+ $this->assertFalse($store->tableExists($store->nonces_table_name));
+ $this->assertFalse($store->tableExists($store->associations_table_name));
+
+ $store->createTables();
+
+ $this->assertTrue($store->tableExists($store->nonces_table_name));
+ $this->assertTrue($store->tableExists($store->associations_table_name));
+
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+
+ $db->disconnect();
+ unset($db);
+
+ // Connect to template1 again so we can drop the temporary
+ // database.
+ $dsn['database'] = $connect_db_name;
+ $template_db =& DB::connect($dsn);
+
+ if (PEAR::isError($template_db)) {
+ $this->fail("Template database connection (to drop " .
+ "temporary database) failed: " .
+ $template_db->getMessage());
+ return;
+ }
+
+ $result = $template_db->query(sprintf("DROP DATABASE %s",
+ $temp_db_name));
+
+ if (PEAR::isError($result)) {
+ $this->fail("Dropping temporary database failed: " .
+ $result->getMessage());
+ return;
+ }
+
+ $template_db->disconnect();
+ unset($template_db);
+ }
+
+ function test_sqlitestore()
+ {
+ // If the sqlite extension isn't loaded or loadable, succeed
+ // because we can't run the test.
+ if (!(extension_loaded('sqlite') ||
+ @dl('sqlite.so') ||
+ @dl('php_sqlite.dll'))) {
+ print "(not testing SQLite store)";
+ $this->pass();
+ return;
+ }
+
+ require_once 'Auth/OpenID/SQLiteStore.php';
+ require_once 'DB.php';
+
+ $temp_dir = _Auth_OpenID_mkdtemp();
+
+ if (!$temp_dir) {
+ trigger_error('Could not create temporary directory ' .
+ 'with Auth_OpenID_FileStore::_mkdtemp',
+ E_USER_WARNING);
+ return null;
+ }
+
+ $dsn = 'sqlite:///' . urlencode($temp_dir) . '/php_openid_storetest.db';
+ $db =& DB::connect($dsn);
+
+ if (PEAR::isError($db)) {
+ $this->pass("SQLite database connection failed: " .
+ $db->getMessage());
+ } else {
+ $store =& new Auth_OpenID_SQLiteStore($db);
+ $this->assertTrue($store->createTables(), "Table creation failed");
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+ }
+
+ $db->disconnect();
+ unset($db);
+ unset($store);
+ unlink($temp_dir . '/php_openid_storetest.db');
+ rmdir($temp_dir);
+ }
+
+ function test_mysqlstore()
+ {
+ // If the mysql extension isn't loaded or loadable, succeed
+ // because we can't run the test.
+ if (!(extension_loaded('mysql') ||
+ @dl('mysql.' . PHP_SHLIB_SUFFIX))) {
+ print "(not testing MySQL store)";
+ $this->pass();
+ return;
+ }
+
+ require_once 'Auth/OpenID/MySQLStore.php';
+ require_once 'DB.php';
+
+ global $_Auth_OpenID_db_test_host;
+
+ $dsn = array(
+ 'phptype' => 'mysql',
+ 'username' => 'openid_test',
+ 'password' => '',
+ 'hostspec' => $_Auth_OpenID_db_test_host
+ );
+
+ $db =& DB::connect($dsn);
+
+ if (PEAR::isError($db)) {
+ print "MySQL database connection failed: " .
+ $db->getMessage();
+ $this->pass();
+ return;
+ }
+
+ $temp_db_name = _Auth_OpenID_getTmpDbName();
+
+ $result = $db->query("CREATE DATABASE $temp_db_name");
+
+ if (PEAR::isError($result)) {
+ $this->pass("Error creating MySQL temporary database: " .
+ $result->getMessage());
+ return;
+ }
+
+ $db->query("USE $temp_db_name");
+
+ $store =& new Auth_OpenID_MySQLStore($db);
+ $store->createTables();
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+
+ $db->query("DROP DATABASE $temp_db_name");
+ }
+}
+
+/**
+ * This is the host that the store test will use
+ */
+global $_Auth_OpenID_memcache_test_host;
+$_Auth_OpenID_memcache_test_host = 'localhost';
+
+class Tests_Auth_OpenID_MemcachedStore_Test extends Tests_Auth_OpenID_Store {
+ function test_memcache()
+ {
+ // If the memcache extension isn't loaded or loadable, succeed
+ // because we can't run the test.
+ if (!(extension_loaded('memcache') ||
+ @dl('memcache.so') ||
+ @dl('php_memcache.dll'))) {
+ print "(skipping memcache store tests)";
+ $this->pass();
+ return;
+ }
+ require_once 'Auth/OpenID/MemcachedStore.php';
+
+ global $_Auth_OpenID_memcache_test_host;
+
+ $memcached = new Memcache();
+ if (!$memcached->connect($_Auth_OpenID_memcache_test_host)) {
+ print "(skipping memcache store tests - couldn't connect)";
+ $this->pass();
+ } else {
+ $store = new Auth_OpenID_MemcachedStore($memcached);
+
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $this->_testNonceCleanup($store);
+
+ $memcached->close();
+ }
+ }
+}
+
+class Tests_Auth_OpenID_StoreTest extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_OpenID_StoreTest";
+ }
+
+ function Tests_Auth_OpenID_StoreTest()
+ {
+ $this->addTestSuite('Tests_Auth_OpenID_Included_StoreTest');
+ $this->addTestSuite('Tests_Auth_OpenID_MemcachedStore_Test');
+ }
+}
+?>
diff --git a/Tests/Auth/OpenID/TestUtil.php b/Tests/Auth/OpenID/TestUtil.php
new file mode 100644
index 0000000..54b110d
--- /dev/null
+++ b/Tests/Auth/OpenID/TestUtil.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * Utilites for test functions
+ */
+
+require_once 'PHPUnit.php';
+
+function Tests_Auth_OpenID_datafile($name, $reader)
+{
+ $path = dirname(realpath(__FILE__));
+ $sep = DIRECTORY_SEPARATOR;
+ $filename = $path . $sep . 'data' . $sep . $name;
+ $data = $reader($filename);
+ if ($data === false) {
+ $msg = "Failed to open data file: $name";
+ trigger_error($msg, E_USER_ERROR);
+ }
+ return $data;
+}
+
+function Tests_Auth_OpenID_readdata($name)
+{
+ return Tests_Auth_OpenID_datafile($name, 'file_get_contents');
+}
+
+function Tests_Auth_OpenID_readlines($name)
+{
+ return Tests_Auth_OpenID_datafile($name, 'file');
+}
+
+class OpenIDTestMixin extends PHPUnit_TestCase {
+ function failUnlessOpenIDValueEquals($msg, $key, $expected, $ns=null)
+ {
+ if ($ns === null) {
+ $ns = Auth_OpenID_OPENID_NS;
+ }
+
+ $actual = $msg->getArg($ns, $key);
+ $error_format = 'Wrong value for openid.%s: expected=%s, actual=%s';
+ $error_message = sprintf($error_format,
+ $key, $expected, $actual);
+
+ $this->assertEquals($expected, $actual, $error_message);
+ }
+
+ function failIfOpenIDKeyExists($msg, $key, $ns=null)
+ {
+ if ($ns === null) {
+ $ns = Auth_OpenID_OPENID_NS;
+ }
+
+ $actual = $msg->getArg($ns, $key);
+ $error_message = sprintf('openid.%s unexpectedly present: %s',
+ $key, $actual);
+
+ $this->assertFalse($msg->hasKey($ns, $key),
+ $error_message);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/TrustRoot.php b/Tests/Auth/OpenID/TrustRoot.php
new file mode 100644
index 0000000..9c7c40e
--- /dev/null
+++ b/Tests/Auth/OpenID/TrustRoot.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Tests for the TrustRoot module
+ */
+
+require_once "Auth/OpenID/TrustRoot.php";
+require_once "Tests/Auth/OpenID/TestUtil.php";
+require_once "PHPUnit.php";
+
+class Tests_Auth_OpenID_TRParseCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_TRParseCase($desc, $case, $expected)
+ {
+ $this->setName($desc);
+ $this->case = $case;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $is_sane = Auth_OpenID_TrustRoot::isSane($this->case);
+ $parsed = (bool)Auth_OpenID_TrustRoot::_parse($this->case);
+ switch ($this->expected) {
+ case 'sane':
+ $this->assertTrue($parsed, "Did not parse");
+ $this->assertTrue($is_sane, "Is not sane");
+ break;
+ case 'insane':
+ $this->assertTrue($parsed, "Did not parse");
+ $this->assertFalse($is_sane, "Is sane");
+ break;
+ default:
+ $this->assertFalse($parsed, "Did parse");
+ $this->assertFalse($is_sane, "Is sane");
+ }
+ }
+}
+
+class Tests_Auth_OpenID_TRMatchCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_TRMatchCase($desc, $tr, $rt, $matches)
+ {
+ $this->setName($desc);
+ $this->tr = $tr;
+ $this->rt = $rt;
+ $this->matches = $matches;
+ }
+
+ function runTest()
+ {
+ $matches = Auth_OpenID_TrustRoot::match($this->tr, $this->rt);
+ $this->assertEquals((bool)$this->matches, (bool)$matches);
+ }
+}
+
+function Tests_Auth_OpenID_parseHeadings($data, $c)
+{
+ $heading_pat = '/(^|\n)' . $c . '{40}\n([^\n]+)\n' . $c . '{40}\n()/';
+ $offset = 0;
+ $headings = array();
+ while (true) {
+ preg_match($heading_pat, substr($data, $offset), $matches,
+ PREG_OFFSET_CAPTURE);
+ if (!$matches) {
+ break;
+ }
+ $start = $matches[0][1];
+ $heading = $matches[2][0];
+ $end = $matches[3][1];
+ $headings[] = array('heading' => $heading,
+ 'start' => $offset + $start,
+ 'end' => $offset + $end,
+ );
+ $offset += $end;
+ }
+ return $headings;
+}
+
+function Tests_Auth_OpenID_getSections($data)
+{
+ $headings = Tests_Auth_OpenID_parseHeadings($data, '-');
+ $sections = array();
+ $n = count($headings);
+ for ($i = 0; $i < $n; ) {
+ $secdata = $headings[$i];
+ list($numtests, $desc) = explode(': ', $secdata['heading']);
+ $start = $secdata['end'];
+ $i += 1;
+ if ($i < $n) {
+ $blob = substr($data, $start, $headings[$i]['start'] - $start);
+ } else {
+ $blob = substr($data, $start);
+ }
+ $lines = explode("\n", trim($blob));
+ if (count($lines) != $numtests) {
+ trigger_error('Parse failure: ' . var_export($secdata, true),
+ E_USER_ERROR);
+ }
+ $sections[] = array('desc' => $desc, 'lines' => $lines,);
+ }
+ return $sections;
+}
+
+function Tests_Auth_OpenID_trParseTests($head, $tests)
+{
+ $tests = array('fail' => $tests[0],
+ 'insane' => $tests[1],
+ 'sane' => $tests[2]);
+ $testobjs = array();
+ foreach ($tests as $expected => $testdata) {
+ $lines = $testdata['lines'];
+ foreach ($lines as $line) {
+ $desc = sprintf("%s - %s: %s", $head,
+ $testdata['desc'], var_export($line, true));
+ $testobjs[] = new Tests_Auth_OpenID_TRParseCase(
+ $desc, $line, $expected);
+ }
+ }
+ return $testobjs;
+}
+
+function Tests_Auth_OpenID_trMatchTests($head, $tests)
+{
+ $tests = array(true => $tests[0], false => $tests[1]);
+ $testobjs = array();
+ foreach ($tests as $expected => $testdata) {
+ $lines = $testdata['lines'];
+ foreach ($lines as $line) {
+ $pat = '/^([^ ]+) +([^ ]+)$/';
+ preg_match($pat, $line, $matches);
+ list($_, $tr, $rt) = $matches;
+ $desc = sprintf("%s - %s: %s %s", $head, $testdata['desc'],
+ var_export($tr, true), var_export($rt, true));
+ $testobjs[] = new Tests_Auth_OpenID_TRMatchCase(
+ $desc, $tr, $rt, $expected);
+ }
+ }
+ return $testobjs;
+}
+
+function Tests_Auth_OpenID_trustRootTests()
+{
+ $data = Tests_Auth_OpenID_readdata('trustroot.txt');
+ list($parsehead, $matchhead) = Tests_Auth_OpenID_parseHeadings($data, '=');
+ $pe = $parsehead['end'];
+ $parsedata = substr($data, $pe, $matchhead['start'] - $pe);
+ $parsetests = Tests_Auth_OpenID_getSections($parsedata);
+ $parsecases = Tests_Auth_OpenID_trParseTests($parsehead['heading'],
+ $parsetests);
+
+ $matchdata = substr($data, $matchhead['end']);
+ $matchtests = Tests_Auth_OpenID_getSections($matchdata);
+ $matchcases = Tests_Auth_OpenID_trMatchTests($matchhead['heading'],
+ $matchtests);
+
+ return array_merge($parsecases, $matchcases);
+}
+
+class Tests_Auth_OpenID_TrustRoot extends PHPUnit_TestSuite {
+ function Tests_Auth_OpenID_TrustRoot($name)
+ {
+ $this->setName($name);
+
+ foreach (Tests_Auth_OpenID_trustRootTests() as $test) {
+ $this->_addTestByValue($test);
+ }
+ }
+
+ function _addTestByValue($test) {
+ $this->addTest($test);
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/URINorm.php b/Tests/Auth/OpenID/URINorm.php
new file mode 100644
index 0000000..a184034
--- /dev/null
+++ b/Tests/Auth/OpenID/URINorm.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Tests for the URI normalization routines used by the OpenID
+ * library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID/URINorm.php';
+require_once 'Tests/Auth/OpenID/TestUtil.php';
+
+class Tests_Auth_OpenID_URINorm_TestCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_URINorm_TestCase(
+ $name, $uri, $expected)
+ {
+
+ $this->setName($name);
+ $this->uri = $uri;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $actual = Auth_OpenID_urinorm($this->uri);
+ $this->assertEquals($this->expected, $actual);
+ }
+}
+
+class Tests_Auth_OpenID_URINorm extends PHPUnit_TestSuite {
+ function _readTestCases()
+ {
+ $lines = Tests_Auth_OpenID_readlines('urinorm.txt');
+ $cases = array();
+ $case = array();
+ for ($i = 0; $i < count($lines) && ($i + 3 <= count($lines)); $i += 4) {
+ $name = trim($lines[$i]);
+ $uri = trim($lines[$i + 1]);
+ $expected = trim($lines[$i + 2]);
+ if ($expected == 'fail') {
+ $expected = null;
+ }
+ $cases[] = array($name, $uri, $expected);
+ }
+
+ return $cases;
+ }
+
+ function Tests_Auth_OpenID_URINorm($name)
+ {
+ $this->setName($name);
+ $cases = $this->_readTestCases();
+ foreach ($cases as $case) {
+ list($name, $uri, $expected) = $case;
+ $this->addTest(new Tests_Auth_OpenID_URINorm_TestCase($name, $uri, $expected));
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Util.php b/Tests/Auth/OpenID/Util.php
new file mode 100644
index 0000000..a53da47
--- /dev/null
+++ b/Tests/Auth/OpenID/Util.php
@@ -0,0 +1,320 @@
+<?php
+
+/**
+ * Tests for utility functions used by the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/OpenID.php';
+
+class Tests_Auth_OpenID_Util extends PHPUnit_TestCase {
+ function test_base64()
+ {
+ // This is not good for international use, but PHP doesn't
+ // appear to provide access to the local alphabet.
+ $letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ $digits = "0123456789";
+ $extra = "+/=";
+ $allowed_s = $letters . $digits . $extra;
+ $allowed_d = array();
+
+ for ($i = 0; $i < strlen($allowed_s); $i++) {
+ $c = $allowed_s[$i];
+ $allowed_d[$c] = null;
+ }
+
+ function checkEncoded($obj, $str, $allowed_array)
+ {
+ for ($i = 0; $i < strlen($str); $i++) {
+ $obj->assertTrue(array_key_exists($str[$i],
+ $allowed_array));
+ }
+ }
+
+ $cases = array(
+ "",
+ "x",
+ "\x00",
+ "\x01",
+ str_repeat("\x00", 100),
+ implode("", array_map('chr', range(0, 255)))
+ );
+
+ foreach ($cases as $s) {
+ $b64 = base64_encode($s);
+ checkEncoded($this, $b64, $allowed_d);
+ $s_prime = base64_decode($b64);
+ $this->assertEquals($s_prime, $s);
+ }
+
+ function random_ordinal($unused)
+ {
+ return rand(0, 255);
+ }
+
+ // Randomized test
+ foreach (range(0, 49) as $i) {
+ $n = rand(0, 2048);
+ $s = implode("", array_map('chr',
+ array_map('random_ordinal',
+ range(0, $n))));
+ $b64 = base64_encode($s);
+ checkEncoded($this, $b64, $allowed_d);
+ $s_prime = base64_decode($b64);
+ $this->assertEquals($s_prime, $s);
+ }
+ }
+
+ function test_urldefrag()
+ {
+ $cases = array(
+ array('http://foo.com', 'http://foo.com'),
+ array('http://foo.com/', 'http://foo.com/'),
+ array('http://foo.com/path', 'http://foo.com/path'),
+ array('http://foo.com/path?query', 'http://foo.com/path?query'),
+ array('http://foo.com/path?query=v', 'http://foo.com/path?query=v'),
+ array('http://foo.com/?query=v', 'http://foo.com/?query=v'),
+ );
+
+ foreach ($cases as $pair) {
+ list($orig, $after) = $pair;
+ list($base, $frag) = Auth_OpenID::urldefrag($orig);
+ $this->assertEquals($after, $base);
+ $this->assertEquals($frag, '');
+
+ list($base, $frag) = Auth_OpenID::urldefrag($orig . "#fragment");
+ $this->assertEquals($after, $base);
+ $this->assertEquals('fragment', $frag);
+ }
+ }
+
+ function test_normalizeUrl()
+ {
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID::normalizeUrl("foo.com"));
+
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID::normalizeUrl("http://foo.com"));
+
+ $this->assertEquals("https://foo.com/",
+ Auth_OpenID::normalizeUrl("https://foo.com"));
+
+ $this->assertEquals("http://foo.com/bar",
+ Auth_OpenID::normalizeUrl("foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/bar",
+ Auth_OpenID::normalizeUrl("http://foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID::normalizeUrl("http://foo.com/"));
+
+ $this->assertEquals("https://foo.com/",
+ Auth_OpenID::normalizeUrl("https://foo.com/"));
+
+ $this->assertEquals("https://foo.com/bar" ,
+ Auth_OpenID::normalizeUrl("https://foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/bar" ,
+ Auth_OpenID::normalizeUrl("HTtp://foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/bar" ,
+ Auth_OpenID::normalizeUrl("HTtp://foo.com/bar#fraggle"));
+
+ $this->assertEquals("http://foo.com/bAr/" ,
+ Auth_OpenID::normalizeUrl("HTtp://fOo.com/bAr/.#fraggle"));
+
+ if (0) {
+ $this->assertEquals("http://foo.com/%E8%8D%89",
+ Auth_OpenID::normalizeUrl("foo.com/\u8349"));
+
+ $this->assertEquals("http://foo.com/%E8%8D%89",
+ Auth_OpenID::normalizeUrl("http://foo.com/\u8349"));
+ }
+
+ $non_ascii_domain_cases = array(
+ array("http://xn--vl1a.com/",
+ "\u8349.com"),
+
+ array("http://xn--vl1a.com/",
+ "http://\u8349.com"),
+
+ array("http://xn--vl1a.com/",
+ "\u8349.com/"),
+
+ array("http://xn--vl1a.com/",
+ "http://\u8349.com/"),
+
+ array("http://xn--vl1a.com/%E8%8D%89",
+ "\u8349.com/\u8349"),
+
+ array("http://xn--vl1a.com/%E8%8D%89",
+ "http://\u8349.com/\u8349"),
+ );
+
+ // XXX
+ /*
+ codecs.getencoder('idna')
+ except LookupError:
+ # If there is no idna codec, these cases with
+ # non-ascii-representable domain names should fail.
+ should_raise = True
+ else:
+ should_raise = False
+
+ for expected, case in non_ascii_domain_cases:
+try:
+actual = Auth_OpenID::normalizeUrl(case)
+ except UnicodeError:
+ assert should_raise
+ else:
+assert not should_raise and actual == expected, case
+ */
+
+ $this->assertNull(Auth_OpenID::normalizeUrl(null));
+ $this->assertNull(Auth_OpenID::normalizeUrl(''));
+ $this->assertNull(Auth_OpenID::normalizeUrl('http://'));
+ }
+
+ function test_appendArgs()
+ {
+
+ $simple = 'http://www.example.com/';
+
+ $cases = array(
+ array('empty list',
+ array($simple, array()),
+ $simple),
+
+ array('empty dict',
+ array($simple, array()),
+ $simple),
+
+ array('one list',
+ array($simple, array(array('a', 'b'))),
+ $simple . '?a=b'),
+
+ array('one dict',
+ array($simple, array('a' => 'b')),
+ $simple . '?a=b'),
+
+ array('two list (same)',
+ array($simple, array(array('a', 'b'),
+ array('a', 'c'))),
+ $simple . '?a=b&a=c'),
+
+ array('two list',
+ array($simple, array(array('a', 'b'),
+ array('b', 'c'))),
+ $simple . '?a=b&b=c'),
+
+ array('two list (order)',
+ array($simple, array(array('b', 'c'),
+ array('a', 'b'))),
+ $simple . '?b=c&a=b'),
+
+ array('two dict (order)',
+ array($simple, array('b' => 'c',
+ 'a' => 'b')),
+ $simple . '?a=b&b=c'),
+
+ array('escape',
+ array($simple, array(array('=', '='))),
+ $simple . '?%3D=%3D'),
+
+ array('escape (URL)',
+ array($simple, array(array('this_url',
+ $simple))),
+ $simple .
+ '?this_url=http%3A%2F%2Fwww.example.com%2F'),
+
+ array('use dots',
+ array($simple, array(array('openid.stuff',
+ 'bother'))),
+ $simple . '?openid.stuff=bother'),
+
+ array('args exist (empty)',
+ array($simple . '?stuff=bother', array()),
+ $simple . '?stuff=bother'),
+
+ array('args exist',
+ array($simple . '?stuff=bother',
+ array(array('ack', 'ack'))),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist',
+ array($simple . '?stuff=bother',
+ array(array('ack', 'ack'))),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist (dict)',
+ array($simple . '?stuff=bother',
+ array('ack' => 'ack')),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist (dict 2)',
+ array($simple . '?stuff=bother',
+ array('ack' => 'ack', 'zebra' => 'lion')),
+ $simple . '?stuff=bother&ack=ack&zebra=lion'),
+
+ array('three args (dict)',
+ array($simple, array('stuff' => 'bother',
+ 'ack' => 'ack',
+ 'zebra' => 'lion')),
+ $simple . '?ack=ack&stuff=bother&zebra=lion'),
+
+ array('three args (list)',
+ array($simple, array(
+ array('stuff', 'bother'),
+ array('ack', 'ack'),
+ array('zebra', 'lion'))),
+ $simple . '?stuff=bother&ack=ack&zebra=lion'),
+ );
+
+ // Tests.
+ foreach ($cases as $case) {
+ list($desc, $data, $expected) = $case;
+ list($url, $query) = $data;
+ $this->assertEquals($expected,
+ Auth_OpenID::appendArgs($url, $query));
+ }
+ }
+
+ function test_getQuery()
+ {
+ $queries = array(
+ '' => array(),
+ 'single' => array(),
+ 'no&pairs' => array(),
+ 'x%3Dy' => array(),
+ 'single&real=value' => array('real' => 'value'),
+ 'x=y&m=x%3Dn' => array('x' => 'y', 'm' => 'x=n'),
+ '&m=x%20y' => array('m' => 'x y'),
+ 'single&&m=x%20y&bogus' => array('m' => 'x y'),
+ // Even with invalid encoding. But don't do that.
+ 'too=many=equals&' => array('too' => 'many=equals')
+ );
+
+ foreach ($queries as $s => $data) {
+ $query = Auth_OpenID::getQuery($s);
+
+ foreach ($data as $key => $value) {
+ $this->assertTrue($query[$key] === $value);
+ }
+
+ foreach ($query as $key => $value) {
+ $this->assertTrue($data[$key] === $value);
+ }
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/VerifyDisco.php b/Tests/Auth/OpenID/VerifyDisco.php
new file mode 100644
index 0000000..f3b63a3
--- /dev/null
+++ b/Tests/Auth/OpenID/VerifyDisco.php
@@ -0,0 +1,424 @@
+<?php
+
+require_once "PHPUnit.php";
+require_once "Tests/Auth/OpenID/TestUtil.php";
+require_once "Tests/Auth/OpenID/MemStore.php";
+
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Consumer.php";
+
+class Tests_Auth_OpenID_VerifyDisco_1 extends Auth_OpenID_GenericConsumer {
+ function _discoverAndVerify($claimed_id, $to_match_endpoints)
+ {
+ $this->test_case->assertEquals($this->endpoint->claimed_id, $claimed_id);
+ return new Auth_OpenID_FailureResponse(null, $this->text);
+ }
+}
+
+class __VerifiedError extends Auth_OpenID_FailureResponse {
+}
+
+class VerifyDisco_Consumer_verifiedError extends Auth_OpenID_GenericConsumer {
+ function _discoverAndVerify($to_match)
+ {
+ return new __VerifiedError(null, 'verified error');
+ }
+}
+
+class _DiscoverAndVerify extends OpenIDTestMixin {
+ var $consumer_class = 'Auth_OpenID_GenericConsumer';
+
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $cl = $this->consumer_class;
+ $this->consumer = new $cl($this->store);
+ $this->return_to = "http://some.host/path";
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+
+ $this->server_id = "sirod";
+ $this->server_url = "serlie";
+ $this->consumer_id = "consu";
+
+ $this->endpoint->claimed_id = $this->consumer_id;
+ $this->endpoint->server_url = $this->server_url;
+ $this->endpoint->local_id = $this->server_id;
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+ }
+
+ function failUnlessProtocolError($thing)
+ {
+ $this->assertTrue(Auth_OpenID::isFailure($thing));
+ }
+}
+
+class _Tests_discoveryOverride {
+ function _Tests_discoveryOverride($endpoint)
+ {
+ $this->endpoint = $endpoint;
+ }
+
+ function discover($unused_url)
+ {
+ return array($this->endpoint->claimed_id, array($this->endpoint));
+ }
+}
+class Tests_openID1Fallback1_0 extends _DiscoverAndVerify {
+ function test_openID1Fallback1_0()
+ {
+ $claimed_id = 'http://claimed.id/';
+ $resp_msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID1_NS,
+ 'identity' => $claimed_id));
+ $resp_msg->setArg(Auth_OpenID_BARE_NS, 'openid1_claimed_id',
+ $claimed_id);
+ $expected_endpoint = new Auth_OpenID_ServiceEndpoint();
+ $expected_endpoint->type_uris = array(Auth_OpenID_TYPE_1_0);
+ $expected_endpoint->local_id = null;
+ $expected_endpoint->claimed_id = $claimed_id;
+
+ $discovery_override = new _Tests_discoveryOverride($expected_endpoint);
+ $this->consumer->discoverMethod = array($discovery_override, 'discover');
+
+ $actual_endpoint = $this->consumer->_verifyDiscoveryResults(
+ $resp_msg, null);
+
+ $this->assertTrue(is_a($actual_endpoint, "Auth_OpenID_ServiceEndpoint"));
+
+ $this->assertEquals($expected_endpoint->local_id,
+ $actual_endpoint->local_id);
+ $this->assertEquals($expected_endpoint->server_url,
+ $actual_endpoint->server_url);
+
+ $this->assertEquals($expected_endpoint->type_uris,
+ $actual_endpoint->type_uris);
+
+ $this->assertEquals($expected_endpoint->claimed_id,
+ $actual_endpoint->claimed_id);
+
+ }
+}
+
+class Tests_Auth_OpenID_VerifyDisco extends _DiscoverAndVerify {
+ function test_openID1NoLocalID()
+ {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->claimed_id = 'bogus';
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array());
+ // 'Missing required field openid.identity'
+ $this->failUnlessProtocolError($this->consumer->_verifyDiscoveryResults($msg, $endpoint));
+ }
+
+ function test_openID1NoEndpoint()
+ {
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array('identity' => 'snakes on a plane'));
+ $this->failUnlessProtocolError($this->consumer->_verifyDiscoveryResults($msg));
+ }
+
+ function test_openID2NoOPEndpointArg()
+ {
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array('ns' => Auth_OpenID_OPENID2_NS));
+ $this->failUnlessProtocolError($this->consumer->_verifyDiscoveryResults($msg, null));
+ }
+
+ function test_openID2LocalIDNoClaimed()
+ {
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array('ns' => Auth_OpenID_OPENID2_NS,
+ 'op_endpoint' => 'Phone Home',
+ 'identity' => 'Jose Lius Borges'));
+ // 'openid.identity is present without',
+ $this->failUnlessProtocolError($this->consumer->_verifyDiscoveryResults($msg));
+ }
+
+ function test_openID2NoLocalIDClaimed()
+ {
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array('ns' => Auth_OpenID_OPENID2_NS,
+ 'op_endpoint' => 'Phone Home',
+ 'claimed_id' => 'Manuel Noriega'));
+ // 'openid.claimed_id is present without',
+ $this->failUnlessProtocolError(
+ $this->consumer->_verifyDiscoveryResults($msg));
+ }
+
+ function test_openID2NoIdentifiers()
+ {
+ $op_endpoint = 'Phone Home';
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(array('ns' => Auth_OpenID_OPENID2_NS,
+ 'op_endpoint' => $op_endpoint));
+ $result_endpoint = $this->consumer->_verifyDiscoveryResults($msg);
+ $this->assertTrue($result_endpoint->isOPIdentifier());
+ $this->assertEquals($op_endpoint, $result_endpoint->server_url);
+ $this->assertEquals(null, $result_endpoint->claimed_id);
+ }
+
+ function test_openid2UsePreDiscovered()
+ {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->local_id = 'my identity';
+ $endpoint->claimed_id = 'i am sam';
+ $endpoint->server_url = 'Phone Home';
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => $endpoint->local_id,
+ 'claimed_id' => $endpoint->claimed_id,
+ 'op_endpoint' => $endpoint->server_url));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $endpoint);
+ $this->assertTrue($result === $endpoint);
+ }
+
+ function test_openid2UsePreDiscoveredWrongType()
+ {
+ $this->consumer =& new Tests_Auth_OpenID_VerifyDisco_1($this->store);
+ $this->consumer->test_case =& $this;
+ $this->consumer->text = "verify failed";
+
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->local_id = 'my identity';
+ $endpoint->claimed_id = 'i am sam';
+ $endpoint->server_url = 'Phone Home';
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+
+ $this->consumer->endpoint =& $endpoint;
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => $endpoint->local_id,
+ 'claimed_id' => $endpoint->claimed_id,
+ 'op_endpoint' => $endpoint->server_url));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $endpoint);
+ $this->failUnlessProtocolError($result);
+ $this->assertTrue($result->message == "verify failed");
+ }
+
+ function test_openid1UsePreDiscovered()
+ {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->local_id = 'my identity';
+ $endpoint->claimed_id = 'i am sam';
+ $endpoint->server_url = 'Phone Home';
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID1_NS,
+ 'identity' => $endpoint->local_id));
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $endpoint);
+ $this->assertTrue($result == $endpoint);
+ }
+
+ function test_openid2Fragment()
+ {
+ $claimed_id = "http://unittest.invalid/";
+ $claimed_id_frag = $claimed_id . "#fragment";
+
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->local_id = 'my identity';
+ $endpoint->claimed_id = $claimed_id;
+ $endpoint->server_url = 'Phone Home';
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => $endpoint->local_id,
+ 'claimed_id' => $claimed_id_frag,
+ 'op_endpoint' => $endpoint->server_url));
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $endpoint);
+
+ $this->assertEquals($result->local_id, $endpoint->local_id);
+ $this->assertEquals($result->server_url, $endpoint->server_url);
+
+ $this->assertEquals($result->type_uris, $endpoint->type_uris);
+
+ $this->assertEquals($result->claimed_id, $claimed_id_frag);
+ }
+
+}
+
+class Tests_openid1UsePreDiscoveredWrongType extends _DiscoverAndVerify {
+ var $consumer_class = 'VerifyDisco_Consumer_verifiedError';
+
+ function test_openid1UsePreDiscoveredWrongType()
+ {
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->local_id = 'my identity';
+ $endpoint->claimed_id = 'i am sam';
+ $endpoint->server_url = 'Phone Home';
+ $endpoint->type_uris = array(Auth_OpenID_TYPE_2_0);
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID1_NS,
+ 'identity' => $endpoint->local_id));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $endpoint);
+ $this->failUnlessProtocolError($result);
+ $this->assertTrue(is_a($result, '__VerifiedError'));
+ }
+}
+
+// XXX: test the implementation of _discoverAndVerify
+
+class Tests_openID2NoEndpointDoesDisco_sentinel extends Auth_OpenID_GenericConsumer {
+ var $sentinel = 'blah';
+
+ function _discoverAndVerify($to_match)
+ {
+ return $this->sentinel;
+ }
+}
+
+class Tests_openID2NoEndpointDoesDisco_failure extends Auth_OpenID_GenericConsumer {
+ var $failure_message = 'A fake failure response message';
+
+ function _verifyDiscoverySingle($to_match)
+ {
+ return new Auth_OpenID_FailureResponse(null, $this->failure_message);
+ }
+}
+
+class Tests_openID2NoEndpointDoesDisco extends Tests_Auth_OpenID_VerifyDisco {
+ var $consumer_class = 'Tests_openID2NoEndpointDoesDisco_sentinel';
+
+ function test_openID2NoEndpointDoesDisco()
+ {
+ $op_endpoint = 'Phone Home';
+ $this->consumer->sentinel = new Auth_OpenID_ServiceEndpoint();
+ $this->consumer->sentinel->claimed_id = 'monkeysoft';
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => 'sour grapes',
+ 'claimed_id' => 'monkeysoft',
+ 'op_endpoint' => $op_endpoint));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg);
+ $this->assertEquals($this->consumer->sentinel, $result);
+ }
+}
+
+class Tests_openID2MismatchedDoesDisco extends Tests_Auth_OpenID_VerifyDisco {
+ var $consumer_class = 'Tests_openID2NoEndpointDoesDisco_sentinel';
+
+ function test_openID2MismatchedDoesDisco()
+ {
+ $mismatched = new Auth_OpenID_ServiceEndpoint();
+ $mismatched->identity = 'nothing special, but different';
+ $mismatched->local_id = 'green cheese';
+
+ $sentinel = new Auth_OpenID_ServiceEndpoint();
+ $sentinel->claimed_id = 'monkeysoft';
+ $this->consumer->sentinel = $sentinel;
+
+ $op_endpoint = 'Phone Home';
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => 'sour grapes',
+ 'claimed_id' => 'monkeysoft',
+ 'op_endpoint' => $op_endpoint));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $mismatched);
+ $this->assertEquals($this->consumer->sentinel, $result);
+ }
+}
+
+class Tests_openID2MismatchedDoesDisco_failure extends PHPUnit_TestCase {
+ var $consumer_class = 'Tests_openID2NoEndpointDoesDisco_failure';
+
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $cl = $this->consumer_class;
+ $this->consumer = new $cl($this->store);
+ $this->return_to = "http://some.host/path";
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+
+ $this->consumer->discoverMethod = array($this, "_getServices");
+
+ $this->server_id = "sirod";
+ $this->server_url = "serlie";
+ $this->consumer_id = "consu";
+
+ $this->endpoint->claimed_id = $this->consumer_id;
+ $this->endpoint->server_url = $this->server_url;
+ $this->endpoint->local_id = $this->server_id;
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+ }
+
+ function _getServices($claimed_id, $fetcher=null) {
+ return array(null, array($this->endpoint));
+ }
+
+ function test_openID2MismatchedDoesDisco_failure()
+ {
+ $mismatched = new Auth_OpenID_ServiceEndpoint();
+ $mismatched->identity = 'nothing special, but different';
+ $mismatched->local_id = 'green cheese';
+
+ $op_endpoint = 'Phone Home';
+
+ $msg = Auth_OpenID_Message::fromOpenIDArgs(
+ array('ns' => Auth_OpenID_OPENID2_NS,
+ 'identity' => 'sour grapes',
+ 'claimed_id' => 'monkeysoft',
+ 'op_endpoint' => $op_endpoint));
+
+ $result = $this->consumer->_verifyDiscoveryResults($msg, $mismatched);
+ $this->assertTrue(Auth_OpenID::isFailure($result));
+ }
+}
+
+class TestVerifyDiscoverySingle extends OpenIDTestMixin {
+ var $consumer_class = 'Auth_OpenID_GenericConsumer';
+
+ function setUp()
+ {
+ $this->store = new Tests_Auth_OpenID_MemStore();
+ $cl = $this->consumer_class;
+ $this->consumer = new $cl($this->store);
+ $this->return_to = "http://some.host/path";
+ $this->endpoint = new Auth_OpenID_ServiceEndpoint();
+
+ $this->server_id = "sirod";
+ $this->server_url = "serlie";
+ $this->consumer_id = "consu";
+
+ $this->endpoint->claimed_id = $this->consumer_id;
+ $this->endpoint->server_url = $this->server_url;
+ $this->endpoint->local_id = $this->server_id;
+ $this->endpoint->type_uris = array(Auth_OpenID_TYPE_1_1);
+ }
+
+ function test_endpointWithoutLocalID()
+ {
+ // An endpoint like this with no local_id is generated as a
+ // result of e.g. Yadis discovery with no LocalID tag.
+ $endpoint = new Auth_OpenID_ServiceEndpoint();
+ $endpoint->server_url = "http://localhost:8000/openidserver";
+ $endpoint->claimed_id = "http://localhost:8000/id/id-jo";
+
+ $to_match = new Auth_OpenID_ServiceEndpoint();
+ $to_match->server_url = "http://localhost:8000/openidserver";
+ $to_match->claimed_id = "http://localhost:8000/id/id-jo";
+ $to_match->local_id = "http://localhost:8000/id/id-jo";
+
+ $result = $this->consumer->_verifyDiscoverySingle($endpoint, $to_match);
+
+ // result should always be None, raises exception on failure.
+ $this->assertEquals($result, null);
+ }
+}
+
+global $Tests_Auth_OpenID_VerifyDisco_other;
+$Tests_Auth_OpenID_VerifyDisco_other = array(
+ new Tests_openID2MismatchedDoesDisco(),
+ new Tests_openID2NoEndpointDoesDisco(),
+ new Tests_openID2MismatchedDoesDisco_failure(),
+ new Tests_openid1UsePreDiscoveredWrongType(),
+ new Tests_openID1Fallback1_0(),
+ );
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/data/dhexch b/Tests/Auth/OpenID/data/dhexch
new file mode 100644
index 0000000..7a8be07
--- /dev/null
+++ b/Tests/Auth/OpenID/data/dhexch
@@ -0,0 +1,25 @@
+2126517416 1503105722 107994742031567165457540274858809652167995244913839787589743578103625285208352685332617657612917013907706708046204110421143061814711041523408378123857885283119340960531860106377561159933747570456852792031929014142490977574671361334052940195520731561436107832724927338342871107723160776334443535305873323500256
+1726325708 97982531 120276049194830643562108817809662062106546923236483207874835831104433621725428405166395533625121884325430201809382464614237831903082554679507511419420917850293136497504021313018779166242697363853538302653639224106865664333535348491338293646599577557630201300627170167862878616413985927131488374258664616392092
+7191572 1822336109 122056637146069839485450157659463602136773435897258591538866604089577736061707581662949502933519623787237101424065925246227311788026398025228549571401681364284397681558495127293422405005519207011429758784679359026564663723906503072635741603815702201571462971153413211041068663899032199555395016545688330586044
+228786056 1675584603 117701151389970889003978379762267752133865163176557074701079147801046451813688145274964215828365957142552336288689654120601548113748929766366564174096775189852190965077394508456052823068488823291767811224538103381867631753777200820579564678159674117155563703553000884461777658090456283332220216734612556688513
+804908984 2135801843 64993745755656098807721600357147815184322152457013249228399624894951891365784451431980303960531348451816046446458097670030866575746640795583720668211568084611960590087205609635406810868042628237527172170079769791670793545166757565720288711565725471066283638538704293790079806106677373567711048953361333211210
+1122280790 1614259350 3970244390792364343312138669070883638127263175075582263096706481850448381703964455173288740017450440943500797144425033043394229962284552755667989452357422108026327690718801740503572676309934059296211509079412739861064178751353597300902277808636740745335474263237517162977595705012834985895341553367459792583
+554314515 998420920 123643361743671701825618173162391028658772950477687990712748798605914570427945055208640384218469111438408345424338893652923941552893606133066783001743721804859308011765507616515353882559991735865794891472781955621601467016660304188272123202130977476560852093272543539966026915472638392018462692756028817601255
+719945347 612820861 103551249110130410018728043034553056272870332237608830696804104933749752848182147616875273399120950956495972830177071656956289995248469136767050516935071277259217339496102844766065836859503647533631994664364053659677674596380230790347281302210808329346735637394258018381272973124437749621859047096780954428763
+1030625109 1519412381 15696617275088442746387785148060623054817901281945029743077460769180096631404983383910114661025034557230747207080210469011273591184527303007260363112037932265980126744700626024259985586947347501172863220738584453809309462129610346067407238209251289710742647393829531889303218223237301268534338307710560528439
+1711254768 1710707291 57671766580359527580383190392835992822988951359063099518333951473557157636791373831886967456320589708220219137556141104065094734173377513568373511612097050435132426608350879787688784646390242899955969071718348216328885834450215105058725433533776719158074043390881257587614495125963197168525883771897032429145
+350065369 319208735 44521456496863698728911963510653524876630475042295240074435222668882607096381437705850621136342821688618111659046306438623837465097724847737566157513351593063095577843263064035230462006868686576892037899378382406468120801597507834123281075093108794208153836881908434178839513489161646768450411118658866064760
+2060218994 1946043223 56312469398022909670236635086334904553302665496424591277087996113064393075310693844995965663947160222486178761741935506327835516277624460430181450292802834360724976701838361338230904004764511115279873515265325364671729600765057941485718305803874368460265173324375012707038078949514720443784809672434739391394
+348859478 486047271 63578529904279717544096579659734885121575283886072626718230632949600891106018451131129915865157675764791879663149288069798959505461654979937263948081560345222746334083402817181164255208790802816536212306902000509334761465449621953806270899950736579351124776383450108496252367170418747525612150563944975123906
+1012847796 1311216437 107243486601777772242638374457577339776317528440551074937135087437181884726459082109032187432358497015564158022857522392034581988349463322793155498885898276448910563934149930379053835676169014345745737841013305105289515557002942278171260275549569040668192618881235525671100756834785472005323827534045854021808
+1108188659 73002956 151810407586486667408500610375120927048683500513617431444670840241105574837701928593342245869128797827409414723271900961746330458448581633550115101600384307415146567797051023727766743006773542272526168313129133103058023736384944187039543948615045687254043611794926502253898300807539332514119558228945387167129
+1367891527 957164137 106888874248390844568539366153235739322099571679913873665077300044384432133087328354115380340807163549209282323027334598550750155549975114208460003069900549945106924101337174939911191001778776920788324219234143471273850920009578258747072782983631129326451246547584416492036977756842649955247390532642313031673
+1109319782 312705549 68015190517529836158634537187194145539209089151286211608940739126031607591236786876471227901028349157506546942329120582174574792522929377067808957872180985535362179866434055727341930725758279092655515659945664689974113139170079360543337269872976473433045950679959300152245802435127006127508284128226462808242
+77629902 1442208847 80002290434058357698705037287975366182731667140415670086832039653095542318592553515737200055739316505804591069679885064388918352070456242079053263046801427045473800954002156858264359891351667747947419377687560365442620710551484084591458509139700723197713694698034159851521977928611736821392017020477832037627
+1876665427 42392232 94638321177007894302596716790742601595610267055803051893624262442254201910985079684859058170169970420428917385076321338591735591117741499259022286688741758915429225227910702796255294628145448897362890044237980198958757175908654342104958253051607028469935368687936664986431151922321609065078691893879183189566
+559635525 1782490275 71365295096950865667427967092027725943054589808884646377867956234326896501650860934260905567087143525158504721587985301638221372965891635800328428504369765880956526297788284176796001565732103141822914955442743632126166019769189834699258461912602048002960149362882398622111007162709444738907309082349930416022
+743575094 32085276 110453584602878746921666429659148701413696049424461554973529870857842263810553552249241246313332783204009612578170167391820378603413812193480492843470042238103670641705732755583940134259794253885826115668806244330875357074130716388274584300227667628005544555311079499447940768673150499033922449576383527638186
+129818206 137481306 140835473021876998102027624369898079740454145360699735493249477450544517213808389172240396819192163023303266715591396745357472463341356969319556862236385556442904650009255138279232984377682804793224148996875324569988553808409865099882747099149617352970774643108291836908871124753511856578160489828404865664010
+570689556 1980693879 108353275894436996626884805554770441694866167783124178905252902978286824751598925059178987939656961896173921225105217325495780672752694383439806863122466053616930970271706866769895033633709670957150865005763659847698663978549871624628298476651867451354816053985969930735100533712902146229305011837945607699037
+2103057616 691067770 27024056452709622585420653808400360576905666723601175215091499609471301967744143329187436673102391151329548614036436716051750524604202541651425479133617399916946398092715923340837394931898418514658066714568415577105694330058750941172815095999450748361179045856199026312487393802486505084466623313733605784416
+481705774 1641440342 117722260864906582664053412535574009960206959347375143271559843536103545468155917636456429488071536410856812908716077868452921005581676036410474437330482920141777150620686622782118823530416466223519936589968714322291361670902315520017103387742935706013660879451297004924070011539277017717095949755265539759012
diff --git a/Tests/Auth/OpenID/data/dhpriv b/Tests/Auth/OpenID/data/dhpriv
new file mode 100644
index 0000000..0fa5231
--- /dev/null
+++ b/Tests/Auth/OpenID/data/dhpriv
@@ -0,0 +1,29 @@
+130706940119084053627151828062879423433929180135817317038378606310097533503449582079984816816837125851552273641820339909167103200910805078308128174143174269944095368580519322913514764528012639683546377014716235962867583443566164615728897857285824741767070432119909660645255499710701356135207437699643611094585 139808169914464096465921128085565621767096724855516655439365028496569658038844954238931647642811548254956660405394116677296461848124300258439895306367561416289126854788101396379292925819850897858045772500578222021901631436550118958972312221974009238050517034542286574826081826542722270952769078386418682059418
+91966407878983240112417790733941098492087186469785726449910011271065622315680646030230288265496017310433513856308693810812043160919214636748486185212617634222158204354206411031403206076739932806412551605172319515223573351072757800448643935018534945933808900467686115619932664888581913179496050117713298715475 88086484332488517006277516020842172054013692832175783214603951240851750819999098631851571207693874357651112736088114133607400684776234181681933311972926752846692615822043533641407510569745606256772455614745111122033229877596984718963046218854103292937700694160593653595134512369959987897086639788909618660591
+94633950701209990078055218830969910271587805983595045023718108184189787131629772007048606080263109446462048743696369276578815611098215686598630889831104860221067872883514840819381234786050098278403321905311637820524177879167250981289318356078312300538871435101338967079907049912435983871847334104247675360099 136836393035803488129856151345450008294260680733328546556640578838845312279198933806383329293483852515700876505956362639881210101974254765087350842271260064592406308509078284840473735904755203614987286456952991025347168970462354352741159076541157478949094536405618626397435745496863324654768971213730622037771
+24685127248019769965088146297942173464487677364928435784091685260262292485380918213538979925891771204729738138857126454465630594391449913947358655368215901119137728648638547728497517587701248406019427282237279437409508871300675355166059811431191200555457304463617727969228965042729205402243355816702436970430 103488011917988946858248200111251786178288940265978921633592888293430082248387786443813155999158786903216094876295371112716734481877806417714913656921169196196571699893360825510307056269738593971532017994987406325068886420548597161498019372380511676314312298122272401348856314619382867707981701472607230523868
+116791045850880292989786005885944774698035781824784400772676299590038746153860847252706167458966356897309533614849402276819438194497464696186624618374179812548893947178936305721131565012344462048549467883494038577857638815386798694225798517783768606048713198211730870155881426709644960689953998714045816205549 25767875422998856261320430397505398614439586659207416236135894343577952114994718158163212134503751463610021489053571733974769536157057815413209619147486931502025658987681202196476489081257777148377685478756033509708349637895740799542063593586769082830323796978935454479273531157121440998804334199442003857410
+75582226959658406842894734694860761896800153014775231713388264961517169436476322183886891849966756849783437334069692683523296295601533803799559985845105706728538458624387103621364117548643541824878550074680443708148686601108223917493525070861593238005735446708555769966855130921562955491250908613793521520082 51100990616369611694975829054222013346248289055987940844427061856603230021472379888102172458517294080775792439385531234808129302064303666640376750139242970123503857186428797403843206765926798353022284672682073397573130625177187185114726049347844460311761033584101482859992951420083621362870301150543916815123
+22852401165908224137274273646590366934616265607879280260563022941455466297431255072303172649495519837876946233272420969249841381161312477263365567831938496555136366981954001163034914812189448922853839616662859772087929140818377228980710884492996109434435597500854043325062122184466315338260530734979159890875 35017410720028595029711778101507729481023945551700945988329114663345341120595162378885287946069695772429641825579528116641336456773227542256911497084242947904528367986325800537695079726856460817606404224094336361853766354225558025931211551975334149258299477750615397616908655079967952372222383056221992235704
+37364490883518159794654045194678325635036705086417851509136183713863262621334636905291385255662750747808690129471989906644041585863034419130023070856805511017402434123099100618568335168939301014148587149578150068910141065808373976114927339040964292334109797421173369274978107389084873550233108940239410902552 40916262212189137562350357241447034318002130016858244002788189310078477605649010031339865625243230798681216437501833540185827501244378529230150467789369234869122179247196276164931090039290879808162629109742198951942358028123056268054775108592325500609335947248599688175189333996086475013450537086042387719925
+42030470670714872936404499074069849778147578537708230270030877866700844337372497704027708080369726758812896818567830863540507961487472657570488625639077418109017434494794778542739932765561706796300920251933107517954265066804108669800167526425723377411855061131982689717887180411017924173629124764378241885274 124652439272864857598747946875599560379786580730218192165733924418687522301721706620565030507816884907589477351553268146177293719586287258662025940181301472851649975563004543250656807255226609296537922304346339513054316391667044301386950180277940536542183725690479451746977789001659540839582630251935163344393
+33176766914206542084736303652243484580303865879984981189372762326078776390896986743451688462101732968104375838228070296418541745483112261133079756514082093269959937647525005374035326747696591842313517634077723301677759648869372517403529488493581781546743147639937580084065663597330159470577639629864369972900 67485835091897238609131069363014775606263390149204621594445803179810038685760826651889895397414961195533694176706808504447269558421955735607423135937153901140512527504198912146656610630396284977496295289999655140295415981288181545277299615922576281262872097567020980675200178329219970170480653040350512964539
+131497983897702298481056962402569646971797912524360547236788650961059980711719600424210346263081838703940277066368168874781981151411096949736205282734026497995296147418292226818536168555712128736975034272678008697869326747592750850184857659420541708058277866000692785617873742438060271311159568468507825422571 5400380840349873337222394910303409203226429752629134721503171858543984393161548520471799318518954232197106728096866840965784563043721652790856860155702760027304915133166173298206604451826182024471262142046935060360564569939062438160049193241369468208458085699995573492688298015026628427440418009025072261296
+83265103005695640943261961853521077357830295830250157593141844209296716788437615940096402365505416686459260302419338241462783388722843946886845478224048360927114533590583464979009731440049610985062455108831881153988321298531365779084012803908832525921630534096740755274371500276660832724874701671184539131864 141285570207910287798371174771658911045525474449663877845558585668334618068814605961306961485855329182957174312715910923324965889174835444049526313968571611940626279733302104955951067959291852710640374412577070764165811275030632465290729619533330733368808295932659463215921521905553936914975786500018720073003
+68435028583616495789148116911096163791710022987677894923742899873596891423986951658100606742052014161171185231735413902875605720814417622409817842932759492013585936536452615480700628719795872201528559780249210820284350401473564919576289210869896327937002173624497942136329576506818749730506884927872345019446 134655528287263100540003157571441260698452262106680191153945271167894435782028803135774578949200580551016388918860856991026082917835209212892423567114480975540305860034439015788120390011692862968771136814777768281366591257663821495720134621172848947971117885754539770645621669309650476331439675400544167728223
+97765390064836080322590528352647421920257073063706996347334558390461274981996865736612531330863478931481491964338380362350271734683183807511097331539820133036984271653285063355715726806139083282458695728902452215405696318402583540317419929113959816258829534543044153959951908676300847164682178008704099351835 92552521881196975294401505656851872247567784546370503402756239533783651371688190302773864319828182042605239246779598629409815474038541272600580320815319709309111399294952620375093803971373108792300726524826209329889463854451846561437729676142864421966497641824498079067929811613947148353921163336822026640804
+145767094672933012300753301037546647564595762930138884463767054235112032706630891961371504668013023047595721138624016493638510710257541241706724342585654715468628355455898091951826598092812212209834746162089753649871544789379424903025374228231365026585872808685759231756517703720396301355299998059523896918448 116669462839999965355861187716880953863237226719689755457884414384663576662696981997535568446560375442532084973721539944428004043491468494548231348032618218312515409944970197902589794303562379864012797605284844016184274353252071642511293089390472576498394410829972525726474727579603392265177009323768966538608
+34172517877854802711907683049441723730724885305592620486269966708379625109832852005775048584124451699198484092407720344962116726808090368739361658889584507734617844212547181476646725256303630128954338675520938806905779837227983648887192531356390902975904503218654196581612781227843742951241442641220856414232 126013077261793777773236390821108423367648447987653714614732477073177878509574051196587476846560696305938891953527959347566502332765820074506907037627115954790645652211088723122982633069089920979477728376746424256704724173255656757918995039125823421607024407307091796807227896314403153380323770001854211384322
+9979624731056222925878866378063961280844793874828281622845276060532093809300121084179730782833657205171434732875093693074415298975346410131191865198158876447591891117577190438695367929923494177555818480377241891190442070100052523008290671797937772993634966511431668500154258765510857129203107386972819651767 76559085024395996164590986654274454741199399364851956129137304209855150918182685643729981600389513229011956888957763987167398150792454613751473654448162776379362213885827651020309844507723069713820393068520302223477225569348080362344052033711960892643036147232270133731530049660264526964146237693063093765111
+18162696663677410793062235946366423954875282212790518677684260521370996677183041664345920941714064628111537529793170736292618705900247450994864220481135611781148410617609559050220262121494712903009168783279356915189941268264177631458029177102542745167475619936272581126346266816618866806564180995726437177435 63244550218824945129624987597134280916829928261688093445040235408899092619821698537312158783367974202557699994650667088974727356690181336666077506063310290098995215324552449858513870629176838494348632073938023916155113126203791709810160925798130199717340478393420816876665127594623142175853115698049952126277
+4817943161362708117912118300716778687157593557807116683477307391846133734701449509121209661982298574607233039490570567781316652698287671086985501523197566560479906850423709894582834963398034434055472063156147829131181965140631257939036683622084290629927807369457311894970308590034407761706800045378158588657 61612160237840981966750225147965256022861527286827877531373888434780789812764688703260066154973576040405676432586962624922734102370509771313805122788566405984830112657060375568510809122230960988304085950306616401218206390412815884549481965750553137717475620505076144744211331973240555181377832337912951699135
+36363324947629373144612372870171042343590861026293829791335153646774927623889458346817049419803031378037141773848560341251355283891019532059644644509836766167835557471311319194033709837770615526356168418160386395260066262292757953919140150454538786106958252854181965875293629955562111756775391296856504912587 86831561031659073326747216166881733513938228972332631084118628692228329095617884068498116676787029033973607066377816508795286358748076949738854520048303930186595481606562375516134920902325649683618195251332651685732712539073110524182134321873838204219194459231650917098791250048469346563303077080880339797744
+26406869969418301728540993821409753036653370247174689204659006239823766914991146853283367848649039747728229875444327879875275718711878211919734397349994000106499628652960403076186651083084423734034070082770589453774926850920776427074440483233447839259180467805375782600203654373428926653730090468535611335253 100139935381469543084506312717977196291289016554846164338908226931204624582010530255955411615528804421371905642197394534614355186795223905217732992497673429554618838376065777445760355552020655667172127543653684405493978325270279321013143828897100500212200358450649158287605846102419527584313353072518101626851
+92613116984760565837109105383781193800503303131143575169488835702472221039082994091847595094556327985517286288659598094631489552181233202387028607421487026032402972597880028640156629614572656967808446397456622178472130864873587747608262139844319805074476178618930354824943672367046477408898479503054125369731 30023391082615178562263328892343821010986429338255434046051061316154579824472412477397496718186615690433045030046315908170615910505869972621853946234911296439134838951047107272129711854649412919542407760508235711897489847951451200722151978578883748353566191421685659370090024401368356823252748749449302536931
+31485815361342085113278193504381994806529237123359718043079410511224607873725611862217941085749929342777366642477711445011074784469367917758629403998067347054115844421430072631339788256386509261291675080191633908849638316409182455648806133048549359800886124554879661473112614246869101243501787363247762961784 114503770698890543429251666713050844656853278831559195214556474458830029271801818536133531843456707474500106283648085144619097572354066554819887152106174400667929098257361286338795493838820850475790977445807435511982704395422526800272723708548541616513134676140304653112325071112865020365664833601046215694089
+76882090884790547431641385530818076533805072109483843307806375918023300052767710853172670987385376253156912268523505310624133905633437815297307463917718596711590885553760690350221265675690787249135345226947453988081566088302642706234126002514517416493192624887800567412565527886687096028028124049522890448168 15056463217273240496622619354104573042767532856243223052125822509781815362480522535564283485059790932505429110157271454207173426525345813426696743168079246510944969446574354255284952839036431873039487144279164893710061580467579842173706653409487110282515691099753380094215805485573768509475850463001549608836
+52345178981230648108672997265819959243255047568833938156267924185186047373470984278294897653277996726416846430969793375429223610099546622112048283560483136389901514170116723365811871938630317974150540909650396429631704968748113009366339718498979597226137532343384889080245796447593572468846438769413505393967 32148494517199936472358017244372701214529606506776255341152991328091526865643069587953759877295255050519124541457805199596762210567333445908166076384465183589342153762720515477404466193879418014196727238972417616122646440870364200208488239778452378059236162633837824948613596114768455832408342040970780086
+41095268619128788015767564971105114602454449306041732792746397800275041704886345704294273937217484580365505320134717320083763349380629342859670693445658118959823430378844830923452105707338162448974869312012791385772125813291388247857971218575518319578818336960572244046567099555399203328678654466958536663208 92166550199033418923713824997841892577149715275633481076285269142670107687867024550593869464613175882141630640739938334001211714884975032600306279287443909448541179109981755796752132502127330056736913454039526413284519137059580845856736918773597087836203497066909257930043736166431682872083389105176299181629
+40049143661018504441607875135884755310012910557581028447435354354754245291878800571089144452035026644953322330676651798951447670184106450649737772686119714700743396359069052813433030118630105307022867200053964644574786137276428546712005171080129190959914708907200288299169344380390093918556722227705114244981 108159089972386282154772900619022507336076619354549601813179459338897131937353741544606392560724999980281424266891537298473163753022749859939445293926707568015958367188089915420630082556748668489756475027008449860889202622698060097015044886961901650857610841562477736791450080980702347705778074391774667412741
+69905259478181995876884927656894491893594530150260951315109404530530357998889589977208787140430938039028941393673520799460431992051993157468616168400324834880926190141581037597526917869362292931957289043707855837933490285814769110495657056206391880865972389421774822461752702336812585852278453803972600333734 71821415380277072313878763768684432371552628204186742842154591000123020597011744840460964835414360968627162765288463383113375595799297552681618876474019263288277398833725479226930770694271622605114061622753165584075733358178384410640349907375170170910499615355511313349300918885560131539570707695789106185664
+26945345439378873515011714350080059082081595419023056538696949766471272811362104837806324694947413603019863785876836706911406330379274553386254346050697348395574746891556054334903838949157798006141473389066020212044825140294048709654273698482867946522782450500680195477050110145664069582549935651920545151500 80313315938584480048642653013876614091607852535582224914294013785054094052454758327935781971746329853786568549510067442145637007308960551652864942042189241081946607011847245280773379099020221884296226818685556430275385068764313042226925852500883894269809033380734632866477789520106865758504064806906234130588
diff --git a/Tests/Auth/OpenID/data/hmac-sha1.txt b/Tests/Auth/OpenID/data/hmac-sha1.txt
new file mode 100644
index 0000000..4299a96
--- /dev/null
+++ b/Tests/Auth/OpenID/data/hmac-sha1.txt
@@ -0,0 +1,49 @@
+test_case = 1
+key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+key_len = 20
+data = "Hi There"
+data_len = 8
+digest = 0xb617318655057264e28bc0b6fb378c8ef146be00
+
+test_case = 2
+key = "Jefe"
+key_len = 4
+data = "what do ya want for nothing?"
+data_len = 28
+digest = 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+
+test_case = 3
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 20
+data = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+data_len = 50
+digest = 0x125d7342b9ac11cd91a39af48aa17b4f63f175d3
+
+test_case = 4
+key = 0x0102030405060708090a0b0c0d0e0f10111213141516171819
+key_len = 25
+data = 0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+data_len = 50
+digest = 0x4c9007f4026250c6bc8414f9bf50c86c2d7235da
+
+test_case = 5
+key = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c
+key_len = 20
+data = "Test With Truncation"
+data_len = 20
+digest = 0x4c1a03424b55e07fe7f27be1d58bb9324a9a5a04
+digest-96 = 0x4c1a03424b55e07fe7f27be1
+
+test_case = 6
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 80
+data = "Test Using Larger Than Block-Size Key - Hash Key First"
+data_len = 54
+digest = 0xaa4ae5e15272d00e95705637ce8a3b55ed402112
+
+test_case = 7
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 80
+data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"
+data_len = 73
+digest = 0xe8e99d0f45237d786d6bbaa7965c7808bbff1a91
diff --git a/Tests/Auth/OpenID/data/hmac-sha256.txt b/Tests/Auth/OpenID/data/hmac-sha256.txt
new file mode 100644
index 0000000..a0b2e73
--- /dev/null
+++ b/Tests/Auth/OpenID/data/hmac-sha256.txt
@@ -0,0 +1,29 @@
+test_case = 1
+key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+data = 0x4869205468657265
+digest = 0xb0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
+
+test_case = 2
+key = 0x4a656665
+data = 0x7768617420646f2079612077616e7420666f72206e6f7468696e673f
+digest = 0x5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
+
+test_case = 3
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+data = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+digest = 0x773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe
+
+test_case = 4
+key = 0x0102030405060708090a0b0c0d0e0f10111213141516171819
+data = 0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+digest = 0x82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b
+
+test_case = 6
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+data = 0x54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374
+digest = 0x60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54
+
+test_case = 7
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+data = 0x5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e
+digest = 0x9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2
diff --git a/Tests/Auth/OpenID/data/linkparse.txt b/Tests/Auth/OpenID/data/linkparse.txt
new file mode 100644
index 0000000..eb13e6b
--- /dev/null
+++ b/Tests/Auth/OpenID/data/linkparse.txt
@@ -0,0 +1,594 @@
+Num Tests: 73
+
+OpenID link parsing test cases
+Copyright (C) 2005-2008, JanRain, Inc.
+See COPYING for license information.
+
+File format
+-----------
+
+All text before the first triple-newline (this chunk) should be ignored.
+
+This file may be interpreted as Latin-1 or UTF-8.
+
+Test cases separated by three line separators (`\n\n\n'). The test
+cases consist of a headers section followed by a data block. These are
+separated by a double newline. The headers consist of the header name,
+followed by a colon, a space, the value, and a newline. There must be
+one, and only one, `Name' header for a test case. There may be zero or
+more link headers. The `Link' header consists of whitespace-separated
+attribute pairs. A link header with an empty string as a value
+indicates an empty but present link tag. The attribute pairs are `='
+separated and not quoted.
+
+Optional Links and attributes have a trailing `*'. A compilant
+implementation may produce this as output or may not. A compliant
+implementation will not produce any output that is absent from this
+file.
+
+
+Name: Well-formed link rel (in CAPS)
+Link: rel=openid.server href=http://www.myopenid.com/server
+
+<HTML>
+ <HEAD>
+ <LINK REL="openid.server"
+ HREF="http://www.myopenid.com/server" />
+ </HEAD>
+</HTML>
+
+
+Name: No link tag at all
+
+<html>
+<head>
+</head>
+</html>
+
+
+Name: Link element first
+
+<link>
+
+
+Name: Link inside HTML, not head
+
+<html>
+<link>
+
+
+Name: Link inside head, not html
+
+<head>
+<link>
+
+
+Name: Link inside html, after head
+
+<html>
+<head>
+</head>
+<link>
+
+
+Name: Link inside html, before head
+
+<html>
+<link>
+<head>
+
+
+Name: Link before html and head
+
+<link>
+<html>
+<head>
+
+
+Name: Link after html document with head
+
+<html>
+<head>
+</head>
+</html>
+<link>
+
+
+Name: Link inside html inside head, inside another html
+
+<html>
+<head>
+<html>
+<link>
+
+
+Name: Link inside html inside head
+
+<head>
+<html>
+<link>
+
+
+Name: link inside body inside head inside html
+
+<html>
+<head>
+<body>
+<link>
+
+
+Name: Link inside head inside head inside html
+
+<html>
+<head>
+<head>
+<link>
+
+
+Name: Link inside script inside head inside html
+
+<html>
+<head>
+<script>
+<link>
+</script>
+
+
+Name: Link inside comment inside head inside html
+
+<html>
+<head/>
+<link>
+
+
+Name: Link inside of head after short head
+
+<html>
+<head/>
+<head>
+<link>
+
+
+Name: Plain vanilla
+Link:
+
+<html>
+<head>
+<link>
+
+
+Name: Ignore tags in the <script:... > namespace
+Link*:
+
+<html>
+<head>
+<script:paddypan>
+<link>
+</script:paddypan>
+
+
+Name: Short link tag
+Link:
+
+<html>
+<head>
+<link/>
+
+
+Name: Spaces in the HTML tag
+Link:
+
+<html >
+<head>
+<link>
+
+
+Name: Spaces in the head tag
+Link:
+
+<html>
+<head >
+<link>
+
+
+Name: Spaces in the link tag
+Link:
+
+<html>
+<head>
+<link >
+
+
+Name: No whitespace
+Link:
+
+<html><head><link>
+
+
+Name: Closed head tag
+Link:
+
+<html>
+<head>
+<link>
+</head>
+
+
+Name: One good, one bad (after close head)
+Link:
+
+<html>
+<head>
+<link>
+</head>
+<link>
+
+
+Name: One good, one bad (after open body)
+Link:
+
+<html>
+<head>
+<link>
+<body>
+<link>
+
+
+Name: ill formed (missing close head)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+
+
+Name: Ill formed (no close head, link after </html>)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+<link>
+
+
+Name: Ignore random tags inside of html
+Link:
+
+<html>
+<delicata>
+<head>
+<title>
+<link>
+
+
+Name: case-folding
+Link*:
+
+<HtMl>
+<hEaD>
+<LiNk>
+
+
+Name: unexpected tags
+Link:
+
+<butternut>
+<html>
+<summer>
+<head>
+<turban>
+<link>
+
+
+Name: un-closed script tags
+Link*:
+
+<html>
+<head>
+<script>
+<link>
+
+
+Name: un-closed script tags (no whitespace)
+Link*:
+
+<html><head><script><link>
+
+
+Name: un-closed comment
+Link*:
+
+<html>
+<head>
+<!--
+<link>
+
+
+Name: un-closed CDATA
+Link*:
+
+<html>
+<head>
+<![CDATA[
+<link>
+
+
+Name: cdata-like
+Link*:
+
+<html>
+<head>
+<![ACORN[
+<link>
+]]>
+
+
+Name: comment close only
+Link:
+
+<html>
+<head>
+<link>
+-->
+
+
+Name: Vanilla, two links
+Link:
+Link:
+
+<html>
+<head>
+<link>
+<link>
+
+
+Name: extra tag, two links
+Link:
+Link:
+
+<html>
+<gold nugget>
+<head>
+<link>
+<link>
+
+
+Name: case-fold, body ends, two links
+Link:
+Link*:
+
+<html>
+<head>
+<link>
+<LiNk>
+<body>
+<link>
+
+
+Name: simple, non-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server>
+
+
+Name: short tag has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server/>
+
+
+Name: short tag w/space has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server />
+
+
+Name: extra non-attribute, has rel
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server>
+
+
+Name: non-attr, has rel, short
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server/>
+
+
+Name: non-attr, has rel, short, space
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server />
+
+
+Name: misplaced slash has rel
+Link: rel=openid.server
+
+<html><head><link / rel=openid.server>
+
+
+Name: quoted rel
+Link: rel=openid.server
+
+<html><head><link rel="openid.server">
+
+
+Name: single-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel='openid.server'>
+
+
+Name: two links w/ rel
+Link: x=y
+Link: a=b
+
+<html><head><link x=y><link a=b>
+
+
+Name: non-entity
+Link: x=&y
+
+<html><head><link x=&y>
+
+
+Name: quoted non-entity
+Link: x=&y
+
+<html><head><link x="&y">
+
+
+Name: quoted entity
+Link: x=&
+
+<html><head><link x="&amp;">
+
+
+Name: entity not processed
+Link: x=&#26;
+
+<html><head><link x="&#26;">
+
+
+Name: &lt;
+Link: x=<
+
+<html><head><link x="&lt;">
+
+
+Name: &gt;
+Link: x=>
+
+<html><head><link x="&gt;">
+
+
+Name: &quot;
+Link: x="
+
+<html><head><link x="&quot;">
+
+
+Name: &amp;&quot;
+Link: x=&"
+
+<html><head><link x="&amp;&quot;">
+
+
+Name: mixed entity and non-entity
+Link: x=&"&hellip;>
+
+<html><head><link x="&amp;&quot;&hellip;&gt;">
+
+
+Name: mixed entity and non-entity (w/normal chars)
+Link: x=x&"&hellip;>x
+
+<html><head><link x="x&amp;&quot;&hellip;&gt;x">
+
+
+Name: broken tags
+Link*: x=y
+
+<html><head><link x=y<>
+
+
+Name: missing close pointy
+Link: z=y
+
+<html><head><link x=y<link z=y />
+
+
+Name: missing attribute value
+Link: x=y y*=
+Link: x=y
+
+<html><head><link x=y y=><link x=y />
+
+
+Name: Missing close pointy (no following)
+Link*: x=y
+
+<html><head><link x=y
+
+
+Name: Should be quoted
+Link: x*=<
+
+<html><head><link x="<">
+
+
+Name: Should be quoted (2)
+Link: x*=>
+
+<html><head><link x=">">
+
+
+Name: Repeated attribute
+Link: x=y
+
+<html><head><link x=z x=y>
+
+
+Name: Repeated attribute (2)
+Link: x=y
+
+<html><head><link x=y x=y>
+
+
+Name: Two attributes
+Link: x=y y=z
+
+<html><head><link x=y y=z>
+
+
+Name: Well-formed link rel="openid.server"
+Link: rel=openid.server href=http://www.myopenid.com/server
+
+<html>
+ <head>
+ <link rel="openid.server"
+ href="http://www.myopenid.com/server" />
+ </head>
+</html>
+
+
+Name: Well-formed link rel="openid.server" and "openid.delegate"
+Link: rel=openid.server href=http://www.myopenid.com/server
+Link: rel=openid.delegate href=http://example.myopenid.com/
+
+<html><head><link rel="openid.server"
+ href="http://www.myopenid.com/server" />
+ <link rel="openid.delegate" href="http://example.myopenid.com/" />
+</head></html>
+
+
+Name: from brian's livejournal page
+Link: rel=stylesheet href=http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711 type=text/css
+Link: rel=openid.server href=http://www.livejournal.com/openid/server.bml
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet"
+ href="http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711"
+ type="text/css" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="foaf:maker"
+ content="foaf:mbox_sha1sum '12f8abdacb5b1a806711e23249da592c0d316260'" />
+ <meta name="robots" content="noindex, nofollow, noarchive" />
+ <meta name="googlebot" content="nosnippet" />
+ <link rel="openid.server"
+ href="http://www.livejournal.com/openid/server.bml" />
+ <title>Brian</title>
+ </head>
+
+
+Name: non-ascii (Latin-1 or UTF8)
+Link: x=®
+
+<html><head><link x="®">
+
+
diff --git a/Tests/Auth/OpenID/data/n2b64 b/Tests/Auth/OpenID/data/n2b64
new file mode 100644
index 0000000..b12a246
--- /dev/null
+++ b/Tests/Auth/OpenID/data/n2b64
@@ -0,0 +1,650 @@
+AA== 0
+AQ== 1
+Ag== 2
+Aw== 3
+BA== 4
+BQ== 5
+Bg== 6
+Bw== 7
+CA== 8
+CQ== 9
+Cg== 10
+Cw== 11
+DA== 12
+DQ== 13
+Dg== 14
+Dw== 15
+EA== 16
+EQ== 17
+Eg== 18
+Ew== 19
+FA== 20
+FQ== 21
+Fg== 22
+Fw== 23
+GA== 24
+GQ== 25
+Gg== 26
+Gw== 27
+HA== 28
+HQ== 29
+Hg== 30
+Hw== 31
+IA== 32
+IQ== 33
+Ig== 34
+Iw== 35
+JA== 36
+JQ== 37
+Jg== 38
+Jw== 39
+KA== 40
+KQ== 41
+Kg== 42
+Kw== 43
+LA== 44
+LQ== 45
+Lg== 46
+Lw== 47
+MA== 48
+MQ== 49
+Mg== 50
+Mw== 51
+NA== 52
+NQ== 53
+Ng== 54
+Nw== 55
+OA== 56
+OQ== 57
+Og== 58
+Ow== 59
+PA== 60
+PQ== 61
+Pg== 62
+Pw== 63
+QA== 64
+QQ== 65
+Qg== 66
+Qw== 67
+RA== 68
+RQ== 69
+Rg== 70
+Rw== 71
+SA== 72
+SQ== 73
+Sg== 74
+Sw== 75
+TA== 76
+TQ== 77
+Tg== 78
+Tw== 79
+UA== 80
+UQ== 81
+Ug== 82
+Uw== 83
+VA== 84
+VQ== 85
+Vg== 86
+Vw== 87
+WA== 88
+WQ== 89
+Wg== 90
+Ww== 91
+XA== 92
+XQ== 93
+Xg== 94
+Xw== 95
+YA== 96
+YQ== 97
+Yg== 98
+Yw== 99
+ZA== 100
+ZQ== 101
+Zg== 102
+Zw== 103
+aA== 104
+aQ== 105
+ag== 106
+aw== 107
+bA== 108
+bQ== 109
+bg== 110
+bw== 111
+cA== 112
+cQ== 113
+cg== 114
+cw== 115
+dA== 116
+dQ== 117
+dg== 118
+dw== 119
+eA== 120
+eQ== 121
+eg== 122
+ew== 123
+fA== 124
+fQ== 125
+fg== 126
+fw== 127
+AIA= 128
+AIE= 129
+AII= 130
+AIM= 131
+AIQ= 132
+AIU= 133
+AIY= 134
+AIc= 135
+AIg= 136
+AIk= 137
+AIo= 138
+AIs= 139
+AIw= 140
+AI0= 141
+AI4= 142
+AI8= 143
+AJA= 144
+AJE= 145
+AJI= 146
+AJM= 147
+AJQ= 148
+AJU= 149
+AJY= 150
+AJc= 151
+AJg= 152
+AJk= 153
+AJo= 154
+AJs= 155
+AJw= 156
+AJ0= 157
+AJ4= 158
+AJ8= 159
+AKA= 160
+AKE= 161
+AKI= 162
+AKM= 163
+AKQ= 164
+AKU= 165
+AKY= 166
+AKc= 167
+AKg= 168
+AKk= 169
+AKo= 170
+AKs= 171
+AKw= 172
+AK0= 173
+AK4= 174
+AK8= 175
+ALA= 176
+ALE= 177
+ALI= 178
+ALM= 179
+ALQ= 180
+ALU= 181
+ALY= 182
+ALc= 183
+ALg= 184
+ALk= 185
+ALo= 186
+ALs= 187
+ALw= 188
+AL0= 189
+AL4= 190
+AL8= 191
+AMA= 192
+AME= 193
+AMI= 194
+AMM= 195
+AMQ= 196
+AMU= 197
+AMY= 198
+AMc= 199
+AMg= 200
+AMk= 201
+AMo= 202
+AMs= 203
+AMw= 204
+AM0= 205
+AM4= 206
+AM8= 207
+ANA= 208
+ANE= 209
+ANI= 210
+ANM= 211
+ANQ= 212
+ANU= 213
+ANY= 214
+ANc= 215
+ANg= 216
+ANk= 217
+ANo= 218
+ANs= 219
+ANw= 220
+AN0= 221
+AN4= 222
+AN8= 223
+AOA= 224
+AOE= 225
+AOI= 226
+AOM= 227
+AOQ= 228
+AOU= 229
+AOY= 230
+AOc= 231
+AOg= 232
+AOk= 233
+AOo= 234
+AOs= 235
+AOw= 236
+AO0= 237
+AO4= 238
+AO8= 239
+APA= 240
+APE= 241
+API= 242
+APM= 243
+APQ= 244
+APU= 245
+APY= 246
+APc= 247
+APg= 248
+APk= 249
+APo= 250
+APs= 251
+APw= 252
+AP0= 253
+AP4= 254
+AP8= 255
+AQA= 256
+AQE= 257
+AQI= 258
+AQM= 259
+AQQ= 260
+AQU= 261
+AQY= 262
+AQc= 263
+AQg= 264
+AQk= 265
+AQo= 266
+AQs= 267
+AQw= 268
+AQ0= 269
+AQ4= 270
+AQ8= 271
+ARA= 272
+ARE= 273
+ARI= 274
+ARM= 275
+ARQ= 276
+ARU= 277
+ARY= 278
+ARc= 279
+ARg= 280
+ARk= 281
+ARo= 282
+ARs= 283
+ARw= 284
+AR0= 285
+AR4= 286
+AR8= 287
+ASA= 288
+ASE= 289
+ASI= 290
+ASM= 291
+ASQ= 292
+ASU= 293
+ASY= 294
+ASc= 295
+ASg= 296
+ASk= 297
+ASo= 298
+ASs= 299
+ASw= 300
+AS0= 301
+AS4= 302
+AS8= 303
+ATA= 304
+ATE= 305
+ATI= 306
+ATM= 307
+ATQ= 308
+ATU= 309
+ATY= 310
+ATc= 311
+ATg= 312
+ATk= 313
+ATo= 314
+ATs= 315
+ATw= 316
+AT0= 317
+AT4= 318
+AT8= 319
+AUA= 320
+AUE= 321
+AUI= 322
+AUM= 323
+AUQ= 324
+AUU= 325
+AUY= 326
+AUc= 327
+AUg= 328
+AUk= 329
+AUo= 330
+AUs= 331
+AUw= 332
+AU0= 333
+AU4= 334
+AU8= 335
+AVA= 336
+AVE= 337
+AVI= 338
+AVM= 339
+AVQ= 340
+AVU= 341
+AVY= 342
+AVc= 343
+AVg= 344
+AVk= 345
+AVo= 346
+AVs= 347
+AVw= 348
+AV0= 349
+AV4= 350
+AV8= 351
+AWA= 352
+AWE= 353
+AWI= 354
+AWM= 355
+AWQ= 356
+AWU= 357
+AWY= 358
+AWc= 359
+AWg= 360
+AWk= 361
+AWo= 362
+AWs= 363
+AWw= 364
+AW0= 365
+AW4= 366
+AW8= 367
+AXA= 368
+AXE= 369
+AXI= 370
+AXM= 371
+AXQ= 372
+AXU= 373
+AXY= 374
+AXc= 375
+AXg= 376
+AXk= 377
+AXo= 378
+AXs= 379
+AXw= 380
+AX0= 381
+AX4= 382
+AX8= 383
+AYA= 384
+AYE= 385
+AYI= 386
+AYM= 387
+AYQ= 388
+AYU= 389
+AYY= 390
+AYc= 391
+AYg= 392
+AYk= 393
+AYo= 394
+AYs= 395
+AYw= 396
+AY0= 397
+AY4= 398
+AY8= 399
+AZA= 400
+AZE= 401
+AZI= 402
+AZM= 403
+AZQ= 404
+AZU= 405
+AZY= 406
+AZc= 407
+AZg= 408
+AZk= 409
+AZo= 410
+AZs= 411
+AZw= 412
+AZ0= 413
+AZ4= 414
+AZ8= 415
+AaA= 416
+AaE= 417
+AaI= 418
+AaM= 419
+AaQ= 420
+AaU= 421
+AaY= 422
+Aac= 423
+Aag= 424
+Aak= 425
+Aao= 426
+Aas= 427
+Aaw= 428
+Aa0= 429
+Aa4= 430
+Aa8= 431
+AbA= 432
+AbE= 433
+AbI= 434
+AbM= 435
+AbQ= 436
+AbU= 437
+AbY= 438
+Abc= 439
+Abg= 440
+Abk= 441
+Abo= 442
+Abs= 443
+Abw= 444
+Ab0= 445
+Ab4= 446
+Ab8= 447
+AcA= 448
+AcE= 449
+AcI= 450
+AcM= 451
+AcQ= 452
+AcU= 453
+AcY= 454
+Acc= 455
+Acg= 456
+Ack= 457
+Aco= 458
+Acs= 459
+Acw= 460
+Ac0= 461
+Ac4= 462
+Ac8= 463
+AdA= 464
+AdE= 465
+AdI= 466
+AdM= 467
+AdQ= 468
+AdU= 469
+AdY= 470
+Adc= 471
+Adg= 472
+Adk= 473
+Ado= 474
+Ads= 475
+Adw= 476
+Ad0= 477
+Ad4= 478
+Ad8= 479
+AeA= 480
+AeE= 481
+AeI= 482
+AeM= 483
+AeQ= 484
+AeU= 485
+AeY= 486
+Aec= 487
+Aeg= 488
+Aek= 489
+Aeo= 490
+Aes= 491
+Aew= 492
+Ae0= 493
+Ae4= 494
+Ae8= 495
+AfA= 496
+AfE= 497
+AfI= 498
+AfM= 499
+AfQ= 500
+AfU= 501
+AfY= 502
+Afc= 503
+Afg= 504
+Afk= 505
+Afo= 506
+Afs= 507
+Afw= 508
+Af0= 509
+Af4= 510
+Af8= 511
+AgA= 512
+ALDs7paJl5xPh6ORH61iDA6pONpV0rTjGiTkLEW2JsVsRKaRiS4AGn2PTR1UZXP0vXAmRXwdSegQgWPUp3Hm3RofRcDh1SykZBLif7ulau1hVO+rhwRyKc7F8F+7LcMf/v+s73eOXUDbbI2r52wfr7skZy/IELhsC8EK6HzhACI3 124241322153253947064453752054205174382289463089695815605736438952932114700118408072544073767229325045596832952652232288773280299665950768731398747700657715829631597019676014848183966683866396215048196276450953653433516126074463193382764063985175903718735372053536664711482497859539116009770850968340298474039
+AOzgU1s6Pd2IkrJlvGND8legXTe50nyDCocI5mwT9rW0YsisY5jaaEOcu51BAq9MmXBPeVX0k/jlXwH4Pn3mCpUAU1rEOsTdcmSJp35siKliDdhTZHHdZNMW+igfXGX5OCsA/BaBcGnE6NnrGWXKyTOoVUGQLEkL2T5yhNUaCT83 166340174936369324883416612727439279977041963320514134445183426741643586944819834936989524033374309932122967866930503619179389342537723598234062828695747850043368572301869699886931403612266216965783079972698791813140295203826980649434652168563255385527187360027803388963151668338040517316899628026707657178935
+AO8hrpw+lDiJ13JahLtCb1RenupQcNd0wlTSck9OLL8wB/x6gAoj0PTLV05eZIbz43N3GUSDmmckjlxdHXiBJ9rsoB0P95l1CWIV+4rXblCqxmOdmlm6VZ13bqbI0x7l0cjeMrkmk+yJ067WqUolqQBlUWMTuJVfkxALJYH5xr/C 167923899524385316022824282304301434707626789716026029252173742527362300338760906999615029022863637963070711762128687835779073122264515776657475985362344360699359591353388569856862973447791264902182048648600267737826849280828116753682917256540180401899752566540869918949003470368970029744573140084219550547906
+QxAn7yrdVs5tlHV+Glbqdaj67c6Ni8am3bBLOL8PV5HbdrLf2xIPmNugo6MfUwFSnT+ZPJ51+VTOsItaNwCFju0Eh1cqyP3JWyLRPE7emKuo6xRhf+5ik0pTg77LEF4JXW6ofDqirpR4alFi0G2d9yImQPphsYJwYGF/nNT8u0Q= 47093316905427544098193936500644355852669366083115552072584429220248776817916430034648347490325490701471113667554329499736495877969341478442613611948220957798780043076906836236556612316544460763366275536846463456405604189392790111985912854476264292503164100482712281088955640964034295834935468665872932715332
+AI9PVzrbJUvmCihwSFans1lBKwudGEZpWWu8pkSK2zVgzGhMvUoGgMp6TG2zsUd1tV8zv7KsVD2t6pXmjT1wPUynufq97GVHI06SGpflDTt30WboYRh3DgYxvso1sOjUXpnDezcaqc2Aiz4nV5MSShkBlyBjA8z2worHDE+uXqw0 100635651531872121827765663065728398779771663753008344681972226973080394359405041113312675686974926993279775427390065833081040771269307007695807025882757371805607979134114890454059957194316765342461291139168706134406917264848659448693866813989352429841300235734400772946895458374870482441457514575059390213172
+FiinVicXOqqRLpxcGTorQpSAGeQ/PfDOuzYK9ViFtmPv6D0cYPfhUH4qXEHOejvmX+0b4lfaX8MWPVZxlqpfXiU9BhG76HJxkLF4ysipukeOvhoHzvcxE5bnhSF1i//bOSifATBLBEZInwqSVg5tHHPuuCkwTL91NqhOulp7Lsk= 15560440884463435471963622630292643727112462888414585143143739400703889930416938984547754943252935620248108237258540176511252143752416771350868493435174026287082706690332705481726295797196444796135827460509780634261726494455068460028424141500629527968240913757449787164107068039175831847071025316475940056777
+aYrxyQN/hkBne2ayqo2/iDLF3DZGgk080SOMJfsj9h3Z1OfFZM7TJA+y+/O7niqatosvKrfHrAw+Qs7c6tCZ6NPwYJ4QJLOF9bqH2u2a3fkI954voNUctlUagYUJsZXV8hdhLM6NwUyIZ3ZFkPcpTZp7nKQQ84tr1a8VjDIT5/o= 74114640794666001532816944350975062126079079113921109750255283189037502412929005615388097912507598112836936032143435813588205939470002911374442844578739574773399427907766548612582213272643279263782396527705126350063372192910060171635870872236876399794128383338399728947176692692942605589343038282957050865658
+AMpCUeKUX/vtRslWiUUuXNl1KA9uDAWjMUkTrdsxxRDESI7iZIn3TR9lW+0kV5fzkLF18iYLAwSGBmX1PS/T0UVFmoBPJ9yS7yktNL0lpQ3noyGFn8HHZ6XB3FkH3jegIfGbvwwhnhhFzpHPrXlpO5iU5Y+rexzp2XHWt4yJGuIL 142031143422642739313498629438991149460874309300342349421794421544918823888598660275343727563280565210534243383322796489809683834300630555650331646026843796764549231159336347965502383849513994449309613369541991287590422095953275586374371960367000083487965487661436037637475372929033613295072397262739084075531
+AIMIQVz0JIEKEI+PREu94m3v9XoiU/Q0CpsSuqkwSSje+Wyul5ea9oU5qgtOpdkMUOW91BJo0DW/GMZ8v3C4qyyP29TtjCcAHObJi9hfLSlnTSuzXZnDStooYYKqzfToLToCaAJKCXiXAVW0vWtapLnyqafrf/KgyGZ5u4HfXKY0 92013973253053602863003242446596060337454881568126916916519869242232429836082762281129448384605359749247852792606718908482332975424967542242332487707042773885428473061056052851768940900752317020681189773407893371297668591494665352294885305475871917069040377145530889271334616499701769138948975263435137525300
+ANfP+zPBTR27afneyac1KJhOB5Pq3AXB+SoAXJvQI/GkSoNhw5KdfqoIkLcoJi8wClCm424Gm1AdrdGwDFOM/iKTSPkrvMag93+b2EbQGX66/n2X3YRFNkgq/Gtb+2M8oCcAL054Z/iiMD67aU5RWzjqS64ePHsn01bJ7dqLgpMO 151548639867177154896951257541227014781655576169318283047778755573323724856619156348444192550664853912434681577093459933599575436686424046466113215132845213008587152894642577278656978304699131916299275797578171518984206145555369576872231567191579337901913492071976578289189524123204040497290426960375042970382
+AK0kHtacLGu1NFWMADq2rG8hpzM4UEYyPOL+aMJbnwXcUYptRIxb0YFZg35RN/RiZs4lQsiq+kEJKzMMV71TsJq59vMkIZhZoB3t8g9ZqBZuq0JYcTICDwRpNSttJidVpJ6P9sR3s1xPMYKdlSwt6EEc9htOXfZU+yHKYgn98X60 121583812047864398969816595368193171848971298823388059338224714026742264861090347096116404814514279627148994345584790617974476594451626305761040465570524035369799925437276511604752129817947910677564301623631349399504187314174538914591944778074509068973226322566160587813128746039859381466427380402262866230964
+W3sZlWW1Aev3x/DiH9MzwCAZzBj++x9cknLfGAHwgFqkLH6vimEH/r8hi85hzlCOG5CjwhoZ0D/Hsfr26ZJ3X4chG84byrfDnek1V9mm1++v+clJvlYgcuVgn2Opsba2TILTm1MDB+Ujs9brJ2AAKrE9+ep5nvtQVeG9PUGtdlo= 64240043913835461386212515483198059541440539167395859777194837833769712010594411295323900074066077107346806786205590345517755715510695858065925747020336398305793661773798243627926904542715123849691490667964262778804487343218972081260210371192903128886030021862362141928329650003493687310970684093289133340250
+FTQRk9/BIj21gbLwI22fHJWYj+8Ghdcc613hOtJ+/hQmh73HwTXLpaGK9aCptxVbpjW0r/bxaRjmgxu9u1CCZh5yRd7Z46Wk/LIPXGd3ycQzqRMFB7TISFQGJIcFoxRp3Eb5wa2OyrUg7c/D+kb7oFJq9P7mEwIh8TpLzwmu4SU= 14889529068556301710329043521845510156960298822469914567758538023025100741826628180855835334285179977296740667353391766487166458692144569279381035582718738461626140662441222061900764829681913534146898551570916312642104487829660946024590782808750587095559047648957238487820069966851521487428624726655438348581
+APYXO6uGvs9qWiEAkcWsaCaCrGJJCP2Z1g++XlJ67oZIgEoWITn3T/R2/c4edAfwUUzNHAYZN1h2dSrRoqlrRXrbxFtGOuRCUrXcGLFFcEbTrtm+z5z8xGRfcorx7Cu3FIBPMK5XcGPcbRZdyP1gBkeDMvuBNAo0/To+LP/dhCNM 172810804474418448604443090732221483571611665465870520701624598983692130272337358406272727413570938793741430131635927237320173996217984860203754686741782921346604605228620148450611724714868551781003004492587584071978757421616871762681049508123223983431502852926521520561941051298696758046005573332373854233420
+AIDNxhnDEe1kTJ3XGfTS8zKXeXPRdw5yifm8j8Ibzj/quExy7hFPtKct8hRskPR2qwTlMiW9Ra8Npg2USsqHV0rBoIkX7E3psxq5LBfp/q00l3SEBuLL4K2FGR87bPgU+Duk3RVrNMnColiTcnAR5XkoeWhn/r9MfJMIN9Y0FEh8 90449107125498302548188660544012777357148118984122992664008792590422284061463729084479315745509706793674355738023180454297730948397413371686013210006834869294564190666543874617716180411178090109573192518129248278410216362657350215009192850017507998797754539132540293137589672869131300859207213449571846080636
+AIRWavxYRsGlH0Yr0DudwrpYtbrByf9ZsDawKom7ubiRfepqYzcBlwt4adMMnkYSaXeYtOsD4KBm2ZvLKN3++RkYNmxgkyarORBEg7ERyiThBj7Ksw57pNHCAoHtBEhH7Wp9mHhuZtPvPgCEptmwCu9rYhLt4zZp+Zq8a02dkXvM 92930601962515884925250459851491509622611227724602941760145671636277317511265759558869239180653492283311584982044597979173761619470326096725838197524704577188104121460089235709339932110663536557497112887112782062772810759971739760085128369628777812332518137107605855679096146402427144185104230596200130247628
+AMNJGLcAiJtL5fUfkesWKYJurdYSnvsOZeZcrg7bemkEVjF6S9CcojimUl+ncr/YY5/EXnU0mg84fObtDxWWdJ7z7l0CFcoALTyEatDYKshT0xvdKY3u+LUShxIAyk8EcGnf+KoEaa4Mx3tg2oTBnVegXClOakNTWw8bu2ItagoQ 137134165107366719462230252606689766470445826753581409513106273517221906418464863733870948759313279128624638614534848890858250894834883265387344539280755177217350585564186248554307335197387734431939154077778003706720017441895613190141376534460438929588407764609772857975000507660651583780079804513519571438096
+BmGPZt8XqqI1PuLN4K1/PZMi2rfOYtHEMrcwZdSjKRm5qTkd0Pbb/5zPV07TnM0uLRvIQYTLloEY+GYyn0K5gDTEZpEtQ8ee6Y87zYGDwcf20eqYNxkA7FVV71vqCP/Uw3Oi6B+hMvsWZbvv2vH6MkAeADCrezOtwqVS+irftyc= 4480956865245875120472829476982311611308898564405318773810939350829150182630548948231116574193987272498161864310429976564278532538229396846813874244969927890037756969704618336242255039858182439641759659872128285423988638335967412040624105824571426792562334458751137508116412821914961236269913776304372561703
+APqFgCIYbJWuRyEGuOStPvcprj2PILQ0JpgwQ2jLKn3DvkWSd83qh7PWGKozGavsjh803K+ZzI7P2wP+Nc0r0El3q4nzaHvKaCtVRyMwbXv9wYLFZICeM6J1l9ljUMts4tbDoPzkIy3ScU7pYxarBWqMkcBU8qL6NN1vEdkeu0fW 175922170410080716883576123079908758276229469783745771772401183721225804343343063277676406040455068452258961299511343441961963941297631097736305638850193978800615558067791016294285848963023036905095022181004058235239390870177623185946205281141386416867569004073524130001309977475780893497185890756991672600534
+APA/rCcGeH6A+6ZwaBBDM6mB6tTD8mjkrOWEo/pK3MCZ+rrErMBnFp2S19GhlLOfuY8BHS+D834Fdm8+3wKYkWnXZpGb+e3v8ofOQ34G1HvzULOYtrEiC4ISZRt2SSyz2hU+PBXjVnplsHWTRxZDmBxTJdgli4ItAqxGCxj/aJ9m 168708388929747822981923386197903561880341990893945097067702518857172133291360611402092714329372304718329568897960770488377524912057166920574319430820488930520807742026377043178502591886293565177404635365772829346030773275726024973460121300339258054215286249329967181244588558220467488638468686270735376228198
+AKmwrLP108dCGWOWxE/6woJVLRi/Kra/DvdsPkkrZQmWIlUT7IvwM4gU6bUr4f6wpT08cIQls2cGh7dbSEaO0xLa3mmtKhPiAlzSnz0wuifF3JT9U3uXgUfCZuFtE0z7Oi7WTOrpl3k3GA7JFvXnY0lwblIQALVf6oWyNETnajGl 119160465301384937485959146028591622947513292915838943629387700439301197965652871741710280647524383590817798553034250156068573474278225305190573334054718387045488098320076877626430189054572361967283632592181431701411266656256255758079114072932140551282607247364388070762970060420036793573956057551235306893733
+VTe3rCzAL1Sljo3QAXEkAdBy1ZARHZwtrj6ZNRa5ttqd6/l21g4z3iHCeGo4rnE2F8wYTy+NlugjXw86OS+XojW5y6UzTtx0HX5IJ4POqN64aXWmaklGzroBEYWeuFFKcgQN3NOxkuJoDQ6VElP7Epz69kj5CsKJUwL0SjbNrFY= 59841866347633473702601462509811342285929528424012250265905695635971518533504187799047710303717472950129869674786231155102509311322791323986824635569605105662070745033595366004805920086888891275288347907772640070278731650628917037915863439204501060041944275512863990729926528905725569467329169134226609384534
+AIZt1xGhC/HrvpPASsvVIVdsu//tn0noyJmVYh3FdQ6yIh1uce47iCsrV1yvYqx5ZTbC0vnfnbjFcWqH+HtLX/DelgvhEwzqJ8hwQrfE1ShLG4ZjAVo1Z4GCjrDcEUMlwKcunuSJssuxeQuXwTLS92+q6QeBSS7OmfxPX29CLb4B 94399298271083745508290936113986978382457275531684761701599029877008571741877683365769553170771833981099580359640421358853566501815723434822307977440496208486103754978934472597505865596938563438311337045817621762649604204720249750058676095769230214181772215323235427976398686727606000594646472236822594174465
+NIhTPpWXS82VTA0LTd6TfM+HgLgUcmvnMYtLqPpuqCKZwalAycwl0XFYNyVvaY21J94j92ts/lRYgVtHDhk7/9nLXq5js/lsUnG8rWPHJo11JTxvW+df88aX0pw8u+biOWt87vc1MW1dsMTTsJFJAeBx77qU/Cwto95IVqM7vSE= 36889590210230649939994518345793530042252563793069578097360569338647730438860274349862767107939590441616825589851005429465345268710487649366046960918184701290985280638488938340668212498212581853679035928093386035688597446809895381618260692378376844452061580510108168030682664507293277674052032318576713776417
+KXdi4A2Z7tSiiX9YGtDtxUXIfQvPhcc48rUH+Q2SnXL7fLNmr+F4Rf3RiFBRiHKocPfE94pothop5qQJ5X221/DbEKWK6s+ChfQ636jvRxojoLMab3dPtaAPpDJHrfZMxbT4ZaDJT0tpA2e+zZrzBuDs+UUgCpty9nxtdm1gS7A= 29118662951481660380477444121362422614202367719725087486810943918529894738076273660245405874301505615796632229852040910511025841576465052938308369421493312085081188509808322692130449282585522349552501983296872614029139293444558468751646868108213623606366977549477663987815308260383403466635254115908032940976
+AIOTBZQR2EJJRmoWdRNFLG4fceoS3KnRTHRpPdllhHODqdg+QxTOcOvqIzBqgdD0JgO12SuNAjLQOiz0jhd02qkXw9Y1adGuKvL97ARFtNEuJiNzFAj7KpDLy2zk2rPJp4Lp7cjQs0fe8BQYnTzTsNRGm+4ybln/gse1YWu9w8y5 92394618277596007469808288231093678404089765494062813665106014405059399079199990128824492247005602685377185496959522609467906358619318009231448503013528692450191782140091818984176967246749464502089280153086163239846744554575017530385347720563798041108608545014076448155956762636929707905789978331102411214009
+NzfbJRBF4pqEeborJrjoknJgpfK+DZh2k9cE5dcElMPZ2Zn9im7desWGiBSQnu3KbTO4L/t4+m6nFTNcbIJnqbVSMDHdsfG72rG/t89aOuECQw0HMVVgONNNa6i/mw0jZSWnRLD4fa1YgbUlMd8jeqO9XcBDB4mVtDTxyeGa3vU= 38775530011374537813502898274019389132620116890266344603221997943675706375698597061571989090674289834838060050848545748579361837989319487970580969082824601965845786771062335733318139530316825802589479118956745739691326447349403950997231306042638797277408335778415717988679050762936401945487285814799382535925
+Y4BVPZ6necuLSwaqYEPeZp0lt9tTGFl/WCJJbwg7XpyvuwYKtzagC1NLzY5ymBfwGFw1yRlQuyGsYd9mBfC99DuVCIeh0JNrhJN1bNfoSzy5UO5+dmTr+dm66VGSRS0tFcViDTfCIleTV+zxo/xuZT+Bjxq7kZue8zGkjp42Kmo= 69872189501616471647606976308259279995249122669120675885925763529037695584466011511740991152346215507625265226811128801733353566555339153627478941716586678793853828514394269931890370517258825006937741437480128878717892485074131232336852490940507703859793477547154689914725314529986438108117871674332626168426
+AKCP9Mto4q/a2xNqM4N7PekbKspwt48OGPre+iqVwPrSP/jWKxg3CvvLNZzN5P+/FiUGIklMMFJ8w76OaHIPqKuwckj1gvCLECJEE+UAZWrNKPmpzd/ootN9/kQhNMuloTFCyhXAUUOXJ7Z0WVLb2u6fn4zroszSMBoWQEKC6lcq 112750701794692134675959811050012620191158543234019977304167102486465198271340022889272244811582365901584420008564301920174477182946432553537794834985703732129975734658113610563794129371053853971031300761815004524681756388784922001759202643614966614186697992611399618828963452661554240362943588548146868410154
+APOTAFA2waoAODECaGNgCHa8dNN+cjMnD01M+IeQFytzo9RLMzzzg/gpTUFpyLtFMcfbCkDYQMLXwE4crTimdz5sVvjGQ+5fSFQjoDY6Bw7MO6NAcLzlV/sI/1WyNBKaLQbcl2720n16tdUcdckQNnV+cC2J48CVxYM1c7QQlxA0 171043636512232272455501595416608280460445723238023572475354665686544174728784633443479486247342724860289312593374524429736857970220153680852977711594899595712511352458264354251161579203922747468321999465061463474727943140910084880926005209538535217464825087114791420210981711903880998556269523363208766099508
+AMGpxRlB8WVnsGqyyiy3/mzrPymtJW1o1HcDErK11ZwQV5PwTF3c0THwlnxDmcziLWHSWgPQwfRddVDCXMGW9BffJn+XO6aTcWDPmDAh+1DbWJPE1aqApGbHvQ8HONy90dQMZf1ayuwceWCVTuU1wnHdo9F/sIsRbuu7ic2OJDzY 135994898408425255747055209966103741651849229328236418804928584233229830656742052333413774490626915784901255640138520158698845938184666683995579777154437927013722740366497459963753542029774185193376253885864514386760437194444013834088425088260658670140534670789371556026135595577395047002643901630053097946328
+AJAw4uDYdSYkOrjtwJVWLv3pi1+UxWge4RmkWKqVquTsAVcT2tRZ+MFdHM457Hl7fmFIyxvGZQy4c2v1AbHEfPR8ID2sCRQpdcfrxEUZPMDqxfnHHm0ziny6W4X6ggdBzMp/sBWaVNTBL0e61/pELBGYNRGFMzGws7HQkr/sro1D 101254336834199527040756567675327011562230719161388328289463594628690618298993695452746353237675715087353241661592074446889034411683413957950360025295995263477031608845241728493807755308798509893719674568267846671753070163272328014412744008880395248474446310603301447848026040555910147467745595720879397834051
+AM09TdtXgYL4FI5CGNiVjf0T/AN/pZ5zZsBOi1MAUKMURiXnc1x8VKYTqM9Xb86mqNBBqphynIQG6/3e/YbGJgHlsSdrmKbo+P9daMr02I/7Z76/7Osa8+7Ky6lhVCbU3F0tBH4WvopkCQmuJ267afgvDD5kB+9uNr28deMH00cY 144124056591600568767398029380314564902309327093641173350205276895603332085753288682409279238417493662029954512382520307259348748813767324609446500382301421328754981718014234615523158887865271179104711373675849713359713282937065993613915015084108700238420759344034475478243507306107546245540340758766909867800
+AKDhK+/BKGXbrbBh2vM61OP8LN81YwlJKe68KNwUu4tjXlQg7i49Jis7QKPI/YFPUpSNTu5N2iCgeMnCX4+r3NAfivOao9lw4N3nc9bi839SIWdlokhwBHBYmCIgjehUeBAdkU4jKqlE06pIrpRmSvBtn7O4aWTbT+C++ViYAcGF 112973480670453665543892521898882856059335781900313607790238402438320486344365203510769919022496690291280873287383392088872774202832124927485754495093552572232234532821756395965072330282810574669371524103814871172318519695921477775100282448247625395376072233777533359104085023946019406729587713120941266551173
+ALxDiSxHjfxvP8ETvpE+SyDPTS7q3o3zCK519WTepygC58KSRfvDnIVIyV3toQKzgqD50kF1Ni5D/wuaSs62y3zg3kELX1g+WuBCc8+x50+kDtbHXa1Me3et/OqVS/QeppkcjK1UZMU29fXze6P/w6aQfvKQkE7koeQtZBKkYc0p 132203344567902304830160099595561253300484092355345272411265169562971473393256361094745618829297250316196312398486598077249124198329075791740755862221465178128527292695331061023291345396067863215552021206609309872689233899464919108147533679134727064586730810633196817136739658243232643507412032417747255282985
+VF0YUTvy8Mfi5o6X06DEvLm87r72mAtTdyyLNr0/GXlk0Xj3L2Oi2bVUMtcXQNRXg/mkdGP88pgdaP/eMzqkUU++vJ7t3UgOC1i3SHegpiBhhZh+aZHH/wjFV8Mz2XZB5z8MpMgN+QwALK1TT2Pyt/feQTsOy0imVanB5+OvCeQ= 59242171319056188000481457618922567543461456096441095927600135114274111606802456239311634638536207588762066940095527920532936960549439269891703098017342732142860571277442598349453761561189719823290643146391349978698217357430495238876700400634593256155537598291759795109752990651995982467695091946768443574756
+ezpwBt0N6QhTusiPcKrBvSB6yuk/KShTLUFQHdf5J1u1fgDYrp+aOWuXOFVfOd0bweiG4UxBQNXB2IDFWfYON0fBoaDqNk/41YyqXBSkKbiNWLi1y3zPmwTAiwK0PzYp2EPfk/t/j0HsDbvebu0ygcxb2tPqj4EQ1TXEdD007kU= 86533835313999945727720083706940213467453975054116752898416709637030456504024135513972566184073843025739226187558143854850980654667596935003124034699919861200483994576288766702308068265526535622439762454501169018136389983894783905946543636163866717367545972667876983557989192393479830223914708619684891389509
+U8BT26zT46tTZnkmTNxGUAlXbJhk5cNi4AMSd8fSvZHm55siMFGJ8Jl7mtdzEFR1UFAyEztf2fUhxdtMLe8ei/OJgM0j7myQ9STucEwnsShT7QS/DjBmfvcC42sl1CRpXkb0ZLrEJCPf+crtLKGrG7ExS1oawIAgALBiMQIL6mE= 58812148564290791415180898639607206220554150794356494356250223429674091688305329629529905854147200457536549527135776329004085047145097927266797668252160196098870200925284256433894773392353678965699083286106628662506590268955650280670838340651598082083455821825076016227525614626726458235627297885815646710369
+HfYii3U1SIkBZl09RHaGGA7H3np+qxyNeeCNY07PDl8LwZAaaYk/bHPeBVboan0I2X4o78zCD/gFXFBJ4rxwwUsVjHEioyO2JcpV2/oDOelJBD//78WzBMMSWt7ZKbJV9uYr9ZUM0BUD3fsk1esFCEdnDJdr86U0UMmiig2R+ME= 21039655953870571289679214995029926285040274249531458675115179004718812090027267801012507748013357317597416722235988917212676802092082137617336199787762782958420742299451435320649616271885264333948336627286638368859041172783505464468640994920853000441536629081040963398001710173320125308624362209157720438977
+AICOlee3daFyqTrTdtWjVb5M2rclh9BpIo1CRvKo2bF7NYcjrU0/VvbOnTVXDwdeGMLupbi76f0BrfDxYtkzMXvIZlgoTit4g5ennnklDHFBC5cogaGlri8U28w4/h5oMunZ1O4ezdpRgVJe9nTP/sSEMYiNS5IA7Zshdvm/XccF 90275777798511290102824338787811725003177532250296755103300529948194832904403489332420505850668003332750291879153080212231952155092379375422537931240723308384652734942204313672973885652497290433943089371705605128843469306776615573873479312715317072986990219294942040272550822460408702072075001377245051602693
+L0QUSVIjxvE201b1ztRZyOOxy8vkUz6626TH4tbLwXjjc+AhmrvplaVlavnOgHqve+/L18XNuAYP4BqdxIcWTx+yxBKm4ZS92dRJdcAtccvZpEJtYjdJvI6qbL5Ph6HluaVZwp4dyFyXuZOJGTfYdTb7PUWM0jNT/xsqyjxSQ2U= 33191267986826803728285073844005357792766429917696698533494382218509532051029343127452480789088572904364699220151221680328978554239767633887572649589456766209242252549993823283929686430100804479376247660556781589549613316880150951333982646510273364068770923588389668733632648346075516618646974067295703417701
+APlD9ECKJuACUmQUsbd2GTOpb2PgQVT08C/5hyNEVdA5bWoICX7epmoCKCybdolk+cfEBP6fSz33j+Vn8MbeiHBLdmF6ETbmcyOjldJ902MDvU8dqAa8IgEZN5Uh5x/xzN+3dqk9o0ji7yi291u90rpfIh85PPpDat2B4l5zs9i5 175040148659257809883308984693597046378367187659749953472629929701758633206586720399909808941145946314755491399962797299295431089674294356220216615950668954164397362123668926410543898553191541662075745481299747832013627018846822876386760538344447600390187421938699064459451308870669878673306013635576901916857
+KB7N0tE+A5vFhyrd/m6Qe1wTihkjqmBn+rinfmMAzRlvtxIBSyDLzQsOQs7L4oTG64ABU+YwcWVijvoeZNamaxGl4hatAH1pRqmC/r8FMvC4vqiFTbFHzQhkjM7uoHD1aKnxyBVgjMj0E0KZjrRxydZjIR2p13FXjLP3UQSFtII= 28173452509830313810392326357601136401754938805266458365469366750775669869895498658593356375710132149836430968810246171974040975430205200958564616924399794768861923079158311829444850822144940112488994119845741191519421434257276977333662656888696213514226866147767570046232093727585815615828360199830275208322
+bxFgV7eXwnbQScl4VzS3RTdcMW+NY6pcGkT1UsqHIeDVyBb8DnH/2/Z+DX3zniR1iW6FPdvhJJeQyPIax1ohILa11R27C1TLxGvTrRBGUycxjEcBIxamHveBsXbECWusYLEakeSDg9x4BTWMz1rTQajkorBoeEjYuW+xBxQtXME= 77994515143740690952370766995249847650881300682406161400195705464876513409097078624084133111941171517535435606295232558665316819077765607639545069239931096306624817379462598756505457054433358548941076472902905065316335603665413114267741896000877284610377452471067725794013283338924419969559537339967562669249
+AOH6E2eBzD76QdTJ6QbR/7OeF7AagUif9pEYx7fMqrIsXCJKKpLV/RHIItCDYP2WO4URCaVueoAJe3M/Shj4o6efvH9pf5Q8MLM0rn5MTHWhThivqYQDwjCp1ZsPgq1VFS+gcnmwgHhj2W7XzJxiNPeRXlxI2vL+XTT/wPBYhqEP 158686346608862569574095184731081143351413141116869402750758091813874232272198082464382169470744476593016502816563462778075467588097653320101723165887488327616477297401486647183409348122990505635004320879840358339260797834264972100385692477324858942142372580281421734058008608134075577990829273447077276721423
+ANDDgNXOB/rXwmS4KEjiHj7RCDocVrMv5SU0aw6AJzNTBfseFngqidXx2AJKOEeG7RDDN2gzn4K4qJktF0AIPG2JbELlLUu0MFlpOLxamp586qyp67Cl9OuPq3UZTyQhIsSIE3VQkvxuQkGsaV1owDV3BKIWQbQEqMQI3yT4ELHm 146598844784260148346676185962272439320781765598895126402049215152385925250917998794921584290777625240122575975327405909800121511343265147922400813488099624745229653124857224399973509428158163452130086943873214460600035260925149630502192183407327427517292065083168010281295559088633086659209316582810260124134
+Vprr6oBnWuxIzyTZjuxlKSdZhBc0upeNBHVIlXpQEnN1Q+XURKzp4/6Vg/koITftr3SMSgGpE7LkrERMGFgYaqM5XZ1RXYFKT9dRJnz9VRDITVZtdkDrU04bqo2Ur+jvZhvg/oHBDTgQ4nPLJfHO3+GEmUtck+g/wOVozMMgufY= 60816213163057201559480662231646403262735082707152897397414589876256824040344252799972529759737904461369360580708093117244392116003622336721789703580184437841209963565058475060017600871779929808204093448248984201640754565635410002090180110910120481044515630478472999135146756643143415057403006410330361346550
+do4LGsm0afQLHl9alWF2RVyEKPxLIErsf4pTPgScRE7ZiTSVErbCDeyzd/KHzhBLQs/DhHHcw+OXj541cIRm6jaLVKiT8EwLW/dVG0AkVli83sFh2f56Kk+bCGSKvfGEQcGLY2k7nQ06zoMlYR/xbZCka6Q6kSq4YBDQgigQ1lU= 83252051731120517035090523892596419800592471447735288551342681962005778435125655090199060145942826521644585427683714084736143440310518046334877897672493531918539106001203807757254797471481884534543367685912500572052457610702790097953420236852480969038388056545966568595395722585797418296411673622376893961813
+OL2Qoj4xkqRrQmuuLwrABG3BMMBNGjfBtVBNTdBf7g027Ghkk/z3aK3jKT1EPpdiOdn8zXYBSO1mTRGyK3n7Jo8ICOcnlBOF6cZtDsb9bvSVE26MOD2wzl6irU7vzS+s3vGBkN3AazrxPD4czk3xezA9y13DJVnNzgAgIQHEols= 39844525812817530522650122383059885756573694015271773938493414420875846359054562126060762455794481186614035892021706051863945033061233991184379580556219478200155757966121832613842937722944431875100059046588723473670448006803481527981834627086055642349130254917244469014754132003347635357123155857820000494171
+Ljgn+3Hcg5DOf6usRumk7P+ZrdTBRmo968HdZU1mS7LwLW3Hii2KNkwMV7J77zA0P1pnvhMSEEeh1RbCUjLtSIbt3RIcOEoc+aO0eINF8r99l83xF57CBI3MDA3AAbtaYATy/NUXSC2h4W5kdsQuR88139MFi5y8E5njqxHu3UI= 32456338403763561215581247445990611953939298888251578685087656354454727113846722731945605696397627662593375001096230320486703167389461057538581895745078593206660798580358701927596287363374862536765135996838944212622199018632046955402325290145163082309469649329852148345837780541107029165352782710901375425858
+AMt5/u+ZUNm+Xsucr4RQPUu6ExAOq/Jbcjm/Kb2YIAaEQ1czIL82wsu6YmpHcfMaxLjY+EnaaF+eCWQPeGd1av919+QFbQPeh5DT7ZT9klK7BFyVsN0nEDJQ3AMMJqq6lm4sUeVxDVTmMypYnkzRl7jqzyCRY1MHA+o2LyMECdOg 142886089970163885609957244378225169093559131065687633458877059657380607541767850701139140472705242750285722732461954100519608059127637509286558848391554697942686619832870045594188204522385787253648018847569919409782188708374165437385572046835539379151066214153911415525465041951116179326632238059135825466272
+AMvXeHCaa+zk5VdB27KoS8XpjSUngaw7Gwlq6e2RrkEOxBhU2rGWGJ3fhq1HBNRxDf0quqfYTMd1speisaEr3cIyx9BhYwB6A+Nex/Sf9DSixezhcgEz6c5CfwUYP0QTTOiZDqzz+GcjKikjN7DKJTO0WSXMRG8qX8FBbH0rlc9l 143142496664357119491819741364830737485524654099662921673419335301323845847085335210884201567922636945282124120681371777665458057821603161276185071778040317947168899788341482064834489328957963447735297898161379277478278414388733161844053774747425459239004132791029364174047523473372650441001639174571312926565
+AMxoMXHfE2i4khsAkv/lPtLQhbWUjP3kxYmlJkpacpicBB6z/TmG5zjmTC/sqzBvBn3J4UvMzKYFyk9/l7Wnuc480500a3S4HRVtMtirPueV8v/SPktL67eN2zoj1VZA/Rex0aRGjW2CzEKGwEn3G2bZSgdT8hKv7AypF69ppjz6 143539479941314279463880342636704987025205547180882175105616955926182352311179043850344463145750154442573797875223178075233807385237935671604701513551125937539235111702655902037518920150424691586943553275517626347557879039695678271564616114192941679606063184290901862703975921261779714258077775731727612132602
+ODvOKg7l9RCn5CePG1FfMitkR5l9+7JK67eU+WeA5p1YXCcKS8GbYAKCtXPD2QfxmQcrNYfAc6Yb/kksaq29oW7MzZuTDzK0HXY5xBc/fJzEuvU51gaI0PR3cuU1qRlLqwmIlyt16gto+2E64BgPgIKJcAjx+TfH/EqNeJ77/W4= 39488587053253042573878502921384752550143716864908041972426777545317969264945056510991363961916339225192727727267483337259701961148978214005913510275048195308792987888118270387288989623193626554910652030960235845935461155296845475356011099372367616732243132816329531758943935324760665826550992788664237161838
+AKkznyQtB+PGvbVroM5nUIzhJUjiNj7q4fC9sSFbmDgvehnwPElVlie6PimH2FKonGV4GSaxZ+osil+9omfkb4rO3pq8fy5KcFSw/gs09X/U2eEEcUt/4oSbjs2NaMIxQftM2CauULiwfkWdkMFTBkHnh7Bbyocc8dtmrBDdoI8a 118817437232756222334188081193205110010964766506378146125932730686679941224328135190204402802650523704343176483564284220367074983943319572348376466341132480772885833789613392397284313483009178508647973749522358005819092779831781339778163122774381387989185969990310049504391258988402795259963134610905036263194
+AJfwWA7XnYbTjlJt+9hO/Q/OubHkUkyMYrN6Jd0cN5MG9Rg8W3i8U6oJxT18p4XozkiOgPlF1lE7hIAW9KRKJKGTue+iw0okLq5UNMu2Ha6l5/wzKi0QzRVTBnQm2zjPlQpgUorBBty5mcbt/B/Y3vOE4I3iVXklVtjQ7zIBHaNK 106695084438708194568048926154027115609888551145480521213711726807296356271397749432698558860759334362315257102647885062353922543502466463770991058956633500180245599467233361812610650830611712448187310827443315947425061886163301613989593906515923245020641415290300558869209909418659128196109640872398602216266
+aCXItk5XhuNrbrqJr1Qm04U4y4AzSKDMms11PgVcdf5fCGdizibh6/oZqx5OitM26nRz2vob8F+ZIP0CIyIJU0T1M50dVTbbpwuVNdv/XI6gHekQt0d2g34x1TQJIcsT1VWwGWTPNMtht1hezBAYxwv105AGKnqdLiz04YAdEk0= 73134927546833985031652237686088635686032103401394612286045377544136784429757461671691980910279873140130943470029643791712859175007885735170485461366406852784845528918253441791024065848540598601036357817496637108534035807393364939272891745520961269029038360205258229770737579266643408540634722493263322616397
+APNeoaWlyNa554OtHP8F7GAY5V9F7LMoF2ssg5wBmsgGFktrRH1C4FdyD0COrzIb0Vcko1/HiTnA9JXlfGKc3gTHEnO0gxBSDjK41L+EIgUfR0EhAD9iftTaCoBM7qZN3R1MYrSz3sevQZNMFOOnRrzwWEXnJaPKAZXvsqPzOIF9 170899982929163229592439208307232242235219591108657660041403142612622997092685093132858257827585941687488772925553142105567685213341947938835403410054637382864108739466539574004149772568683507025358331323655651148107044968424043673850583150424463706583215452211942132017052425497789362680979074312857823248765
+ALhwBfBYpOk1pfJcNut0C2fEAd4hhYU03/ZQBqVe/7MgpEDjro7oMvSdba5kjH/VBssmZVqpvuZ5lG+vI9lXLukhwRKJg7m67HG8lZXvjDmjU/PCjxBPNt5r8/DziETYmMa+fhaMTw4hedZcwDe37t1VPIflvM94sBKu6be9yJAn 129516480651398210587505113546142851617282590236388547627336279692965778911450075230961856270046942312918567973875005814982283590898552829322178788678196583244198944578081007477482775130405341039067711963061287597331433268366003672643052056973656674139309732186091974604170508497340243515339072325943686631463
+c9vpoiZvtnj71b8XguD67WayOF57QgOX4V4L++nG2u/RY9VT2+0tJ/C4NIawVa7ScQZAPVLuhV4J50HJX7FZgtY5n+lwMzNo0av7i0IqTS+1BBO8eNJy2wkCbWWBxNybuNnF6OK7eXdPb2Mmwm2OmhN2/j7HAr0cD7rK/Hnif7I= 81358980280155473712258342299472964374474635149963153129588784719499494479288254287754874893180126149146558961101860327826747785201363745989346818037655063262173536227595206355647880155693272153902647256175878517626925488264893732295267833614283963802283320574654949992393798458265266551024756663538388467634
+APArEXNLzDydcHrieLDReJryWxFzcsN1dxjpJIVGeJp6itsJOrUtnmXVnETtaZhWsmN3/Zh0R7TgJ253f7PZ/Z2xCEdqF0hs2MmnERSywdWZQ0a0McbDUUaDjBNYFht1wvS6djbI1b8RfayrnEZ0miYdzrrP1ntU+5cM1QBAvj6T 168651870043094856205824264282870999215855903395882323164614939540734011037112413507417141209480771157672307388419164831992909066097194364645695794831939514470650008210390333649278806163193463937050083854756730458780288720541495880958909249273048328511615821480782977316719631334570687241232556472064072892051
+RhGyx6xibf0OvY1XjnmX5na3G7emG8PWbvEa1kIjR6pK6K1MrMZnxFefXpHWInFS7ADESNI9LHjZB8VW5QrjRVPMksgdEAlkhY7MyQxaclUlShFl2AfKYBfIIro+vg7mUMzMctD+07BLk+jejRHtPVIxHmNnZrZYds80ve5z3Xw= 49204219353786910100605282012781696579642953908541693903348594981245301165936599174304121350092894937817100350990938057159324959104937469442065996667276651025661016077514839755853073999975805394464570132481314896694678249282338429544941873047382467276103868995474424700207571657816852575364781281563515280764
+AKbFfU3GL6NILVyONPVD/X0tffk5HS//7FBp7n6JKMXu3VXvWnfTl32R0WyVHk2yP0iIyi6SUusSicOH9ncO8KJHmaoMGN9Fn+Zq94FTFqZne5NxHmCtwRAbFNDVGg4FeemGXEe1S5Kk1VcvWqnp+QgY0uwa7RtT8C7/T+1pZlwq 117110890075563714812929271250884717870581483065920538069845585667296154465072587148155060755111295509684258790280104272121160614620669593483929827848744548171793187278583947500205314283462739235860439216105116687015890394925743036369717346234391524403038196640934551590543386844279091801685432977718405127210
+AJ0xZ9dfRc6P4W31bMHBymgOq+38ETEIMvMtr+wB5WTcsquZY1IUB4IVkrHaOo3W2SIr479IfJOOQhmvyRS4iB05yDI88Z/fJfXarkH53gDivECuo+5+JmV7e0S6gCvOuVamwoQjlK3G32bCV2946ry4EyIsVZ6Alk9xk7X5HfGU 110384671994603894282707302829898242894456931176497230904862171369974466400767175784681299142670706023468915238955836087425993929524341269289746060546848852729416925808186253355106621584826213979718185296723694190658548757311188764342751280681935289121682174507629679900374674992438818324999211250580434317716
+fjzmb1D+YBU5Wn1GlwhxjiJS07k+fXxjeNRbOv5SjktzxOXmautO8xZ5ACOlYrTt5G2gzW2PU6sYNfByQ0xoUSyutOuQlD2r+8MnDrxCo6RxT3P0dUSX7q0IVj+oLK4GPbscnKLfe6KqUcYLMgKnDYnc+ztFD+csL6BQnM9WMLk= 88647261832601702291191332432291274285041869480562430895152086741320122435409959711452438332192792226899741738806447713240934608106883094466050154088410020909933636902495700779087737304255058561688767369900548260278700135161077055869478387490726087630962098228537973426295306997128615315548440548541717688505
+YDg99aHkQSh9RjytWknbXzcgLD8MrWUEHF46yQLHYANKXaQYyf3yGM9TYPCDUqWbOapqQe+XfOCoACLyRg7vVDsnOPRDI9ZFUgCQBNG06ZOxzktEhnNJoRC99da8jyodFqqk2f9UD1lVa8tsQdatjUDocwgJaDAOpYEyGnUlbXo= 67567767932654827067250684965667741848878457020992905661955722020937161710030993261011062929936964216357930453809610708591260182295097124272956485574313839759737390934220465669626974544253750900911093325004172643146669082793591441922014060981070503803266774197958528843445580649512373693546027107823355522426
+ANdsfO+cNtWsbT/QJHGkYAL2WCHWVPrX6oEz78pO8lUwiigVEow5roLI5Tm7GP7XffjF95z5WDxzpoam+Bfp4za75D6ZEHQmuFnpWQAmNLUHdKUE6UcsWN1rbV1uY+x+Nr5Vni/M7PfQi1yRTTJTYav40tFPb9rY48FsUotivoxd 151275723772668372472508916060743043308364940375633847663054782759325087560768667906829087958412643723335046123025802453213225972572697773468957759328009026531148112732519692142632237595562259864125679649273054426879080697360204352423668940795473103047320116317252295126635024518179060076282921965794883439709
+D2Z8YA0G/vzEVVQ6itLPUC92r9n9FKRpf6lDPWIgpZOOfIkukPp7zzTlo9Ej5IsBrZBbtGz/eYmlHeZ8Y9pQj8HFW24HeKYqjmR0ujbNxI0QgoE+VUwPVg0HhoQsOGmq47zpXpkDwpOAZbMh/t1Bafq6r2zM0qmiwOacJ8KFUas= 10814483230552506566705634583020057064935800294861277580077052473134972003523900930560478187758928889017740705417070994563709463926267126567504805864719383185267204810142444719634360655595490833208838383875687102074846353850310954150927702228780599083427768247170427544730791038729428517279760042619935478187
+XoZpSMHqlOyPYJS7dWSRNDJHCkjbo6+DECzu0FpB9O8bftcxan/06Twbo5d1lEqPlLx3w0XeWtrmCSCaeVcXVtlY3QuPjdKPv8LBnnhslPOVcbGyflaTPXU+ITWE6rwnIF+yWQl3NIwCV4EBtCT+3U//Dt/Ebif9gzfKpKltD6U= 66377743237695515693282032069691369056215169443985727092982918806809030742478033317158686828712146024066618073633406428345129492010236994055590530566431286733776441810601990431112187030942086686719669823512292071202675269428014136307286941704297995292544712278047959299939833088742083527714893795660235870117
+QUbbkyJQ0Nru9c/nPbphM6VxHp5DWlai6407KIDbTGvUReVYI7de1gO/BFphL9GA7gDareYoMuej3/SVp8lEujXywtXzjiI+j2TzR3YYiMBAMhsJO1wU9pxy69Cj5xeFFlrOycjE9sPS9nrqnEEEFNPiK/GDDTHj0KuNbWSCLrI= 45838919357034925862751142472777409057791233610959872523563363744902783251621354580995921495295078179996083468819097423327554678806691589090814275138081407920379810144694354354954459732280968086760894209634364189264517251735804373673532012530665557440070501687207620525228416650281363557992436992284712644274
+F+uI7ARCeAlnPLO1YR7RJj8LyhtE/EJMcY45lsNMff0YeENe8KOITZVxNA55FcxDYpg9sKi1UV3/ASqkqpH8MOxWpBdT2UwSX3oBkp6ETfJKqiag0C4MS8cQVsfcKF39BJ6KUE7X6KUEj11j2YIIRREmLPyZ0LatG7dN7Rmv2iI= 16797235966984072293396362937533957334369977688369659112225970370748312376722010874726300554329794854683394163379447263409228872034356195791733533528404245739693397078461712458035888813157166614479153484688995068722288153129390850561042173295997770817893349738328312152341860704179681230323810266038959856162
+ALkEoXznA7BJlBIfA3Avl9kygQcxexEMApwduVRiXeYG0uEXMQU4rgMJBlPqs+ly8LTIcLFaLnJAG2KFQn2GXz2TNa7w4xkegkrslIJEtBWX/lc7VzRtcLbhaXEs0Ci1ValnW9Up7dYOj3Qw9eNo/9M9b1fD9TI+0QXFtp1ge728 129924120553920201168632484268654219915712271781591182777925696006023100660478316445751842982460082888615429513674356810187315558964251402722465707617058251479494744427428152566665405423424700027316505872162698141109433045594670140335040479559124757490095995568556894332243767736124299898808796118800328801724
+Ki3FNTEE870E9GaNtbT418CLSmf++s6Di3hzAy8NgiDOFo+uuicJa54V3JNRxOBc99sl/chfZuaBQt14BFOQ0i+9rm2KD82okNABd+SNfXOb0Ow2taZX8CpkVJYDyphFPyHbPIKmzwMShNx9X2z9w4++tJgzBzGcFTPv1nhAlxc= 29618953883711174042338818332957726953262658484143534778541769862244883781157097499904047532839425875312731531093860721544220959674634750905085721866390609141599426547378130082409488797303960018348798930232014390380383063108812922828160584483043190739354817699497573863286563890071313017508437166939160221463
+AJq8tcSnAq6M32ViO4hVGiHY7Tb08cLVyxpl/v0Y5adYblvjrbsFcCmsNDi5PnBOBl5awR7KZdQ1xgq6jIs+SQbccEMvJvGUZW5MgcHrXBj9XVd+8oB0z0eahqXpgYBqLDeHLU6238xR3dJYFf+Xrcrzjg8swx66OmQKkAQVJtdq 108660120968150664552423780971948386965268856900017812123107864829782135741514930439461240950044759098603910762272795612101834680870627850178371693837566833495418727543557712057554231215186486008080050486837716071537742708913279026303380104388546316647349432118287628353129105425052237438199445863950767806314
+AI3mfrgcRwtE3mA12gSoQV1xyIGy/YA4pCCvja4mTjvzQOAfiZL0efadxZH5awohCC1SpZDCFsE9yYp4LugHKu/A8zMcp4k5ena8sTPDkSod1yucjybgmVJ5h17Pru28AzHQ/YUmCnojQv55aV2+AUhxzIfojY+NT2PKRqr+vuf+ 99645829268436288676280252226747461064597487404802430565833102291706103139410465131373666856042539909746769688396958963177805479987372681967013633920910376342526433530508868114301205524789149997372160919406352823342811006288909548557622230243808373083272214426118230701324879006645047374853535922112549545982
+TmXQ+D8XFKSclXwnTIH8d+sb1IV0gfm7GagJahaFL6A9rvYaZ0NTizkG5DQ0RmXyo0wPmLork/296whsdNdUxVAwnGFlWWvMV0ftR1fOvN9KoT0WtVZ4Rmu6Fuc7q1PskAZzIp7MkOAxILO4iX5dNuVC+GLZYIbpTel3Ga8fXuU= 55052751096768041533898435453266875315629605001878362193939750978427494147944918632414581744895066623527980497732722163665712245580312596487741856071020477624754815927936394948233480228964159047139170955663289543349257377302556035170334384320502468579367401821986660515827461352578142560630318492817238744805
+EF6KIBWQiQoHOnBdJs1p+WIcAv9ILt0cnQVo+o/2niOtI0C+eFBSiNgeddhotkQFgHvGUjq8BPYgtLC8A5IFKGzXu4SYj5ziagka0hqfhVs9zVHKNx2NUoMhPDG5R7+giwEGGPOayGHVNbsBf1FBYG91+mwy8hnNbhcHSnvLGk4= 11494909948912248031301686864833544028186348338729984264372557659364976118965740281229664413031002362633393381744365783802034700038490736736266032000546393704814403638058993380993275865674190555703046732456017652317200288968188655019374159412919163798248766655991273308390043613040731449231289437754791500366
+AL7wCh8tkFe07qChFAzRkrnNehvda/Teroj65X1Bmcr14+/zeJlZDObYRYBOm8YYSYNgJekcL3o9lLFE34sCMbSJgm4dGwpEVexiLVi+zc8ndnqBDSAnRqtC+3jbInm/v8l6cUvuzrUNtzXIQ/H4FrmPMiVy0EMerkMtkfw5GBsd 134080980697158076909534078193319899756347955848461100874771253577754225619652121295523443912922220564492468474647193062555347746840044705102003079330399499915801536721237211615317000955332058281901995149084303143543150689010335818219129745452688372571010816270728441637278434982752674030696337642893239393053
+APunLhlblRi3bbRBwSV8dsw8h5SvT8ncAmXPnca+e1dLzrQZzL7P2OhFope0mW1MCDl2kJPiGTdK3SiYJVsAFeR3r/0z96g3oq+8uS66T6VaJym0QToMsqQF4/fUMaTo9HsukyPyOgjVIU+6TiFd3SxQKIu1/GpQWVQIP2pkHFKM 176716779397275986910036615967409090183531310366246043951791503601618945774743601662530806467045971394247287367421508126613573039423674729894091424105133906122821596079925540513892022311039293333114333317886304014722168786051080135090242879622144693440448171583324154550086458411590240882982297314605229953676
+MM6B5AgdJKe5OLlPzcXwi9WhqQjx5KsnBYxxa3kWdGNTdk/IN6TVd4Ptn8lWkLm78mw3DXP4Ol1sQbIfkHRoKFUN6TaWg5aDCJBDXyHSTZI2FDc1di0Te1SwziYn0sIOe+R+rfuLuHlcT1xaZBgL6+dDLAZaZza36UEjn5i/pTs= 34273208848307582992498656582721015257885595139328466874135636009184357438445251703533153492315835793684794951576799764181908090765379592683793969576893243386892292517067596035059342970830813419330530731370385186653239446376170533147020072285887964430731437765184844167400169982662183791828762458682426369339
+AJK1dx77ZA4F0sYCgRL1LKSTvjGTKBHd4QBeVnE6FKJxIow82puqtsVZ7TBxbECex+LkLQPrEbuQaVr3giUDjg0aJCE0D9ZVXCUS06qulqcCCdWgGFHXDOQzTWDn6TlJCGxtTEMbMxSlUq1q0iKZ19kwMHiT3GydBn8/G7tIYd23 103022457217861194294329435482792508957642944252832971366936865663608381648431732294396977429863681671686490913575377744795372643599438468695483808375208871881849232129651519218503507811863794426234594709451104684234156597418383183271923307418704786548452806494411689822939919114966188329657999811363991575991
+fPZNsqUYBbVGA2FAiglnByxGJOZkVSpj8Y4QNW5wq6o/1e/PRwp0TLYJXIoCJRs82pAj0QDpQbHl5lCZmNxEIQP8o8xI//HCPxPIdgBJmSfm3VGetrOpqEGU0KJJqK4IsjoVpAfPFMUMOpGNz9CSvCHGk1AKrtYvrTJEKmETuig= 87751387019308584846595931543798879607048239290774788042055795835726250309378365187899578817976976035304304847968410200168743967600896348021636654074952051821111673620467434295067182213181329543946368332581250062140819766061014427755090798550122401239987766844126425179573454145697756278292448630509686471208
+EmT6DUd0bxcdprYhAnycQaxm89kltJOlIOGFFRmEK90H3RhzBGr5PRVTJVqemFVpVliO1gy1nPHgqDGVNIE1GXhrhyFJU6m+HJeNcduippRe38xPCiuraRkXao79X7WAiVYUq6RIH+UIRnfTvHBgzTwjrOvKJ5853hYmGaanjh0= 12917015385266582065020051081997430892582163827812227349569911846746592973268746845211126663077128575098045461893559476227689488349263954564361736197688317585888118974603264677576027836032271531903881104937422976121352854003385726888601980526287956222142458858211589791399646989299770657341412683499692330525
+APtOYyWzdY1A/YU0SGrtjPdMZA5E50Y3hJVXppwuuSk04TjXzcbu2Sqp7sMnKYbToRW4nB5p2UnaLPhTRy0yszOd1auLngW+0ttCybD6nTcVoP65gYOwXGfSEQysqKLb1OfV8kYq5Ba92Efn+CcWWWuS0wEr97W5M/Hccx9bGu0r 176473215292413922394356058789571494026727424839036665031567966488209592078148711908841964690807374236235612412767651029865069639786447019874344449598703213025389428836803984245755885691094364960118900160737925054803955567361126391353868279642836569627177281508980029006921064654964339077608785831304875404587
+Vs6bjpYfFA1R/QTeCfhMuZLZ+Zxo6wxq1jFZpi5SBR1LaUwAtOAj38OJC8L7zmxSOj/RGEmJHkulI3E1MH7P7xlWbY468/azfot5fX9BgHrtptV6Q0dkBUg7H91+tcxdbm4/V0HGQGa2rZp+XK1rO+U/d0ki6iNbsCsCR+OeyvI= 60957991334776853645581868230398759578123373154273044785333939425321390401088800849629483265841435899835570419798325123273632247193463641611211088549152950252041797959644227170492417662363676228611376046334386877555777556575818860902071813120592757466883038430756577949025778080997296219236534786815367760626
+GiauT9A+wmwJsFbS2OPIM6ultIbU+kT2NgACn1jFAy+vNBahdfHMCH0jJdCs5TbmKTCeiEf3ITc5TV1OSvIejJ0GRkTf80nY47TAhiP1aehZvMAv59NQHHTDUE1U4TPVYKIyFpm1V1A+JBHKJzuGrB4lvqB2ed7k4m/ZD5lFLMM= 18363925023885496669420377869542744504974590667921570026763131637088916425434675950812384919000566852243714758512996458727914094904422651029609645299422563453163291342992902510788457007623888307499601267675322986672697397389663297565071582648674012080122614260400848960757021864980761735684874056409664531651
+AL/9KOZLtZu4+ZQYQsmOgbST8F4RV4N/Z+l8qsbCFlHdXHqTTkcN0chsccE/3KkVTZsAnAyJqogbAvB/RZqttaK5a8iKlOEoerUS92FVQw/42WhsVaFggR9cHVuvCD6QqclZjSBQKQzUMy0YWPWlycAZDIv96tooA+V+Fk0jbcFs 134819194171226950171930028888667967094069342154233489571728632904658607624703819928943642011918061760802468868660586005724399808048609316802502143143910585363214684061242274402109137825176291816945489430125510625857564490981683683589784133305376252294774711594646923226452625156299996630452243345104727556460
+AK5x2N/4+PKlsW/fNrw76CnE+nS76Rd7Ugo3IKhMTB/IuCc5xG4MQHo5MlWE0oVkZ+Gs4CxUpvD/WCCjHHFlSxKG4mC6ehz3NVLglBt+f1RWfPkF28JPd0UaIOG3um8kG4J3JDN48PXOPP86A0H8ZYbE5+ImmXsGAcwvScUQRInU 122499245103202714319465533564374494931278163571999934877854825659720649344163774228004853964635693562785966889622928722984134944784141208867445419597834322541679973956606275877526560988151196822256754309120410807075405427166696093800381410682490767468563176131997424692783482903880902119461752084196789357012
+ALZ12i0hqFhwRAikcoahYzH/BUolhgZ9Jz6adLvvTO4wk6LLOpNC/zCz+LjM7HazZomT1SqeYJ2X+WeGFLADHuWo+Gp/I3S0UEneYHKJxoU7OoOtE0mB0BCncLckHao/LmbpnQpS+Lx5bRsr0yE6oWNea6gbyRm/R0to74MI3/KK 128128022342420083856194424802390993133863171077961467523372211039771843125192435716337829530528063182315478279257832480290950255315151577221042903861075751839976362752440630888566422581799720709574650482021111126414843635330535518992034746102956214991673417580508389225948159518319625680855827280146399752842
+APXxvLifWgehdwdTRAJP5KrchRzgbUsyMWKcPGm2ZkwGDJjoTl2LIOOGVFiL4CyPBxahkEHf0nMxBN5oNGX/Y4W4PuOAC8gMgHzdLkPWkpnTcyoe5DD+fQsqNuKVw9nvyB15fx8k0d6b056nfFjnnRqgybby7MSllAWSKRYRdxVm 172707950911363219032118650562553641123743396229371815589867086054370029540557395298194067635069298952836929253340374819975848769009260895874615676938511747311585257140973518651959463416682165208985512233703837931718385346209362040743041262031997793519095342415901373534535662377972036003546589624834285049190
+O+9ohtZ9SzGLJoZM8IRQAjhc/GPt2X5G+M22ZidYjx9WgOTrZDXorSyxLuHxay6djsJSgjxYMj8MuanYSn/DzPWBB1Gn4cDmIsfeYuzO+vUJ4l6d0nIvBg9Iqs61/PGFd46YxhnDiVQ9HEznyTjzESnNqc0+/OkQVJcwNHAcZBg= 42087920806448980363073662127262313840530298932643042322138035915324224188032438119079107631420338701086802583985117830416851550991102672642532160807467909040086448764318690465254898516502941122327185894900817634110254371864896139724173087625913998657136384357741816102965779105122269429701537815263708996632
+VJOZmvqrqsIUTQSSJpZPhbQIYN2tsfBhAciWnfAYpwjK9/ts7OP4Qgdp6T/V2EsSRPnfZ0VKdLg1CnEWDhfcODo+/BZcUrJ0AviFAEtdeUhoMSWXtjel9Ln2guHY4s33z2cN70+e8gfjes65lCzrxUIXEF4nKxzKBnScoooQP5k= 59391682001673484862915842850714742391303140646889359425353339320546979084250010101273851580028171449840778038774656177449549941659895629203970455580974953864068394275066532699748911169800076515776388213090834432354601344176559839798153004796057709798368011673585434643656820656931921831615507416411999846297
+FRyJCOgPziO6RDHX1JgYGZRcSAuoQFIZM4niD/B0twK3l+TRpmVigKZAJnZZFtmX+0JQkDwQn3lcBGQIL6mgy+j0hD58U2/Wd6xebuHSzf4OHVGo1cYoqZLplszA+hVCoDVTHi2YAZ+GtfQEggumcNVxqfEZd6D9Nu//hm0t21M= 14824975573460749317081504809641216868382341402512168178334301409725840669112911061147252565570697788806398498723577368905065980113760265945344671897779830912242224090954834750057278285419880820811348943398148063418809729356397202526234113316098584002071850758705282845646489058224513019380757604894853946195
+dUk5LyS7mduFJlvh5o8R73kJIeeTh0Zli/y3XjtIXfCaNRf+wDlD/pX91JEwsQ5Mvj8yq/Uq13QyWhoNwsPpXVcJtJ+02wtIn5darsBDfzcD/LbWhl7zTRUeMjZ72gAWi1djx94SWjrZJS2oWZU92Og1yOyKRG+ua0AhHfYYh6g= 82361050315899968537319599868832189063658136463903643442673674137187842597528653416212822014359684261704550279153006971937114135373937934986951573613797195556144113400128502946618028800530164890707031379614952207482505803377774320259789692177752930767589642007257364960987343146063216186985472686575891023784
+AI6rejwEznR35rIPuIz0CP2aWyhRUR3unJ90YfxyuVYxrqOJQGSDTSf6SGDDw5MqpZXa9pWuwpyrb6smOq4ZtC3Er7lipJfXDjhy+0k1qcfMjmqbATUscwXGpgW+MO71cttccEz6vhbjndi8gvG5M/vfL2l1jA8nXuBd4e254dbz 100186164434910864539376019601151338080943067893748898987236087770762310617199833479771711726248130012472861788210345311298499515751355424063761182369333224929721733015910055321263016834247318907562652286587380604998130368845939290804442878127169587599285040969551065995197981341260363722618429042861484922611
+AJ5vLZX0fSs8dUSBqd5hki48T9cYuR0atxR+qv7cRu9nD1vP8uNVR8dLitg3XH0RARt3ZmOgi/AuggZt6tTxuIBg+9JhBY9WW+BLL5CnYWHC3AKMi7MQBWciLtmBpyF152bDaEcV1PXxtml2KxX0Ba0C+hGVDmJSdi8Kjd4AkfU6 111256341508463539324514225759801553679558662737345522765042612717818066374840372549356543720386819501973783940451033901079765311790026584654529398345993992144903839534037331533660672892487693477412528974248713261092693018326068480417183236210881306241164169849090833681510163753605662526243408192127670285626
+ZhXtSzn1GiFfHHnSKUYZiTcEWqlI8owyCKFjCQ+VEvkdk50m8uN7RCQ6ZhI545tN7Uy0WdLstJhgJETBYLHHIoWsJn07mgPxuyO0XsqNroICMQEOO/YWQFk1c0VqZifcohQAwJj7fONzM7hTcA22/7gVigJ3iLq178jZOJsEPQs= 71686982768953132894579286530164112027530221141251507987469672039995314435159469907420372652392376452531392493658576814100773556880394271726970628960571077839124343525055625420896355363707908511865700866168843075071778015504724409171911254647909938237551680861008772396291072284353858575645679153885560978699
+Vc8Cw5m5yI+bJ5sUJYm/F2wyZ5x3D4ydyL0uU/3eVF2ZJu55OOlC9pUyyv7WGExClHvWpR9mhMnsqCLyseLfM2Q/YXJ7cjGPKp2xd+fvwHa4hRi1FdOxs96rJnb+HUt9hTwQByXgzpnUfs7AqrqaNf4WSlBNMu0IOOqDdB4iVHU= 60256873326783629723455608618518793848697944184579877638436234491615392142659293975260290798403892159720925893207048153291000664050780029732557737984085196691225472664027706406879051455184548871511448456651238810812870905640934953489289909009741493031472382758586341375517766302753448531830002512912250459253
+QmeUn6cbpE8YrDfMETz/+KVFaK+d4NHHzcdj/MnjcmqQSLpP/XwCW/aeudlN3SfKd6rNo1XZefunZO/ek+PHEIy899WzjiJaajhf2X05fl9WuPEaMES3Yrr+ClogFNQ+9jL8+7L+J8lDuqQzvchT0U0RPay5HSNZw+ZouVCiQ18= 46630904037845609335515965570673490721137364238213103678233212262384415738654627185220187275286458759154841820256007930773120637898228224906635911124921895934056288121005350040349882413280772888907627838315559544636626856478316691755270725623680935763476199888127096014398699432042227882284223578563208692575
+ALUBYIShA4w5kRUa6iNF8S33DqaprdOWjVBnO+j9CCGtUh+NNwfpKR8AKf536MtuFFtwaQvRIlkLpaTYXuRxzyU/YG2+UfRQF3pEmXQhcMxJqFzqZ5nWCIWlJ/KtYS4lcC/B7hD2UGAktnIdjVUTSxX60VzA+zxeunV2iBZXQlEs 127106299687401374061881872616647348819431126560557369258073443762502337592227172639640997680536372567116568811258505773087926491911004324918919511363985868314578663758269650473780772688462266790559846182685481907703974916356209771821075179827563487466641669110315430790405454641953880582274165368514679034156
+ANyAdMnVCVjmUZGiVdyvGE5mUQpKoJOJINqMAfzVUGvvxXFmGdoAx+xsDRNAP4KoijpXk6E3yPBPBZEWyhiHnyjEkktK/gX6gnb745afS0QIlsjhKCk/W/BHXkzC862Llnc1ZGAIsERnGceEoZHdICfDUh/7nMFp5WuSMzPB7nEO 154841617115465511611746667401422322067517612306328612547616471923266281876818466022676728696273611923942543658633762267658490816264271663863494188027433799849037906883352478212451733963905925106470599843045599411842850386623187980045961158399934160107237440980574028985561404965317132715808604373199725949198
+AJ4nfhDe+HojR2YrprDHW9FVUxsZvoIekwlNL2iKFRFcTB9IcEdh6QnGcaRinev7yEYUsL6saSxUj39uWlqo8udJFdszuuQUmnloIi34L5uj0m1OpLy2dawpFQr8pqyA7go4ugMMj6XCtiVnISUcK8wjHgY3Jed/EKK8k5ce0Jxt 111059703393618496515021583605572584329116596402705082562306930876194742195701060137568030171429700588269665205795898835699633817098262654446852249498668467827435829513531633390969638488553144849154126899372953755511962841193763362947708260103832329116485114451074371844037650417731807385491783373627950406765
+AL+heSTflb2MkRYFTKghfzqlVQ1oE5vcx0eCIsy9NJ2NGFXCRRvoGDVoB8UEsUWIRnaA+MIpwDKGpbOS8kRQrvBvPe/xM/t3jrGkaS6pN064+bCBx8Y/Jq31ZXNG8oUol+y1Eo6fkUKNl4EOetmZWK8VmhVwol5YngDffj4Q8ned 134567692290185631768518572983694048149859804864902017394351513816079806629664302312927579302025923096596995134868068794900003728293470554490807959649153000914807604036531509869958441069678002226922395630284261949256022972967357884468325217602330254290548618134453007903724438628204981673400911693835033278365
+AI272d2sbYIi637kHZC+6lievgcDvT5VKaCnus3fHwm2vfao7oYu31P4st9DlqPWJ635X6QtLkU5HgvVSy66MDj2fcOfwVL09ffkZYnoGNdhMADVgOq62Ro5cCpOdw8Ko0cCyVpVIaSysPuqY7kiClf9GTdyZz/uYHDgwWeNrc4R 99528854246023003959943182132914587584844397870416002887630245681136432049666385367430032197518895755482367603560037194955739661569172773017279832774100155646116233705958563163070414171045438199561777058338188494271322834524386565519620661180246416329082614115142485663975718653564590519408413408765689056785
+AN9S8vPzo4SkyKsk07nfyD0um1riJzRqqWF9KCL+kWMHajurgPACikYzu61tL7l1mNEaIU16Ndz541o+y76DgsTLYszu4KXUOEt1Gu3eHy05Fq18zCDlNesSVjkZjPmuJr2ku+p0cP0TLLMn7/KuVOm4GlEVc6OvBNZuEzRriSYZ 156823459768092337875922818543729136404805918580285507923139232733465414368775678369646914249412830351437211620056021568154043505276475345347569200977945836210758870414054407438380975491139001471954448623922841964684437333066353208837709613982022690623722155151315252634380695513434502419141555410441456920089
+AMc5H8kywLgiT4zz5xgoI90jejsHorbqUGtBeX9wke7zyvEKyWxRKScZwzRbinjDZzN48eg/30qTZOV2Rw97JFg+EA63iZ0vqfF8jErIt3hODniKX8zayCuNmiSb5kiZL0UDU1SNh8ER4m6o5vshBKkmqs0PeozfCGQtR3bZXlx4 139899247405256530335276706333424670310599977544642091674186635734421385499036688803073040921114325725234673132788498809189814711681909865484671959982394306416477300458309408833281654917008031099378445580498219376391819745965887864647387211647794422908411100892195529730435423964537342228510107659017578765432
+AKv+3H/TruTX3wdMWnLzD05em8u/QMl6lCHT4VkK+uZwBXoLeji54Tcs/hZIhj0Bdj0URrRt+7JdGSTy4Sr986AtVFxBJZA3lT+JT4JSrq3oY1Tv+tX/yg8ZodQmbpQyyfaFg3BgeHNmsUoCrdqhj4IwBqEXoOBRIXnzaTuqqSEw 120779384043726135670909127168686589868907326577918074234323699599475436892003731971700278391108690400460261929381703781833059801757700386671579819341589048987186473249926590758009001670959004477454905417357202448886738669226760846888369186457452643053236389556969071303251275912453385963613554945645058007344
+ANXIB+HxOyJd3YYsscMpqZpi/eYjZi5q6A0MohU4BiWEJK/E4uIObLJDH5yd4ng+hn7UMhc+R/AxG88hIdOc5NyG/QyFs95ZLUC26F9rkRifu2CBkgqR5EQi2cgwC8jGxQOkC62YND6cAn/ILsKTYaH0iavtO9Tz04vQp9Ypc82H 150122383481070201614242107655752525590609186454390549085509458064289390813495886095936526832230958746095739308601699615024239939948911472291507190108935262129646691795733786714291498653838550751365834947465294261687773081563139416397262227609481906371677917295227469553787085145970923979142676551778927103367
+ZQLFoW+dJ7vrHdMlcLRGKY6T6PZKnE2L3NjXymS/55my2CDBLdDf3oXwLlRjVt9KnEiXyQzLhyY2PrFA4k3N/3P5lVDLHero5c36TMshbHgbIKRGN2CGWPEFeQ4j040IwVbQCPJeuF3jL5ikCxWZFXfeEnTL6TqumLfD9yLQfKA= 70932215714423143395949105745758445705072524008235214324766464113352968998429901322485575506330607802260244612268338586532462314021433435523464635419846126736185176246740838082062856583684393425704173881940108783636582561707441482446854068022535943408999200681879161519209676205165680598258447492092651404448
+LzzvPw0FdtM2G/RRiqoajJiIH+Lw3jpL4H+08yOpp1bNITR2Aq0beu2nP0H4o2Z1/FNr2hzuGakkAhVbmmRXc8keoOkeaAQAP/8OYxHpjrqou3WPWaKx+vUCTSqVYYf8gnVKpAAC2cD+3lW+/ZJ538o+c0ovbUKNu1u1j1OBtA0= 33171669664542509840621265032202455391098253465550501094201777336478104142847268103467889435377685359857979277521589539506627375165485879405453566052091202280471235979376217319335800766353336252760793484157724210008639813552207624049019149744883918494762511376489708611103181576211531366514802868659603747853
+APrGj1lIIlxA57DNh+bTEAFbJK2Y2P3MxLShb4fPx2aY6j88k3umoe07ISQLf9PzNPeml4/0I3w0KNd2x4s9KHbj7NsIT64lhO6eQSEteqZXZGXUYUyNzhrTbAjt+Q9LVKItQhsTkTW2HTQ5RQZfGrkL118b/I18J4P+T8CGZdDz 176100632478477421621142147788721746818712752858710594712903769452749028606541677227413333567013253138397373757811889654342173021761934591400685421771460440213093509170325205622261487145789848227404883040799927313402244625239515162996390018403365063394514244196976794479529075569412676472840544017222373593331
+Fvcl/LemWk29I5LCjU1QedTjGlkvFF/kZXNkRJv+vNZ7qgq6pX8WB9yVkk6AoclDYAhCRfKTKuEpR23iafVuHpprPfNXcqBH8n01kq3U27xqIy2hS+D6BRBK67PQaekq31EB0aOcEb/DuNaXakS9+mtTMx6BKt+WoEY+NkzHK6c= 16126868736093163702771491576570380743773057522016869811780571865928979861357811080042796140032050364543242385458140594532945509386155523162799601656485075247603490060565663264947465987286983338572455184901756399862440455644131755848583379822279676555143231305246033911608913609591095831135803702269767527335
+AKW8tvaB8YZ7J5W2lmquBniJzUhRfqFdPZPqvBoMzR4cRh1CMNdSFsYsnsaF3KolNzogdsxFpHAaEMG6zSvpNJAoi4nixCqb5SETXrSLASXvNjI9MvCoE2JCRq7kMbjPL7cem+mBPWZITGUI6KVlJPLxQngHYSFxukqlx7jznwJH 116384596458828069344020651216200368975621068920641012055593076864629080375946542748377736186556382088448816531408136815533164209947323588157210859294774679831647934533061547276394884474877353537242203645373945111105805934070657589374883764420038511061919092743520704686962593876316976299391579463759429567047
+D5N2P4FrqDf7/2Z2BJsqah4SjUtolic/yNqdNzvNEogDKZKAJyGq4zhnHvkYXkEm2ueU/FDPJRqisszG0oULdU6c7p8acirEwsGLVh4RamnFRgmQSK1vbiYB3bR+P+iFX/bZ+TWjN2Y3YMa5UB//I6Zb5kEIjmTpjY2LEPI1e6s= 10937855369372570149476727082965401421189236366492771695094788039313362971972373068736123833330006002198346944149230147444718818161877123407713821100752433128205189334393732633989950841577315682292180735057952587083688644195300641998709155269462601925653013312848413290208844194513502358901613104779186502571
+V/A1ktS0xrcwlI8xrYqvlLCFYrdVp8tEzZaZ9iNNpPH/pzVsA0WbnnUeHbdilkje+4OdoX9C4U2xaOuWOfvqLR0c7GeCkSffCqyf4ZsBmjy/BQL6rCpxMF0gIHXO5O8aJ1h17hy9LTuNzWm4zVh4pNFuHC9L6nAcf92udMiIQzk= 61752386563628388546439207444896778638632243226541303179646524864765343154194512297447627825411023405896612559648434895675553567405277169056807223959390559391191382555701580549902639604424290133917402316755076644943742815711432111554988540913643347167948778404861099845961151998728662878854088239266688156473
+APoPgEKA0/r1FYmt/Iso6ChYK6dDU62Y+vH5h/LVE00biBYG1f7aL3GdllUTN+XQSHpqlDw8CD+9xojwZIMfgpgjOwLbbe7Aso460zLrg3R8aHBpbVt8iZUgjACwPYr5UyKbFzIAWaXcnYYQ+tCO9aDIuOz+/7eIF62C81zXFJVZ 175598490446477604563905754135475294999639698464908622773037381109011373179895295130424828038708319325919451724985361900259676699137657615076219968061941008972496322083528922054390781811699677037439989404270415929836486610353098273115864435328533577114470407444852521009919911888840405368858409835197558461785
+cL54ymLJhRx3U20Y9aUTIsXy9Ags+XHy4qk3F7uJyO46eiXSL7VrrR9vTQXAbETbu1YiVWfslsPht810eUDUVaVir6yLnXkywn46Ci42FEvVoTEFjO22uYcCh8nqB8H589w/+lVSlNrcILugwfdfCvK1iZzVimOO6l3qzfXToOU= 79171550718114578361958369278761819285111811576818442980166457146638966315793211967882077899426611721874954146020093740153495693185472340728106727284441726113022873005252623222594060645383105757498856463065370975867121188445567981809371870213273555432308279508351518168027875538720367440153667708369625129189
+QdQN4qW2QZq8/fmSaqlRiPSoDbhmF0oYjaY29HcKYGHdlOH0AMJb+RUIq1aszvVtjh7AYay2TNhaZMWQ6Qi3c42SNk3A1MVknT6zqiRCGjNFfxf/matbRLbTFQF832MAId708vrFLF/o2HpekMkc5hcHB6bkUUhEI1NLcMXwGck= 46226230186280253581676626651942823886592433541360244612432763620730826574920825070086312767146345247802570752482654580909236388357139147786783758670999083804670979821212991224400629053427330483809790366665043598754931511997925850227997764381723288657884346974360232490075739442406431704368767588177525348809
+cxHvCK/dyVDvaqCCQyLeaiBGA36mV5el+1lc2eUTkHGUzX5gU0QQCEp+iSXNJhIOON8VFpKOFsziuV0Z+3cegWRw/VnxnjXcBh6IDKdupzOPB+Yl8MA1ti/GrQjLC6ikcNYNjQT0ZThL7KTqEvvZJH68WYmD0IuK26swjNGIGaI= 80804939616399473443737611589382762718815989847332356984276911837267997590368701684135326680567847542004499684038240485603420973682522792156533112356849436451918522884749244246467852622918805139990256619014116276456718693703261686778030658826952213058982142604346352178078750879100976710761147710018148637090
+AIQ3OIZevkYoRGBmsFaXJobSfLeInuKKReVYNjP5VEPoMq0mXTltY6l09/rQ3d1JjsMD1PfA7emhxex+H9t3leBIfCi6Ux34GQEjXWpQc4awuiy9tbR077HaJyecvb8Qy1FTnOHoH5C043QJzrKYT/sFXjgB60piI8Y0R/hwxO4r 92845026347218330987427785323244729176754623818531419911990153715676845614711324345879159989637824921793015074978358052562420379797956750450245721653716740651389924718711940869162230097839047895842495414221110468446944827052871968998907462191349838598297775847512250220907563815783358238473966349820476321323
+LoG6ib5lUh57rdmSkZSWzBoudytFohS4uoU/uly6OaQDOi34GeNVxu/yr6RszJyL9JWkGNgFaBIv/HirH5zA9VQAL/6kpL93a0/GQ/nuHkHy3GWZPF/2+yJ0PfazQ40fWhHZfRxBngWslbguFPjj1XaJ37YzpQAYb/+QcUai9ic= 32658152290878644668906121702816147999633088014476055330179597550087921141413344679134407016170035735846077181424615228657687216737432274043674411132745299610950657139041836412322040866250189120286839287690983293111362228893996267791120043532014262644480689231457941173330523718758287779526551822788227954215
+AKu2jgOQCCfYZ3CLkXEH44aO4TtwMPeK/eq4FtNj9HZ9FxT0LLNJh0ZXPOaPJjgznvIw5C7/hNm7rUs1JeV8I8dj3nbS3EVERQz1gc/ckYB3H1bViWREOD5+TScDusi86YO/z4ar3dauKkg5kT1kKDuU/OP5kNMWvtJjHc4Vd3L3 120581042599355202025471829872601846477331097842315143148145881424071317426176264583672725691485724160094190478865850305422057632110749683552966861219554215519032344086824849470294473808177223497912069335635933312949412445851201918768630656712413082629164792850095444166888072453190903931430551124946191872759
+ANLs7OsR7oBM5jSjVADrk+Mx9d0TeieTIkxwWiJ5STKNQmW2EzPOjgbfcLhbYEhzzDFJveXO2dzz6/c8V5oW2yqg7VMx88DzEbpQnQpk/rOQRw9jbI4fxXNJHkNZCeysEVvFfLJb4ecsGA0xJ3Aylny/jP10ahPv2z5K99edGZSU 148116916208650944522110872759145096907599612943009577897396622287067669897712748449324334650112672914917664881091633448764667172850435775162090891556266912697811031318228334453406561952979778127173704706529448647577013482442758465809198730066784986763500579667100246958959793527011919373534159474250508506260
+AL+Er3n1qj+SBsZVtOMJYg4m0CN+DE6gRnC1F7nPvd2XnBe+QE0+LKfcpUDHVNxoydW4BDzNVwnUNbyjXZ+iuddPtO9hchVEI36UiuL0ydeldFpOZ9mtHJaAF6abd0MlHw4vXRf8CbOvXb5N4s76ggijlZBjRtU563sSmBcyq6Zt 134488725667189507159811764480908602790838430340670328479145818969651133017546803581865897303917708192047926432630297993507146075655594931523561067937580218599890162311074002344315818494246433967228889645359283635389151927472221799543158424012020308449895562192866672439712148770104592027035768027605661099629
+AK/04XOBSjjPpuFXTDF82RNWnKqZz9mJQbS2B5bn0ehFnBa6j+B+MazX+AxXTL/d5+hPLT1uexcnSMl3DcGGwKipOXg7Dtuj3pfJXHTrCqXAUYrIXI+8vKVQO55yQPGfzIg9SVgetwW1sDk+a28ZhJ5a9OddqNoi5C+dLce7ZtNb 123560902006294001923570614486104726169564351074482936927091682096999779538353161007361361829586988452098646362280351148131540524964916445100589671458589346440250329883789099771417949746709217272531950438336245613419967556433467843237384555807236658182067742367748737224684334525934210197178231424396818830171
+PzOEGHlihiveoWFAALY+LOfkRJfm0NUF/uR6cSU/tbpGAq4onNpr+iZIzEP5o3JBLOtDC595/NBPI0fzaXl0vQvgJs6KG8iKANjsLKQjIpZBkoKhdbG9MzTVQuAeuDW0w3sn2iMZ/v2dgAzRwfqmQYXJr3I2BbcwWraIJuZXw5A= 44381416070253681813077725822442106641846565789204187691647505370231831464947935035197059366680327425453811558282831465960889061956588244308214943856009686127871667376028831540813257349779756631357122923723235595360268572998278795110672666089470210929411514949652537714634611421849780859192966935514197771152
+APnuduN01GS9dO2m2uCLs400AR2lX7elOnIPC5U6e17qbukxWYzNhilZlM4kdGXAIeYpzFdSIW/gxRMZe6TXq9krFWRaaPyT2QwRfGHYnazS9F1QNYmW1zXdt+qVp0JGxmh5PyDstbP8Z3x50/E8Mb0gLLPhNAvzY2Jnr9A8Q1Hy 175507868985304663005133968393406051624825489142498103948374797086106732382869120248515993626061853699363294022457032257026588816021007648668265488426495800459085474654859258116280251546902009156490112550154951965894022789029787886785376415437170872937201839249103828294508088966180386198213606090453461193202
+QHEhL4iVzNdUsfG0izTEepwTOvxka8t/9MwuF1Ey6kxsI+ry4g4sJPgR2xMnbtOmvQn2NitAkfvA8JPCiL7a8+gmf+DVRDjKDfpfrtgAVmo+3rH+uJYTrKhAp8R7ggU2xIrvbIrgeUj7ieThPI3Rtap+IdkPCL853JC/+oKtytM= 45252649968839515171157821292772647085425694172492111870169593872127007254353374581972876464918186509502070064028725519394859148593053614163356612260257013360168930649423732336969778875205250872728821432415158634190866775855521719727700464116412886964736859295086745723651735554245035077902615220578218265299
+APeaekK4mVhEShCfM0mkRebcg1Iq5CgrFIEGOoh1nHzgebr5A9Wrhm9yD1Vd3e+fFD9urDRB4y5MHPJHX1U2NFToC+H8nQkFXL8bfd/9Wl2c7y8m0Mxwi53pLIdzETLbbfeOOtJvuSYYT3n8+/PeMnJ46UD8OfqtnFuS0/bVpFLS 173873040145444066957050580959132871919216036714423404143335635770937773583761934638398867981658394368476005882852706046614562314432695052874974848076542261910660410561876043187368112065303981001507235893831108658530338308496461162623683138693880482650786841100027392293758260448606244283355655751440485602002
+FqC/wgZDPTUoObPFSH5w4QR79zj/O+ZiHGTEnsBMwNZD3Gl/ClRDIsFMDDupNLgwgXsqCQbpwSOHOtAvUuAFwRpzt5B7lwIgtP5ism/AZRno5p+9WVSmUAM3glHsNtvYydz2MkXtnXzSMIR1ZVoLrdwMnckE4pbMzggqz+JZqxw= 15889870005716350976759704672045310928616256175405784574141006779373730686049218680335525720670897894546334915362899913262232170795516176419192840427996647372619000239408311568577050460995518058850793096827271653902583271225799114408537346367483775593212272587811309978019791973449354003275559762102731778844
+AJXNbv2AMWadF5h99ZAUy5gLnVK/hMaakFo0ZedtPNRJobxPmwj+h52G+Czd0U48G0V0wpdeUJC9v/4BhjzhCvNhNsdAT1+vQXDuteYQ1aspsEKLQ6b+NknO88QSbRJw53+KeOY2xe7PKOa4V89XnFFBF7wljRnIYrM8vvcqVQDk 105194875227030598769888785590198577650278341586165110611689226597424766274486797264032300493674927704016605741286512271390703088626381669060095573361828932336327125438452066548897528158329044309005232090053420259033538936293519762277428283316506398965916381374819450858053512398634116052299066189424983605476
+AIDRnUpBHepjBqYAlU4MG/8JxzX1mPxVNHpWvnEVgvqTQx/bisFPpXrYs3jAKIR/lzevYwhH0K/8Vvw4NK9iTMFqgSnU44AZztKsoxUXsEsl1UU56UscY5C7ciKU6vjjWI7nm/uHNOXdE82TQXkk2WX8ferNqZU5DaLFCb+zxb7w 90459642084794142567976043425270153270545560059973413835786695756473295513758287577749768786155290305189883600338986370836806413936196854410098516254596146039255388020628703824195128439558127783534033672712705194483515442668075394018677699876614329419492391568463215822656901183478205197671375262145069825776
+AIdvVNzJqWPgAShvi3GhbhMQft+SLigKGrhoqas2Saz/bA9u9Td6fAxa2LjrAqshW6cnm2aalc3Yv6RW/Y8vg7Ho31NSaRjT4zMUenykcC0/Y88UNxREi85wdnHwGytms6Lq49H8/7EFGJIyL1PLRWPmZn6XFkegscI/HUq/hiKm 95105613103051650721863964216778532448106311156426028879315612217763044797186635476805213120469258258125661666950525364331551671653846368977016286153840829836509696804585927581668281228810410814602664419962214359687545209312836366693384158782798559255789953908588601637765910472073600954502095647132310971046
+DdchOPjXrI6lpV84IdKCisPmdqZan8AARXRLADEhixsfXCYuO+WhNatI/fM1vgv+/TxwwIQjIfG1vOZcB36JUfjHYdItYQ70vUXaVFdpqvoBGyfOTU50Ds/11iGPCF8mWiQwR30/XAXytqDZtaVJVWsgHD3RigBSnSHhnvZAWYg= 9719024770319024562623340689338530708271347986326272393419504304391837979619189392867902307307106771234732135400958362219711925045600118964223238147375808749507928768896918369395426933218443166133187066167663170936604731896932630589251946733237697936733924510107175304126061649311812536190882160340308613512
+I+Z6rdTOt26/v3dtUP1plITb15fjb6aMDvqFS3AD1+nxBqnnk7ISGE9j6dv762EIWQpMzcCG5NCCq35KOHEwRXP28zup6olOMt3CBFgYVcBE2pWOpGiO19G/iFweYZXZPY5HgIkex7HBbb7l6HhomPc2sLL/IRhh2oogyHx2JMM= 25210054612455888156900839678249806510561198051210010474517915819801056434402727631042894881559517808906460418029149538469607239850657781476308872923928122553395468026744382526167194202058040459679991391557937527079948356545086684521068912222036707113005006607012596093923970784177288565193670152033981048003
+ALbBoyelCs4UkfnPjMT3S67ujhBHBEE0uxLx6kSGZq2IOMU/QdWYPFElRgYC/y++334FSEycjS6NAJJo2ITpZCO5AjNJ93J3WYgbDLiwu1VzKHX6ItfFNEk45km+QTi07+pDKcKNd1k0mxqpLd/PuZd5hRpPDDoKBb6i+mrCb2yF 128335905497646745013379107761994003743181143126608677203818152878840562628631384684712779135591095534911406031545494164782375276574093777950840330452805743803067864740000758175436633463846967335728314347497013853264454015790847388463800323796888198433722196292529074568758149650782323407298620158495364705413
+ANwlxEkeqmqYTxw1ZwMi1v2wo4ntPaEYZYoTLTJQfa+kuIksnHW9va243HAiOixd+rviVdm1dEwzESBbX0wiJNtRBpP+bnRxy4xOBjNoOB0c/tfka5JVwu5eeskyHx4V3inLviUaj86Yck42n5NaJFMfBvhzVftZ/YF9WBITI8g6 154592850289860621115358362871905683265658659789986179554827712019629689749439795961607030363152337159590319622241556795951071651584979664762468782303706550885785493534656062553770262954861884613383561063525714923031691298088562054236178003658891902606245782350998076658704876516153027797371814038658244397114
diff --git a/Tests/Auth/OpenID/data/openid.html b/Tests/Auth/OpenID/data/openid.html
new file mode 100644
index 0000000..1a57d44
--- /dev/null
+++ b/Tests/Auth/OpenID/data/openid.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <link rel="openid.server" href="http://www.myopenid.com/server" />
+ <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid.html b/Tests/Auth/OpenID/data/test_discover_openid.html
new file mode 100644
index 0000000..1a57d44
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <link rel="openid.server" href="http://www.myopenid.com/server" />
+ <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid2.html b/Tests/Auth/OpenID/data/test_discover_openid2.html
new file mode 100644
index 0000000..a74c042
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <link rel="openid2.provider" href="http://www.myopenid.com/server" />
+ <link rel="openid2.local_id" href="http://smoker.myopenid.com/" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid2_xrds.xml b/Tests/Auth/OpenID/data/test_discover_openid2_xrds.xml
new file mode 100644
index 0000000..8091ab9
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid2_xrds.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <LocalID>http://smoker.myopenid.com/</LocalID>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid2_xrds_no_local_id.xml b/Tests/Auth/OpenID/data/test_discover_openid2_xrds_no_local_id.xml
new file mode 100644
index 0000000..e6a0eb9
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid2_xrds_no_local_id.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_1_and_2.html b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2.html
new file mode 100644
index 0000000..5e58128
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <link rel="openid2.provider openid.server" href="http://www.myopenid.com/server" />
+ <link rel="openid2.local_id openid.delegate" href="http://smoker.myopenid.com/" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds.xml b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds.xml
new file mode 100644
index 0000000..6d85d57
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <Type>http://openid.net/signon/1.1</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <LocalID>http://smoker.myopenid.com/</LocalID>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds_bad_delegate.xml b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds_bad_delegate.xml
new file mode 100644
index 0000000..db7282e
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_1_and_2_xrds_bad_delegate.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <Type>http://openid.net/signon/1.0</Type>
+ <Type>http://openid.net/signon/1.1</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <LocalID>http://smoker.myopenid.com/</LocalID>
+ <openid:Delegate>http://localid.mismatch.invalid/</openid:Delegate>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_and_yadis.html b/Tests/Auth/OpenID/data/test_discover_openid_and_yadis.html
new file mode 100644
index 0000000..3befa6f
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_and_yadis.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <meta http-equiv="X-XRDS-Location" content="http://someuser.unittest/xrds" />
+ <link rel="openid.server" href="http://www.myopenid.com/server" />
+ <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_no_delegate.html b/Tests/Auth/OpenID/data/test_discover_openid_no_delegate.html
new file mode 100644
index 0000000..f5180b3
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_no_delegate.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>Identity Page for Smoker</title>
+ <link rel="openid.server" href="http://www.myopenid.com/server" />
+ </head>
+ <body>
+ <p>foo</p>
+ </body>
+</html>
diff --git a/Tests/Auth/OpenID/data/test_discover_openid_ssl.xml b/Tests/Auth/OpenID/data/test_discover_openid_ssl.xml
new file mode 100644
index 0000000..111945c
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_openid_ssl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://nossl.vroom.unittest/server</URI>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+ <Service priority="11">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>https://ssl.vroom.unittest/server</URI>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_0entries.xml b/Tests/Auth/OpenID/data/test_discover_yadis_0entries.xml
new file mode 100644
index 0000000..f161a0b
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_0entries.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+ <Service >
+ <Type>http://is-not-openid.unittest/</Type>
+ <URI>http://noffing.unittest./</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_2_bad_local_id.xml b/Tests/Auth/OpenID/data/test_discover_yadis_2_bad_local_id.xml
new file mode 100644
index 0000000..68c2ce1
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_2_bad_local_id.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <LocalID>http://smoker.myopenid.com/</LocalID>
+ <LocalID>http://localid.mismatch.invalid/</LocalID>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_2entries_delegate.xml b/Tests/Auth/OpenID/data/test_discover_yadis_2entries_delegate.xml
new file mode 100644
index 0000000..372955b
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_2entries_delegate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+ <CanonicalID>=!1000</CanonicalID>
+
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+
+ <Service priority="20">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.livejournal.com/openid/server.bml</URI>
+ <openid:Delegate>http://frank.livejournal.com/</openid:Delegate>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_2entries_idp.xml b/Tests/Auth/OpenID/data/test_discover_yadis_2entries_idp.xml
new file mode 100644
index 0000000..9a07b3d
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_2entries_idp.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+ <CanonicalID>=!1000</CanonicalID>
+
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:LocalID>http://smoker.myopenid.com/</openid:LocalID>
+ </Service>
+
+ <Service priority="20">
+ <Type>http://specs.openid.net/auth/2.0/server</Type>
+ <URI>http://www.livejournal.com/openid/server.bml</URI>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_another_delegate.xml b/Tests/Auth/OpenID/data/test_discover_yadis_another_delegate.xml
new file mode 100644
index 0000000..2f3b9af
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_another_delegate.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://vroom.unittest/server</URI>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_idp.xml b/Tests/Auth/OpenID/data/test_discover_yadis_idp.xml
new file mode 100644
index 0000000..f570d04
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_idp.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/server</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_idp_delegate.xml b/Tests/Auth/OpenID/data/test_discover_yadis_idp_delegate.xml
new file mode 100644
index 0000000..5410600
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_idp_delegate.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ >
+ <XRD>
+ <Service>
+ <Type>http://specs.openid.net/auth/2.0/server</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/test_discover_yadis_no_delegate.xml b/Tests/Auth/OpenID/data/test_discover_yadis_no_delegate.xml
new file mode 100644
index 0000000..fbd6734
--- /dev/null
+++ b/Tests/Auth/OpenID/data/test_discover_yadis_no_delegate.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)"
+ >
+ <XRD>
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/OpenID/data/trustroot.txt b/Tests/Auth/OpenID/data/trustroot.txt
new file mode 100644
index 0000000..1b6cb4c
--- /dev/null
+++ b/Tests/Auth/OpenID/data/trustroot.txt
@@ -0,0 +1,149 @@
+========================================
+Trust root parsing checking
+========================================
+
+----------------------------------------
+20: Does not parse
+----------------------------------------
+baz.org
+*.foo.com
+http://*.schtuff.*/
+ftp://foo.com
+ftp://*.foo.com
+http://*.foo.com:80:90/
+foo.*.com
+http://foo.*.com
+http://www.*
+http://*foo.com/
+http://foo.com\/
+http://localhost:1900foo/
+http://foo.com/invalid#fragment
+http://Ï€.pi.com/
+http://lambda.com/Λ
+
+
+
+5
+http:///
+
+----------------------------------------
+15: Insane
+----------------------------------------
+http://*/
+https://*/
+http://*.com
+http://*.com/
+https://*.com/
+http://*.com.au/
+http://*.co.uk/
+http://*.foo.notatld/
+https://*.foo.notatld/
+http://*.museum/
+https://*.museum/
+http://www.schtuffcom/
+http://it/
+http://..it/
+http://.it/
+
+----------------------------------------
+18: Sane
+----------------------------------------
+http://*.schtuff.com./
+http://*.schtuff.com/
+http://*.foo.schtuff.com/
+http://*.schtuff.com
+http://www.schtuff.com/
+http://www.schtuff.com./
+http://www.schutff.com
+http://*.this.that.schtuff.com/
+http://*.foo.com/path
+http://*.foo.com/path?action=foo2
+http://x.foo.com/path?action=foo2
+http://x.foo.com/path?action=%3D
+http://localhost:8081/
+http://localhost:8082/?action=openid
+https://foo.com/
+http://kink.fm/should/be/sane
+http://beta.lingu.no/
+http://goathack.livejournal.org:8020/openid/login.bml
+
+========================================
+return_to matching
+========================================
+
+----------------------------------------
+45: matches
+----------------------------------------
+http://foo.com/ HTTP://foo.com/
+http://*/ http://cnn.com/
+http://*/ http://livejournal.com/
+http://*/ http://met.museum/
+http://*:8081/ http://met.museum:8081/
+http://localhost:8081/x?action=openid http://localhost:8081/x?action=openid
+http://*.foo.com http://b.foo.com
+http://*.foo.com http://b.foo.com/
+http://*.foo.com/ http://b.foo.com
+http://b.foo.com http://b.foo.com
+http://b.foo.com http://b.foo.com/
+http://b.foo.com/ http://b.foo.com
+http://*.b.foo.com http://b.foo.com
+http://*.b.foo.com http://b.foo.com/
+http://*.b.foo.com/ http://b.foo.com
+http://*.b.foo.com http://x.b.foo.com
+http://*.b.foo.com http://w.x.b.foo.com
+http://*.bar.co.uk http://www.bar.co.uk
+http://*.uoregon.edu http://x.cs.uoregon.edu
+http://x.com/abc http://x.com/abc
+http://127.1/abc http://127.1/abc
+http://10.0.0.1/abc http://10.0.0.1/abc
+http://x.com/abc http://x.com/abc/def
+http://*.x.com http://x.com/gallery
+http://*.x.com http://foo.x.com/gallery
+http://foo.x.com http://foo.x.com/gallery/xxx
+http://*.x.com/gallery http://foo.x.com/gallery
+http://localhost:8082/?action=openid http://localhost:8082/?action=openid
+http://goathack.livejournal.org:8020/ http://goathack.livejournal.org:8020/openid/login.bml
+https://foo.com https://foo.com
+http://Foo.com http://foo.com
+http://foo.com http://Foo.com
+http://foo.com:80/ http://foo.com/
+http://foo.com/?x=y http://foo.com/?x=y&a=b
+http://foo.com/x http://foo.com/x?y
+http://mylid.net/j3h. http://mylid.net/j3h.?x=y
+http://j3h.us http://j3h.us?ride=unicycle
+https://www.filmclans.com:443/mattmartin/FilmClans https://www.filmclans.com/mattmartin/FilmClans/Logon.aspx?nonce=BVjqSOee
+http://foo.com:80 http://foo.com
+http://foo.com http://foo.com:80
+http://foo.com http://foo.com/
+http://foo.com/ http://foo.com
+http://foo.com/ http://foo.com:80
+http://foo.com:80/ http://foo.com:80/stuff
+http://foo.com:80/ http://foo.com/stuff
+
+----------------------------------------
+24: does not match
+----------------------------------------
+http://*/ ftp://foo.com/
+http://*/ xxx
+http://*.x.com/abc http://foo.x.com
+http://*.x.com/abc http://*.x.com
+http://*.com/ http://*.com/
+http://x.com/abc http://x.com/
+http://x.com/abc http://x.com/a
+http://x.com/abc http://x.com/ab
+http://x.com/abc http://x.com/abcd
+http://*.cs.uoregon.edu http://x.uoregon.edu
+http://*.foo.com http://bar.com
+http://*.foo.com http://www.bar.com
+http://*.bar.co.uk http://xxx.co.uk
+https://foo.com http://foo.com
+http://foo.com https://foo.com
+http://foo.com:81 http://foo.com:80
+http://*:80 http://foo.com:81
+http://foo.com/?a=b http://foo.com/?x=y
+http://foo.com/?a=b http://foo.com/?x=y&a=b
+http://foo.com/?a=b http://foo.com/
+http://*.oo.com/ http://foo.com/
+http://foo.com/* http://foo.com/anything
+http://foo.com http://foo.com:443
+https://foo.com https://foo.com:80
diff --git a/Tests/Auth/OpenID/data/urinorm.txt b/Tests/Auth/OpenID/data/urinorm.txt
new file mode 100644
index 0000000..9e10135
--- /dev/null
+++ b/Tests/Auth/OpenID/data/urinorm.txt
@@ -0,0 +1,87 @@
+Already normal form
+http://example.com/
+http://example.com/
+
+Add a trailing slash
+http://example.com
+http://example.com/
+
+Remove an empty port segment
+http://example.com:/
+http://example.com/
+
+Remove a default port segment
+http://example.com:80/
+http://example.com/
+
+Capitalization in host names
+http://wWw.exaMPLE.COm/
+http://www.example.com/
+
+Capitalization in scheme names
+htTP://example.com/
+http://example.com/
+
+Capitalization in percent-escaped reserved characters
+http://example.com/foo%2cbar
+http://example.com/foo%2Cbar
+
+Unescape percent-encoded unreserved characters
+http://example.com/foo%2Dbar%2dbaz
+http://example.com/foo-bar-baz
+
+remove_dot_segments example 1
+http://example.com/a/b/c/./../../g
+http://example.com/a/g
+
+remove_dot_segments example 2
+http://example.com/mid/content=5/../6
+http://example.com/mid/6
+
+remove_dot_segments: single-dot
+http://example.com/a/./b
+http://example.com/a/b
+
+remove_dot_segments: double-dot
+http://example.com/a/../b
+http://example.com/b
+
+remove_dot_segments: leading double-dot
+http://example.com/../b
+http://example.com/b
+
+remove_dot_segments: trailing single-dot
+http://example.com/a/.
+http://example.com/a/
+
+remove_dot_segments: trailing double-dot
+http://example.com/a/..
+http://example.com/
+
+remove_dot_segments: trailing single-dot-slash
+http://example.com/a/./
+http://example.com/a/
+
+remove_dot_segments: trailing double-dot-slash
+http://example.com/a/../
+http://example.com/
+
+Test of all kinds of syntax-based normalization
+hTTPS://a/./b/../b/%63/%7bfoo%7d
+https://a/b/c/%7Bfoo%7D
+
+Unsupported scheme
+ftp://example.com/
+fail
+
+Non-absolute URI
+http:/foo
+fail
+
+Illegal character in URI
+http://<illegal>.com/
+fail
+
+Non-ascii character in URI
+http://foo.com/
+fail \ No newline at end of file
diff --git a/Tests/Auth/Yadis/DiscoverData.php b/Tests/Auth/Yadis/DiscoverData.php
new file mode 100644
index 0000000..816a06b
--- /dev/null
+++ b/Tests/Auth/Yadis/DiscoverData.php
@@ -0,0 +1,153 @@
+<?php
+
+require_once "Auth/Yadis/Yadis.php";
+require_once "Tests/Auth/Yadis/TestUtil.php";
+
+global $testlist;
+$testlist = array(
+ // success, input_name, id_name, result_name
+ array(true, "equiv", "equiv", "xrds"),
+ array(true, "header", "header", "xrds"),
+ array(true, "lowercase_header", "lowercase_header", "xrds"),
+ array(true, "xrds", "xrds", "xrds"),
+ array(true, "xrds_ctparam", "xrds_ctparam", "xrds_ctparam"),
+ array(true, "xrds_ctcase", "xrds_ctcase", "xrds_ctcase"),
+ array(false, "xrds_html", "xrds_html", "xrds_html"),
+ array(true, "redir_equiv", "equiv", "xrds"),
+ array(true, "redir_header", "header", "xrds"),
+ array(true, "redir_xrds", "xrds", "xrds"),
+ array(false, "redir_xrds_html", "xrds_html", "xrds_html"),
+ array(true, "redir_redir_equiv", "equiv", "xrds"),
+ array(false, "404_server_response", null, null),
+ array(false, "404_with_header", null, null),
+ array(false, "404_with_meta", null, null),
+ array(false, "201_server_response", null, null),
+ array(false, "500_server_response", null, null)
+ );
+
+function getExampleXRDS()
+{
+ return Tests_Auth_Yadis_readdata('example-xrds.xml');
+}
+
+global $example_xrds;
+$example_xrds = getExampleXRDS();
+
+global $default_test_file;
+$default_test_file = 'test1-discover.txt';
+
+global $discover_tests;
+$discover_tests = array();
+
+function readTests($filename)
+{
+ $data = Tests_Auth_Yadis_readdata($filename);
+
+ if ($data === null) {
+ return null;
+ }
+
+ $tests = array();
+ foreach (preg_split("/\f\n/", $data) as $case) {
+ list($name, $content) = explode("\n", $case, 2);
+ $tests[$name] = $content;
+ }
+ return $tests;
+}
+
+function getData($filename, $name)
+{
+ global $discover_tests;
+ if (!array_key_exists($filename, $discover_tests)) {
+ $data = readTests($filename);
+ if ($data === null) {
+ return null;
+ }
+ $discover_tests[$filename] = $data;
+ }
+
+ $file_tests = $discover_tests[$filename];
+
+ if (array_key_exists($name, $file_tests)) {
+ return $file_tests[$name];
+ } else {
+ return null;
+ }
+}
+
+function fillTemplate($test_name, $template, $base_url, $example_xrds)
+{
+ $mapping = array(
+ array('URL_BASE/', $base_url),
+ array('<XRDS Content>', $example_xrds),
+ array('YADIS_HEADER', 'X-XRDS-Location'),
+ array('NAME', $test_name));
+
+ foreach ($mapping as $pair) {
+ list($k, $v) = $pair;
+ $template = str_replace($k, $v, $template);
+ }
+
+ return $template;
+}
+
+function generateSample($test_name, $base_url,
+ $_example_xrds = null, $filename = null)
+{
+ global $example_xrds, $default_test_file;
+
+ if ($_example_xrds === null) {
+ $_example_xrds = $example_xrds;
+ }
+
+ if ($filename === null) {
+ $filename = $default_test_file;
+ }
+
+ $template = getData($filename, $test_name);
+
+ if ($template === null) {
+ return null;
+ }
+
+ return fillTemplate($test_name, $template, $base_url, $_example_xrds);
+}
+
+function generateResult($base_url, $input_name, $id_name, $result_name, $success)
+{
+ $input_url = $base_url . $input_name; // urlparse.urljoin(base_url, input_name)
+
+ // If the name is null then we expect the protocol to fail, which
+ // we represent by null
+ if ($id_name === null) {
+ // assert result_name is null
+ return array($input_url, null); // DiscoveryFailure
+ }
+
+ $result = generateSample($result_name, $base_url);
+ list($headers, $content) = explode("\n\n", $result, 2);
+ $header_lines = explode("\n", $headers);
+ $ctype = null;
+ foreach ($header_lines as $header_line) {
+ if (strpos($header_line, 'Content-Type:') === 0) {
+ list($temp, $ctype) = explode(":", $header_line, 2);
+ $ctype = trim($ctype);
+ break;
+ }
+ }
+
+ $id_url = $base_url . $id_name;
+
+ $result = new Auth_Yadis_Yadis();
+ $result->uri = $id_url;
+ if ($success) {
+ $result->xrds_uri = $base_url . $result_name;
+ } else {
+ $result->xrds_uri = null;
+ }
+ $result->content_type = $ctype;
+ $result->body = $content;
+ return array($input_url, $result);
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/Discover_Yadis.php b/Tests/Auth/Yadis/Discover_Yadis.php
new file mode 100644
index 0000000..8f308cf
--- /dev/null
+++ b/Tests/Auth/Yadis/Discover_Yadis.php
@@ -0,0 +1,232 @@
+<?php
+
+require_once "PHPUnit.php";
+require_once "Tests/Auth/Yadis/DiscoverData.php";
+require_once "Auth/Yadis/Yadis.php";
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+global $__status_header_re;
+$__status_header_re = '/Status: (\d+) .*?$/m';
+
+function mkResponse($data)
+{
+ global $__status_header_re;
+
+ $matches = array();
+ $status_mo = preg_match($__status_header_re, $data, $matches);
+ list($headers_str, $body) = explode("\n\n", $data, 2);
+ $headers = array();
+ foreach (explode("\n", $headers_str) as $line) {
+ list($k, $v) = explode(":", $line, 2);
+ $k = strtolower(trim($k));
+ $v = trim($v);
+ $headers[$k] = $v;
+ }
+ $status = intval($matches[1]);
+ $r = new Auth_Yadis_HTTPResponse(null, $status, $headers, $body);
+ return $r;
+}
+class TestFetcher {
+ function TestFetcher($base_url)
+ {
+ $this->base_url = $base_url;
+ }
+
+ function get($url, $headers = null)
+ {
+ $current_url = $url;
+ while (true) {
+ $parsed = parse_url($current_url);
+ $path = substr($parsed['path'], 1);
+ $data = generateSample($path, $this->base_url);
+
+ if ($data === null) {
+ return new Auth_Yadis_HTTPResponse($current_url,
+ 404,
+ array(),
+ '');
+ }
+
+ $response = mkResponse($data);
+ if (in_array($response->status, array(301, 302, 303, 307))) {
+ $current_url = $response->headers['location'];
+ } else {
+ $response->final_url = $current_url;
+ return $response;
+ }
+ }
+ }
+}
+
+class BlankContentTypeFetcher {
+ function get($url, $headers=null)
+ {
+ return new Auth_Yadis_HTTPResponse(
+ $url, 200, array("Content-Type" => ""), '');
+ }
+}
+
+class NoContentTypeFetcher {
+ function get($url, $headers=null)
+ {
+ return new Auth_Yadis_HTTPResponse($url, 200, array(), '');
+ }
+}
+
+class MockFetcher {
+ function MockFetcher() {
+ $this->count = 0;
+ }
+
+ function get($uri, $headers = null, $body = null)
+ {
+ $this->count++;
+ if ($this->count == 1) {
+ $headers = array(strtolower('X-XRDS-Location') . ': http://unittest/404');
+ return new Auth_Yadis_HTTPResponse($uri, 200, $headers, '');
+ } else {
+ return new Auth_Yadis_HTTPResponse($uri, 404);
+ }
+ }
+}
+
+class TestSecondGet extends PHPUnit_TestCase {
+ function test_404()
+ {
+ $uri = "http://something.unittest/";
+ $response = null;
+ $fetcher = new MockFetcher();
+ $this->assertTrue(
+ Auth_Yadis_Yadis::discover($uri, $response, $fetcher) === null);
+ }
+}
+
+class _TestCase extends PHPUnit_TestCase {
+ var $base_url = 'http://invalid.unittest/';
+
+ function _TestCase($input_name, $id_name, $result_name, $success)
+ {
+ $this->input_name = $input_name;
+ $this->id_name = $id_name;
+ $this->result_name = $result_name;
+ $this->success = $success;
+ $this->fetcher = new TestFetcher($this->base_url);
+ parent::PHPUnit_TestCase();
+ }
+
+ function setUp()
+ {
+ list($this->input_url, $this->expected) = generateResult($this->base_url,
+ $this->input_name,
+ $this->id_name,
+ $this->result_name,
+ $this->success);
+ }
+
+ function runTest()
+ {
+ if ($this->expected === null) {
+ $result = Auth_Yadis_Yadis::discover($this->input_url,
+ $this->fetcher);
+ $this->assertTrue($result->isFailure());
+ } else {
+ $result = Auth_Yadis_Yadis::discover($this->input_url,
+ $this->fetcher);
+
+ if ($result === null) {
+ $this->fail("Discovery result was null");
+ return;
+ }
+
+ $this->assertEquals($this->input_url, $result->request_uri);
+
+ $msg = 'Identity URL mismatch: actual = %s, expected = %s';
+ $msg = sprintf($msg, $result->normalized_uri, $this->expected->uri);
+ $this->assertEquals($this->expected->uri, $result->normalized_uri, $msg);
+
+ $msg = 'Content mismatch: actual = %s, expected = %s';
+ $msg = sprintf($msg, $result->response_text, $this->expected->body);
+ $this->assertEquals($this->expected->body, $result->response_text, $msg);
+
+ $this->assertEquals($this->expected->xrds_uri, $result->xrds_uri);
+ $this->assertEquals($this->expected->content_type, $result->content_type);
+ }
+ }
+
+ function getName()
+ {
+ if ($this->input_url) {
+ return $this->input_url;
+ } else {
+ return $this->input_name;
+ }
+ }
+}
+
+class Tests_Auth_Yadis_Discover_Yadis extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_Yadis_Discover_Yadis";
+ }
+
+ function Tests_Auth_Yadis_Discover_Yadis()
+ {
+ global $testlist;
+
+ foreach ($testlist as $test) {
+ list($success, $input_name, $id_name, $result_name) = $test;
+ $this->addTest(new _TestCase($input_name, $id_name, $result_name, $success));
+ }
+ }
+}
+
+class Tests_Auth_Yadis_Discover_Yadis_ContentTypes extends PHPUnit_TestCase {
+ function test_is_xrds_yadis_location()
+ {
+ $result = new Auth_Yadis_DiscoveryResult('http://request.uri/');
+ $result->normalized_uri = "http://normalized/";
+ $result->xrds_uri = "http://normalized/xrds";
+
+ $this->assertTrue($result->isXRDS());
+ }
+
+ function test_is_xrds_content_type()
+ {
+ $result = new Auth_Yadis_DiscoveryResult('http://request.uri/');
+ $result->normalized_uri = $result->xrds_uri = "http://normalized/";
+ $result->content_type = Auth_Yadis_CONTENT_TYPE;
+
+ $this->assertTrue($result->isXRDS());
+ }
+
+ function test_is_xrds_neither()
+ {
+ $result = new Auth_Yadis_DiscoveryResult('http://request.uri/');
+ $result->normalized_uri = $result->xrds_uri = "http://normalized/";
+ $result->content_type = "another/content-type";
+
+ $this->assertTrue(!$result->isXRDS());
+ }
+
+ function test_no_content_type()
+ {
+ $fetcher = new NoContentTypeFetcher();
+ $result = Auth_Yadis_Yadis::discover("http://bogus", $fetcher);
+ $this->assertEquals(null, $result->content_type);
+ }
+
+ function test_blank_content_type()
+ {
+ $fetcher = new BlankContentTypeFetcher();
+ $result = Auth_Yadis_Yadis::discover("http://bogus", $fetcher);
+ $this->assertEquals("", $result->content_type);
+ }
+}
+
+global $Tests_Auth_Yadis_Discover_Yadis_other;
+$Tests_Auth_Yadis_Discover_Yadis_other = array(
+ new Tests_Auth_Yadis_Discover_Yadis_ContentTypes()
+ );
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/ParseHTML.php b/Tests/Auth/Yadis/ParseHTML.php
new file mode 100644
index 0000000..aae9ad7
--- /dev/null
+++ b/Tests/Auth/Yadis/ParseHTML.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Tests for the Yadis HTML parsing functionality.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Tests/Auth/Yadis/TestUtil.php';
+require_once 'Auth/Yadis/ParseHTML.php';
+require_once 'PHPUnit.php';
+
+class Tests_Auth_Yadis_ParseTest extends PHPUnit_TestCase {
+ function Tests_Auth_Yadis_ParseTest($case)
+ {
+ list($result, $comment, $html) = $case;
+
+ $this->result = $result;
+ $this->comment = $comment;
+ $this->html_string = $html;
+ $this->parser = new Auth_Yadis_ParseHTML();
+ }
+
+ function getName()
+ {
+ return $this->comment;
+ }
+
+ function runTest()
+ {
+ $value = $this->parser->getHTTPEquiv($this->html_string);
+
+ if ($this->result == "EOF") {
+ $this->assertTrue($value === null);
+ } else if ($this->result == "None") {
+ $this->assertTrue($value === null);
+ } else {
+ $this->assertEquals($this->result, $value);
+ }
+ }
+}
+
+class Tests_Auth_Yadis_ParseHTML extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_Yadis_Parse";
+ }
+
+ function parseTests($s)
+ {
+ $tests = array();
+
+ $cases = preg_split("/\f\n/", $s);
+
+ foreach ($cases as $case) {
+ // Split the case text on newline, and keep the first two
+ // lines and re-join the rest (those are the HTML).
+ $parts = explode("\n", $case);
+ $result = $parts[0];
+ $html_comment = $parts[1];
+ $html_string = implode("\n", array_slice($parts, 2));
+ $tests[] = array($result, $html_comment, $html_string);
+ }
+
+ return $tests;
+ }
+
+ function Tests_Auth_Yadis_ParseHTML()
+ {
+ $test_data = Tests_Auth_Yadis_readdata('test1-parsehtml.txt');
+
+ $test_cases = $this->parseTests($test_data);
+
+ foreach ($test_cases as $case) {
+ $this->addTest(new Tests_Auth_Yadis_ParseTest($case));
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/TestUtil.php b/Tests/Auth/Yadis/TestUtil.php
new file mode 100644
index 0000000..8936c0e
--- /dev/null
+++ b/Tests/Auth/Yadis/TestUtil.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Utilites for test functions
+ */
+
+function Tests_Auth_Yadis_datafile($name, $reader)
+{
+ $path = dirname(realpath(__FILE__));
+ $sep = DIRECTORY_SEPARATOR;
+ $filename = $path . $sep . 'data' . $sep . $name;
+ $data = $reader($filename);
+ if ($data === false) {
+ $msg = "Failed to open data file: $name";
+ trigger_error($msg, E_USER_ERROR);
+ }
+ return $data;
+}
+
+function Tests_Auth_Yadis_readdata($name)
+{
+ return Tests_Auth_Yadis_datafile($name, 'file_get_contents');
+}
+
+function Tests_Auth_Yadis_readlines($name)
+{
+ return Tests_Auth_Yadis_datafile($name, 'file');
+}
+
+?>
diff --git a/Tests/Auth/Yadis/XRDS.php b/Tests/Auth/Yadis/XRDS.php
new file mode 100644
index 0000000..466b207
--- /dev/null
+++ b/Tests/Auth/Yadis/XRDS.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * XRDS-parsing tests for the Yadis library.
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/Yadis/XRDS.php';
+require_once 'Auth/Yadis/XRIRes.php';
+require_once 'Auth/Yadis/XRI.php';
+require_once 'Tests/Auth/Yadis/TestUtil.php';
+
+class Tests_Auth_Yadis_XRDS extends PHPUnit_TestCase {
+
+ function test_good()
+ {
+ $files = array(
+ 'brian.xrds' => 1,
+ 'pip.xrds' => 2
+ );
+
+ foreach ($files as $filename => $service_count) {
+ $xml = Tests_Auth_Yadis_readdata($filename);
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml);
+
+ $this->assertTrue($xrds !== null);
+
+ if ($xrds) {
+ $this->assertEquals(count($xrds->services()), $service_count);
+ } else {
+ $this->fail("Could not test XRDS service list because the ".
+ "XRDS object is null");
+ }
+ }
+ }
+
+ function test_good_multi()
+ {
+ $xml = Tests_Auth_Yadis_readdata("brian.multi.xrds");
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml);
+ $this->assertTrue($xrds !== null);
+ $this->assertEquals(count($xrds->services()), 1);
+ $s = $xrds->services();
+ $s = $s[0];
+
+ $types = $s->getTypes();
+
+ $this->assertTrue(count($types) == 1);
+ $this->assertEquals('http://openid.net/signon/1.0',
+ $types[0]);
+ }
+
+ function test_good_uri_multi()
+ {
+ $xml = Tests_Auth_Yadis_readdata("brian.multi_uri.xrds");
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml);
+ $this->assertTrue($xrds !== null);
+ $this->assertEquals(1, count($xrds->services()));
+ }
+
+ function test_uri_sorting()
+ {
+ $xml = Tests_Auth_Yadis_readdata("uri_priority.xrds");
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml);
+ $services = $xrds->services();
+ $uris = $services[0]->getURIs();
+
+ $expected_uris = array(
+ "http://zero.priority/",
+ "http://one.priority/",
+ "http://no.priority/"
+ );
+
+ $this->assertEquals($uris, $expected_uris);
+ }
+
+ function test_bad()
+ {
+ $this->assertTrue(Auth_Yadis_XRDS::parseXRDS(null) === null);
+ $this->assertTrue(Auth_Yadis_XRDS::parseXRDS(5) === null);
+ $this->assertTrue(Auth_Yadis_XRDS::parseXRDS('') === null);
+ $this->assertTrue(Auth_Yadis_XRDS::parseXRDS('<html></html>') ===
+ null);
+ $this->assertTrue(Auth_Yadis_XRDS::parseXRDS("\x00") === null);
+ }
+
+ function test_getCanonicalID()
+ {
+ $canonicalIDtests = array(
+ array("@ootao*test1", "delegated-20060809.xrds",
+ "@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01"),
+ array("@ootao*test1", "delegated-20060809-r1.xrds",
+ "@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01"),
+ array("@ootao*test1", "delegated-20060809-r2.xrds",
+ "@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01"),
+ array("@ootao*test1", "sometimesprefix.xrds",
+ "@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01"),
+ array("@ootao*test1", "prefixsometimes.xrds",
+ "@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01"),
+ array("=keturn*isDrummond", "spoof1.xrds", null),
+ array("=keturn*isDrummond", "spoof2.xrds", null),
+ array("@keturn*is*drummond", "spoof3.xrds", null),
+ // Don't let IRI authorities be canonical for the GCS.
+ array("phreak.example.com", "delegated-20060809-r2.xrds", null)
+ // TODO: Refs
+ // ("@ootao*test.ref", "ref.xrds", "@!BAE.A650.823B.2475")
+ );
+
+ foreach ($canonicalIDtests as $tupl) {
+ list($iname, $filename, $expectedID) = $tupl;
+
+ $xml = Tests_Auth_Yadis_readdata($filename);
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml);
+ $this->_getCanonicalID($iname, $xrds, $expectedID);
+ }
+ }
+
+ function _getCanonicalID($iname, $xrds, $expectedID)
+ {
+ if ($expectedID === null) {
+ $result = Auth_Yadis_getCanonicalID($iname, $xrds);
+ if ($result !== false) {
+ $this->fail($iname.' (got '.$result.')');
+ }
+ } else {
+ $cid = Auth_Yadis_getCanonicalID($iname, $xrds);
+ $this->assertEquals(Auth_Yadis_XRI($expectedID), $cid);
+ }
+ }
+
+ function test_services_filters()
+ {
+ // First, just be sure that service objects do the right
+ // thing.
+ $xml = Tests_Auth_Yadis_readdata("brian_priority.xrds");
+ $xrds = Auth_Yadis_XRDS::parseXRDS($xml,
+ array('openid' =>
+ 'http://openid.net/xmlns/1.0'));
+ $this->assertTrue($xrds !== null);
+
+ // Get list of service objects.
+ $services = $xrds->services();
+ $this->assertEquals(count($services), 2, "first service count");
+
+ // Query the two service objecs.
+ $s1 = $services[0];
+ $this->assertEquals($s1->getPriority(), 1, "first priority check");
+ $types = $s1->getTypes();
+ $this->assertEquals(count($types), 1, "first type check");
+
+ $s2 = $services[1];
+ $this->assertEquals($s2->getPriority(), 2, "second priority check");
+ $types = $s2->getTypes();
+ $this->assertEquals(count($types), 1, "second type check");
+
+ function _DelegateFilter(&$service)
+ {
+ if ($service->getElements('openid:Delegate')) {
+ return true;
+ }
+ return false;
+ }
+
+ // Make sure that a filter which matches both DOES match both.
+ $this->assertEquals(count(
+ $xrds->services(array("_DelegateFilter"))), 2,
+ "_DelegateFilter check");
+
+ // This filter should match all services in the document.
+ function _HasTypeAndURI(&$service)
+ {
+ if ($service->getTypes() &&
+ $service->getURIs()) {
+ return true;
+ }
+ return false;
+ }
+
+ // This filter should only match one.
+ function _URIMatchesSchtuff(&$service)
+ {
+ $uris = $service->getURIs();
+
+ foreach ($uris as $uri) {
+ if (preg_match("|schtuff|", $uri)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // This filter should only match one.
+ function _URIMatchesMyOpenID(&$service)
+ {
+ $uris = $service->getURIs();
+
+ foreach ($uris as $uri) {
+ if (preg_match("|myopenid|", $uri)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Make sure a pair of filters in ALL mode only match one service.
+ $this->assertEquals(count(
+ $xrds->services(array("_HasTypeAndURI",
+ "_URIMatchesSchtuff"),
+ SERVICES_YADIS_MATCH_ALL)), 1,
+ "_HasTypeAndURI / _URIMatchesSchtuff check");
+
+ // Make sure a pair of filters in ALL mode only match one service.
+ $this->assertEquals(count(
+ $xrds->services(array("_HasTypeAndURI",
+ "_URIMatchesMyOpenID"),
+ SERVICES_YADIS_MATCH_ALL)), 1,
+ "_HasTypeAndURI / _URIMatchesMyOpenID check");
+
+ // Make sure a pair of filters in ANY mode matches both services.
+ $this->assertEquals(count(
+ $xrds->services(array("_URIMatchesMyOpenID",
+ "_URIMatchesSchtuff"))), 2,
+ "_URIMatchesMyOpenID / _URIMatchesSchtuff check");
+
+ // Make sure the order of the services returned (when using
+ // filters) is correct.
+ $s = $xrds->services(array("_URIMatchesMyOpenID",
+ "_URIMatchesSchtuff"));
+
+ $this->assertTrue($s[0]->getPriority() === 1, "s[0] priority check");
+ $this->assertTrue($s[1]->getPriority() === 2, "s[1] priority check");
+
+ // Make sure a bad filter mode gets us a null service list.
+ $this->assertTrue($xrds->services(array("_URIMatchesMyOpenID",
+ "_URIMatchesSchtuff"),
+ "bogus") === null,
+ "bogus filter check");
+ }
+
+ function test_multisegment_xri()
+ {
+ $xml = Tests_Auth_Yadis_readdata('subsegments.xrds');
+ $xmldoc = Auth_Yadis_XRDS::parseXRDS($xml);
+ $result = Auth_Yadis_getCanonicalId('xri://=nishitani*masaki', $xmldoc);
+ $this->assertEquals($result, "xri://=!E117.EF2F.454B.C707!0000.0000.3B9A.CA01");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/XRI.php b/Tests/Auth/Yadis/XRI.php
new file mode 100644
index 0000000..8960d7a
--- /dev/null
+++ b/Tests/Auth/Yadis/XRI.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * XRI resolution / handling tests.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once "PHPUnit.php";
+require_once "Auth/Yadis/XRIRes.php";
+require_once "Auth/Yadis/XRI.php";
+require_once "Auth/Yadis/Yadis.php";
+
+class Tests_Auth_Yadis_XriDiscoveryTestCase extends PHPUnit_TestCase {
+ function runTest()
+ {
+ $this->assertEquals(
+ Auth_Yadis_identifierScheme('=john.smith'), 'XRI');
+
+ $this->assertEquals(
+ Auth_Yadis_identifierScheme(''), 'URI');
+
+ $this->assertEquals(
+ Auth_Yadis_identifierScheme('@smiths/john'), 'XRI');
+
+ $this->assertEquals(
+ Auth_Yadis_identifierScheme('smoker.myopenid.com'), 'URI');
+
+ $this->assertEquals(
+ Auth_Yadis_identifierScheme('xri://=john'), 'XRI');
+ }
+}
+
+class Tests_Auth_Yadis_XriEscapingTestCase extends PHPUnit_TestCase {
+ function test_escaping_percents()
+ {
+ $this->assertEquals(Auth_Yadis_escapeForIRI('@example/abc%2Fd/ef'),
+ '@example/abc%252Fd/ef');
+ }
+
+ function runTest()
+ {
+ // no escapes
+ $this->assertEquals('@example/foo/(@bar)',
+ Auth_Yadis_escapeForIRI('@example/foo/(@bar)'));
+
+ // escape slashes
+ $this->assertEquals('@example/foo/(@bar%2Fbaz)',
+ Auth_Yadis_escapeForIRI('@example/foo/(@bar/baz)'));
+
+ $this->assertEquals('@example/foo/(@bar%2Fbaz)/(+a%2Fb)',
+ Auth_Yadis_escapeForIRI('@example/foo/(@bar/baz)/(+a/b)'));
+
+ // escape query ? and fragment
+ $this->assertEquals('@example/foo/(@baz%3Fp=q%23r)?i=j#k',
+ Auth_Yadis_escapeForIRI('@example/foo/(@baz?p=q#r)?i=j#k'));
+ }
+}
+
+class Tests_Auth_Yadis_ProxyQueryTestCase extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->proxy_url = 'http://xri.example.com/';
+ $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $this->proxy = new Auth_Yadis_ProxyResolver($fetcher,
+ $this->proxy_url);
+ $this->servicetype = 'xri://+i-service*(+forwarding)*($v*1.0)';
+ $this->servicetype_enc = 'xri%3A%2F%2F%2Bi-service%2A%28%2Bforwarding%29%2A%28%24v%2A1.0%29';
+ }
+
+ function runTest()
+ {
+ $st = $this->servicetype;
+ $ste = $this->servicetype_enc;
+ $args_esc = "_xrd_r=application%2Fxrds%2Bxml&_xrd_t=" . $ste;
+ $h = $this->proxy_url;
+ $this->assertEquals($h . '=foo?' . $args_esc,
+ $this->proxy->queryURL('=foo', $st));
+ $this->assertEquals($h . '=foo/bar?baz&' . $args_esc,
+ $this->proxy->queryURL('=foo/bar?baz', $st));
+ $this->assertEquals($h . '=foo/bar?baz=quux&' . $args_esc,
+ $this->proxy->queryURL('=foo/bar?baz=quux', $st));
+ $this->assertEquals($h . '=foo/bar?mi=fa&so=la&' . $args_esc,
+ $this->proxy->queryURL('=foo/bar?mi=fa&so=la', $st));
+
+ $args_esc = "_xrd_r=application%2Fxrds%2Bxml&_xrd_t=" . $ste;
+ $h = $this->proxy_url;
+ $this->assertEquals($h . '=foo/bar??' . $args_esc,
+ $this->proxy->queryURL('=foo/bar?', $st));
+ $this->assertEquals($h . '=foo/bar????' . $args_esc,
+ $this->proxy->queryURL('=foo/bar???', $st));
+ }
+}
+
+class Tests_Auth_Yadis_TestGetRootAuthority extends PHPUnit_TestCase {
+ function runTest()
+ {
+ $xris = array(
+ array("@foo", "@"),
+ array("@foo*bar", "@"),
+ array("@*foo*bar", "@"),
+ array("@foo/bar", "@"),
+ array("!!990!991", "!"),
+ array("!1001!02", "!"),
+ array("=foo*bar", "="),
+ array("(example.com)/foo", "(example.com)"),
+ array("(example.com)*bar/foo", "(example.com)"),
+ array("baz.example.com/foo", "baz.example.com"),
+ array("baz.example.com:8080/foo", "baz.example.com:8080")
+ // Looking at the ABNF in XRI Syntax 2.0, I don't think you can
+ // have example.com*bar. You can do (example.com)*bar, but that
+ // would mean something else.
+ // ("example.com*bar/(=baz)", "example.com*bar"),
+ // ("baz.example.com!01/foo", "baz.example.com!01"),
+ );
+
+ foreach ($xris as $tupl) {
+ list($thexri, $expected_root) = $tupl;
+ $this->assertEquals(Auth_Yadis_XRI($expected_root),
+ Auth_Yadis_rootAuthority($thexri),
+ 'rootAuthority test ('.$thexri.')');
+ }
+ }
+}
+
+class Tests_Auth_Yadis_XRI extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_Yadis_XRI";
+ }
+
+ function Tests_Auth_Yadis_XRI()
+ {
+ $this->addTest(new Tests_Auth_Yadis_ProxyQueryTestCase());
+ $this->addTest(new Tests_Auth_Yadis_XriEscapingTestCase());
+ $this->addTest(new Tests_Auth_Yadis_XriDiscoveryTestCase());
+ $this->addTest(new Tests_Auth_Yadis_TestGetRootAuthority());
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/Yadis.php b/Tests/Auth/Yadis/Yadis.php
new file mode 100644
index 0000000..837e447
--- /dev/null
+++ b/Tests/Auth/Yadis/Yadis.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Tests for the core of the PHP Yadis library.
+ */
+
+require_once 'PHPUnit.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'Tests/Auth/Yadis/TestUtil.php';
+
+class Tests_Auth_Yadis_DiscoveryTest extends PHPUnit_TestCase {
+
+ function Tests_Auth_Yadis_DiscoveryTest($input_url, $redir_uri,
+ $xrds_uri, $num)
+ {
+ $this->input_url = $input_url;
+ $this->redir_uri = $redir_uri;
+ $this->xrds_uri = $xrds_uri;
+ $this->num = $num;
+ }
+
+ function getName()
+ {
+ return "Yadis discovery test ".$this->num;
+ }
+
+ function runTest()
+ {
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $y = Auth_Yadis_Yadis::discover(
+ $this->input_url, $fetcher);
+ $this->assertTrue($y !== null);
+
+ // Compare parts of returned Yadis object to expected URLs.
+ $this->assertEquals($this->redir_uri, $y->normalized_uri, "tried $this->input_url");
+
+ if ($this->xrds_uri) {
+ $this->assertEquals($this->xrds_uri, $y->xrds_uri);
+ // Compare contents of actual HTTP GET with that of Yadis
+ // response.
+ $f = Auth_Yadis_Yadis::getHTTPFetcher();
+ $http_response = $f->get($this->xrds_uri);
+
+ $this->assertEquals($http_response->body, $y->response_text);
+ } else {
+ $this->assertTrue($y->xrds_uri === null);
+ }
+ }
+}
+
+class Tests_Auth_Yadis_Yadis extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_Yadis_Yadis";
+ }
+
+ function parseTests($data)
+ {
+ $cases = explode("\n", $data);
+ $tests = array();
+
+ foreach ($cases as $line) {
+ if ($line && ($line[0] != "#")) {
+ $tests[] = explode("\t", $line, 3);
+ }
+ }
+
+ return $tests;
+ }
+
+ function Tests_Auth_Yadis_Yadis()
+ {
+ $test_data = file_get_contents('http://www.openidenabled.com/resources/yadis-test/discover/manifest.txt');
+
+ $test_cases = $this->parseTests($test_data);
+
+ $i = 0;
+ foreach ($test_cases as $case) {
+ $i++;
+ list($input, $redir, $xrds) = $case;
+ $this->addTest(new Tests_Auth_Yadis_DiscoveryTest($input,
+ $redir,
+ $xrds, $i));
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/data/README b/Tests/Auth/Yadis/data/README
new file mode 100644
index 0000000..3739cf1
--- /dev/null
+++ b/Tests/Auth/Yadis/data/README
@@ -0,0 +1,12 @@
+delegated-20060809.xrds - results from proxy.xri.net, determined by
+ Drummond and Kevin to be incorrect.
+delegated-20060809-r1.xrds - Drummond's 1st correction
+delegated-20060809-r2.xrds - Drummond's 2nd correction
+
+spoofs: keturn's (=!E4)'s attempts to log in with Drummond's i-number (=!D2)
+spoof1.xrds
+spoof2.xrds
+spoof3.xrds - attempt to steal @!C0!D2 by having "at least one" CanonicalID
+ match the $res service ProviderID.
+
+ref.xrds - resolving @ootao*test.ref, which refers to a neustar XRI.
diff --git a/Tests/Auth/Yadis/data/accept.txt b/Tests/Auth/Yadis/data/accept.txt
new file mode 100644
index 0000000..0853321
--- /dev/null
+++ b/Tests/Auth/Yadis/data/accept.txt
@@ -0,0 +1,118 @@
+# Accept: [Accept: header value from RFC2616,
+# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html]
+# Available: [whitespace-separated content types]
+# Expected: [Accept-header like list, containing the available content
+# types with their q-values]
+
+Accept: */*
+Available: text/plain
+Expected: text/plain; q=1.0
+
+Accept: */*
+Available: text/plain, text/html
+Expected: text/plain; q=1.0, text/html; q=1.0
+
+# The order matters
+Accept: */*
+Available: text/html, text/plain
+Expected: text/html; q=1.0, text/plain; q=1.0
+
+Accept: text/*, */*; q=0.9
+Available: text/plain, image/jpeg
+Expected: text/plain; q=1.0, image/jpeg; q=0.9
+
+Accept: text/*, */*; q=0.9
+Available: image/jpeg, text/plain
+Expected: text/plain; q=1.0, image/jpeg; q=0.9
+
+# wildcard subtypes still reject differing main types
+Accept: text/*
+Available: image/jpeg, text/plain
+Expected: text/plain; q=1.0
+
+Accept: text/html
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html, text/*
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html, text/*
+Available: text/plain, text/html
+Expected: text/plain; q=1.0, text/html; q=1.0
+
+Accept: text/html, text/*; q=0.9
+Available: text/plain, text/html
+Expected: text/html; q=1.0, text/plain; q=0.9
+
+# If a more specific type has a higher q-value, then the higher value wins
+Accept: text/*; q=0.9, text/html
+Available: text/plain, text/html
+Expected: text/html; q=1.0, text/plain; q=0.9
+
+Accept: */*, text/*; q=0.9, text/html; q=0.1
+Available: text/plain, text/html, image/monkeys
+Expected: image/monkeys; q=1.0, text/plain; q=0.9, text/html; q=0.1
+
+Accept: text/*, text/html; q=0
+Available: text/html
+Expected:
+
+Accept: text/*, text/html; q=0
+Available: text/html, text/plain
+Expected: text/plain; q=1.0
+
+Accept: text/html
+Available: text/plain
+Expected:
+
+Accept: application/xrds+xml, text/html; q=0.9
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.9
+
+Accept: application/xrds+xml, */*; q=0.9
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.9
+
+Accept: application/xrds+xml, application/xhtml+xml; q=0.9, text/html; q=0.8, text/xml; q=0.7
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.8
+
+# See http://www.rfc-editor.org/rfc/rfc3023.txt, section A.13
+Accept: application/xrds
+Available: application/xrds+xml
+Expected:
+
+Accept: application/xrds+xml
+Available: application/xrds
+Expected:
+
+Accept: application/xml
+Available: application/xrds+xml
+Expected:
+
+Available: application/xrds+xml
+Accept: application/xml
+Expected:
+
+
+
+#################################################
+# The tests below this line are documentation of how this library
+# works. If the implementation changes, it's acceptable to change the
+# test to reflect that. These are specified so that we can make sure
+# that the current implementation actually works the way that we
+# expect it to given these inputs.
+
+Accept: text/html;level=1
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html; level=1, text/html; level=9; q=0.1
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html; level=9; q=0.1, text/html; level=1
+Available: text/html
+Expected: text/html; q=1.0
diff --git a/Tests/Auth/Yadis/data/brian.multi.xrds b/Tests/Auth/Yadis/data/brian.multi.xrds
new file mode 100644
index 0000000..1bc95de
--- /dev/null
+++ b/Tests/Auth/Yadis/data/brian.multi.xrds
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="2">
+ <Type>http://openid.net/signon/1.1</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://frank.myopenid.com/</openid:Delegate>
+ </Service>
+
+ </XRD>
+ <XRD>
+
+ <Service priority="1">
+ <Type>http://bar.com/</Type>
+ <URI>http://bar.com/server</URI>
+ </Service>
+
+ <Service priority="2">
+ <Type>http://foo.com</Type>
+ <URI>http://foo.com/server</URI>
+ </Service>
+
+ </XRD>
+ <XRD>
+
+ <Service priority="0">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
+
diff --git a/Tests/Auth/Yadis/data/brian.multi_uri.xrds b/Tests/Auth/Yadis/data/brian.multi_uri.xrds
new file mode 100644
index 0000000..fce5ef7
--- /dev/null
+++ b/Tests/Auth/Yadis/data/brian.multi_uri.xrds
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <URI>http://example.com/server</URI>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
+
diff --git a/Tests/Auth/Yadis/data/brian.xrds b/Tests/Auth/Yadis/data/brian.xrds
new file mode 100644
index 0000000..c7539fe
--- /dev/null
+++ b/Tests/Auth/Yadis/data/brian.xrds
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="0">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
+
diff --git a/Tests/Auth/Yadis/data/brian_priority.xrds b/Tests/Auth/Yadis/data/brian_priority.xrds
new file mode 100644
index 0000000..273077d
--- /dev/null
+++ b/Tests/Auth/Yadis/data/brian_priority.xrds
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="2">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.schtuff.com/?action=openid_server</URI>
+ <openid:Delegate>http://users.schtuff.com/brian</openid:Delegate>
+ </Service>
+
+ <Service priority="1">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://brian.myopenid.com/</openid:Delegate>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
+
diff --git a/Tests/Auth/Yadis/data/delegated-20060809-r1.xrds b/Tests/Auth/Yadis/data/delegated-20060809-r1.xrds
new file mode 100644
index 0000000..f994b14
--- /dev/null
+++ b/Tests/Auth/Yadis/data/delegated-20060809-r1.xrds
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-09T22:07:13.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID>xri://!!1003</ProviderID>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test1</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://!!1003</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/delegated-20060809-r2.xrds b/Tests/Auth/Yadis/data/delegated-20060809-r2.xrds
new file mode 100644
index 0000000..68c08dc
--- /dev/null
+++ b/Tests/Auth/Yadis/data/delegated-20060809-r2.xrds
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-09T22:07:13.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test1</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/delegated-20060809.xrds b/Tests/Auth/Yadis/data/delegated-20060809.xrds
new file mode 100644
index 0000000..073ee68
--- /dev/null
+++ b/Tests/Auth/Yadis/data/delegated-20060809.xrds
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-09T22:07:13.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID/>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test1</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://!!1003</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/example-xrds.xml b/Tests/Auth/Yadis/data/example-xrds.xml
new file mode 100644
index 0000000..101ba3b
--- /dev/null
+++ b/Tests/Auth/Yadis/data/example-xrds.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Sample XRDS file at: NAME -->
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="0">
+ <Type>http://example.com/</Type>
+ <URI>http://www.openidenabled.com/</URI>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/Yadis/data/no-xrd.xml b/Tests/Auth/Yadis/data/no-xrd.xml
new file mode 100644
index 0000000..ca66f73
--- /dev/null
+++ b/Tests/Auth/Yadis/data/no-xrd.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns:typekey="http://typekey.com/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+</xrds:XRDS>
diff --git a/Tests/Auth/Yadis/data/not-xrds.xml b/Tests/Auth/Yadis/data/not-xrds.xml
new file mode 100644
index 0000000..7f5bfd5
--- /dev/null
+++ b/Tests/Auth/Yadis/data/not-xrds.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<x></x>
diff --git a/Tests/Auth/Yadis/data/pip.xrds b/Tests/Auth/Yadis/data/pip.xrds
new file mode 100644
index 0000000..ca271ab
--- /dev/null
+++ b/Tests/Auth/Yadis/data/pip.xrds
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.1</Type>
+ <Type>http://openid.net/sreg/1.0</Type>
+ <URI>https://pip.verisignlabs.com/server</URI>
+ </Service>
+
+ <Service priority="20">
+ <Type>http://openid.net/signon/1.0</Type>
+ <Type>http://openid.net/sreg/1.0</Type>
+ <URI>https://pip.verisignlabs.com/server</URI>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
+
diff --git a/Tests/Auth/Yadis/data/prefixsometimes.xrds b/Tests/Auth/Yadis/data/prefixsometimes.xrds
new file mode 100644
index 0000000..5522a6e
--- /dev/null
+++ b/Tests/Auth/Yadis/data/prefixsometimes.xrds
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-09T22:07:13.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test1</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>xri://@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/ref.xrds b/Tests/Auth/Yadis/data/ref.xrds
new file mode 100644
index 0000000..69cf683
--- /dev/null
+++ b/Tests/Auth/Yadis/data/ref.xrds
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test.ref" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-15T18:56:09.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID/>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test.ref</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://!!1003</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA03</LocalID>
+ <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA03</CanonicalID>
+ <Ref>@!BAE.A650.823B.2475</Ref>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRDS ref="xri://@!BAE.A650.823B.2475" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>!BAE.A650.823B.2475</Query>
+ <Status code="100"/>
+ <Expires>2006-08-15T18:56:10.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!BAE.A650.823B.2475</LocalID>
+ <CanonicalID priority="10">@!BAE.A650.823B.2475</CanonicalID>
+ <Service priority="10">
+ <Type select="true">(+wdnc)</Type>
+ <ProviderID/>
+ <Path select="true">(+wdnc)</Path>
+ <URI append="none" priority="10">http://www.tcpacompliance.us</URI>
+ </Service>
+ <Service priority="10">
+ <Type match="content" select="true">xri://$res*auth*($v*2.0)</Type>
+ <ProviderID/>
+ <MediaType match="content" select="false">application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://dev.dready.org/cgi-bin/xri</URI>
+ </Service>
+ <Service priority="10">
+ <Type match="content" select="true">(+i-name)</Type>
+ <ProviderID/>
+ <Path match="content" select="true">(+i-name)</Path>
+ <URI append="none" priority="10">http://www.inames.net</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+ <Type match="default" select="false"/>
+ <ProviderID>xri://!!1001</ProviderID>
+ <Path select="true">(+contact)</Path>
+ <Path match="null" select="false"/>
+ <MediaType select="false">text/html</MediaType>
+ <MediaType match="default" select="false"/>
+ <URI append="none" priority="10">http://www.neustar.biz</URI>
+ </Service>
+ </XRD>
+ </XRDS>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>!BAE.A650.823B.2475</Query>
+ <Status code="100"/>
+ <Expires>2006-08-15T18:56:10.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!BAE.A650.823B.2475</LocalID>
+ <CanonicalID priority="10">@!BAE.A650.823B.2475</CanonicalID>
+ <Service priority="10">
+ <Type select="true">(+wdnc)</Type>
+ <ProviderID/>
+ <Path select="true">(+wdnc)</Path>
+ <URI append="none" priority="10">http://www.tcpacompliance.us</URI>
+ </Service>
+ <Service priority="10">
+ <Type match="content" select="true">xri://$res*auth*($v*2.0)</Type>
+ <ProviderID/>
+ <MediaType match="content" select="false">application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://dev.dready.org/cgi-bin/xri</URI>
+ </Service>
+ <Service priority="10">
+ <Type match="content" select="true">(+i-name)</Type>
+ <ProviderID/>
+ <Path match="content" select="true">(+i-name)</Path>
+ <URI append="none" priority="10">http://www.inames.net</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+ <Type match="default" select="false"/>
+ <ProviderID>xri://!!1001</ProviderID>
+ <Path select="true">(+contact)</Path>
+ <Path match="null" select="false"/>
+ <MediaType select="false">text/html</MediaType>
+ <MediaType match="default" select="false"/>
+ <URI append="none" priority="10">http://www.neustar.biz</URI>
+ </Service>
+ </XRD>
+</XRDS> \ No newline at end of file
diff --git a/Tests/Auth/Yadis/data/sometimesprefix.xrds b/Tests/Auth/Yadis/data/sometimesprefix.xrds
new file mode 100644
index 0000000..eff7555
--- /dev/null
+++ b/Tests/Auth/Yadis/data/sometimesprefix.xrds
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*ootao</Query>
+ <Status code="100"/>
+ <Expires>2006-08-09T22:07:13.000Z</Expires>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+ <CanonicalID priority="10">xri://@!5BAD.2AA.3C72.AF46</CanonicalID>
+ <Service priority="10">
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*test1</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID/>
+ <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/spoof1.xrds b/Tests/Auth/Yadis/data/spoof1.xrds
new file mode 100644
index 0000000..8e870c8
--- /dev/null
+++ b/Tests/Auth/Yadis/data/spoof1.xrds
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID>!E4</LocalID>
+ <CanonicalID>=!E4</CanonicalID>
+
+ <Service>
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <URI>http://keturn.example.com/resolve/</URI>
+ <ProviderID>=!E4</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*isDrummond</Query>
+ <ProviderID>=!E4</ProviderID>
+ <LocalID>!D2</LocalID>
+ <CanonicalID>=!D2</CanonicalID>
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://keturn.example.com/openid</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/spoof2.xrds b/Tests/Auth/Yadis/data/spoof2.xrds
new file mode 100644
index 0000000..7547561
--- /dev/null
+++ b/Tests/Auth/Yadis/data/spoof2.xrds
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID>!E4</LocalID>
+ <CanonicalID>=!E4</CanonicalID>
+
+ <Service>
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <URI>http://keturn.example.com/resolve/</URI>
+ <ProviderID>xri://=</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*isDrummond</Query>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID>!D2</LocalID>
+ <CanonicalID>=!D2</CanonicalID>
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://keturn.example.com/openid</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/spoof3.xrds b/Tests/Auth/Yadis/data/spoof3.xrds
new file mode 100644
index 0000000..f4c43c9
--- /dev/null
+++ b/Tests/Auth/Yadis/data/spoof3.xrds
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID>@E4</LocalID>
+ <CanonicalID>@!E4</CanonicalID>
+
+ <Service>
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <URI>http://keturn.example.com/resolve/</URI>
+ <ProviderID>@!E4</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*is</Query>
+ <ProviderID>@!E4</ProviderID>
+ <LocalID>!D2</LocalID>
+ <CanonicalID>=!C0</CanonicalID>
+ <CanonicalID>=!E4!01</CanonicalID>
+ <Service>
+ <Type>xri://$res*auth*($v*2.0)</Type>
+ <URI>http://keturn.example.com/resolve/</URI>
+ <ProviderID>@!C0</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*drummond</Query>
+ <ProviderID>@!C0</ProviderID>
+ <LocalID>!D2</LocalID>
+ <CanonicalID>@!C0!D2</CanonicalID>
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://keturn.example.com/openid</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/subsegments.xrds b/Tests/Auth/Yadis/data/subsegments.xrds
new file mode 100644
index 0000000..11d2e91
--- /dev/null
+++ b/Tests/Auth/Yadis/data/subsegments.xrds
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=nishitani*masaki" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*nishitani</Query>
+ <Status code="100"/>
+ <Expires>2007-12-25T11:33:39.000Z</Expires>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID priority="10">!E117.EF2F.454B.C707</LocalID>
+ <CanonicalID priority="10">=!E117.EF2F.454B.C707</CanonicalID>
+ <Service priority="10">
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <URI append="none" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ <Service priority="10">
+ <Type select="true">xri://$res*auth*($v*2.0)</Type>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <MediaType>application/xrds+xml;trust=none</MediaType>
+ <URI priority="10">http://resolve.ezibroker.net/resolve/=nishitani/</URI>
+ </Service>
+ <Service priority="1">
+ <Type match="content" select="true">xri://+i-service*(+forwarding)*($v*1.0)</Type>
+ <Type match="null" select="false"/>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <Path match="content">(+index)</Path>
+ <Path match="default"/>
+ <URI append="qxri" priority="1">http://linksafe-forward.ezibroker.net/forwarding/</URI>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*masaki</Query>
+ <Status code="100">SUCCESS</Status>
+ <ProviderID>xri://!!1003</ProviderID>
+ <LocalID>!0000.0000.3B9A.CA01</LocalID>
+ <CanonicalID>=!E117.EF2F.454B.C707!0000.0000.3B9A.CA01</CanonicalID>
+ <Service>
+ <Type select="true">http://openid.net/signon/1.0</Type>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <URI append="none" priority="1">https://linksafe.ezibroker.net/server/</URI>
+ </Service>
+ <Service>
+ <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+ <Type match="null"/>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <Path select="true">(+contact)</Path>
+ <Path match="null"/>
+ <URI append="authority" priority="1">http://linksafe-contact.ezibroker.net/contact/</URI>
+ </Service>
+ <Service priority="1">
+ <Type match="content" select="true">xri://+i-service*(+forwarding)*($v*1.0)</Type>
+ <Type match="null" select="false"/>
+ <ProviderID>xri://!!1003!103</ProviderID>
+ <Path match="content">(+index)</Path>
+ <Path match="default"/>
+ <URI append="qxri" priority="1">http://linksafe-forward.ezibroker.net/forwarding/</URI>
+ </Service>
+ </XRD>
+</XRDS>
diff --git a/Tests/Auth/Yadis/data/test1-discover.txt b/Tests/Auth/Yadis/data/test1-discover.txt
new file mode 100644
index 0000000..7ec9b87
--- /dev/null
+++ b/Tests/Auth/Yadis/data/test1-discover.txt
@@ -0,0 +1,137 @@
+equiv
+Status: 200 OK
+Content-Type: text/html
+
+<html>
+<head>
+<meta http-equiv="YADIS_HEADER" content="URL_BASE/xrds">
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+</html>
+
+header
+Status: 200 OK
+Content-Type: text/html
+YADIS_HEADER: URL_BASE/xrds
+
+<html>
+<head>
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+
+xrds
+Status: 200 OK
+Content-Type: application/xrds+xml
+
+<XRDS Content>
+
+xrds_ctparam
+Status: 200 OK
+Content-Type: application/xrds+xml; charset=UTF8
+
+<XRDS Content>
+
+xrds_ctcase
+Status: 200 OK
+Content-Type: appliCATION/XRDS+xml
+
+<XRDS Content>
+
+xrds_html
+Status: 200 OK
+Content-Type: text/html
+
+<XRDS Content>
+
+redir_equiv
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/equiv
+
+You are presently being redirected.
+
+redir_header
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/header
+
+You are presently being redirected.
+
+redir_xrds
+Status: 302 Found
+Content-Type: application/xrds+xml
+Location: URL_BASE/xrds
+
+<XRDS Content>
+
+redir_xrds_html
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/xrds_html
+
+You are presently being redirected.
+
+redir_redir_equiv
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/redir_equiv
+
+You are presently being redirected.
+
+lowercase_header
+Status: 200 OK
+Content-Type: text/html
+x-xrds-location: URL_BASE/xrds
+
+<html>
+<head>
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+
+404_server_response
+Status: 404 Not Found
+
+EEk!
+
+500_server_response
+Status: 500 Server error
+
+EEk!
+
+201_server_response
+Status: 201 Created
+
+EEk!
+
+404_with_header
+Status: 404 Not Found
+YADIS_HEADER: URL_BASE/xrds
+
+EEk!
+
+404_with_meta
+Status: 404 Not Found
+Content-Type: text/html
+
+<html>
+<head>
+<meta http-equiv="YADIS_HEADER" content="URL_BASE/xrds">
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+</html>
diff --git a/Tests/Auth/Yadis/data/test1-parsehtml.txt b/Tests/Auth/Yadis/data/test1-parsehtml.txt
new file mode 100644
index 0000000..752ad09
--- /dev/null
+++ b/Tests/Auth/Yadis/data/test1-parsehtml.txt
@@ -0,0 +1,149 @@
+found
+<!-- minimal well-formed success case -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found"></head></html>
+
+found
+<!-- minimal well-formed success case, xhtml closing, whitespace -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found" /></head></html>
+
+found
+<!-- minimal well-formed success case, xhtml closing, no whitespace -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found"/></head></html>
+
+found
+<!-- minimal success case -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found">
+
+found
+<!-- ignore bogus top-level tags -->
+</porky><html><head><meta http-equiv="X-XRDS-Location" content="found">
+
+found
+<!-- Case folding for header name -->
+<html><head><meta http-equiv="x-xrds-location" content="found">
+
+found
+<!-- missing <html> tag -->
+<head><meta http-equiv="X-XRDS-Location" content="found">
+
+found
+<!-- javascript in head -->
+<html><head><script type="text/javascript">document.write("<body>");</script><META http-equiv="X-XRDS-Location" content="found">
+
+EOF
+<!-- javascript in head -->
+<html><head><script type="text/javascript">document.write("<body>");<META http-equiv="X-XRDS-Location" content="found">
+
+found
+<!-- case folding for tag names -->
+<html><head><META http-equiv="X-XRDS-Location" content="found">
+
+found
+<!-- Stop after first one found -->
+<html><head>
+<meta http-equiv="x-xrds-location" content="found">
+<meta http-equiv="x-xrds-location" content="not-found">
+
+&
+<!-- standard entity -->
+<head><meta http-equiv="X-XRDS-Location" content="&amp;">
+
+found
+<!-- hex entity -->
+<html>
+ <head>
+ <meta http-equiv="X-XRDS-Location" content="&#x66;ound">
+ </head>
+</html>
+
+found
+<!-- decimal entity -->
+<html>
+ <head>
+ <meta http-equiv="X-XRDS-Location" content="&#102;ound">
+ </head>
+</html>
+
+/
+<!-- hex entity -->
+<html>
+ <head>
+ <meta http-equiv="X-XRDS-Location" content="&#x2f;">
+ </head>
+</html>
+
+
+<!-- empty string -->
+<html><head><meta http-equiv="X-XRDS-Location" content="">
+
+EOF
+<!-- No markup, except this comment -->
+
+None
+<!-- No meta, just standard HTML -->
+<html>
+ <head>
+ <title>A boring document</title>
+ </head>
+ <body>
+ <h1>A boring document</h1>
+ <p>There's really nothing interesting about this</p>
+ </body>
+</html>
+
+EOF
+<!-- No <html> or <head> -->
+<meta http-equiv="X-XRDS-Location" content="found">
+
+EOF
+<!-- No <head> tag -->
+<html><meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- No <html> or <head> and a <body> -->
+<body><meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- <head> and <html> reversed -->
+<head><html><meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- <meta> is inside of <body> -->
+<html><head><body><meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- <meta> is inside comment -->
+<html>
+ <head>
+ <!--<meta http-equiv="X-XRDS-Location" content="found">-->
+ </head>
+</html>
+
+None
+<!-- <meta> is inside of <body> -->
+<html>
+ <head>
+ <title>Someone's blog</title>
+ </head>
+ <body>
+ <h1>My blog</h1>
+ <p>This is my blog</p>
+ <h2>Comments</h2>
+ <p><meta http-equiv="X-XRDS-Location" content="found"></p>
+ </body>
+</html>
+
+None
+<!-- short head tag -->
+<html><head/>
+<meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- <body> comes first -->
+<body><html><head>
+<meta http-equiv="X-XRDS-Location" content="found">
+
+None
+<!-- </body> comes first -->
+</body><html><head>
+<meta http-equiv="X-XRDS-Location" content="found">
diff --git a/Tests/Auth/Yadis/data/test1-xrd.xml b/Tests/Auth/Yadis/data/test1-xrd.xml
new file mode 100644
index 0000000..60e5ca7
--- /dev/null
+++ b/Tests/Auth/Yadis/data/test1-xrd.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns:typekey="http://typekey.com/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service priority="0">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.myopenid.com/server</URI>
+ <openid:Delegate>http://josh.myopenid.com/</openid:Delegate>
+ </Service>
+
+ <Service priority="20">
+ <Type>http://lid.netmesh.org/sso/2.0b5</Type>
+ <Type>http://lid.netmesh.org/2.0b5</Type>
+ <URI>http://mylid.net/josh</URI>
+ </Service>
+
+ <Service priority="10">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.livejournal.com/openid/server.bml</URI>
+ <openid:Delegate>http://www.livejournal.com/users/nedthealpaca/</openid:Delegate>
+ </Service>
+
+ <Service priority="15">
+ <Type>http://typekey.com/services/1.0</Type>
+ <typekey:MemberName>joshhoyt</typekey:MemberName>
+ </Service>
+
+ <Service priority="5">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://www.schtuff.com/openid</URI>
+ <openid:Delegate>http://users.schtuff.com/josh</openid:Delegate>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/Auth/Yadis/data/uri_priority.xrds b/Tests/Auth/Yadis/data/uri_priority.xrds
new file mode 100644
index 0000000..b1a2f65
--- /dev/null
+++ b/Tests/Auth/Yadis/data/uri_priority.xrds
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns:openid="http://openid.net/xmlns/1.0"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>http://no.priority/</URI>
+ <URI priority="1">http://one.priority/</URI>
+ <URI priority="0">http://zero.priority/</URI>
+ </Service>
+
+ </XRD>
+</xrds:XRDS>
diff --git a/Tests/TestDriver.php b/Tests/TestDriver.php
new file mode 100644
index 0000000..a19b502
--- /dev/null
+++ b/Tests/TestDriver.php
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * A driver for the PHP OpenID unit tests.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'PHPUnit.php';
+require_once 'PHPUnit/GUI/HTML.php';
+
+error_reporting(E_ALL);
+
+global $__test_errors;
+$__test_errors = array();
+
+function __handler($code, $message)
+{
+ global $__test_errors;
+
+ if ($code == E_USER_WARNING) {
+ $__test_errors[] = $message;
+ }
+}
+
+function __raiseError($message)
+{
+ set_error_handler('__handler');
+ trigger_error($message, E_USER_WARNING);
+ restore_error_handler();
+}
+
+function __getError()
+{
+ global $__test_errors;
+ if ($__test_errors) {
+ return array_pop($__test_errors);
+ }
+ return null;
+}
+
+/**
+ * Load the tests that are defined in the named modules.
+ *
+ * If you have Tests/Foo.php which defines a test class called
+ * Tests_Foo, the call would look like:
+ *
+ * loadTests('Tests/', array('Foo'))
+ *
+ * @param string $test_dir The root of the test hierarchy. Must end
+ * with a /
+ *
+ * @param array $test_names The names of the modules in which the
+ * tests are defined. This should not include the root of the test
+ * hierarchy.
+ */
+function loadTests($test_dir, $test_names)
+{
+ global $_tests;
+ $suites = array();
+
+ foreach ($test_names as $filename) {
+ $filename = $test_dir . $filename . '.php';
+
+ if (!global_require_once($filename)) {
+ continue;
+ }
+
+ $class_name = str_replace('/', '_', $filename);
+ $class_name = basename($class_name, '.php');
+
+ $suites[] = makeSuite($class_name);
+ }
+
+ return $suites;
+}
+
+function makeSuite($class_name) {
+ $test = new $class_name($class_name);
+
+ if (is_a($test, 'PHPUnit_TestCase')) {
+ $s = new PHPUnit_TestSuite();
+ $s->setName($class_name);
+ $s->addTestSuite($class_name);
+ $test = $s;
+ }
+
+ $tc_array_name = $class_name . '_other';
+ if (array_key_exists($tc_array_name, $GLOBALS) &&
+ is_array($GLOBALS[$tc_array_name])) {
+
+ foreach ($GLOBALS[$tc_array_name] as $tc) {
+ $test->addTestSuite(get_class($tc));
+ }
+ }
+
+ return $test;
+}
+
+
+function global_require_once($name)
+{
+ $f = include_once $name;
+ if (!$f) {
+ print("global require once skipping $name\n");
+ return false;
+ }
+ foreach (get_defined_vars() as $k => $v) {
+ if (!in_array($k, array('name', 'GLOBALS'))) {
+ $GLOBALS[$k] = $v;
+ }
+ }
+ return true;
+}
+
+$_tests = array(
+ array(
+ 'dir' => 'Tests/Auth/OpenID/',
+ 'files' => array(
+ 'Association',
+ 'AssociationResponse',
+ 'AuthRequest',
+ 'AX',
+ 'BigMath',
+ 'Consumer',
+ 'CryptUtil',
+ 'DiffieHellman',
+ 'Discover_OpenID',
+ 'Extension',
+ 'HMAC',
+ 'KVForm',
+ 'Message',
+ 'Negotiation',
+ 'Nonce',
+ 'OpenID_Yadis',
+ 'PAPE',
+ 'Parse',
+ 'RPVerify',
+ 'Server',
+ 'SReg',
+ 'StoreTest',
+ 'TrustRoot',
+ 'URINorm',
+ 'Util',
+ 'VerifyDisco'),
+ ),
+ array(
+ 'dir' => 'Tests/Auth/Yadis/',
+ 'files' => array(
+ 'ParseHTML',
+ 'XRDS',
+ 'Yadis',
+ 'Discover_Yadis',
+ 'XRI'
+ )
+ )
+ );
+
+function selectTests($package, $names)
+{
+ global $_tests;
+ $lnames = array_map('strtolower', $names);
+ $include = array();
+ $exclude = array();
+ foreach ($package['files'] as $t) {
+ $l = strtolower($t);
+ if (in_array($l, $lnames)) {
+ $include[] = $t;
+ }
+
+ if (in_array("/$l", $lnames)) {
+ $exclude[] = $t;
+ }
+ }
+
+ return array_diff($include, $exclude);
+}
+
+// Load OpenID library tests
+function loadSuite($names=null)
+{
+ global $_tests;
+ $result = array();
+ foreach ($_tests as $package) {
+ if (!$names) {
+ $selected = $package['files'];
+ } else {
+ $selected = selectTests($package, $names);
+ }
+ $result = array_merge($result, loadTests($package['dir'], $selected));
+ }
+
+ return $result;
+}
+?>
diff --git a/admin/adminutil.php b/admin/adminutil.php
new file mode 100644
index 0000000..7389838
--- /dev/null
+++ b/admin/adminutil.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Add a directory to the include path
+ *
+ * @param dir: The directory to add to the path
+ * @param at_start: If true, place this directory at the beginning of
+ * the include path. Otherwise, place it at the end.
+ */
+function includeAdd($dir, $at_start=false)
+{
+ $path = ini_get('include_path');
+ if (strlen($path)) {
+ $newpath = $at_start ? "$dir:$path" : "$path:$dir";
+ } else {
+ $newpath = $dir;
+ }
+
+ ini_set('include_path', $newpath);
+}
+
+/**
+ * Return the parent directory of this module.
+ */
+function getParent()
+{
+ return dirname(dirname(realpath(__FILE__)));
+}
+
+?> \ No newline at end of file
diff --git a/admin/brace_style.pl b/admin/brace_style.pl
new file mode 100644
index 0000000..ed8332f
--- /dev/null
+++ b/admin/brace_style.pl
@@ -0,0 +1,81 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+my $filename = $ARGV[0];
+
+if (!$filename) {
+ print "Usage: modified_otb.pl <filename>\n";
+ exit(1);
+}
+
+my @results = ();
+my $line_num = 0;
+my ($NONE, $BRACE, $PAREN) = (0, 1, 2);
+my $looking_for = $NONE;
+my $last_func_name = "";
+
+open(HANDLE, "<", $filename) or die "Cannot open $filename\n";
+
+# Read the file and track the lines with length > $max_length.
+while (<HANDLE>) {
+ $line_num++;
+ # Subtract one because the newline doesn't count toward the
+ # length.
+ chomp;
+
+ if (!$looking_for &&
+ ($_ =~ /^\s*function/) &&
+ ($_ =~ /\{/)) {
+ # Done (bad): we found a function whose opening line ends with
+ # a brace, which goes against the PEAR coding guidelines.
+
+ ($last_func_name) = $_ =~ /function\s*(.*)\(/;
+
+ push @results, "'$last_func_name' prototype ends with opening ".
+ "brace, line $line_num";
+ } elsif (!$looking_for &&
+ ($_ =~ /^\s*function/) &&
+ ($_ !~ /\)/)) {
+ ($last_func_name) = $_ =~ /function\s*(.*)\(/;
+ $looking_for = $PAREN;
+ } elsif (($looking_for == $PAREN) &&
+ ($_ =~ /\)/) &&
+ ($_ =~ /\{/)) {
+ # Done (bad): function prototype and brace are on the same
+ # line.
+ push @results, "'$last_func_name' prototype ends with with ".
+ "opening brace, line $line_num";
+ $looking_for = $NONE;
+ } elsif (($looking_for == $PAREN) &&
+ ($_ =~ /\)/) &&
+ ($_ !~ /\{/)) {
+ $looking_for = $BRACE;
+ } elsif (!$looking_for &&
+ ($_ =~ /^\s*function/) &&
+ ($_ =~ /\)/) &&
+ ($_ !~ /\{/)) {
+ ($last_func_name) = $_ =~ /function\s*(.*)\(/;
+ $looking_for = $BRACE;
+ } elsif (($looking_for == $BRACE) &&
+ ($_ eq "{")) {
+ $looking_for = $NONE;
+ # Done (good): the brace was found on the line after the
+ # function prototype.
+ } else {
+ # We got here because we got a line that we're not interested
+ # in.
+ $looking_for = $NONE;
+ }
+}
+
+# If any long lines were found, notify and exit(1); otherwise,
+# exit(0).
+if (@results) {
+ foreach my $result (@results) {
+ print "$filename: $result\n";
+ }
+ exit(1);
+} else {
+ exit(0);
+}
diff --git a/admin/checkimport b/admin/checkimport
new file mode 100644
index 0000000..05776d8
--- /dev/null
+++ b/admin/checkimport
@@ -0,0 +1,4 @@
+#!/usr/bin/env php
+<?php
+require_once $argv[1];
+?> \ No newline at end of file
diff --git a/admin/checkimports b/admin/checkimports
new file mode 100644
index 0000000..c4fa471
--- /dev/null
+++ b/admin/checkimports
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+./admin/findphp | xargs -L 1 ./admin/checkimport \ No newline at end of file
diff --git a/admin/darcs-ignore b/admin/darcs-ignore
new file mode 100644
index 0000000..8291ce2
--- /dev/null
+++ b/admin/darcs-ignore
@@ -0,0 +1,3 @@
+~$
+^doc(/|$)
+^CHANGELOG$
diff --git a/admin/docblocks b/admin/docblocks
new file mode 100644
index 0000000..a7f05c3
--- /dev/null
+++ b/admin/docblocks
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+#set -e
+
+bad_files=$(./admin/findphp | xargs -L 1 /usr/bin/env perl admin/docblocks.pl)
+
+if [ "$bad_files" ]
+ then
+ cat <<EOF 1>&2
+These files do not start with docblocks:
+
+$bad_files
+
+EOF
+ exit 1
+fi
diff --git a/admin/docblocks.pl b/admin/docblocks.pl
new file mode 100644
index 0000000..0483dbb
--- /dev/null
+++ b/admin/docblocks.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+my $filename = $ARGV[0];
+
+if (!$filename) {
+ print "Usage: docblocks.pl <filename>\n";
+ exit(1);
+}
+
+my %allowed = ("" => 1,
+ "<?php" => 1);
+
+open(HANDLE, "<", $filename) or die "Cannot open $filename\n";
+
+while (<HANDLE>) {
+ chomp;
+
+ if ($_ =~ /\/\*\*/) {
+ exit(0);
+ } elsif (!$allowed{$_}) {
+ print $filename."\n";
+ exit(1);
+ }
+}
diff --git a/admin/findallphp b/admin/findallphp
new file mode 100644
index 0000000..59a7306
--- /dev/null
+++ b/admin/findallphp
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+find Auth Tests \
+ -name _darcs -prune -o \
+ \( -type f \
+ -a -name \*.php \
+ -a ! -name .\* \
+ \)
diff --git a/admin/findglobals b/admin/findglobals
new file mode 100644
index 0000000..2fcb0e6
--- /dev/null
+++ b/admin/findglobals
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+# Find all PHP modules that are likely to have global variables
+set -e
+
+./admin/findphp | xargs grep '^\$'
diff --git a/admin/findphp b/admin/findphp
new file mode 100644
index 0000000..d529af7
--- /dev/null
+++ b/admin/findphp
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+find Auth Tests \
+ -name _darcs -prune -o \
+ \( -type f \
+ -a -name \*.php \
+ -a ! -name .\* \
+ \) | grep -v Tests
diff --git a/admin/fixperms b/admin/fixperms
new file mode 100644
index 0000000..0ea0944
--- /dev/null
+++ b/admin/fixperms
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+cat <<EOF | xargs chmod +x
+admin/checkimport
+admin/checkimports
+admin/docblocks
+admin/findphp
+admin/findglobals
+admin/fixperms
+admin/makedoc.sh
+admin/mathlib
+admin/prepare-release
+admin/runtests
+admin/findallphp
+admin/syntaxcheck
+EOF
diff --git a/admin/gettlds.py b/admin/gettlds.py
new file mode 100644
index 0000000..4300638
--- /dev/null
+++ b/admin/gettlds.py
@@ -0,0 +1,47 @@
+"""
+Fetch the current TLD list from the IANA Web site, parse it, and print
+an expression suitable for direct insertion into each library's trust
+root validation module
+
+Usage:
+ python gettlds.py (php|python|ruby)
+
+Then cut-n-paste.
+"""
+
+import urllib2
+
+import sys
+
+langs = {
+ 'php': (r"'/\.(",
+ "'", "|", "|' .",
+ r")\.?$/'"),
+ 'python': ("['",
+ "'", "', '", "',",
+ "']"),
+ 'ruby': ("%w'",
+ "", " ", "",
+ "'"),
+ }
+
+lang = sys.argv[1]
+prefix, line_prefix, separator, line_suffix, suffix = langs[lang]
+
+f = urllib2.urlopen('http://data.iana.org/TLD/tlds-alpha-by-domain.txt')
+tlds = []
+output_line = ""
+for input_line in f:
+ if input_line.startswith('#'):
+ continue
+
+ tld = input_line.strip().lower()
+ new_output_line = output_line + prefix + tld
+ if len(new_output_line) > 60:
+ print output_line + line_suffix
+ output_line = line_prefix + tld
+ else:
+ output_line = new_output_line
+ prefix = separator
+
+print output_line + suffix
diff --git a/admin/library-name b/admin/library-name
new file mode 100644
index 0000000..c67163c
--- /dev/null
+++ b/admin/library-name
@@ -0,0 +1 @@
+php-openid \ No newline at end of file
diff --git a/admin/longlines.pl b/admin/longlines.pl
new file mode 100644
index 0000000..6ce65e8
--- /dev/null
+++ b/admin/longlines.pl
@@ -0,0 +1,46 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+my $filename = $ARGV[0];
+
+if (!$filename) {
+ print "Usage: longlines.pl <filename> [length]\n";
+ exit(1);
+}
+
+# Set a default maximum line length.
+my $max_length = $ARGV[1] || 80;
+
+my @lines = ();
+my $line_num = 0;
+
+open(HANDLE, "<", $filename) or die "Cannot open $filename\n";
+
+# Read the file and track the lines with length > $max_length.
+while (<HANDLE>) {
+ $line_num++;
+ # Subtract one because the newline doesn't count toward the
+ # length.
+ if (length($_) - 1 > $max_length) {
+ push @lines, $line_num;
+ }
+}
+
+# If more than five long lines were found, truncate to five and
+# indicate that others were present, too.
+if (@lines > 5) {
+ @lines = @lines[0..4];
+ push @lines, "and others";
+}
+
+# If any long lines were found, notify and exit(1); otherwise,
+# exit(0).
+if (@lines) {
+ print $filename." (line".((@lines > 1) ? "s" : "")." ".
+ join(", ", @lines)." exceed".((@lines == 1) ? "s" : "").
+ " length $max_length)\n";
+ exit(1);
+} else {
+ exit(0);
+}
diff --git a/admin/makedoc.sh b/admin/makedoc.sh
new file mode 100644
index 0000000..f07f6c6
--- /dev/null
+++ b/admin/makedoc.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -v
+phpdoc -p on -t doc -d Auth,admin/tutorials -ti "JanRain OpenID Library" \
+ --ignore \*~,BigMath.php,Discover.php,CryptUtil.php,DiffieHellman.php,HMACSHA1.php,KVForm.php,Parse.php,TrustRoot.php,HTTPFetcher.php,ParanoidHTTPFetcher.php,PlainHTTPFetcher.php,ParseHTML.php,URINorm.php,XRI.php,XRIRes.php,Misc.php \
+ -dn "OpenID" -o "HTML:frames:phphtmllib"
diff --git a/admin/mathlib b/admin/mathlib
new file mode 100644
index 0000000..53f50d0
--- /dev/null
+++ b/admin/mathlib
@@ -0,0 +1,18 @@
+#!/usr/bin/env php
+<?php
+
+require_once 'adminutil.php';
+includeAdd(getParent());
+
+require_once 'Auth/OpenID/CryptUtil.php';
+
+$lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+
+if ($lib === null) {
+ fwrite(STDERR, 'No math library present\n');
+ exit(1);
+} else {
+ print $lib->type;
+}
+
+?>
diff --git a/admin/nobadbraces b/admin/nobadbraces
new file mode 100644
index 0000000..ee060dc
--- /dev/null
+++ b/admin/nobadbraces
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -e
+
+./admin/findphp | xargs -L 1 /usr/bin/env perl admin/brace_style.pl
diff --git a/admin/nobadcase b/admin/nobadcase
new file mode 100644
index 0000000..c2c2662
--- /dev/null
+++ b/admin/nobadcase
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+bad=$(./admin/findphp | xargs egrep -n "^[^\'\"]*\b(TRUE|FALSE|NULL)\b")
+
+if [ ! -z "$bad" ]
+ then
+ cat <<EOF 1>&2
+These files contain wrongly capitalized constants:
+
+$bad
+
+EOF
+ exit 1
+fi
diff --git a/admin/nolonglines b/admin/nolonglines
new file mode 100644
index 0000000..3e2addc
--- /dev/null
+++ b/admin/nolonglines
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+files_with_long_lines=$(./admin/findphp |
+ xargs -L 1 -I FILENAME /usr/bin/env perl admin/longlines.pl FILENAME 80)
+
+if [ "$files_with_long_lines" ]
+ then
+ cat <<EOF 1>&2
+Found lines > 80 characters in:
+
+$files_with_long_lines
+EOF
+ exit 1
+fi
diff --git a/admin/notabs b/admin/notabs
new file mode 100644
index 0000000..b6a7df0
--- /dev/null
+++ b/admin/notabs
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+#
+# Look in the local directory for PHP files that have tabs in them. If
+# there are files with tabs, print the list of files and exit with
+# non-zero status.
+
+tabs=$(./admin/findphp | xargs egrep -n ' ' | sort)
+
+if [ ! -z "$tabs" ]
+ then
+ cat <<EOF 1>&2
+Found tabs in:
+$tabs
+EOF
+ exit 1
+fi
diff --git a/admin/open_tag b/admin/open_tag
new file mode 100644
index 0000000..27fe1c5
--- /dev/null
+++ b/admin/open_tag
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+bad_files=$(./admin/findphp |
+ xargs -L 1 grep -H -m 1 "<?php" -c |
+ grep ":0" |
+ awk -F: '{ print $1 }')
+
+if [ "$bad_files" ]
+ then
+ cat <<EOF 1>&2
+These PHP files do NOT begin with <?php :
+
+$bad_files
+
+EOF
+ exit 1
+fi
diff --git a/admin/otb_test.php b/admin/otb_test.php
new file mode 100644
index 0000000..b2fc99f
--- /dev/null
+++ b/admin/otb_test.php
@@ -0,0 +1,20 @@
+<?php
+
+function fail1() {
+}
+
+function pass1()
+{
+}
+
+function fail2(
+ ) {
+
+}
+
+function pass2(
+ )
+{
+}
+
+?> \ No newline at end of file
diff --git a/admin/package.xml b/admin/package.xml
new file mode 100644
index 0000000..c959ba9
--- /dev/null
+++ b/admin/package.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<package version="1.0">
+ <name>%(package_name)s</name>
+ <summary>%(package_summary)s</summary>
+ <description>
+ %(package_description)s
+ </description>
+ <license>%(license_name)s</license>
+ %(maintainers)s
+ <release>
+ <version>%(version)s</version>
+ <date>%(date)s</date>
+ <state>%(release_stability)s</state>
+ <notes>
+ <![CDATA[
+ %(release_notes)s
+ ]]>
+ </notes>
+ %(contents_version_1)s
+ </release>
+ <deps>
+ <dep type="php" rel="ge" version="4.3.0" />
+ </deps>
+</package>
diff --git a/admin/package2.xml b/admin/package2.xml
new file mode 100644
index 0000000..d3bd525
--- /dev/null
+++ b/admin/package2.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<package version="2.0" xmlns="http://pear.php.net/dtd/package-2.0"
+ xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>%(package_name)s</name>
+ <uri>%(uri)s</uri>
+ <summary>%(package_summary)s</summary>
+ <description>
+ %(package_description)s
+ </description>
+ %(leads)s
+ <date>%(date)s</date>
+ <version>
+ <release>%(version)s</release>
+ <api>%(version)s</api>
+ </version>
+ <stability>
+ <release>%(release_stability)s</release>
+ <api>%(release_stability)s</api>
+ </stability>
+ <license uri="%(license_uri)s">%(license_name)s</license>
+ <notes>
+ <![CDATA[
+ %(release_notes)s
+ ]]>
+ </notes>
+ <contents>
+ %(contents)s
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>4.3.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.4.5</min>
+ </pearinstaller>
+ </required>
+ <optional>
+ <package>
+ <name>PHPUnit</name>
+ <channel>pear.php.net</channel>
+ <min>1.1.1</min>
+ </package>
+ <package>
+ <name>PEAR_DB</name>
+ <channel>pear.php.net</channel>
+ <min>1.80</min>
+ </package>
+ <extension>
+ <name>pgsql</name>
+ </extension>
+ <extension>
+ <name>mysql</name>
+ </extension>
+ <extension>
+ <name>sqlite</name>
+ </extension>
+ <extension>
+ <name>bcmath</name>
+ </extension>
+ <extension>
+ <name>gmp</name>
+ </extension>
+ </optional>
+ </dependencies>
+ <!-- There really isn't much we should put in the phprelease tag,
+ although we should probably make a windows-specific release tag. -->
+ <phprelease/>
+</package>
diff --git a/admin/packagexml.py b/admin/packagexml.py
new file mode 100644
index 0000000..e832405
--- /dev/null
+++ b/admin/packagexml.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+
+import os
+import os.path
+
+def makeMaintainerXML(leads):
+ maintainer_template = """
+ <maintainer>
+ <user>%(user)s</user>
+ <name>%(name)s</name>
+ <email>%(email)s</email>
+ <role>lead</role>
+ </maintainer>
+ """
+
+ return "<maintainers>" + \
+ "".join([maintainer_template % l for l in leads]) + \
+ "</maintainers>"
+
+def makeLeadXML(leads):
+ lead_template = """
+<lead>
+ <name>%(name)s</name>
+ <user>%(user)s</user>
+ <email>%(email)s</email>
+ <active>%(active)s</active>
+</lead>
+ """
+
+ return "".join([lead_template % l for l in leads])
+
+INDENT_STRING = " "
+
+def buildContentsXMLFordir(dir_or_file, roles, depth=0, dir_role=None,
+ all_files=False):
+ """
+ Returns a list of strings, each of which is either a <file> XML
+ element for the given file or a <dir> element which contains other
+ <file> elements.
+ """
+
+ try:
+ entries = os.listdir(dir_or_file)
+ dir_role_s = ''
+ if dir_role:
+ dir_role_s = ' role="%s"' % (dir_role)
+ lines = ['%s<dir name="%s"%s>' % (INDENT_STRING * depth,
+ os.path.basename(dir_or_file),
+ dir_role_s)]
+
+ for entry in entries:
+ lines += buildContentsXMLFordir(dir_or_file + os.sep + entry, roles,
+ depth + 1, dir_role, all_files)
+
+ lines.append('%s</dir>' % (INDENT_STRING * depth))
+
+ return lines
+ except OSError:
+ try:
+ extension = dir_or_file.split(".")[-1]
+ except:
+ if not all_files:
+ return []
+
+ if all_files and dir_role:
+ return ['%s<file name="%s" role="%s" />' %
+ (INDENT_STRING * depth, os.path.basename(dir_or_file), dir_role)]
+ elif extension in roles: # Ends in an extension we care about
+ return ['%s<file name="%s" role="%s" />' %
+ (INDENT_STRING * depth, os.path.basename(dir_or_file),
+ roles[extension])]
+ else:
+ return []
+
+def buildContentsXML(roles, *dirs):
+ lines = []
+
+ for directory in dirs:
+ lines.append("\n".join(buildContentsXMLFordir(directory, roles, 1)))
+
+ return "\n".join(lines)
+
+def buildDocsXML(*dirs):
+ lines = []
+
+ for directory in dirs:
+ lines.append("\n".join(buildContentsXMLFordir(directory, {}, 1, 'doc',
+ all_files=True)))
+
+ return "\n".join(lines)
+
+if __name__ == "__main__":
+ def usage(progname):
+ print "Usage: %s <package version> <xml template file> <release notes file>" % (progname)
+
+ import sys
+ import time
+
+ try:
+ import xmlconfig
+ except:
+ print "Could not import XML configuration module xmlconfig"
+ sys.exit(1)
+
+ # Expect sys.argv[2] to be the name of the XML file template to
+ # use for processing.
+ try:
+ template_f = open(sys.argv[2], 'r')
+ except Exception, e:
+ usage(sys.argv[0])
+ print "Could not open template file:", str(e)
+ sys.exit(1)
+
+ # Expect sys.argv[1] to be the version number to include in the
+ # package.xml file.
+ try:
+ version = sys.argv[1]
+ except:
+ usage(sys.argv[0])
+ sys.exit(2)
+
+ # Expect sys.argv[3] to be the name of the release notes file.
+ try:
+ release_file = sys.argv[3]
+ release_file_h = open(release_file, 'r')
+ release_notes = release_file_h.read().strip()
+ release_file_h.close()
+ except Exception, e:
+ usage(sys.argv[0])
+ print str(e)
+ sys.exit(3)
+
+ data = xmlconfig.__dict__.copy()
+
+ contentsXml = buildContentsXML({'php': 'php'}, *xmlconfig.contents_dirs)
+ docsXml = buildDocsXML(*xmlconfig.docs_dirs)
+
+ contents = '<dir name="/">\n' + contentsXml + \
+ "\n" + docsXml + '\n </dir>'
+
+ contents_v1 = '<filelist><dir name="/" baseinstalldir="Auth">\n' + contentsXml + \
+ "\n" + docsXml + '\n </dir></filelist>'
+
+ data['contents'] = contents
+ data['contents_version_1'] = contents_v1
+ data['leads'] = makeLeadXML(xmlconfig.leads)
+ data['maintainers'] = makeMaintainerXML(xmlconfig.leads)
+ data['date'] = time.strftime("%Y-%m-%d")
+ data['version'] = version
+ data['uri'] = "%s%s-%s.tgz" % (data['package_base_uri'], data['package_name'],
+ version)
+ data['release_notes'] = release_notes
+
+ template_data = template_f.read()
+ print template_data % data
diff --git a/admin/phpaliases.py b/admin/phpaliases.py
new file mode 100644
index 0000000..c4ce216
--- /dev/null
+++ b/admin/phpaliases.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+
+"""This script searches files for functions that are just aliases in
+PHP source code. This is not 100% reliable, so it should not be
+automated, but it's useful to run once in a while to make sure that
+all of the matches it finds are not really legitimate aliases.
+
+Usage:
+
+ parse_aliases.py <name of alias file> [PHP source code filename]...
+"""
+
+import sys
+
+# Fetch this URL to get the file that is parsed into the aliases list
+alias_url = 'http://www.zend.com/phpfunc/all_aliases.php'
+
+header_tok = '<!-- END OF HEADER -->';
+footer_tok = '<!-- FOOTER -->';
+
+# Example line of the table that we parse:
+# '<tr bgcolor="#EFEFFF"><td><a href="function.bzclose.php">bzclose</a></td><td><a href="http://lxr.php.net/source/php-src/ext/bz2/bz2.c#48">php-src/ext/bz2/bz2.c</a></td><td><a href="function.fclose.php">fclose</a></td></tr>'
+
+import re
+
+line_re = re.compile(r'''
+\A
+
+<tr\ bgcolor="[^">]+">
+
+<td><a\ href="[^>"]+\.php">([^<>]+)</a></td>
+
+<td><a\ href="[^">]+">[^<>]+</a></td>
+
+<td>
+(?:
+ <a\ href="[^">]+\.php">
+ ( [^<>]+ )
+ </a>
+| ( [^<>]+ )
+)
+</td>
+
+</tr>
+
+\Z
+''', re.VERBOSE)
+
+def parseString(s):
+ _, rest = s.split(header_tok, 1)
+ body, _ = rest.split(footer_tok, 1)
+
+ lines = body.split('\n')
+ assert [s.strip() for s in lines[-2:]] == ['</table>', '']
+ assert lines[0].strip().startswith('<table')
+ del lines[0], lines[-2:]
+ aliases = {}
+ for line in lines:
+ mo = line_re.match(line)
+ assert mo, line
+ alias, master1, master2 = mo.groups()
+ if master1:
+ master = master1
+ else:
+ assert master2
+ master = master2
+ aliases[alias] = master
+
+ return aliases
+
+def parseFile(f):
+ return parseString(f.read())
+
+def parseFileName(fn):
+ return parseFile(file(fn, 'r'))
+
+def parseURL(url):
+ return parseFile(urllib2.urlopen(url))
+
+def getAliasRE(aliases):
+ return re.compile(r'(->|\$|)\s*\b(%s)\b' % ('|'.join(aliases.keys())))
+
+def checkAliasesFile(alias_re, f):
+ found = []
+ line_num = 1
+ for line in f:
+ for mo in alias_re.finditer(line):
+ if mo.group(1):
+ continue
+ alias = mo.group(2)
+ found.append((line_num, alias))
+ line_num += 1
+ return found
+
+def checkAliases(alias_re, filename):
+ return checkAliasesFile(alias_re, file(filename, 'r'))
+
+def checkAliasesFiles(alias_re, filenames):
+ found = []
+ for filename in filenames:
+ file_found = checkAliases(alias_re, filename)
+ found.extend([(filename, n, a) for (n, a) in file_found])
+ return found
+
+def dumpResults(aliases, found, out=sys.stdout):
+ for filename, n, a in found:
+ print >>out, "%s:%d %s -> %s" % (filename, n, a, aliases[a])
+
+def main(alias_file, *filenames):
+ aliases = parseFileName(alias_file)
+ alias_re = getAliasRE(aliases)
+ found = checkAliasesFiles(alias_re, filenames)
+ dumpResults(aliases, found)
+ return found
+
+if __name__ == '__main__':
+ found = main(*sys.argv[1:])
+ if found:
+ sys.exit(1)
diff --git a/admin/prepare-release b/admin/prepare-release
new file mode 100644
index 0000000..98415a9
--- /dev/null
+++ b/admin/prepare-release
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+#
+# Prepare this repository for release
+
+REPO_ROOT=$(dirname $(dirname $(readlink --canonicalize "$0")))
+cd "$REPO_ROOT"
+
+bash ./admin/fixperms
+
+./admin/makedoc.sh
+
+darcs changes --from-tag=. --summary > CHANGELOG
diff --git a/admin/runtests b/admin/runtests
new file mode 100644
index 0000000..b018c87
--- /dev/null
+++ b/admin/runtests
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+
+# Get the absolute path containing this script
+cd $(dirname "$0")
+HERE=$PWD
+
+test_import () {
+ ./admin/checkimports
+}
+
+test_syntax () {
+ ./admin/syntaxcheck
+}
+
+test_tabs () {
+ /usr/bin/env bash "$HERE/notabs"
+}
+
+test_longlines () {
+ /usr/bin/env bash "$HERE/nolonglines"
+}
+
+test_nobadbraces () {
+ /usr/bin/env bash "$HERE/nobadbraces"
+}
+
+test_nobadcase () {
+ /usr/bin/env bash "$HERE/nobadcase"
+}
+
+test_opentag () {
+ /usr/bin/env bash "$HERE/open_tag"
+}
+
+test_docblocks () {
+ /usr/bin/env bash "$HERE/docblocks"
+}
+
+test_php () {
+ if uname -a | grep -i cygwin >/dev/null 2>/dev/null ; then
+ /usr/bin/env php "$(dirname "$0")/texttest.php" --insecure-rand \
+ $TEXTTEST_ARGS
+ else
+ /usr/bin/env php "$HERE/texttest.php" $TEXTTEST_ARGS
+ fi
+}
+
+tests="syntax tabs longlines nobadbraces nobadcase opentag docblocks php import"
+
+failures=
+
+# Run in repository root (parent of this directory)
+cd $(dirname "$HERE")
+
+chmod +x ./admin/fixperms
+./admin/fixperms
+
+for test_name in $tests
+ do
+ echo "Running test $test_name" 1>&2
+ if ! eval "test_$test_name"
+ then
+ failures="$failures $test_name"
+ fi
+done
+
+if [ ! -z "$failures" ]
+ then
+ echo "Failures in: $failures" 1>&2
+ exit 1
+fi
diff --git a/admin/syntaxcheck b/admin/syntaxcheck
new file mode 100644
index 0000000..f94b7fa
--- /dev/null
+++ b/admin/syntaxcheck
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+for file in `./admin/findallphp`
+ do php -l $file
+done
diff --git a/admin/texttest.php b/admin/texttest.php
new file mode 100644
index 0000000..b1c0a3c
--- /dev/null
+++ b/admin/texttest.php
@@ -0,0 +1,197 @@
+<?php
+
+require_once 'Tests/TestDriver.php';
+require_once 'PHPUnit/TestResult.php';
+require_once 'Console/Getopt.php';
+
+class TextTestResult extends PHPUnit_TestResult {
+ function addError(&$test, &$t)
+ {
+ parent::addError($test, $t);
+ echo "E";
+ }
+
+ function addFailure(&$test, &$t)
+ {
+ parent::addFailure($test, $t);
+ echo "F";
+ }
+
+ function addPassedTest(&$test)
+ {
+ parent::addPassedTest($test);
+ echo ".";
+ }
+
+ function dumpBadResults()
+ {
+ foreach ($this->failures() as $failure) {
+ echo $failure->toString();
+ }
+
+ foreach ($this->errors() as $failure) {
+ echo $failure->toString();
+ }
+ }
+}
+
+function microtime_float()
+{
+ list($usec, $sec) = explode(" ", microtime());
+ return ((float)$usec + (float)$sec);
+}
+
+$longopts = array('no-math',
+ 'buggy-gmp',
+ 'no-curl',
+ 'math-lib=',
+ 'insecure-rand',
+ 'thorough',
+ 'extra-tests=');
+
+$con = new Console_Getopt;
+$args = $con->readPHPArgv();
+array_shift($args);
+$options = $con->getopt2($args, "", $longopts);
+
+if (PEAR::isError($options)) {
+ print $options->message . "\n";
+ exit(1);
+}
+
+list($flags, $tests_to_run) = $options;
+
+$math_type = array();
+$extra_test_modules = array();
+$thorough = false;
+foreach ($flags as $flag) {
+ list($option, $value) = $flag;
+ switch ($option) {
+ case '--insecure-rand':
+ define('Auth_OpenID_RAND_SOURCE', null);
+ break;
+ case '--no-math':
+ define('Auth_OpenID_NO_MATH_SUPPORT', true);
+ break;
+ case '--buggy-gmp':
+ define('Auth_OpenID_BUGGY_GMP', true);
+ break;
+ case '--no-curl':
+ define('Auth_Yadis_CURL_OVERRIDE', true);
+ break;
+ case '--thorough':
+ define('Tests_Auth_OpenID_thorough', true);
+ break;
+ case '--extra-tests':
+ $extra_test_modules[] = $value;
+ break;
+ default:
+ print "Unrecognized option: $option\n";
+ exit(1);
+ }
+}
+
+// ******** Math library selection ***********
+// XXX FIXME
+// case '--math-lib':
+// $math_type[] = $value;
+// break;
+if ($math_type && false) {
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ print "--no-math and --math-lib are mutually exclusive\n";
+ exit(1);
+ }
+ require_once('Auth/OpenID/BigMath.php');
+ $new_extensions = array();
+ foreach ($math_type as $lib) {
+ $found = false;
+ foreach (Auth_OpenID_math_extensions() as $ext) {
+ if ($ext['extension'] == $lib) {
+ $new_extensions[] = $ext;
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ print "Unknown math library specified: $lib\n";
+ exit(1);
+ }
+ }
+ $_Auth_OpenID_math_extensions = $new_extensions;
+}
+
+// ******** End math library selection **********
+
+$suites = loadSuite($tests_to_run);
+
+// ******** Load additional test suites ********
+foreach ($extra_test_modules as $filename) {
+ if (!global_require_once($filename)) {
+ continue;
+ }
+ $module_name = basename($filename, '.php');
+ $class_name = "Tests_Auth_OpenID_${module_name}_Test";
+ $suites[] = makeSuite($class_name);
+}
+
+
+$totals = array(
+ 'run' => 0,
+ 'error' => 0,
+ 'failure' => 0,
+ 'time' => 0
+ );
+
+foreach ($suites as $suite) {
+ $name = $suite->getName();
+ echo "==========================================
+Test suite: $name
+------------------------------------------
+";
+
+ $result = new TextTestResult();
+ $before = microtime_float();
+ $suite->run($result);
+ $after = microtime_float();
+
+ $run = $result->runCount();
+ $error = $result->errorCount();
+ $failure = $result->failureCount();
+ $delta = $after - $before;
+ $totals['run'] += $run;
+ $totals['error'] += $error;
+ $totals['failure'] += $failure;
+ $totals['time'] += $delta;
+ $human_delta = round($delta, 3);
+ echo "\nRan $run tests in $human_delta seconds";
+ if ($error || $failure) {
+ echo " with $error errors, $failure failures";
+ }
+ echo "
+==========================================
+
+";
+
+ $failures = $result->failures();
+ foreach($failures as $failure) {
+ $test = $failure->failedTest();
+ $testName = $test->getName();
+ $exception = $failure->thrownException();
+ echo "* Failure in $testName: $exception
+
+";
+ }
+}
+
+$before = microtime_float();
+$run = $totals['run'];
+$error = $totals['error'];
+$failure = $totals['failure'];
+$time = round($totals['time'], 3);
+echo "Ran a total of $run tests in $time seconds with $error errors, $failure failures\n";
+if ($totals['error'] || $totals['failure']) {
+ exit(1);
+}
+
+?>
diff --git a/admin/tutorials/OpenID/OpenID.pkg b/admin/tutorials/OpenID/OpenID.pkg
new file mode 100644
index 0000000..3f6c996
--- /dev/null
+++ b/admin/tutorials/OpenID/OpenID.pkg
@@ -0,0 +1,82 @@
+<refentry id="{@id}">
+ <refnamediv>
+ <refname>PHP OpenID API</refname>
+
+ </refnamediv>
+ <refsynopsisdiv>
+ <author>
+ JanRain, Inc.
+ <authorblurb>
+ {@link mailto:openid@janrain.com openid@janrain.com}
+ </authorblurb>
+ </author>
+ </refsynopsisdiv>
+
+ <para>
+ This is a complete implementation of the OpenID authentication
+ protocol. This package contains:
+
+ <itemizedlist>
+ <listitem>An OpenID server</listitem>
+ <listitem>An OpenID consumer</listitem>
+ <listitem>Stores for MySQL, PostgreSQL, SQLite, and filesystem-based OpenID data storage</listitem>
+ <listitem>PHPUnit unit tests</listitem>
+ <listitem>An example server and consumer</listitem>
+ </itemizedlist>
+ </para>
+
+ <refsect1 id="{@id users}">
+ <title>For Package Users</title>
+
+ <para>
+ To install this package, copy the <literal>Auth/</literal>
+ directory in this package to a directory in your PHP include path.
+ Alternatively, modify your PHP include path to include the
+ directory that contains <literal>Auth/</literal>. Any
+ applications that need this package will then be able to use it.
+ </para>
+
+ </refsect1>
+
+ <refsect1 id="{@id developers}">
+ <title>For Developers</title>
+
+ <para>
+
+ See the server and consumer examples in the
+ <literal>examples/</literal> directory of the package. For
+ details on how to run the examples, see
+ <literal>examples/README</literal>. If you want to create your
+ own OpenID data storage class, please see the {@link Auth_OpenID_OpenIDStore}
+ class. For information on integrating OpenID relying party support into your site, see
+ the class documentation for {@link Consumer.php}.
+
+ </para>
+
+ </refsect1>
+
+ <refsect1 id="{@id references}">
+ <title>References</title>
+ <para>
+ <itemizedlist>
+ <listitem>
+ {@link http://www.openidenabled.com/openid/libraries/php PHP OpenID Library}
+ </listitem>
+
+ <listitem>
+ {@link http://www.janrain.com JanRain, Inc.}
+ </listitem>
+
+ <listitem>
+ {@link http://lists.openidenabled.com/mailman/listinfo/dev OpenID Development Discussion List}
+ </listitem>
+
+ <listitem>
+ {@link http://www.openidenabled.com/yadis/libraries/php PHP Yadis Library}
+ </listitem>
+ </itemizedlist>
+ </para>
+ </refsect1>
+
+</refentry>
+
diff --git a/admin/webtest.php b/admin/webtest.php
new file mode 100644
index 0000000..1076623
--- /dev/null
+++ b/admin/webtest.php
@@ -0,0 +1,12 @@
+<?php
+
+require_once 'Tests/TestDriver.php';
+
+$suites = loadSuite();
+
+// Create and run the user interface
+$gui = new PHPUnit_GUI_HTML();
+$gui->addSuites($suites);
+$gui->show();
+
+?>
diff --git a/admin/xmlconfig.py b/admin/xmlconfig.py
new file mode 100644
index 0000000..ac029b5
--- /dev/null
+++ b/admin/xmlconfig.py
@@ -0,0 +1,55 @@
+
+"""
+This is the package.xml data needed for the PHP OpenID PEAR
+package.xml file. Use the 'packagexml.py' program to generate a
+package.xml file for a release of this library.
+"""
+
+# This is a list of dicts describing the project leads. This will be
+# used to generate <lead> XML elements.
+leads = [
+ {'name': 'Jonathan Daugherty',
+ 'user': 'cygnus',
+ 'email': 'cygnus@janrain.com',
+ 'active': 'yes'},
+ {'name': 'Josh Hoyt',
+ 'user': 'jhoyt',
+ 'email': 'josh@janrain.com',
+ 'active': 'yes'}
+ ]
+
+# The package name.
+package_name = 'Auth_OpenID'
+
+# The package description.
+package_description = 'An implementation of the OpenID single sign-on authentication protocol.'
+
+# Package summary.
+package_summary = 'PHP OpenID'
+
+# License string.
+license_name = 'Apache'
+
+# License URI.
+license_uri = 'http://www.apache.org/licenses/LICENSE-2.0'
+
+# Director(ies) containing package source, relative to the admin/
+# directory. All .php files in these directories will be included in
+# the <contents> element of the output XML and will be assigned the
+# role 'php'.
+contents_dirs = ['../Auth',]
+
+# Director(ies) containing package documentation. All files and
+# subdirectories in these directories will be included in the
+# <contents> element in the output XML and will be assigned the role
+# 'doc'.
+docs_dirs = ['../doc', '../examples']
+
+# The HTTP package base URI. This is the place on the web where the
+# PEAR-installable tarballs will live, and this (plus the package
+# tarball name) will be the URL that users pass to "pear install".
+package_base_uri = 'http://www.openidenabled.com/resources/downloads/php-openid/pear/'
+
+# The release stability. Maybe this should be a commandline parameter
+# since it might differ from release to release.
+release_stability = 'stable'
diff --git a/contrib/signed_assertions/AP.php b/contrib/signed_assertions/AP.php
new file mode 100644
index 0000000..a242650
--- /dev/null
+++ b/contrib/signed_assertions/AP.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * Introduces the notion of an Attribute Provider that attests and signs
+ * attributes
+ * Uses OpenID Signed Assertions(Sxip draft) for attesting attributes
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author Santosh Subramanian <subrasan@cs.sunysb.edu>
+ * @author Shishir Randive <srandive@cs.sunysb.edu>
+ * Stony Brook University.
+ *
+ */
+require_once 'Auth/OpenID/SAML.php';
+/**
+ * The Attribute_Provider class which signs the attribute,value pair
+ * for a given openid.
+ */
+class Attribute_Provider
+{
+ private $public_key_certificate=null;
+ private $private_key=null;
+ private $authenticatedUser=null;
+ private $notBefore=null;
+ private $notOnOrAfter=null;
+ private $rsadsa=null;
+ private $acsURI=null;
+ private $attribute=null;
+ private $value=null;
+ private $assertionTemplate=null;
+ /**
+ * Creates an Attribute_Provider object initialized with startup values.
+ * @param string $public_key_certificate - The public key certificate
+ of the signer.
+ * @param string $private_key - The private key of the signer.
+ * @param string $notBefore - Certificate validity time
+ * @param string $notOnOrAfter - Certificate validity time
+ * @param string $rsadsa - Choice of the algorithm (RSA/DSA)
+ * @param string $acsURI - URI of the signer.
+ * @param string $assertionTemplate - SAML template used for assertion
+ */
+ function Attribute_Provider($public_key_certificate,$private_key,$notBefore,$notOnOrAfter,$rsadsa,$acsURI,
+ $assertionTemplate)
+ {
+ $this->public_key_certificate=$public_key_certificate;
+ $this->private_key=$private_key;
+ $this->notBefore=$notBefore;
+ $this->notOnOrAfter=$notOnOrAfter;
+ $this->rsadsa=$rsadsa;
+ $this->acsURI=$acsURI;
+ $this->assertionTemplate=$assertionTemplate;
+ }
+ /**
+ * Create the signed assertion.
+ * @param string $openid - Openid of the entity being asserted.
+ * @param string $attribute - The attribute name being asserted.
+ * @param string $value - The attribute value being asserted.
+ */
+ function sign($openid,$attribute,$value)
+ {
+ $samlObj = new SAML();
+ $responseXmlString = $samlObj->createSamlAssertion($openid,
+ $this->notBefore,
+ $this->notOnOrAfter,
+ $this->rsadsa,
+ $this->acsURI,
+ $attribute,
+ sha1($value),
+ $this->assertionTemplate);
+ $signedAssertion=$samlObj->signAssertion($responseXmlString,
+ $this->private_key,
+ $this->public_key_certificate);
+ return $signedAssertion;
+ }
+}
+/**
+ * The Attribute_Verifier class which verifies the signed assertion at the Relying party.
+ */
+class Attribute_Verifier
+{
+ /**
+ * The certificate the Relying party trusts.
+ */
+ private $rootcert;
+ /**
+ * This function loads the public key certificate that the relying party trusts.
+ * @param string $cert - Trusted public key certificate.
+ */
+ function load_trusted_root_cert($cert)
+ {
+ $this->rootcert=$cert;
+ }
+ /**
+ * Verifies the certificate given the SAML document.
+ * @param string - signed SAML assertion
+ * return @boolean - true if verification is successful, false if unsuccessful.
+ */
+ function verify($responseXmlString)
+ {
+ $samlObj = new SAML();
+ $ret = $samlObj->verifyAssertion($responseXmlString,$this->rootcert);
+ return $ret;
+ }
+}
+
+/**
+ * This is a Store Request creating class at the Attribute Provider.
+ */
+class AP_OP_StoreRequest
+{
+ /**
+ * Creates store request and adds it as an extension to AuthRequest object
+ passed to it.
+ * @param &Auth_OpenID_AuthRequest &$auth_request - A reference to
+ the AuthRequest object.
+ * @param &Attribute_Provider &$attributeProvider - A reference to the
+ Attribute Provider object.
+ * @param string $attribute - The attribute name being asserted.
+ * @param string $value - The attribute value being asserted.
+ * @param string $openid - Openid of the entity being asserted.
+ * @return &Auth_OpenID_AuthRequest - Auth_OpenID_AuthRequest object
+ returned with StoreRequest extension.
+ */
+ static function createStoreRequest(&$auth_request,&$attributeProvider,
+ $attribute,$value,$openid)
+ {
+ if(!$auth_request){
+ return null;
+ }
+ $signedAssertion=$attributeProvider->sign($openid,$attribute,$value);
+ $store_request=new Auth_OpenID_AX_StoreRequest;
+ $store_request->addValue($attribute,base64_encode($value));
+ $store_request->addValue($attribute.'/signature',
+ base64_encode($signedAssertion));
+ if($store_request) {
+ $auth_request->addExtension($store_request);
+ return $auth_request;
+ }
+ }
+}
+
+/*
+ *This is implemented at the RP Takes care of getting the attribute from the
+ *AX_Fetch_Response object and verifying it.
+ */
+class RP_OP_Verify
+{
+ /**
+ * Verifies a given signed assertion.
+ * @param &Attribute_Verifier &$attributeVerifier - An instance of the class
+ passed for the verification.
+ * @param Auth_OpenID_Response - Response object for extraction.
+ * @return boolean - true if successful, false if verification fails.
+ */
+ function verifyAssertion(&$attributeVerifier,$response)
+ {
+ $ax_resp=Auth_OpenID_AX_FetchResponse::fromSuccessResponse($response);
+ if($ax_resp instanceof Auth_OpenID_AX_FetchResponse){
+ $ax_args=$ax_resp->getExtensionArgs();
+ if($ax_args) {
+ $value=base64_decode($ax_args['value.ext1.1']);
+ if($attributeVerifier->verify($value)){
+ return base64_decode($ax_args['value.ext0.1']);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
+
+
+?>
diff --git a/contrib/signed_assertions/SAML.php b/contrib/signed_assertions/SAML.php
new file mode 100644
index 0000000..fa6df51
--- /dev/null
+++ b/contrib/signed_assertions/SAML.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ ** PHP versions 4 and 5
+ **
+ ** LICENSE: See the COPYING file included in this distribution.
+ **
+ ** @package OpenID
+ ** @author Santosh Subramanian <subrasan@cs.sunysb.edu>
+ ** @author Shishir Randive <srandive@cs.sunysb.edu>
+ ** Stony Brook University.
+ ** largely derived from
+ **
+ * Copyright (C) 2007 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.
+ **/
+
+class SAML{
+ private $assertionTemplate=null;
+ /**
+ * Returns a SAML response with various elements filled in.
+ * @param string $authenticatedUser The OpenId of the user
+ * @param string $notBefore The ISO 8601 formatted date before which the
+ response is invalid
+ * @param string $notOnOrAfter The ISO 8601 formatted data after which the
+ response is invalid
+ * @param string $rsadsa 'rsa' if the response will be signed with RSA keys,
+ 'dsa' for DSA keys
+ * @param string $requestID The ID of the request we're responding to
+ * @param string $destination The ACS URL that the response is submitted to
+ * @return string XML SAML response.
+ */
+ function createSamlAssertion($authenticatedUser, $notBefore, $notOnOrAfter, $rsadsa, $acsURI,$attribute,$value,$assertionTemplate)
+ {
+ $samlResponse = $assertionTemplate;
+ $samlResponse = str_replace('USERNAME_STRING', $authenticatedUser, $samlResponse);
+ $samlResponse = str_replace('RESPONSE_ID', $this->samlCreateId(), $samlResponse);
+ $samlResponse = str_replace('ISSUE_INSTANT', $this->samlGetDateTime(time()), $samlResponse);
+ $samlResponse = str_replace('NOT_BEFORE', $this->samlGetDateTime(strtotime($notBefore)), $samlResponse);
+ $samlResponse = str_replace('NOT_ON_OR_AFTER', $this->samlGetDateTime(strtotime($notOnOrAfter)),$samlResponse);
+ $samlResponse = str_replace('ASSERTION_ID',$this->samlCreateId(), $samlResponse);
+ $samlResponse = str_replace('RSADSA', strtolower($rsadsa), $samlResponse);
+ $samlResponse = str_replace('ISSUER_DOMAIN', $acsURI, $samlResponse);
+ $samlResponse = str_replace('ATTRIBUTE_NAME', $attribute, $samlResponse);
+ $samlResponse = str_replace('ATTRIBUTE_VALUE', $value, $samlResponse);
+ return $samlResponse;
+ }
+
+ /**
+ * Signs a SAML response with the given private key, and embeds the public key.
+ * @param string $responseXmlString The unsigned Assertion which will be signed
+ * @param string $priKey Private key to sign the certificate
+ * @param string $cert Public key certificate of signee
+ * @return string Signed Assertion
+ */
+ function signAssertion($responseXmlString,$privKey,$cert)
+ {
+ if (file_exists("/tmp/xml")) {
+ $tempFileDir="/tmp/xml/";
+
+ } else {
+ mkdir("/tmp/xml",0777);
+ $tempFileDir="/tmp/xml/";
+ }
+ $tempName = 'saml-response-' . $this->samlCreateId() . '.xml';
+ $tempFileName=$tempFileDir.$tempName;
+ while (file_exists($tempFileName))
+ $tempFileName = 'saml-response-' . $this->samlCreateId() . '.xml';
+
+ if (!$handle = fopen($tempFileName, 'w')) {
+ return null;
+ }
+ if (fwrite($handle, $responseXmlString) === false) {
+ return null;
+ }
+ fclose($handle);
+ $cmd = 'xmlsec1 --sign --privkey-pem ' . $privKey .
+ ',' . $cert . ' --output ' . $tempFileName .
+ '.out ' . $tempFileName;
+ exec($cmd, $resp);
+ unlink($tempFileName);
+
+ $xmlResult = @file_get_contents($tempFileName . '.out');
+ if (!$xmlResult) {
+ return null;
+ } else {
+ unlink($tempFileName . '.out');
+ return $xmlResult;
+ }
+ }
+
+
+ /**
+ * Verify a saml response with the given public key.
+ * @param string $responseXmlString Response to sign
+ * @param string $rootcert trusted public key certificate
+ * @return string Signed SAML response
+ */
+ function verifyAssertion($responseXmlString,$rootcert)
+ {
+ date_default_timezone_set("UTC");
+ if (file_exists("/tmp/xml")) {
+ $tempFileDir="/tmp/xml/";
+
+ } else {
+ mkdir("/tmp/xml",0777);
+ $tempFileDir="/tmp/xml/";
+ }
+
+ $tempName = 'saml-response-' . $this->samlCreateId() . '.xml';
+ $tempFileName=$tempFileDir.$tempName;
+ while (file_exists($tempFileName))
+ $tempFileName = 'saml-response-' . $this->samlCreateId() . '.xml';
+
+ if (!$handle = fopen($tempFileName, 'w')) {
+ return false;
+ }
+
+ if (fwrite($handle, $responseXmlString) === false) {
+ return false;
+ }
+
+ $p=xml_parser_create();
+ $result=xml_parse_into_struct($p,$responseXmlString,$vals,$index);
+ xml_parser_free($p);
+ $cert_info=$index["X509CERTIFICATE"];
+ $conditions=$index["CONDITIONS"];
+ foreach($cert_info as $key=>$value){
+ file_put_contents($tempFileName.'.cert',$vals[$value]['value']);
+ }
+ $cert=$tempFileName.'.cert';
+ $before=0;
+ $after=0;
+ foreach($conditions as $key=>$value){
+ $before=$vals[$value]['attributes']['NOTBEFORE'];
+ $after=$vals[$value]['attributes']['NOTONORAFTER'];
+ }
+ $before=$this->validSamlDateFormat($before);
+ $after=$this->validSamlDateFormat($after);
+ if(strtotime("now") < $before || strtotime("now") >= $after){
+ unlink($tempFileName);
+ unlink($cert);
+ return false;
+ }
+ fclose($handle);
+ $cmd = 'xmlsec1 --verify --pubkey-cert ' . $cert .'--trusted '.$rootcert. ' '.$tempFileName.'* 2>&1 1>/dev/null';
+ exec($cmd,$resp);
+ if(strcmp($resp[0],"FAIL") == 0){
+ $value = false;
+ }elseif(strcmp($resp[0],"ERROR") == 0){
+ $value = false;
+ }elseif(strcmp($resp[0],"OK") == 0){
+ $value = TRUE;
+ }
+ unlink($tempFileName);
+ unlink($cert);
+ return $value;
+ }
+
+ /**
+ * Creates a 40-character string containing 160-bits of pseudorandomness.
+ * @return string Containing pseudorandomness of 160 bits
+ */
+
+ function samlCreateId()
+ {
+ $rndChars = 'abcdefghijklmnop';
+ $rndId = '';
+ for ($i = 0; $i < 40; $i++ ) {
+ $rndId .= $rndChars[rand(0,strlen($rndChars)-1)];
+ }
+ return $rndId;
+ }
+
+ /**
+ * Returns a unix timestamp in xsd:dateTime format.
+ * @param timestamp int UNIX Timestamp to convert to xsd:dateTime
+ * ISO 8601 format.
+ * @return string
+ */
+ function samlGetDateTime($timestamp)
+ {
+ return gmdate('Y-m-d\TH:i:s\Z', $timestamp);
+ }
+ /**
+ * Attempts to check whether a SAML date is valid. Returns true or false.
+ * @param string $samlDate
+ * @return bool
+ */
+
+ function validSamlDateFormat($samlDate)
+ {
+ if ($samlDate == "") return false;
+ $indexT = strpos($samlDate, 'T');
+ $indexZ = strpos($samlDate, 'Z');
+ if (($indexT != 10) || ($indexZ != 19)) {
+ return false;
+ }
+ $dateString = substr($samlDate, 0, 10);
+ $timeString = substr($samlDate, $indexT + 1, 8);
+ list($year, $month, $day) = explode('-', $dateString);
+ list($hour, $minute, $second) = explode(':', $timeString);
+ $parsedDate = gmmktime($hour, $minute, $second, $month, $day, $year);
+ if (($parsedDate === false) || ($parsedDate == -1)) return false;
+ if (!checkdate($month, $day, $year)) return false;
+ return $parsedDate;
+ }
+
+}
+?>
diff --git a/contrib/upgrade-store-1.1-to-2.0 b/contrib/upgrade-store-1.1-to-2.0
new file mode 100644
index 0000000..1f587c3
--- /dev/null
+++ b/contrib/upgrade-store-1.1-to-2.0
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+# SQL Store Upgrade Script
+# for version 1.x to 2.0 of the OpenID library.
+# Doesn't depend on the openid library, so you can run this python
+# script to update databases for ruby or PHP as well.
+#
+# Testers note:
+#
+# A SQLite3 db with the 1.2 schema exists in
+# openid/test/data/openid-1.2-consumer-sqlitestore.db if you want something
+# to try upgrading.
+#
+# TODO:
+# * test data for mysql and postgresql.
+# * automated tests.
+
+import os
+import getpass
+import sys
+from optparse import OptionParser
+
+
+def askForPassword():
+ return getpass.getpass("DB Password: ")
+
+def askForConfirmation(dbname,tablename):
+ print """The table %s from the database %s will be dropped, and
+ an empty table with the new nonce table schema will replace it."""%(
+ tablename, dbname)
+ return raw_input("Continue? ").lower().strip().startswith('y')
+
+def doSQLiteUpgrade(db_conn, nonce_table_name='oid_nonces'):
+ cur = db_conn.cursor()
+ cur.execute('DROP TABLE %s'%nonce_table_name)
+ sql = """
+ CREATE TABLE %s (
+ server_url VARCHAR,
+ timestamp INTEGER,
+ salt CHAR(40),
+ UNIQUE(server_url, timestamp, salt)
+ );
+ """%nonce_table_name
+ cur.execute(sql)
+ cur.close()
+
+def doMySQLUpgrade(db_conn, nonce_table_name='oid_nonces'):
+ cur = db_conn.cursor()
+ cur.execute('DROP TABLE %s'%nonce_table_name)
+ sql = """
+ CREATE TABLE %s (
+ server_url BLOB,
+ timestamp INTEGER,
+ salt CHAR(40),
+ PRIMARY KEY (server_url(255), timestamp, salt)
+ )
+ TYPE=InnoDB;
+ """%nonce_table_name
+ cur.execute(sql)
+ cur.close()
+
+def doPostgreSQLUpgrade(db_conn, nonce_table_name='oid_nonces'):
+ cur = db_conn.cursor()
+ cur.execute('DROP TABLE %s'%nonce_table_name)
+ sql = """
+ CREATE TABLE %s (
+ server_url VARCHAR(2047),
+ timestamp INTEGER,
+ salt CHAR(40),
+ PRIMARY KEY (server_url, timestamp, salt)
+ );
+ """%nonce_table_name
+ cur.execute(sql)
+ cur.close()
+ db_conn.commit()
+
+def main(argv=None):
+ parser = OptionParser()
+ parser.add_option("-u", "--user", dest="username",
+ default=os.environ.get('USER'),
+ help="User name to use to connect to the DB. "
+ "Defaults to USER environment variable.")
+ parser.add_option('-t', '--table', dest='tablename', default='oid_nonces',
+ help='The name of the nonce table to drop and recreate. '
+ ' Defaults to "oid_nonces", the default table name for '
+ 'the openid stores.')
+ parser.add_option('--mysql', dest='mysql_db_name',
+ help='Upgrade a table from this MySQL database. '
+ 'Requires username for database.')
+ parser.add_option('--pg', '--postgresql', dest='postgres_db_name',
+ help='Upgrade a table from this PostgreSQL database. '
+ 'Requires username for database.')
+ parser.add_option('--sqlite', dest='sqlite_db_name',
+ help='Upgrade a table from this SQLite database file.')
+ parser.add_option('--host', dest='db_host',
+ default='localhost',
+ help='Host on which to find MySQL or PostgreSQL DB.')
+ (options, args) = parser.parse_args(argv)
+
+ db_conn = None
+
+ if options.sqlite_db_name:
+ try:
+ from pysqlite2 import dbapi2 as sqlite
+ except ImportError:
+ print "You must have pysqlite2 installed in your PYTHONPATH."
+ return 1
+ try:
+ db_conn = sqlite.connect(options.sqlite_db_name)
+ except Exception, e:
+ print "Could not connect to SQLite database:", str(e)
+ return 1
+
+ if askForConfirmation(options.sqlite_db_name, options.tablename):
+ doSQLiteUpgrade(db_conn, nonce_table_name=options.tablename)
+
+ if options.postgres_db_name:
+ if not options.username:
+ print "A username is required to open a PostgreSQL Database."
+ return 1
+ password = askForPassword()
+ try:
+ import psycopg
+ except ImportError:
+ print "You need psycopg installed to update a postgres DB."
+ return 1
+
+ try:
+ db_conn = psycopg.connect(database = options.postgres_db_name,
+ user = options.username,
+ host = options.db_host,
+ password = password)
+ except Exception, e:
+ print "Could not connect to PostgreSQL database:", str(e)
+ return 1
+
+ if askForConfirmation(options.postgres_db_name, options.tablename):
+ doPostgreSQLUpgrade(db_conn, nonce_table_name=options.tablename)
+
+ if options.mysql_db_name:
+ if not options.username:
+ print "A username is required to open a MySQL Database."
+ return 1
+ password = askForPassword()
+ try:
+ import MySQLdb
+ except ImportError:
+ print "You must have MySQLdb installed to update a MySQL DB."
+ return 1
+
+ try:
+ db_conn = MySQLdb.connect(options.db_host, options.username,
+ password, options.mysql_db_name)
+ except Exception, e:
+ print "Could not connect to MySQL database:", str(e)
+ return 1
+
+ if askForConfirmation(options.mysql_db_name, options.tablename):
+ doMySQLUpgrade(db_conn, nonce_table_name=options.tablename)
+
+ if db_conn:
+ db_conn.close()
+ else:
+ parser.print_help()
+
+ return 0
+
+
+if __name__ == '__main__':
+ retval = main()
+ sys.exit(retval)
diff --git a/examples/README b/examples/README
new file mode 100644
index 0000000..fd01ccb
--- /dev/null
+++ b/examples/README
@@ -0,0 +1,134 @@
+OpenID Example Code
+-------------------
+
+After you've installed this package (see ../README), you can use these
+example packages to get started. They'll show you what this package
+can do, and you can use them as the basis for your own OpenID support.
+
+consumer/: OpenID Example Consumer
+==================================
+
+NOTE: If you want to try the example consumer without installing this
+package, just make sure you add the package's 'Auth' directory to your
+PHP include path.
+
+To try the example consumer implementation, just copy the consumer/
+directory into a place on your web server and point your browser at
+the new directory.
+
+1. Check to be sure that /tmp is in your "open_basedir" configuration,
+ if open_basedir is being used to restrict PHP's file I/O. See
+ http://us2.php.net/features.safe-mode for more information. For
+ example, in your php.ini, change
+
+ open_basedir = "..."
+
+ to
+
+ open_basedir = "/tmp:..."
+
+ (If you really don't want to add /tmp to your open_basedir, you can
+ modify consumer/common.php and change $store_path so it doesn't
+ create the store directory in /tmp.)
+
+2. Copy or symlink the consumer/ directory into a part of your
+ webserver's docroot. For example, if your DocumentRoot is
+ /var/www/, do this:
+
+ # cd /var/www
+ # ln -s /path/to/PHP-OpenID-X.Y.Z/examples/consumer
+
+3. Navigate to http://www.example.com/consumer and enter an OpenID
+ into the form presented there and click "Verify".
+
+consumer/ Files
+===============
+
+The 'index.php' file will render a form and get you started. These
+are the example consumer files:
+
+ consumer/index.php - Renders a form so you can begin the OpenID auth
+process. The form submits the OpenID to try_auth.php.
+
+ consumer/try_auth.php - Starts the authentication with the OpenID
+server that manages your OpenID and redirects your browser to the
+server's login page. Instructs the server to return to
+finish_auth.php when you're done authenticating.
+
+ consumer/finish_auth.php - Finishes the authentication by checking
+the server's response. Tells you if the authentication was
+successful.
+
+ consumer/common.php - Includes the setup code you'll need to create
+a Consumer object and participate in an OpenID authentication.
+
+server/: OpenID Example Server
+==============================
+
+To try the example server, follow these steps:
+
+1. Copy or symlink the server/ directory into a part of your
+ webserver's docroot. For example, if your DocumentRoot is
+ /var/www/, do this:
+
+ # cd /var/www
+ # ln -s /path/to/PHP-OpenID-X.Y.Z/examples/server
+
+2. Navigate to the server example. You'll be redirected to
+ server/setup.php where you can choose some configuration options to
+ generate a configuration. Once finished, you can download a file
+ "config.php." Save that file in the example server directory.
+
+The example server has the following features:
+
+ - It serves its own identity pages, whose URLs are of the form
+
+ http://.../server/server.php/idpage?user=USERNAME
+
+ - It does not require passwords.
+
+ - It does not support a "trusted sites" page, as you pointed out.
+
+In general, the example server is NOT supposed to be treated as a
+fully-equiped OpenID server (i.e., with user accounts and other
+state). It is supposed to demonstrate how to write PHP applications
+that use the library.
+
+Upgrading from the 1.X.X example server
+=======================================
+
+The 2.X.X library's example server is different from the 1.X.X example
+server in the following ways:
+
+ - The new example server does not support authenticating arbitrary
+ URLs. It serves its own URLs. This makes it easier to set up and
+ test.
+
+ - The new example server does not support password authentication.
+ This makes it easier to set up and is not necessary for
+ illustrating the use of the library.
+
+ - The new example server does not have a "trusted sites" page.
+
+server/ Files
+=============
+
+These files make up the server example code:
+
+ config.php - The configuration file you'll need to customize to run
+the example server.
+
+ server.php - The PHP rendering script that takes care of handling
+server requests from both regular user agents and consumers.
+
+ lib/actions.php - Handles the various types of requests that the
+server supports.
+
+ lib/common.php - Supplies functions that wrap the OpenID API calls
+to make them easier to use.
+
+ lib/render.php - Miscellaneous page rendering code.
+
+ lib/session.php - Code to handle session data for user settings.
+
+ lib/render/*.php - Files for each page presented by the server.
diff --git a/examples/consumer/common.php b/examples/consumer/common.php
new file mode 100644
index 0000000..2f01ba0
--- /dev/null
+++ b/examples/consumer/common.php
@@ -0,0 +1,97 @@
+<?php
+
+$path_extra = dirname(dirname(dirname(__FILE__)));
+$path = ini_get('include_path');
+$path = $path_extra . PATH_SEPARATOR . $path;
+ini_set('include_path', $path);
+
+function displayError($message) {
+ $error = $message;
+ include 'index.php';
+ exit(0);
+}
+
+function doIncludes() {
+ /**
+ * Require the OpenID consumer code.
+ */
+ require_once "Auth/OpenID/Consumer.php";
+
+ /**
+ * Require the "file store" module, which we'll need to store
+ * OpenID information.
+ */
+ require_once "Auth/OpenID/FileStore.php";
+
+ /**
+ * Require the Simple Registration extension API.
+ */
+ require_once "Auth/OpenID/SReg.php";
+
+ /**
+ * Require the PAPE extension module.
+ */
+ require_once "Auth/OpenID/PAPE.php";
+}
+
+doIncludes();
+
+global $pape_policy_uris;
+$pape_policy_uris = array(
+ PAPE_AUTH_MULTI_FACTOR_PHYSICAL,
+ PAPE_AUTH_MULTI_FACTOR,
+ PAPE_AUTH_PHISHING_RESISTANT
+ );
+
+function &getStore() {
+ /**
+ * This is where the example will store its OpenID information.
+ * You should change this path if you want the example store to be
+ * created elsewhere. After you're done playing with the example
+ * script, you'll have to remove this directory manually.
+ */
+ $store_path = "/tmp/_php_consumer_test";
+
+ if (!file_exists($store_path) &&
+ !mkdir($store_path)) {
+ print "Could not create the FileStore directory '$store_path'. ".
+ " Please check the effective permissions.";
+ exit(0);
+ }
+
+ return new Auth_OpenID_FileStore($store_path);
+}
+
+function &getConsumer() {
+ /**
+ * Create a consumer object using the store object created
+ * earlier.
+ */
+ $store = getStore();
+ $consumer =& new Auth_OpenID_Consumer($store);
+ return $consumer;
+}
+
+function getScheme() {
+ $scheme = 'http';
+ if (isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') {
+ $scheme .= 's';
+ }
+ return $scheme;
+}
+
+function getReturnTo() {
+ return sprintf("%s://%s:%s%s/finish_auth.php",
+ getScheme(), $_SERVER['SERVER_NAME'],
+ $_SERVER['SERVER_PORT'],
+ dirname($_SERVER['PHP_SELF']));
+}
+
+function getTrustRoot() {
+ return sprintf("%s://%s:%s%s/",
+ getScheme(), $_SERVER['SERVER_NAME'],
+ $_SERVER['SERVER_PORT'],
+ dirname($_SERVER['PHP_SELF']));
+}
+
+?> \ No newline at end of file
diff --git a/examples/consumer/finish_auth.php b/examples/consumer/finish_auth.php
new file mode 100644
index 0000000..b19a665
--- /dev/null
+++ b/examples/consumer/finish_auth.php
@@ -0,0 +1,98 @@
+<?php
+
+require_once "common.php";
+session_start();
+
+function escape($thing) {
+ return htmlentities($thing);
+}
+
+function run() {
+ $consumer = getConsumer();
+
+ // Complete the authentication process using the server's
+ // response.
+ $return_to = getReturnTo();
+ $response = $consumer->complete($return_to);
+
+ // Check the response status.
+ if ($response->status == Auth_OpenID_CANCEL) {
+ // This means the authentication was cancelled.
+ $msg = 'Verification cancelled.';
+ } else if ($response->status == Auth_OpenID_FAILURE) {
+ // Authentication failed; display the error message.
+ $msg = "OpenID authentication failed: " . $response->message;
+ } else if ($response->status == Auth_OpenID_SUCCESS) {
+ // This means the authentication succeeded; extract the
+ // identity URL and Simple Registration data (if it was
+ // returned).
+ $openid = $response->getDisplayIdentifier();
+ $esc_identity = escape($openid);
+
+ $success = sprintf('You have successfully verified ' .
+ '<a href="%s">%s</a> as your identity.',
+ $esc_identity, $esc_identity);
+
+ if ($response->endpoint->canonicalID) {
+ $escaped_canonicalID = escape($response->endpoint->canonicalID);
+ $success .= ' (XRI CanonicalID: '.$escaped_canonicalID.') ';
+ }
+
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+ $sreg = $sreg_resp->contents();
+
+ if (@$sreg['email']) {
+ $success .= " You also returned '".escape($sreg['email']).
+ "' as your email.";
+ }
+
+ if (@$sreg['nickname']) {
+ $success .= " Your nickname is '".escape($sreg['nickname']).
+ "'.";
+ }
+
+ if (@$sreg['fullname']) {
+ $success .= " Your fullname is '".escape($sreg['fullname']).
+ "'.";
+ }
+
+ $pape_resp = Auth_OpenID_PAPE_Response::fromSuccessResponse($response);
+
+ if ($pape_resp) {
+ if ($pape_resp->auth_policies) {
+ $success .= "<p>The following PAPE policies affected the authentication:</p><ul>";
+
+ foreach ($pape_resp->auth_policies as $uri) {
+ $escaped_uri = escape($uri);
+ $success .= "<li><tt>$escaped_uri</tt></li>";
+ }
+
+ $success .= "</ul>";
+ } else {
+ $success .= "<p>No PAPE policies affected the authentication.</p>";
+ }
+
+ if ($pape_resp->auth_age) {
+ $age = escape($pape_resp->auth_age);
+ $success .= "<p>The authentication age returned by the " .
+ "server is: <tt>".$age."</tt></p>";
+ }
+
+ if ($pape_resp->nist_auth_level) {
+ $auth_level = escape($pape_resp->nist_auth_level);
+ $success .= "<p>The NIST auth level returned by the " .
+ "server is: <tt>".$auth_level."</tt></p>";
+ }
+
+ } else {
+ $success .= "<p>No PAPE response was sent by the provider.</p>";
+ }
+ }
+
+ include 'index.php';
+}
+
+run();
+
+?> \ No newline at end of file
diff --git a/examples/consumer/index.php b/examples/consumer/index.php
new file mode 100644
index 0000000..343a39c
--- /dev/null
+++ b/examples/consumer/index.php
@@ -0,0 +1,73 @@
+<?php
+require_once "common.php";
+
+global $pape_policy_uris;
+?>
+<html>
+ <head><title>PHP OpenID Authentication Example</title></head>
+ <style type="text/css">
+ * {
+ font-family: verdana,sans-serif;
+ }
+ body {
+ width: 50em;
+ margin: 1em;
+ }
+ div {
+ padding: .5em;
+ }
+ table {
+ margin: none;
+ padding: none;
+ }
+ .alert {
+ border: 1px solid #e7dc2b;
+ background: #fff888;
+ }
+ .success {
+ border: 1px solid #669966;
+ background: #88ff88;
+ }
+ .error {
+ border: 1px solid #ff0000;
+ background: #ffaaaa;
+ }
+ #verify-form {
+ border: 1px solid #777777;
+ background: #dddddd;
+ margin-top: 1em;
+ padding-bottom: 0em;
+ }
+ </style>
+ <body>
+ <h1>PHP OpenID Authentication Example</h1>
+ <p>
+ This example consumer uses the <a
+ href="http://www.openidenabled.com/openid/libraries/php/">PHP
+ OpenID</a> library. It just verifies that the URL that you enter
+ is your identity URL.
+ </p>
+
+ <?php if (isset($msg)) { print "<div class=\"alert\">$msg</div>"; } ?>
+ <?php if (isset($error)) { print "<div class=\"error\">$error</div>"; } ?>
+ <?php if (isset($success)) { print "<div class=\"success\">$success</div>"; } ?>
+
+ <div id="verify-form">
+ <form method="get" action="try_auth.php">
+ Identity&nbsp;URL:
+ <input type="hidden" name="action" value="verify" />
+ <input type="text" name="openid_identifier" value="" />
+
+ <p>Optionally, request these PAPE policies:</p>
+ <p>
+ <?php foreach ($pape_policy_uris as $i => $uri) {
+ print "<input type=\"checkbox\" name=\"policies[]\" value=\"$uri\" />";
+ print "$uri<br/>";
+ } ?>
+ </p>
+
+ <input type="submit" value="Verify" />
+ </form>
+ </div>
+ </body>
+</html>
diff --git a/examples/consumer/try_auth.php b/examples/consumer/try_auth.php
new file mode 100644
index 0000000..7efec76
--- /dev/null
+++ b/examples/consumer/try_auth.php
@@ -0,0 +1,83 @@
+<?php
+
+require_once "common.php";
+session_start();
+
+function getOpenIDURL() {
+ // Render a default page if we got a submission without an openid
+ // value.
+ if (empty($_GET['openid_identifier'])) {
+ $error = "Expected an OpenID URL.";
+ include 'index.php';
+ exit(0);
+ }
+
+ return $_GET['openid_identifier'];
+}
+
+function run() {
+ $openid = getOpenIDURL();
+ $consumer = getConsumer();
+
+ // Begin the OpenID authentication process.
+ $auth_request = $consumer->begin($openid);
+
+ // No auth request means we can't begin OpenID.
+ if (!$auth_request) {
+ displayError("Authentication error; not a valid OpenID.");
+ }
+
+ $sreg_request = Auth_OpenID_SRegRequest::build(
+ // Required
+ array('nickname'),
+ // Optional
+ array('fullname', 'email'));
+
+ if ($sreg_request) {
+ $auth_request->addExtension($sreg_request);
+ }
+
+ $policy_uris = $_GET['policies'];
+
+ $pape_request = new Auth_OpenID_PAPE_Request($policy_uris);
+ if ($pape_request) {
+ $auth_request->addExtension($pape_request);
+ }
+
+ // Redirect the user to the OpenID server for authentication.
+ // Store the token for this authentication so we can verify the
+ // response.
+
+ // For OpenID 1, send a redirect. For OpenID 2, use a Javascript
+ // form to send a POST request to the server.
+ if ($auth_request->shouldSendRedirect()) {
+ $redirect_url = $auth_request->redirectURL(getTrustRoot(),
+ getReturnTo());
+
+ // If the redirect URL can't be built, display an error
+ // message.
+ if (Auth_OpenID::isFailure($redirect_url)) {
+ displayError("Could not redirect to server: " . $redirect_url->message);
+ } else {
+ // Send redirect.
+ header("Location: ".$redirect_url);
+ }
+ } else {
+ // Generate form markup and render it.
+ $form_id = 'openid_message';
+ $form_html = $auth_request->htmlMarkup(getTrustRoot(), getReturnTo(),
+ false, array('id' => $form_id));
+
+ // Display an error if the form markup couldn't be generated;
+ // otherwise, render the HTML.
+ if (Auth_OpenID::isFailure($form_html)) {
+ displayError("Could not redirect to server: " . $form_html->message);
+ } else {
+ print $form_html;
+ }
+ }
+}
+
+run();
+
+?> \ No newline at end of file
diff --git a/examples/detect.php b/examples/detect.php
new file mode 100644
index 0000000..2d06ae1
--- /dev/null
+++ b/examples/detect.php
@@ -0,0 +1,517 @@
+<?php
+
+$path_extra = dirname(dirname(__FILE__));
+$path = ini_get('include_path');
+$path = $path_extra . PATH_SEPARATOR . $path;
+ini_set('include_path', $path);
+
+define('IS_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
+
+class PlainText {
+ function start($title)
+ {
+ return '';
+ }
+
+ function tt($text)
+ {
+ return $text;
+ }
+
+ function link($href, $text=null)
+ {
+ if ($text) {
+ return $text . ' <' . $href . '>';
+ } else {
+ return $href;
+ }
+ }
+
+ function b($text)
+ {
+ return '*' . $text . '*';
+ }
+
+ function contentType()
+ {
+ return 'text/plain';
+ }
+
+ function p($text)
+ {
+ return wordwrap($text) . "\n\n";
+ }
+
+ function pre($text)
+ {
+ $out = '';
+ $lines = array_map('trim', explode("\n", $text));
+ foreach ($lines as $line) {
+ $out .= ' ' . $line . "\n";
+ }
+ $out .= "\n";
+ return $out;
+ }
+
+ function ol($items)
+ {
+ $out = '';
+ $c = 1;
+ foreach ($items as $item) {
+ $item = wordwrap($item, 72);
+ $lines = array_map('trim', explode("\n", $item));
+ $out .= $c . '. ' . $lines[0] . "\n";
+ unset($lines[0]);
+ foreach ($lines as $line) {
+ $out .= ' ' . $line . "\n";
+ }
+ $out .= "\n";
+ $c += 1;
+ }
+ return $out;
+ }
+
+ function h2($text)
+ {
+ return $this->h($text, 2);
+ }
+
+ function h1($text)
+ {
+ return $this->h($text, 1);
+ }
+
+ function h($text, $n)
+ {
+ $chars = '#=+-.';
+ $c = $chars[$n - 1];
+ return "\n" . $text . "\n" . str_repeat($c, strlen($text)) . "\n\n";
+ }
+
+ function end()
+ {
+ return '';
+ }
+}
+
+class HTML {
+ function start($title)
+ {
+ return '<html><head><title>' . $title . '</title>' .
+ $this->stylesheet().
+ '</head><body>' . "\n";
+ }
+
+ function stylesheet()
+ {
+ return "<style type='text/css'>\n".
+ "p {\n".
+ " width: 50em;\n".
+ "}\n".
+ '</style>';
+ }
+
+ function tt($text)
+ {
+ return '<code>' . $text . '</code>';
+ }
+
+ function contentType()
+ {
+ return 'text/html';
+ }
+
+ function b($text)
+ {
+ return '<strong>' . $text . '</strong>';
+ }
+
+ function p($text)
+ {
+ return '<p>' . wordwrap($text) . "</p>\n";
+ }
+
+ function pre($text)
+ {
+ return '<pre>' . $text . "</pre>\n";
+ }
+
+ function ol($items)
+ {
+ $out = '<ol>';
+ foreach ($items as $item) {
+ $out .= '<li>' . wordwrap($item) . "</li>\n";
+ }
+ $out .= "</ol>\n";
+ return $out;
+ }
+
+ function h($text, $n)
+ {
+ return "<h$n>$text</h$n>\n";
+ }
+
+ function h2($text)
+ {
+ return $this->h($text, 2);
+ }
+
+ function h1($text)
+ {
+ return $this->h($text, 1);
+ }
+
+ function link($href, $text=null)
+ {
+ return '<a href="' . $href . '">' . ($text ? $text : $href) . '</a>';
+ }
+
+ function end()
+ {
+ return "</body>\n</html>\n";
+ }
+}
+
+if (isset($_SERVER['REQUEST_METHOD'])) {
+ $r = new HTML();
+} else {
+ $r = new PlainText();
+}
+
+function detect_math($r, &$out)
+{
+ $out .= $r->h2('Math support');
+ $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
+ if (!isset($ext['extension']) || !isset($ext['class'])) {
+ $out .= $r->p(
+ 'Your PHP installation does not include big integer math ' .
+ 'support. This support is required if you wish to run a ' .
+ 'secure OpenID server without using SSL.');
+ $out .= $r->p('To use this library, you have a few options:');
+
+ $gmp_lnk = $r->link('http://www.php.net/manual/en/ref.gmp.php', 'GMP');
+ $bc_lnk = $r->link('http://www.php.net/manual/en/ref.bc.php', 'bcmath');
+ $out .= $r->ol(array(
+ 'Install the ' . $gmp_lnk . ' PHP extension',
+ 'Install the ' . $bc_lnk . ' PHP extension',
+ 'If your site is low-security, call ' .
+ 'Auth_OpenID_setNoMathSupport(), defined in Auth/OpenID/BigMath.php. ',
+ 'The library will function, but ' .
+ 'the security of your OpenID server will depend on the ' .
+ 'security of the network links involved. If you are only ' .
+ 'using consumer support, you should still be able to operate ' .
+ 'securely when the users are communicating with a ' .
+ 'well-implemented server.'));
+ return false;
+ } else {
+ switch ($ext['extension']) {
+ case 'bcmath':
+ $out .= $r->p('Your PHP installation has bcmath support. This is ' .
+ 'adequate for small-scale use, but can be CPU-intensive. ' .
+ 'You may want to look into installing the GMP extension.');
+ $lnk = $r->link('http://www.php.net/manual/en/ref.gmp.php');
+ $out .= $r->p('See ' . $lnk .' for more information ' .
+ 'about the GMP extension.');
+ break;
+ case 'gmp':
+ $out .= $r->p('Your PHP installation has gmp support. Good.');
+ break;
+ default:
+ $class = $ext['class'];
+ $lib = new $class();
+ $one = $lib->init(1);
+ $two = $lib->add($one, $one);
+ $t = $lib->toString($two);
+ $out .= $r->p('Uh-oh. I do not know about the ' .
+ $ext['extension'] . ' extension!');
+ if ($t != '2') {
+ $out .= $r->p('It looks like it is broken. 1 + 1 = ' .
+ var_export($t, false));
+ return false;
+ } else {
+ $out .= $r->p('But it seems to be able to add one and one.');
+ }
+ }
+ return true; // Math library is OK
+ }
+}
+
+function detect_random($r, &$out)
+{
+ $out .= $r->h2('Cryptographic-quality randomness source');
+ if (Auth_OpenID_RAND_SOURCE === null) {
+ $out .= $r->p('Using (insecure) pseudorandom number source, because ' .
+ 'Auth_OpenID_RAND_SOURCE has been defined as null.');
+ return false;
+ }
+
+ $msg = 'The library will try to access ' . Auth_OpenID_RAND_SOURCE
+ . ' as a source of random data. ';
+
+ $numbytes = 6;
+
+ $f = @fopen(Auth_OpenID_RAND_SOURCE, 'r');
+ if ($f !== false) {
+ $data = fread($f, $numbytes);
+ $stat = fstat($f);
+ $size = $stat['size'];
+ fclose($f);
+ } else {
+ $data = null;
+ $size = true;
+ }
+
+ if ($f !== false) {
+ $dataok = (Auth_OpenID::bytes($data) == $numbytes);
+ $ok = $dataok && !$size;
+ $msg .= 'It seems to exist ';
+ if ($dataok) {
+ $msg .= 'and be readable. Here is some hex data: ' .
+ bin2hex($data) . '.';
+ } else {
+ $msg .= 'but reading data failed.';
+ }
+ if ($size) {
+ $msg .= ' This is a ' . $size . ' byte file. Unless you know ' .
+ 'what you are doing, it is likely that you are making a ' .
+ 'mistake by using a regular file as a randomness source.';
+ }
+ } else {
+ $msg .= Auth_OpenID_RAND_SOURCE .
+ ' could not be opened. This could be because of restrictions on' .
+ ' your PHP environment or that randomness source may not exist' .
+ ' on this platform.';
+ if (IS_WINDOWS) {
+ $msg .= ' You seem to be running Windows. This library does not' .
+ ' have access to a good source of randomness on Windows.';
+ }
+ $ok = false;
+ }
+
+ $out .= $r->p($msg);
+
+ if (!$ok) {
+ $out .= $r->p(
+ 'To set a source of randomness, define Auth_OpenID_RAND_SOURCE ' .
+ 'to the path to the randomness source. If your platform does ' .
+ 'not provide a secure randomness source, the library can' .
+ 'operate in pseudorandom mode, but it is then vulnerable to ' .
+ 'theoretical attacks. If you wish to operate in pseudorandom ' .
+ 'mode, define Auth_OpenID_RAND_SOURCE to null.');
+ $out .= $r->p('You are running on:');
+ $out .= $r->pre(php_uname());
+ $out .= $r->p('There does not seem to be an available source ' .
+ 'of randomness. On a Unix-like platform ' .
+ '(including MacOS X), try /dev/random and ' .
+ '/dev/urandom.');
+ }
+ return $ok;
+}
+
+function detect_stores($r, &$out)
+{
+ $out .= $r->h2('Data storage');
+
+ $found = array();
+ foreach (array('sqlite', 'mysql', 'pgsql') as $dbext) {
+ if (extension_loaded($dbext) || @dl($dbext . '.' . PHP_SHLIB_SUFFIX)) {
+ $found[] = $dbext;
+ }
+ }
+ if (count($found) == 0) {
+ $text = 'No SQL database support was found in this PHP ' .
+ 'installation. See the PHP manual if you need to ' .
+ 'use an SQL database.';
+ } else {
+ $text = 'Support was found for ';
+ if (count($found) == 1) {
+ $text .= $found[0] . '.';
+ } else {
+ $last = array_pop($found);
+ $text .= implode(', ', $found) . ' and ' . $last . '.';
+ }
+ $text = $r->b($text);
+ }
+ $text .= ' The library supports the MySQL, PostgreSQL, and SQLite ' .
+ 'database engines, as well as filesystem-based storage. In ' .
+ 'addition, PEAR DB is required to use databases.';
+ $out .= $r->p($text);
+
+ if (function_exists('posix_getpwuid') &&
+ function_exists('posix_geteuid')) {
+ $processUser = posix_getpwuid(posix_geteuid());
+ $web_user = $r->b($r->tt($processUser['name']));
+ } else {
+ $web_user = 'the PHP process';
+ }
+
+ if (in_array('sqlite', $found)) {
+ $out .= $r->p('If you are using SQLite, your database must be ' .
+ 'writable by ' . $web_user . ' and not available over' .
+ ' the web.');
+ }
+
+ $basedir_str = ini_get('open_basedir');
+ if (gettype($basedir_str) == 'string') {
+ $url = 'http://www.php.net/manual/en/features.safe-mode.php' .
+ '#ini.open-basedir';
+ $lnk = $r->link($url, 'open_basedir');
+ $out .= $r->p('If you are using a filesystem-based store or SQLite, ' .
+ 'be aware that ' . $lnk . ' is in effect. This means ' .
+ 'that your data will have to be stored in one of the ' .
+ 'following locations:');
+ $out .= $r->pre(var_export($basedir_str, true));
+ } else {
+ $out .= $r->p('The ' . $r->b($r->tt('open_basedir')) . ' configuration restriction ' .
+ 'is not in effect.');
+ }
+
+ $out .= $r->p('If you are using the filesystem store, your ' .
+ 'data directory must be readable and writable by ' .
+ $web_user . ' and not availabe over the Web.');
+ return true;
+}
+
+function detect_xml($r, &$out)
+{
+ global $__Auth_Yadis_xml_extensions;
+
+ $out .= $r->h2('XML Support');
+
+ // Try to get an XML extension.
+ $ext = Auth_Yadis_getXMLParser();
+
+ if ($ext !== null) {
+ $out .= $r->p('XML parsing support is present using the '.
+ $r->b(get_class($ext)).' interface.');
+ return true;
+ } else {
+ $out .= $r->p('XML parsing support is absent; please install one '.
+ 'of the following PHP extensions:');
+ foreach ($__Auth_Yadis_xml_extensions as $name => $cls) {
+ $out .= "<li>" . $r->b($name) . "</li>";
+ }
+ return false;
+ }
+}
+
+function detect_fetcher($r, &$out)
+{
+ $out .= $r->h2('HTTP Fetching');
+
+ $result = @include 'Auth/Yadis/Yadis.php';
+
+ if (!$result) {
+ $out .= $r->p('Yadis code unavailable; could not test fetcher support.');
+ return false;
+ }
+
+ if (Auth_Yadis_Yadis::curlPresent()) {
+ $out .= $r->p('This PHP installation has support for libcurl. Good.');
+ } else {
+ $out .= $r->p('This PHP installation does not have support for ' .
+ 'libcurl. CURL is not required but is recommended. '.
+ 'The OpenID library will use an fsockopen()-based fetcher.');
+ $lnk = $r->link('http://us3.php.net/manual/en/ref.curl.php');
+ $out .= $r->p('See ' . $lnk . ' about enabling the libcurl support ' .
+ 'for PHP.');
+ }
+
+ $ok = true;
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $fetch_url = 'http://www.openidenabled.com/resources/php-fetch-test';
+ $expected_url = $fetch_url . '.txt';
+ $result = $fetcher->get($fetch_url);
+
+ if (isset($result)) {
+ $parts = array('An HTTP request was completed.');
+ // list ($code, $url, $data) = $result;
+ if ($result->status != '200' && $result->status != '206') {
+ $ok = false;
+ $parts[] = $r->b(
+ sprintf(
+ 'Got %s instead of the expected HTTP status ' .
+ 'code (200 or 206).', $result->status));
+ }
+
+ $url = $result->final_url;
+ if ($url != $expected_url) {
+ $ok = false;
+ if ($url == $fetch_url) {
+ $msg = 'The redirected URL was not returned.';
+ } else {
+ $msg = 'An unexpected URL was returned: <' . $url . '>.';
+ }
+ $parts[] = $r->b($msg);
+ }
+
+ $data = $result->body;
+ if ($data != 'Hello World!') {
+ $ok = false;
+ $parts[] = $r->b('Unexpected data was returned.');
+ }
+ $out .= $r->p(implode(' ', $parts));
+ } else {
+ $ok = false;
+ $out .= $r->p('Fetching URL ' . $lnk . ' failed!');
+ }
+
+ if ($fetcher->supportsSSL()) {
+ $out .= $r->p('Your PHP installation appears to support SSL, so it ' .
+ 'will be able to process HTTPS identity URLs and server URLs.');
+ } else {
+ $out .= $r->p('Your PHP installation does not support SSL, so it ' .
+ 'will NOT be able to process HTTPS identity URLs and server URLs.');
+ }
+
+ return $ok;
+}
+
+header('Content-Type: ' . $r->contentType() . '; charset=us-ascii');
+
+$title = 'OpenID Library Support Report';
+$out = $r->start($title) .
+ $r->h1($title) .
+ $r->p('This script checks your PHP installation to determine if you ' .
+ 'are set up to use the JanRain PHP OpenID library.');
+
+$body = '';
+
+$_include = include 'Auth/OpenID.php';
+
+if (!$_include) {
+ $path = ini_get('include_path');
+ $body .= $r->p(
+ 'Cannot find the OpenID library. It must be in your PHP include ' .
+ 'path. Your PHP include path is currently:');
+ $body .= $r->pre($path);
+} else {
+ $status = array();
+
+ $status[] = detect_math($r, $body);
+ $status[] = detect_random($r, $body);
+ $status[] = detect_stores($r, $body);
+ $status[] = detect_fetcher($r, $body);
+ $status[] = detect_xml($r, $body);
+
+ $result = true;
+
+ foreach ($status as $v) {
+ if (!$v) {
+ $result = false;
+ break;
+ }
+ }
+
+ if ($result) {
+ $out .= $r->h2('Setup Complete!');
+ $out .= $r->p('Your system should be ready to run the OpenID library.');
+ } else {
+ $out .= $r->h2('Setup Incomplete');
+ $out .= $r->p('Your system needs a few changes before it will be ready to run the OpenID library.');
+ }
+}
+
+$out .= $body . $r->end();
+print $out;
+?>
diff --git a/examples/discover.php b/examples/discover.php
new file mode 100644
index 0000000..31e6b61
--- /dev/null
+++ b/examples/discover.php
@@ -0,0 +1,100 @@
+<?php
+
+require_once "consumer/common.php";
+
+require_once "Auth/OpenID/Discover.php";
+require_once "Auth/Yadis/Yadis.php";
+
+function getOpenIDIdentifier()
+{
+ return $_GET['openid_identifier'];
+}
+
+function escape($x)
+{
+ return htmlentities($x);
+}
+
+
+$identifier = getOpenIDIdentifier();
+?>
+<html>
+<head>
+<title>OpenID discovery</title>
+</head>
+<body>
+ <h2>OpenID discovery tool</h2>
+ <p>
+ Enter an OpenID URL to begin discovery:
+ </p>
+ <form>
+ <input type="text" name="openid_identifier" size="40" />
+ <input type="submit" value="Begin" />
+ </form>
+<?
+if ($identifier) {
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ list($normalized_identifier, $endpoints) = Auth_OpenID_discover(
+ $identifier, $fetcher);
+
+?>
+ <h3>Discovery Results for <?= escape($identifier) ?></h3>
+
+ <table cellpadding="7" cellspacing="0">
+ <tbody>
+ <tr>
+ <th>Claimed Identifier</th>
+ <td><?= escape($normalized_identifier) ?></td>
+ </tr>
+<?
+if (!$endpoints) {
+?>
+ <tr>
+ <td colspan="2">No OpenID services discovered.</td>
+ </tr>
+<?
+} else {
+?>
+ <tr>
+ <td colspan="2">Discovered OpenID services:</td>
+ </tr>
+<?
+foreach ($endpoints as $endpoint) {
+?>
+ <tr>
+ <td colspan="2"><hr/></td>
+ </tr>
+ <tr>
+ <th>Server URL</th>
+ <td><tt><?= escape($endpoint->server_url) ?></tt></td>
+ </tr>
+ <tr>
+ <th>Local ID</th>
+ <td><tt><?= escape($endpoint->local_id) ?></tt></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <h3>Service types:</h3>
+ <ul>
+<?
+foreach ($endpoint->type_uris as $type_uri) {
+?>
+ <li><tt><?= escape($type_uri) ?></tt></li>
+<?
+}
+?>
+ </ul>
+ </td>
+ </tr>
+<?
+}
+}
+?>
+ </tbody>
+</table>
+<?
+}
+?>
+</body>
+</html> \ No newline at end of file
diff --git a/examples/server/index.php b/examples/server/index.php
new file mode 100644
index 0000000..7a95064
--- /dev/null
+++ b/examples/server/index.php
@@ -0,0 +1,5 @@
+<?php
+
+header("Location: server.php");
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/actions.php b/examples/server/lib/actions.php
new file mode 100644
index 0000000..50dc19a
--- /dev/null
+++ b/examples/server/lib/actions.php
@@ -0,0 +1,164 @@
+<?php
+
+require_once "lib/common.php";
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+require_once "lib/render/login.php";
+require_once "lib/render/idpage.php";
+require_once "lib/render/idpXrds.php";
+require_once "lib/render/userXrds.php";
+
+require_once "Auth/OpenID.php";
+
+/**
+ * Handle a standard OpenID server request
+ */
+function action_default()
+{
+ header('X-XRDS-Location: '.buildURL('idpXrds'));
+
+ $server =& getServer();
+ $method = $_SERVER['REQUEST_METHOD'];
+ $request = null;
+ if ($method == 'GET') {
+ $request = $_GET;
+ } else {
+ $request = $_POST;
+ }
+
+ $request = $server->decodeRequest();
+
+ if (!$request) {
+ return about_render();
+ }
+
+ setRequestInfo($request);
+
+ if (in_array($request->mode,
+ array('checkid_immediate', 'checkid_setup'))) {
+
+ if ($request->idSelect()) {
+ // Perform IDP-driven identifier selection
+ if ($request->mode == 'checkid_immediate') {
+ $response =& $request->answer(false);
+ } else {
+ return trust_render($request);
+ }
+ } else if ((!$request->identity) &&
+ (!$request->idSelect())) {
+ // No identifier used or desired; display a page saying
+ // so.
+ return noIdentifier_render();
+ } else if ($request->immediate) {
+ $response =& $request->answer(false, buildURL());
+ } else {
+ if (!getLoggedInUser()) {
+ return login_render();
+ }
+ return trust_render($request);
+ }
+ } else {
+ $response =& $server->handleRequest($request);
+ }
+
+ $webresponse =& $server->encodeResponse($response);
+
+ if ($webresponse->code != AUTH_OPENID_HTTP_OK) {
+ header(sprintf("HTTP/1.1 %d ", $webresponse->code),
+ true, $webresponse->code);
+ }
+
+ foreach ($webresponse->headers as $k => $v) {
+ header("$k: $v");
+ }
+
+ header(header_connection_close);
+ print $webresponse->body;
+ exit(0);
+}
+
+/**
+ * Log out the currently logged in user
+ */
+function action_logout()
+{
+ setLoggedInUser(null);
+ setRequestInfo(null);
+ return authCancel(null);
+}
+
+/**
+ * Check the input values for a login request
+ */
+function login_checkInput($input)
+{
+ $openid_url = false;
+ $errors = array();
+
+ if (!isset($input['openid_url'])) {
+ $errors[] = 'Enter an OpenID URL to continue';
+ }
+ if (count($errors) == 0) {
+ $openid_url = $input['openid_url'];
+ }
+ return array($errors, $openid_url);
+}
+
+/**
+ * Log in a user and potentially continue the requested identity approval
+ */
+function action_login()
+{
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'GET':
+ return login_render();
+ case 'POST':
+ $info = getRequestInfo();
+ $fields = $_POST;
+ if (isset($fields['cancel'])) {
+ return authCancel($info);
+ }
+
+ list ($errors, $openid_url) = login_checkInput($fields);
+ if (count($errors) || !$openid_url) {
+ $needed = $info ? $info->identity : false;
+ return login_render($errors, @$fields['openid_url'], $needed);
+ } else {
+ setLoggedInUser($openid_url);
+ return doAuth($info);
+ }
+ default:
+ return login_render(array('Unsupported HTTP method: $method'));
+ }
+}
+
+/**
+ * Ask the user whether he wants to trust this site
+ */
+function action_trust()
+{
+ $info = getRequestInfo();
+ $trusted = isset($_POST['trust']);
+ return doAuth($info, $trusted, true, @$_POST['idSelect']);
+}
+
+function action_idpage()
+{
+ $identity = $_GET['user'];
+ return idpage_render($identity);
+}
+
+function action_idpXrds()
+{
+ return idpXrds_render();
+}
+
+function action_userXrds()
+{
+ $identity = $_GET['user'];
+ return userXrds_render($identity);
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/common.php b/examples/server/lib/common.php
new file mode 100644
index 0000000..80d05f5
--- /dev/null
+++ b/examples/server/lib/common.php
@@ -0,0 +1,95 @@
+<?php
+
+require_once "lib/render.php";
+require_once "lib/session.php";
+
+require_once "lib/render/login.php";
+require_once "lib/render/about.php";
+require_once "lib/render/trust.php";
+
+require_once "Auth/OpenID/Server.php";
+require_once "Auth/OpenID/SReg.php";
+
+function authCancel($info)
+{
+ if ($info) {
+ setRequestInfo();
+ $url = $info->getCancelURL();
+ } else {
+ $url = getServerURL();
+ }
+ return redirect_render($url);
+}
+
+function doAuth($info, $trusted=null, $fail_cancels=false,
+ $idpSelect=null)
+{
+ if (!$info) {
+ // There is no authentication information, so bail
+ return authCancel(null);
+ }
+
+ if ($info->idSelect()) {
+ if ($idpSelect) {
+ $req_url = idURL($idpSelect);
+ } else {
+ $trusted = false;
+ }
+ } else {
+ $req_url = $info->identity;
+ }
+
+ $user = getLoggedInUser();
+ setRequestInfo($info);
+
+ if ((!$info->idSelect()) && ($req_url != idURL($user))) {
+ return login_render(array(), $req_url, $req_url);
+ }
+
+ $trust_root = $info->trust_root;
+
+ if ($trusted) {
+ setRequestInfo();
+ $server =& getServer();
+ $response =& $info->answer(true, null, $req_url);
+
+ // Answer with some sample Simple Registration data.
+ $sreg_data = array(
+ 'fullname' => 'Example User',
+ 'nickname' => 'example',
+ 'dob' => '1970-01-01',
+ 'email' => 'invalid@example.com',
+ 'gender' => 'F',
+ 'postcode' => '12345',
+ 'country' => 'ES',
+ 'language' => 'eu',
+ 'timezone' => 'America/New_York');
+
+ // Add the simple registration response values to the OpenID
+ // response message.
+ $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest(
+ $info);
+
+ $sreg_response = Auth_OpenID_SRegResponse::extractResponse(
+ $sreg_request, $sreg_data);
+
+ $sreg_response->toMessage($response->fields);
+
+ // Generate a response to send to the user agent.
+ $webresponse =& $server->encodeResponse($response);
+
+ $new_headers = array();
+
+ foreach ($webresponse->headers as $k => $v) {
+ $new_headers[] = $k.": ".$v;
+ }
+
+ return array($new_headers, $webresponse->body);
+ } elseif ($fail_cancels) {
+ return authCancel($info);
+ } else {
+ return trust_render($info);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/render.php b/examples/server/lib/render.php
new file mode 100644
index 0000000..33d2aef
--- /dev/null
+++ b/examples/server/lib/render.php
@@ -0,0 +1,114 @@
+<?php
+
+define('page_template',
+'<html>
+ <head>
+ <meta http-equiv="cache-control" content="no-cache"/>
+ <meta http-equiv="pragma" content="no-cache"/>
+ <title>%s</title>
+%s
+ </head>
+ <body>
+ %s
+<div id="content">
+ <h1>%s</h1>
+ %s
+</div>
+ </body>
+</html>');
+
+define('logged_in_pat', 'You are logged in as %s (URL: %s)');
+
+/**
+ * HTTP response line contstants
+ */
+define('http_bad_request', 'HTTP/1.1 400 Bad Request');
+define('http_found', 'HTTP/1.1 302 Found');
+define('http_ok', 'HTTP/1.1 200 OK');
+define('http_internal_error', 'HTTP/1.1 500 Internal Error');
+
+/**
+ * HTTP header constants
+ */
+define('header_connection_close', 'Connection: close');
+define('header_content_text', 'Content-Type: text/plain; charset=us-ascii');
+
+define('redirect_message',
+ 'Please wait; you are being redirected to <%s>');
+
+
+/**
+ * Return a string containing an anchor tag containing the given URL
+ *
+ * The URL does not need to be quoted, but if text is passed in, then
+ * it does.
+ */
+function link_render($url, $text=null) {
+ $esc_url = htmlspecialchars($url, ENT_QUOTES);
+ $text = ($text === null) ? $esc_url : $text;
+ return sprintf('<a href="%s">%s</a>', $esc_url, $text);
+}
+
+/**
+ * Return an HTTP redirect response
+ */
+function redirect_render($redir_url)
+{
+ $headers = array(http_found,
+ header_content_text,
+ header_connection_close,
+ 'Location: ' . $redir_url,
+ );
+ $body = sprintf(redirect_message, $redir_url);
+ return array($headers, $body);
+}
+
+function navigation_render($msg, $items)
+{
+ $what = link_render(buildURL(), 'PHP OpenID Server');
+ if ($msg) {
+ $what .= ' &mdash; ' . $msg;
+ }
+ if ($items) {
+ $s = '<p>' . $what . '</p><ul class="bottom">';
+ foreach ($items as $action => $text) {
+ $url = buildURL($action);
+ $s .= sprintf('<li>%s</li>', link_render($url, $text));
+ }
+ $s .= '</ul>';
+ } else {
+ $s = '<p class="bottom">' . $what . '</p>';
+ }
+ return sprintf('<div class="navigation">%s</div>', $s);
+}
+
+/**
+ * Render an HTML page
+ */
+function page_render($body, $user, $title, $h1=null, $login=false)
+{
+ $h1 = $h1 ? $h1 : $title;
+
+ if ($user) {
+ $msg = sprintf(logged_in_pat, link_render(idURL($user), $user),
+ link_render(idURL($user)));
+ $nav = array('logout' => 'Log Out');
+
+ $navigation = navigation_render($msg, $nav);
+ } else {
+ if (!$login) {
+ $msg = link_render(buildURL('login'), 'Log In');
+ $navigation = navigation_render($msg, array());
+ } else {
+ $navigation = '';
+ }
+ }
+
+ $style = getStyle();
+ $text = sprintf(page_template, $title, $style, $navigation, $h1, $body);
+ // No special headers here
+ $headers = array();
+ return array($headers, $text);
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/render/about.php b/examples/server/lib/render/about.php
new file mode 100644
index 0000000..bd35836
--- /dev/null
+++ b/examples/server/lib/render/about.php
@@ -0,0 +1,58 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+define('about_error_template',
+ '<div class="error">
+An error occurred when processing your request:
+<br />
+%s
+</div>');
+
+define('about_body',
+ '<p>
+ This is an <a href="http://www.openid.net/">OpenID</a> server
+ endpoint. This server is built on the <a
+ href="http://www.openidenabled.com/openid/libraries/php">JanRain PHP OpenID
+ library</a>. Since OpenID consumer sites will need to directly contact this
+ server, it must be accessible over the Internet (not behind a firewall).
+</p>
+<p>
+ To use this server, you will have to set up a URL to use as an identifier.
+ Insert the following markup into the <code>&lt;head&gt;</code> of the HTML
+ document at that URL:
+</p>
+<pre>&lt;link rel="openid.server" href="%s" /&gt;</pre>
+<p>
+ Then configure this server so that you can log in with that URL. Once you
+ have configured the server, and marked up your identity URL, you can verify
+ that it is working by using the <a href="http://www.openidenabled.com/"
+ >openidenabled.com</a>
+ <a href="http://www.openidenabled.com/resources/openid-test/checkup">OpenID
+ Checkup tool</a>:
+ <form method="post"
+ action="http://www.openidenabled.com/resources/openid-test/checkup/start">
+ <label for="checkup">OpenID URL:
+ </label><input id="checkup" type="text" name="openid_url" />
+ <input type="submit" value="Check" />
+ </form>
+</p>
+');
+
+/**
+ * Render the about page, potentially with an error message
+ */
+function about_render($error=false, $internal=true)
+{
+ $headers = array();
+ $body = sprintf(about_body, buildURL());
+ if ($error) {
+ $headers[] = $internal ? http_internal_error : http_bad_request;
+ $body .= sprintf(about_error_template, htmlspecialchars($error));
+ }
+ $current_user = getLoggedInUser();
+ return page_render($body, $current_user, 'OpenID Server Endpoint');
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/render/idpXrds.php b/examples/server/lib/render/idpXrds.php
new file mode 100644
index 0000000..6e4ae1c
--- /dev/null
+++ b/examples/server/lib/render/idpXrds.php
@@ -0,0 +1,32 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+require_once "Auth/OpenID/Discover.php";
+
+define('idp_xrds_pat', '<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+ <Service priority="0">
+ <Type>%s</Type>
+ <URI>%s</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+');
+
+function idpXrds_render()
+{
+ $headers = array('Content-type: application/xrds+xml');
+
+ $body = sprintf(idp_xrds_pat,
+ Auth_OpenID_TYPE_2_0_IDP,
+ buildURL());
+
+ return array($headers, $body);
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/render/idpage.php b/examples/server/lib/render/idpage.php
new file mode 100644
index 0000000..48c2486
--- /dev/null
+++ b/examples/server/lib/render/idpage.php
@@ -0,0 +1,31 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+define('idpage_pat',
+ '<html>
+<head>
+ <link rel="openid2.provider openid.server" href="%s"/>
+ <meta http-equiv="X-XRDS-Location" content="%s" />
+</head>
+<body>
+ This is the identity page for users of this server.
+</body>
+</html>');
+
+function idpage_render($identity)
+{
+ $xrdsurl = buildURL('userXrds')."?user=".urlencode($identity);
+
+ $headers = array(
+ 'X-XRDS-Location: '.$xrdsurl);
+
+
+ $body = sprintf(idpage_pat,
+ buildURL(),
+ $xrdsurl);
+ return array($headers, $body);
+}
+
+?>
diff --git a/examples/server/lib/render/login.php b/examples/server/lib/render/login.php
new file mode 100644
index 0000000..986a885
--- /dev/null
+++ b/examples/server/lib/render/login.php
@@ -0,0 +1,65 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+define('login_form_pat',
+ '<div class="form">
+ <p>
+
+ Enter your username into this form to log in to this server. It
+ can be anything; this is just for demonstration purposes. For
+ example, entering USERNAME will give you the identity URL
+
+ <pre>%s</pre>
+ </p>
+
+ <form method="post" action="%s">
+ <table>
+ <tr>
+ <th><label for="openid_url">Name:</label></th>
+ <td><input type="text" name="openid_url"
+ value="%s" id="openid_url" /></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <input type="submit" value="Log in" />
+ <input type="submit" name="cancel" value="Cancel" />
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+');
+
+define('login_needed_pat',
+ 'You must be logged in as %s to approve this request.');
+
+function login_render($errors=null, $input=null, $needed=null)
+{
+ $current_user = getLoggedInUser();
+ if ($input === null) {
+ $input = $current_user;
+ }
+ if ($needed) {
+ $errors[] = sprintf(login_needed_pat, link_render($needed));
+ }
+
+ $esc_input = htmlspecialchars($input, ENT_QUOTES);
+ $login_url = buildURL('login', true);
+ $body = sprintf(login_form_pat, idURL('USERNAME'), $login_url, $esc_input);
+ if ($errors) {
+ $body = loginError_render($errors) . $body;
+ }
+ return page_render($body, $current_user, 'Log In', null, true);
+}
+
+function loginError_render($errors)
+{
+ $text = '';
+ foreach ($errors as $error) {
+ $text .= sprintf("<li>%s</li>\n", $error);
+ }
+ return sprintf("<ul class=\"error\">\n%s</ul>\n", $text);
+}
+?> \ No newline at end of file
diff --git a/examples/server/lib/render/trust.php b/examples/server/lib/render/trust.php
new file mode 100644
index 0000000..681d456
--- /dev/null
+++ b/examples/server/lib/render/trust.php
@@ -0,0 +1,56 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+define('trust_form_pat',
+ '<div class="form">
+ <form method="post" action="%s">
+ %s
+ <input type="submit" name="trust" value="Confirm" />
+ <input type="submit" value="Do not confirm" />
+ </form>
+</div>
+');
+
+define('normal_pat',
+ '<p>Do you wish to confirm your identity ' .
+ '(<code>%s</code>) with <code>%s</code>?</p>');
+
+define('id_select_pat',
+ '<p>You entered the server URL at the RP.
+Please choose the name you wish to use. If you enter nothing, the request will be cancelled.<br/>
+<input type="text" name="idSelect" /></p>
+');
+
+define('no_id_pat',
+'
+You did not send an identifier with the request,
+and it was not an identifier selection request.
+Please return to the relying party and try again.
+');
+
+function trust_render($info)
+{
+ $current_user = getLoggedInUser();
+ $lnk = link_render(idURL($current_user));
+ $trust_root = htmlspecialchars($info->trust_root);
+ $trust_url = buildURL('trust', true);
+
+ if ($info->idSelect()) {
+ $prompt = id_select_pat;
+ } else {
+ $prompt = sprintf(normal_pat, $lnk, $trust_root);
+ }
+
+ $form = sprintf(trust_form_pat, $trust_url, $prompt);
+
+ return page_render($form, $current_user, 'Trust This Site');
+}
+
+function noIdentifier_render()
+{
+ return page_render(no_id_pat, null, 'No Identifier Sent');
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/render/userXrds.php b/examples/server/lib/render/userXrds.php
new file mode 100644
index 0000000..a9ea95e
--- /dev/null
+++ b/examples/server/lib/render/userXrds.php
@@ -0,0 +1,34 @@
+<?php
+
+require_once "lib/session.php";
+require_once "lib/render.php";
+
+require_once "Auth/OpenID/Discover.php";
+
+define('user_xrds_pat', '<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+ <Service priority="0">
+ <Type>%s</Type>
+ <Type>%s</Type>
+ <URI>%s</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+');
+
+function userXrds_render($identity)
+{
+ $headers = array('Content-type: application/xrds+xml');
+
+ $body = sprintf(user_xrds_pat,
+ Auth_OpenID_TYPE_2_0,
+ Auth_OpenID_TYPE_1_1,
+ buildURL());
+
+ return array($headers, $body);
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/lib/session.php b/examples/server/lib/session.php
new file mode 100644
index 0000000..201b6ee
--- /dev/null
+++ b/examples/server/lib/session.php
@@ -0,0 +1,178 @@
+<?php
+
+require_once "config.php";
+require_once "lib/render.php";
+require_once "Auth/OpenID/Server.php";
+
+/**
+ * Set up the session
+ */
+function init()
+{
+ session_name('openid_server');
+ session_start();
+}
+
+/**
+ * Get the style markup
+ */
+function getStyle()
+{
+ $parent = rtrim(dirname(getServerURL()), '/');
+ $url = htmlspecialchars($parent . '/openid-server.css', ENT_QUOTES);
+ return sprintf('<link rel="stylesheet" type="text/css" href="%s" />', $url);
+}
+
+/**
+ * Get the URL of the current script
+ */
+function getServerURL()
+{
+ $path = $_SERVER['SCRIPT_NAME'];
+ $host = $_SERVER['HTTP_HOST'];
+ $port = $_SERVER['SERVER_PORT'];
+ $s = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 's' : '';
+ if (($s && $port == "443") || (!$s && $port == "80")) {
+ $p = '';
+ } else {
+ $p = ':' . $port;
+ }
+
+ return "http$s://$host$p$path";
+}
+
+/**
+ * Build a URL to a server action
+ */
+function buildURL($action=null, $escaped=true)
+{
+ $url = getServerURL();
+ if ($action) {
+ $url .= '/' . $action;
+ }
+ return $escaped ? htmlspecialchars($url, ENT_QUOTES) : $url;
+}
+
+/**
+ * Extract the current action from the request
+ */
+function getAction()
+{
+ $path_info = @$_SERVER['PATH_INFO'];
+ $action = ($path_info) ? substr($path_info, 1) : '';
+ $function_name = 'action_' . $action;
+ return $function_name;
+}
+
+/**
+ * Write the response to the request
+ */
+function writeResponse($resp)
+{
+ list ($headers, $body) = $resp;
+ array_walk($headers, 'header');
+ header(header_connection_close);
+ print $body;
+}
+
+/**
+ * Instantiate a new OpenID server object
+ */
+function getServer()
+{
+ static $server = null;
+ if (!isset($server)) {
+ $server =& new Auth_OpenID_Server(getOpenIDStore(),
+ buildURL());
+ }
+ return $server;
+}
+
+/**
+ * Return a hashed form of the user's password
+ */
+function hashPassword($password)
+{
+ return bin2hex(Auth_OpenID_SHA1($password));
+}
+
+/**
+ * Get the openid_url out of the cookie
+ *
+ * @return mixed $openid_url The URL that was stored in the cookie or
+ * false if there is none present or if the cookie is bad.
+ */
+function getLoggedInUser()
+{
+ return isset($_SESSION['openid_url'])
+ ? $_SESSION['openid_url']
+ : false;
+}
+
+/**
+ * Set the openid_url in the cookie
+ *
+ * @param mixed $identity_url The URL to set. If set to null, the
+ * value will be unset.
+ */
+function setLoggedInUser($identity_url=null)
+{
+ if (!isset($identity_url)) {
+ unset($_SESSION['openid_url']);
+ } else {
+ $_SESSION['openid_url'] = $identity_url;
+ }
+}
+
+function getRequestInfo()
+{
+ return isset($_SESSION['request'])
+ ? unserialize($_SESSION['request'])
+ : false;
+}
+
+function setRequestInfo($info=null)
+{
+ if (!isset($info)) {
+ unset($_SESSION['request']);
+ } else {
+ $_SESSION['request'] = serialize($info);
+ }
+}
+
+
+function getSreg($identity)
+{
+ // from config.php
+ global $openid_sreg;
+
+ if (!is_array($openid_sreg)) {
+ return null;
+ }
+
+ return $openid_sreg[$identity];
+
+}
+
+function idURL($identity)
+{
+ return buildURL('idpage') . "?user=" . $identity;
+}
+
+function idFromURL($url)
+{
+ if (strpos($url, 'idpage') === false) {
+ return null;
+ }
+
+ $parsed = parse_url($url);
+
+ $q = $parsed['query'];
+
+ $parts = array();
+ parse_str($q, $parts);
+
+ return @$parts['user'];
+}
+
+?> \ No newline at end of file
diff --git a/examples/server/openid-server.css b/examples/server/openid-server.css
new file mode 100644
index 0000000..badff23
--- /dev/null
+++ b/examples/server/openid-server.css
@@ -0,0 +1,74 @@
+body {
+ padding: 0;
+ margin: 0;
+}
+
+#content {
+ padding: 0.5em;
+ max-width: 50em;
+}
+
+ul.error {
+ background: #ffaaaa;
+ border: 1px solid #ff0000;
+ padding: 0.5em;
+ padding-left: 1.5em;
+}
+
+.login th {
+ text-align: left;
+}
+
+div.form {
+ border: thin solid #777777;
+ background: #dddddd;
+ padding: 0.5em;
+ margin-top: 1em;
+}
+
+div.navigation {
+ border-bottom: thin solid #cccccc;
+ background: #eeeeee;
+ font-size: smaller;
+ padding: 0.5em;
+}
+
+div.navigation h2 {
+ margin-top: 0;
+}
+
+div.navigation p {
+ margin: 0;
+}
+
+div.navigation ul {
+ margin: 0;
+}
+
+div.login p {
+ margin-top: 0;
+}
+
+h1 {
+ margin-top: 0;
+}
+
+pre {
+ padding: 1em;
+ border: 1px solid black;
+ background: #ffeebb;
+}
+
+#checkup {
+ background: url('http://www.openidenabled.com/favicon.ico') no-repeat;
+ padding-left: 16px;
+}
+
+th {
+ text-align: left;
+}
+
+table {
+ border-collapse: collapse;
+ margin-bottom: 1em;
+} \ No newline at end of file
diff --git a/examples/server/server.php b/examples/server/server.php
new file mode 100644
index 0000000..f054be8
--- /dev/null
+++ b/examples/server/server.php
@@ -0,0 +1,48 @@
+<?php
+
+$path_extra = dirname(dirname(dirname(__FILE__)));
+$path = ini_get('include_path');
+$path = $path_extra . PATH_SEPARATOR . $path;
+ini_set('include_path', $path);
+
+$try_include = @include 'config.php';
+
+if (!$try_include) {
+ header("Location: setup.php");
+}
+
+header('Cache-Control: no-cache');
+header('Pragma: no-cache');
+
+if (function_exists('getOpenIDStore')) {
+ require_once 'lib/session.php';
+ require_once 'lib/actions.php';
+
+ init();
+
+ $action = getAction();
+ if (!function_exists($action)) {
+ $action = 'action_default';
+ }
+
+ $resp = $action();
+
+ writeResponse($resp);
+} else {
+?>
+<html>
+ <head>
+ <title>PHP OpenID Server</title>
+ <body>
+ <h1>PHP OpenID Server</h1>
+ <p>
+ This server needs to be configured before it can be used. Edit
+ <code>config.php</code> to reflect your server's setup, then
+ load this page again.
+ </p>
+ </body>
+ </head>
+</html>
+<?php
+}
+?> \ No newline at end of file
diff --git a/examples/server/setup.php b/examples/server/setup.php
new file mode 100644
index 0000000..e25ef34
--- /dev/null
+++ b/examples/server/setup.php
@@ -0,0 +1,558 @@
+<?php
+
+/**
+ * OpenID server configuration script.
+ *
+ * This script generates a config.php file needed by the server
+ * example.
+ *
+ * @package OpenID.Examples
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+$path_extra = dirname(dirname(dirname(__FILE__)));
+$path = ini_get('include_path');
+$path = $path_extra . PATH_SEPARATOR . $path;
+ini_set('include_path', $path);
+require_once "Auth/OpenID.php";
+
+/**
+ * Data.
+ */
+
+$store_types = array("Filesystem" => "Auth_OpenID_FileStore",
+ "MySQL" => "Auth_OpenID_MySQLStore",
+ "PostgreSQL" => "Auth_OpenID_PostgreSQLStore",
+ "SQLite" => "Auth_OpenID_SQLiteStore");
+
+/**
+ * Main.
+ */
+
+$messages = array();
+
+session_start();
+init_session();
+
+if (!check_session() ||
+ isset($_GET['add_openid'])) {
+ render_form();
+} else {
+ print generate_config(isset($_GET['download']));
+}
+
+/**
+ * Functions.
+ */
+
+function check_url($url) {
+ return (Auth_OpenID::normalizeUrl($url) !== null);
+}
+
+function build_url() {
+ $port = (($_SERVER['SERVER_PORT'] == 80) ? null : $_SERVER['SERVER_PORT']);
+
+ $parts = explode("/", $_SERVER['SERVER_PROTOCOL']);
+ $scheme = strtolower($parts[0]);
+
+ if ($port) {
+ return sprintf("%s://%s:%s%s/server.php", $scheme, $_SERVER['SERVER_NAME'],
+ $port, dirname($_SERVER['PHP_SELF']));
+ } else {
+ return sprintf("%s://%s%s/server.php", $scheme, $_SERVER['SERVER_NAME'],
+ dirname($_SERVER['PHP_SELF']));
+ }
+}
+
+function check_open_basedir($path) {
+ if (ini_get('open_basedir')) {
+ $parts = explode(PATH_SEPARATOR, ini_get('open_basedir'));
+
+ $found = false;
+
+ foreach ($parts as $p) {
+ if (strpos($path, $p) === 0) {
+ $found = true;
+ break;
+ }
+ }
+
+ return $found;
+ } else {
+ return true;
+ }
+}
+
+function check_session() {
+
+ global $messages;
+
+ if ($_GET && isset($_GET['clear'])) {
+ session_destroy();
+ $_SESSION = array();
+ init_session();
+ return false;
+ }
+
+ $bad_path = false;
+
+ if (isset($_GET['generate'])) {
+ if (!$_SESSION['server_url']) {
+ $messages[] = "Please enter a server URL.";
+ }
+
+ if (!isset($_SESSION['store_type'])) {
+ $messages[] = "No store type chosen.";
+ } else {
+ switch ($_SESSION['store_type']) {
+ case "Filesystem":
+ if (!@$_SESSION['store_data']['fs_path']) {
+ $messages[] = "Please specify a filesystem store path.";
+ } else {
+ if (!check_open_basedir($_SESSION['store_data']['fs_path'])) {
+ $messages[] = "The filesystem store path violates PHP's <code>open_basedir</code> setting.";
+ $bad_path = true;
+ }
+ }
+ break;
+
+ case "SQLite":
+ if (!@$_SESSION['store_data']['sqlite_path']) {
+ $messages[] = "Please specify a SQLite database path.";
+ } else {
+ if (!check_open_basedir($_SESSION['store_data']['sqlite_path'])) {
+ $messages[] = "The SQLite store path violates PHP's <code>open_basedir</code> setting.";
+ $bad_path = true;
+ }
+ }
+ break;
+
+ default:
+ if (!($_SESSION['store_data']['host'] &&
+ $_SESSION['store_data']['database'] &&
+ $_SESSION['store_data']['username'] &&
+ $_SESSION['store_data']['password'])) {
+ $messages[] = "Please specify database connection details.";
+ }
+ }
+ }
+ }
+
+ if ($_SESSION['store_type'] &&
+ $_SESSION['server_url'] &&
+ (parse_url($_SESSION['server_url']) !== false) &&
+ ((($_SESSION['store_type'] == 'Filesystem') &&
+ $_SESSION['store_data']['fs_path']) ||
+ (($_SESSION['store_type'] == 'SQLite') &&
+ $_SESSION['store_data']['sqlite_path']) ||
+ ($_SESSION['store_data']['host'] &&
+ $_SESSION['store_data']['username'] &&
+ $_SESSION['store_data']['database'] &&
+ $_SESSION['store_data']['password'])) &&
+ !$bad_path) {
+
+ return true;
+ }
+
+ return false;
+}
+
+function render_form() {
+
+ global $store_types, $fields, $messages;
+
+ $basedir_msg = "";
+
+ if (ini_get('open_basedir')) {
+ $basedir_msg = "</br><span class=\"notice\">Note: Due to the ".
+ "<code>open_basedir</code> php.ini setting, be sure to ".
+ "choose a path in one of the following directories:<ul><li>".
+ implode("<li>",
+ explode(PATH_SEPARATOR, ini_get('open_basedir'))).
+ "</ul></span>";
+ }
+
+ $sqlite_found = false;
+ if (extension_loaded('sqlite') ||
+ @dl('sqlite.' . PHP_SHLIB_SUFFIX)) {
+ $sqlite_found = true;
+ }
+
+ $mysql_found = false;
+ if (extension_loaded('mysql') ||
+ @dl('mysql.' . PHP_SHLIB_SUFFIX)) {
+ $mysql_found = true;
+ }
+
+ $pgsql_found = false;
+ if (extension_loaded('pgsql') ||
+ @dl('pgsql.' . PHP_SHLIB_SUFFIX)) {
+ $pgsql_found = true;
+ }
+
+?>
+<html>
+ <head>
+ <style type="text/css">
+span.label {
+ float: left;
+ width: 2in;
+}
+
+span.notice {
+ color: red;
+ font-size: 80%;
+}
+
+div p {
+ border-top: 1px solid #ccc;
+ font-style: italic;
+ padding-top: 0.5em;
+}
+
+div {
+ padding: 3px;
+}
+
+div.store_fields {
+ margin-left: 2in;
+ padding: default;
+}
+
+div.store_fields label.field {
+ float: left;
+ width: 1.75in;
+}
+
+div.store_fields > div {
+ border: 1px solid gray;
+ margin-bottom: 0.5em;
+ background: #eee;
+}
+
+div.store_fields > div > div {
+ margin-left: 0.4in;
+}
+
+div.errors {
+ background: #faa;
+ border: 1px solid red;
+}
+
+</style>
+</head>
+<body>
+
+<h2>OpenID Example Server Configuration</h2>
+
+<?php
+if ($messages) {
+ print "<div class=\"errors\">";
+ foreach ($messages as $m) {
+ print "<div>$m</div>";
+ }
+ print "</div>";
+
+}
+?>
+
+<p>
+Your browser has been redirected to this page so you can configure the
+server example. This form will auto-generate an OpenID example server
+configuration for use with the OpenID server example.
+</p>
+
+<form>
+<div>
+
+ <p>
+ The server URL is the URL that points to the "server.php" file. It
+ looks like your server URL should be <code><?php print build_url(); ?></code>.
+ </p>
+
+ <span class="label"><label for="i_server_url">Server URL:</label></span>
+ <span>
+ <input type="text" id="i_server_url" size="35" name="server_url"
+ value="<?php print $_SESSION['server_url'] ?>">
+ </span>
+</div>
+
+<div>
+
+ <p>
+ If this package isn't installed in the PHP include path, the package's
+ directory should be added. For example, if the package is in
+ <code>/home/me/PHP-OpenID/</code>, you should enter that directory here.
+ </p>
+
+ <span class="label">
+ <label for="i_include_path">Include path (optional):</label>
+ </span>
+ <span>
+ <input type="text" id="i_include_path" size="35" name="include_path"
+ value="<?php print $_SESSION['include_path'] ?>">
+ </span>
+</div>
+
+<div>
+
+ <p>
+ The server needs to store OpenID information in a "store". The
+ following store types are available on your PHP installation:
+ </p>
+
+ <span class="label">Store method:</span>
+ <div class="store_fields">
+
+ <div>
+ <input type="radio" name="store_type" value="Filesystem"
+ id="i_filesystem"<?php if ($_SESSION['store_type'] == 'Filesystem') { print " CHECKED"; } ?>>
+ <label for="i_filesystem">Filesystem</label>
+ <div>
+ <label for="i_fs_path" class="field">Filesystem path:</label>
+ <input type="text" name="fs_path" id="i_fs_path"
+ value="<?php print @$_SESSION['store_data']['fs_path']; ?>">
+ <?php print $basedir_msg; ?>
+ </div>
+ </div>
+
+<?php if ($sqlite_found) { ?>
+ <div>
+ <input type="radio" name="store_type" value="SQLite"
+ id="i_sqlite"<?php if ($_SESSION['store_type'] == 'SQLite') { print " CHECKED"; } ?>>
+ <label for="i_sqlite">SQLite</label>
+ <div>
+ <label for="i_sqlite_path" class="field">SQLite database path:</label>
+ <input type="text" value="<?php print @$_SESSION['store_data']['sqlite_path']; ?>"
+ name="sqlite_path" id="i_sqlite_path">
+ <?php print $basedir_msg; ?>
+ </div>
+ </div>
+<?php } ?>
+
+
+<?php if ($mysql_found || $pgsql_found) { ?>
+ <div>
+
+<?php if ($mysql_found) { ?>
+ <input type="radio" name="store_type" value="MySQL"
+ id="i_mysql"<?php if ($_SESSION['store_type'] == 'MySQL') { print " CHECKED"; } ?>>
+ <label for="i_mysql">MySQL</label>
+<?php } ?>
+
+<?php if ($pgsql_found) { ?>
+ <input type="radio" name="store_type" value="PostgreSQL"
+ id="i_pgsql"<?php if ($_SESSION['store_type'] == 'PostgreSQL') { print " CHECKED"; } ?>>
+ <label for="i_pgsql">PostgreSQL</label>
+<?php } ?>
+
+ <div>
+ <label for="i_m_host" class="field">Host:</label>
+ <input type="text" value="<?php print @$_SESSION['store_data']['host']; ?>" name="host" id="i_m_host">
+ </div>
+ <div>
+ <label for="i_m_database" class="field">Database:</label>
+ <input value="<?php print @$_SESSION['store_data']['database']; ?>" type="text" name="database" id="i_m_database">
+ </div>
+ <div>
+ <label for="i_m_username" class="field">Username:</label>
+ <input type="text" name="username" id="i_m_username" value="<?php print @$_SESSION['store_data']['username']; ?>">
+ </div>
+ <div>
+ <label for="i_m_password" class="field">Password:</label>
+ <input type="password" name="password" id="i_m_password" value="<?php print @$_SESSION['store_data']['password']; ?>">
+ </div>
+ </div>
+<?php } ?>
+</div>
+</div>
+
+<input type="submit" name="generate" value="Generate Configuration">
+</form>
+</body>
+</html>
+<?php
+}
+
+function init_session() {
+
+ global $messages;
+
+ // Set a guess value for the server url.
+ if (!array_key_exists('server_url', $_SESSION)) {
+ $_SESSION['server_url'] = build_url();
+ }
+
+ foreach (array('server_url', 'include_path', 'store_type') as $key) {
+ if (!isset($_SESSION[$key])) {
+ $_SESSION[$key] = "";
+ }
+ }
+
+ if (!isset($_SESSION['store_data'])) {
+ $_SESSION['store_data'] = array();
+ }
+
+ foreach (array('server_url', 'include_path', 'store_type') as $field) {
+ if (array_key_exists($field, $_GET)) {
+ $_SESSION[$field] = $_GET[$field];
+ }
+ }
+
+ foreach (array('username', 'password', 'database', 'host', 'fs_path', 'sqlite_path') as $field) {
+ if (array_key_exists($field, $_GET)) {
+ $_SESSION['store_data'][$field] = $_GET[$field];
+ }
+ }
+}
+
+function generate_config($download = false) {
+
+ if ($download) {
+ // Emit headers to force browser download.
+ header("Content-type: text/plain");
+ header("Content-disposition: attachment; filename=config.php");
+ print "<?php\n";
+ } else {
+?>
+<html>
+<body>
+
+<h2>OpenID Example Server Configuration</h2>
+
+<p>
+Put the following text into <strong><?php print dirname(__FILE__); print DIRECTORY_SEPARATOR; ?>config.php</strong>.
+</p>
+
+<p>
+<a href="setup.php?clear=1">Back to form</a> (resets settings)
+</p>
+
+<p>
+<a href="setup.php?download=1">Download this configuration</a>
+</p>
+
+<pre style="border: 1px solid gray; background: #eee; padding: 5px;">
+<?php
+print "&lt;?php\n";
+}
+?>
+<?php if ($_SESSION['include_path']) { ?>
+/**
+ * Set any extra include paths needed to use the library
+ */
+set_include_path(get_include_path() . PATH_SEPARATOR . "<?php
+print $_SESSION['include_path'];
+?>");
+
+<?php } ?>
+/**
+ * The URL for the server.
+ *
+ * This is the location of server.php. For example:
+ *
+ * $server_url = 'http://example.com/~user/server.php';
+ *
+ * This must be a full URL.
+ */
+$server_url = "<?php
+print $_SESSION['server_url'];
+?>";
+
+/**
+ * Initialize an OpenID store
+ *
+ * @return object $store an instance of OpenID store (see the
+ * documentation for how to create one)
+ */
+function getOpenIDStore()
+{
+ <?php
+
+ switch ($_SESSION['store_type']) {
+ case "Filesystem":
+
+ print "require_once \"Auth/OpenID/FileStore.php\";\n ";
+ print "return new Auth_OpenID_FileStore(\"".$_SESSION['store_data']['fs_path']."\");\n";
+ break;
+
+ case "SQLite":
+
+ print "require_once \"Auth/OpenID/SQLiteStore.php\";\n ";
+ print "\$s = new Auth_OpenID_SQLiteStore(\"".$_SESSION['store_data']['sqlite_path']."\");\n ";
+ print "\$s->createTables();\n ";
+ print "return \$s;\n";
+ break;
+
+ case "MySQL":
+
+ ?>require_once 'Auth/OpenID/MySQLStore.php';
+ require_once 'DB.php';
+
+ $dsn = array(
+ 'phptype' => 'mysql',
+ 'username' => '<?php print $_SESSION['store_data']['username']; ?>',
+ 'password' => '<?php print $_SESSION['store_data']['password']; ?>',
+ 'hostspec' => '<?php print $_SESSION['store_data']['host']; ?>'
+ );
+
+ $db =& DB::connect($dsn);
+
+ if (PEAR::isError($db)) {
+ return null;
+ }
+
+ $db->query("USE <?php print $_SESSION['store_data']['database']; ?>");
+
+ $s =& new Auth_OpenID_MySQLStore($db);
+
+ $s->createTables();
+
+ return $s;
+<?php
+ break;
+
+ case "PostgreSQL":
+
+ ?>require_once 'Auth/OpenID/PostgreSQLStore.php';
+ require_once 'DB.php';
+
+ $dsn = array(
+ 'phptype' => 'pgsql',
+ 'username' => '<?php print $_SESSION['store_data']['username']; ?>',
+ 'password' => '<?php print $_SESSION['store_data']['password']; ?>',
+ 'hostspec' => '<?php print $_SESSION['store_data']['host']; ?>',
+ 'database' => '<?php print $_SESSION['store_data']['database']; ?>'
+ );
+
+ $db =& DB::connect($dsn);
+
+ if (PEAR::isError($db)) {
+ return null;
+ }
+
+ $s =& new Auth_OpenID_PostgreSQLStore($db);
+
+ $s->createTables();
+
+ return $s;
+<?php
+ break;
+ }
+
+ ?>
+}
+
+<?php
+ print "?>";
+ if (!$download) {
+?>
+</pre>
+</body>
+</html>
+<?php
+ }
+ } // end function generate_config ()
+?>