summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Åkre Solberg <andreas.solberg@uninett.no>2007-11-08 09:48:30 +0000
committerAndreas Åkre Solberg <andreas.solberg@uninett.no>2007-11-08 09:48:30 +0000
commit85551fe3e619df8344c20e1a6a73896b5312e88a (patch)
tree1caa1412f9ad7366051537f9f24817053d9391f3
parent96b5b753654a68829f60e598afc2d1f1e63919b7 (diff)
downloadsimplesamlphp-85551fe3e619df8344c20e1a6a73896b5312e88a.zip
simplesamlphp-85551fe3e619df8344c20e1a6a73896b5312e88a.tar.gz
simplesamlphp-85551fe3e619df8344c20e1a6a73896b5312e88a.tar.bz2
Adding OpenID libraries, templates and server endpoint
git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@68 44740490-163a-0410-bde0-09ae8108e29a
-rw-r--r--lib/Auth/OpenID.php412
-rw-r--r--lib/Auth/OpenID/Association.php308
-rw-r--r--lib/Auth/OpenID/BigMath.php444
-rw-r--r--lib/Auth/OpenID/Consumer.php1186
-rw-r--r--lib/Auth/OpenID/CryptUtil.php109
-rw-r--r--lib/Auth/OpenID/DatabaseConnection.php131
-rw-r--r--lib/Auth/OpenID/DiffieHellman.php181
-rw-r--r--lib/Auth/OpenID/Discover.php258
-rw-r--r--lib/Auth/OpenID/DumbStore.php116
-rw-r--r--lib/Auth/OpenID/FileStore.php674
-rw-r--r--lib/Auth/OpenID/HMACSHA1.php72
-rw-r--r--lib/Auth/OpenID/Interface.php188
-rw-r--r--lib/Auth/OpenID/KVForm.php112
-rw-r--r--lib/Auth/OpenID/MySQLStore.php78
-rw-r--r--lib/Auth/OpenID/Parse.php308
-rw-r--r--lib/Auth/OpenID/PostgreSQLStore.php136
-rw-r--r--lib/Auth/OpenID/SQLStore.php658
-rw-r--r--lib/Auth/OpenID/SQLiteStore.php66
-rw-r--r--lib/Auth/OpenID/Server.php1307
-rw-r--r--lib/Auth/OpenID/ServerRequest.php37
-rw-r--r--lib/Auth/OpenID/TrustRoot.php243
-rw-r--r--lib/Auth/OpenID/URINorm.php231
-rw-r--r--lib/Services/Yadis/HTTPFetcher.php92
-rw-r--r--lib/Services/Yadis/Manager.php496
-rw-r--r--lib/Services/Yadis/Misc.php59
-rw-r--r--lib/Services/Yadis/ParanoidHTTPFetcher.php177
-rw-r--r--lib/Services/Yadis/ParseHTML.php258
-rw-r--r--lib/Services/Yadis/PlainHTTPFetcher.php245
-rw-r--r--lib/Services/Yadis/XML.php365
-rw-r--r--lib/Services/Yadis/XRDS.php425
-rw-r--r--lib/Services/Yadis/XRI.php233
-rw-r--r--lib/Services/Yadis/XRIRes.php68
-rw-r--r--lib/Services/Yadis/Yadis.php313
-rw-r--r--templates/default/en/openid-about.php60
-rw-r--r--templates/default/en/openid-sites.php81
-rw-r--r--templates/default/en/openid-trust.php33
-rw-r--r--www/openid/provider/server.php676
37 files changed, 10836 insertions, 0 deletions
diff --git a/lib/Auth/OpenID.php b/lib/Auth/OpenID.php
new file mode 100644
index 0000000..86278b9
--- /dev/null
+++ b/lib/Auth/OpenID.php
@@ -0,0 +1,412 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require the fetcher code.
+ */
+require_once "Services/Yadis/PlainHTTPFetcher.php";
+require_once "Services/Yadis/ParanoidHTTPFetcher.php";
+require_once "Auth/OpenID/BigMath.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) {
+ define('Auth_OpenID_NO_MATH_SUPPORT', true);
+}
+
+/**
+ * The OpenID utility function class.
+ *
+ * @package OpenID
+ * @access private
+ */
+class Auth_OpenID {
+
+ /**
+ * These namespaces are automatically fixed in query arguments by
+ * Auth_OpenID::fixArgs.
+ */
+ function getOpenIDNamespaces()
+ {
+ return array('openid',
+ 'sreg');
+ }
+
+ /**
+ * Rename query arguments back to 'openid.' from 'openid_'
+ *
+ * @access private
+ * @param array $args An associative array of URL query arguments
+ */
+ function fixArgs($args)
+ {
+ foreach (array_keys($args) as $key) {
+ $fixed = $key;
+ if (preg_match('/^openid/', $key)) {
+ foreach (Auth_OpenID::getOpenIDNamespaces() as $ns) {
+ if (preg_match('/'.$ns.'_/', $key)) {
+ $fixed = preg_replace('/'.$ns.'_/', $ns.'.', $fixed);
+ }
+ }
+
+ if ($fixed != $key) {
+ $val = $args[$key];
+ unset($args[$key]);
+ $args[$fixed] = $val;
+ }
+ }
+ }
+
+ return $args;
+ }
+
+ /**
+ * 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 {
+ if (Auth_OpenID::ensureDir(dirname($dir_name))) {
+ return is_dir($dir_name) || @mkdir($dir_name);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Convenience function for getting array values.
+ *
+ * @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 expected " .
+ "array as first parameter", E_USER_WARNING);
+ return false;
+ }
+ }
+
+ /**
+ * 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).
+ *
+ * @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);
+ }
+
+ /**
+ * Turn a string into an ASCII string.
+ *
+ * Replace non-ascii characters with a %-encoded, UTF-8
+ * encoding. This function will fail if the input is a string and
+ * there are non-7-bit-safe characters. It is assumed that the
+ * caller will have already translated the input into a Unicode
+ * character sequence, according to the encoding of the HTTP POST
+ * or GET.
+ *
+ * Do not escape anything that is already 7-bit safe, so we do the
+ * minimal transform on the identity URL
+ *
+ * @access private
+ */
+ function quoteMinimal($s)
+ {
+ $res = array();
+ for ($i = 0; $i < strlen($s); $i++) {
+ $c = $s[$i];
+ if ($c >= "\x80") {
+ for ($j = 0; $j < count(utf8_encode($c)); $j++) {
+ array_push($res, sprintf("%02X", ord($c[$j])));
+ }
+ } else {
+ array_push($res, $c);
+ }
+ }
+
+ return implode('', $res);
+ }
+
+ /**
+ * 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)
+ {
+ if ($url === null) {
+ return null;
+ }
+
+ assert(is_string($url));
+
+ $old_url = $url;
+ $url = trim($url);
+
+ if (strpos($url, "://") === false) {
+ $url = "http://" . $url;
+ }
+
+ $parsed = @parse_url($url);
+
+ if ($parsed === false) {
+ return null;
+ }
+
+ $defaults = array(
+ 'scheme' => '',
+ 'host' => '',
+ 'path' => '',
+ 'query' => '',
+ 'fragment' => '',
+ 'port' => ''
+ );
+
+ $parsed = array_merge($defaults, $parsed);
+
+ if (($parsed['scheme'] == '') ||
+ ($parsed['host'] == '')) {
+ if ($parsed['path'] == '' &&
+ $parsed['query'] == '' &&
+ $parsed['fragment'] == '') {
+ return null;
+ }
+
+ $url = 'http://' + $url;
+ $parsed = parse_url($url);
+
+ $parsed = array_merge($defaults, $parsed);
+ }
+
+ $tail = array_map(array('Auth_OpenID', 'quoteMinimal'),
+ array($parsed['path'],
+ $parsed['query'],
+ $parsed['fragment']));
+ if ($tail[0] == '') {
+ $tail[0] = '/';
+ }
+
+ $url = Auth_OpenID::urlunparse($parsed['scheme'], $parsed['host'],
+ $parsed['port'], $tail[0], $tail[1],
+ $tail[2]);
+
+ assert(is_string($url));
+
+ return $url;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/Association.php b/lib/Auth/OpenID/Association.php
new file mode 100644
index 0000000..109a970
--- /dev/null
+++ b/lib/Auth/OpenID/Association.php
@@ -0,0 +1,308 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/KVForm.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'
+ );
+
+ /**
+ * 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 value of this field at
+ * this time is 'HMAC-SHA1', 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 value of this field at
+ * this time is 'HMAC-SHA1', but new types may be defined in the
+ * future.
+ */
+ function Auth_OpenID_Association(
+ $handle, $secret, $issued, $lifetime, $assoc_type)
+ {
+ if ($assoc_type != 'HMAC-SHA1') {
+ $fmt = 'HMAC-SHA1 is the only supported association type (got %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: ' . strval($keys),
+ 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);
+ return Auth_OpenID_HMACSHA1($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 signDict($fields, $data, $prefix = 'openid.')
+ {
+ $pairs = array();
+ foreach ($fields as $field) {
+ $pairs[] = array($field, $data[$prefix . $field]);
+ }
+
+ return base64_encode($this->sign($pairs));
+ }
+
+ /**
+ * Add a signature to an array of fields
+ *
+ * @access private
+ */
+ function addSignature($fields, &$data, $prefix = 'openid.')
+ {
+ $sig = $this->signDict($fields, $data, $prefix);
+ $signed = implode(",", $fields);
+ $data[$prefix . 'sig'] = $sig;
+ $data[$prefix . 'signed'] = $signed;
+ }
+
+ /**
+ * Confirm that the signature of these fields matches the
+ * signature contained in the data
+ *
+ * @access private
+ */
+ function checkSignature($data, $prefix = 'openid.')
+ {
+ $signed = $data[$prefix . 'signed'];
+ $fields = explode(",", $signed);
+ $expected_sig = $this->signDict($fields, $data, $prefix);
+ $request_sig = $data[$prefix . 'sig'];
+
+ return ($request_sig == $expected_sig);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/BigMath.php b/lib/Auth/OpenID/BigMath.php
new file mode 100644
index 0000000..3113104
--- /dev/null
+++ b/lib/Auth/OpenID/BigMath.php
@@ -0,0 +1,444 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Needed for random number generation
+ */
+require_once 'Auth/OpenID/CryptUtil.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 = strlen($rbytes) - 1;
+ } else {
+ $nbytes = strlen($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.
+ */
+global $_Auth_OpenID_math_extensions;
+$_Auth_OpenID_math_extensions = array(
+ array('modules' => array('gmp', 'php_gmp'),
+ 'extension' => 'gmp',
+ 'class' => 'Auth_OpenID_GmpMathWrapper'),
+ array('modules' => array('bcmath', 'php_bcmath'),
+ 'extension' => 'bcmath',
+ 'class' => 'Auth_OpenID_BcMathWrapper')
+ );
+
+/**
+ * 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
+ * local {@link Auth_OpenID_math_extensions} array 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 (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $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.
+ global $_Auth_OpenID_math_extensions;
+ $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);
+
+ define('Auth_OpenID_NO_MATH_SUPPORT', true);
+ return null;
+ }
+
+ // Instantiate a new wrapper
+ $class = $ext['class'];
+ $lib = new $class();
+
+ return $lib;
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/Consumer.php b/lib/Auth/OpenID/Consumer.php
new file mode 100644
index 0000000..7ea75c7
--- /dev/null
+++ b/lib/Auth/OpenID/Consumer.php
@@ -0,0 +1,1186 @@
+<?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. In addition to saying that, the identity server
+ * provides a URL to which the user can be sent to provide the needed
+ * information and let the server finish handling the original
+ * request.
+ *
+ * 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 any sort of session
+ * framework that provides per-client state management, a dict-like
+ * object to access the session should be passed as the optional
+ * second parameter. (The default behavior is to use PHP's standard
+ * session machinery.)
+ *
+ * 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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/HMACSHA1.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/Discover.php";
+require_once "Services/Yadis/Manager.php";
+require_once "Services/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');
+
+/**
+ * This is the characters that the nonces are made from.
+ */
+define('Auth_OpenID_DEFAULT_NONCE_CHRS',"abcdefghijklmnopqrstuvwxyz" .
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+/**
+ * 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 $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 Services_Yadis_Session 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.
+ */
+ function Auth_OpenID_Consumer(&$store, $session = null)
+ {
+ if ($session === null) {
+ $session = new Services_Yadis_PHPSession();
+ }
+
+ $this->session =& $session;
+ $this->consumer =& new Auth_OpenID_GenericConsumer($store);
+ $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
+ }
+
+ /**
+ * Start the OpenID authentication process. See steps 1-2 in the
+ * overview at the top of this file.
+ *
+ * @param 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.
+ *
+ * @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)
+ {
+ $discoverMethod = '_Auth_OpenID_discoverServiceList';
+ $openid_url = $user_url;
+
+ if (Services_Yadis_identifierScheme($user_url) == 'XRI') {
+ $discoverMethod = '_Auth_OpenID_discoverXRIServiceList';
+ } else {
+ $openid_url = Auth_OpenID::normalizeUrl($user_url);
+ }
+
+ $disco =& new Services_Yadis_Discovery($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 Services_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($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);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @return Auth_OpenID_AuthRequest $auth_request An OpenID
+ * authentication request object.
+ */
+ function &beginWithoutDiscovery($endpoint)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $auth_req = $this->consumer->begin($endpoint);
+ $this->session->set($this->_token_key,
+ $loader->toSession($auth_req->endpoint));
+ 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 array $query An array of the query parameters (key =>
+ * value pairs) for this HTTP request.
+ *
+ * @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($query)
+ {
+ $query = Auth_OpenID::fixArgs($query);
+
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $endpoint_data = $this->session->get($this->_token_key);
+ $endpoint =
+ $loader->fromSession($endpoint_data);
+
+ if ($endpoint === null) {
+ $response = new Auth_OpenID_FailureResponse(null,
+ 'No session state found');
+ } else {
+ $response = $this->consumer->complete($query, $endpoint);
+ $this->session->del($this->_token_key);
+ }
+
+ if (in_array($response->status, array(Auth_OpenID_SUCCESS,
+ Auth_OpenID_CANCEL))) {
+ if ($response->identity_url !== null) {
+ $disco = new Services_Yadis_Discovery($this->session,
+ $response->identity_url,
+ $this->session_key_prefix);
+ $disco->cleanup();
+ }
+ }
+
+ return $response;
+ }
+}
+
+class Auth_OpenID_DiffieHellmanConsumerSession {
+ var $session_type = 'DH-SHA1';
+
+ function Auth_OpenID_DiffieHellmanConsumerSession($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('openid.dh_consumer_public' => $cpub);
+
+ if (!$this->dh->usingDefaultValues()) {
+ $args = array_merge($args, array(
+ 'openid.dh_modulus' =>
+ $math->longToBase64($this->dh->mod),
+ 'openid.dh_gen' =>
+ $math->longToBase64($this->dh->gen)));
+ }
+
+ return $args;
+ }
+
+ function extractSecret($response)
+ {
+ if (!array_key_exists('dh_server_public', $response)) {
+ return null;
+ }
+
+ if (!array_key_exists('enc_mac_key', $response)) {
+ return null;
+ }
+
+ $math =& Auth_OpenID_getMathLib();
+ $spub = $math->base64ToLong($response['dh_server_public']);
+ $enc_mac_key = base64_decode($response['enc_mac_key']);
+
+ return $this->dh->xorSecret($spub, $enc_mac_key);
+ }
+}
+
+class Auth_OpenID_PlainTextConsumerSession {
+ var $session_type = null;
+
+ function getRequest()
+ {
+ return array();
+ }
+
+ function extractSecret($response)
+ {
+ if (!array_key_exists('mac_key', $response)) {
+ return null;
+ }
+
+ return base64_decode($response['mac_key']);
+ }
+}
+
+/**
+ * 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
+ * @access private
+ */
+class Auth_OpenID_GenericConsumer {
+ /**
+ * This consumer's store object.
+ */
+ var $store;
+
+ /**
+ * @access private
+ */
+ var $_use_assocs;
+
+ /**
+ * This is the number of characters in the generated nonce for
+ * each transaction.
+ */
+ var $nonce_len = 8;
+
+ /**
+ * What characters are allowed in nonces
+ */
+ var $nonce_chrs = Auth_OpenID_DEFAULT_NONCE_CHRS;
+
+ /**
+ * 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->_use_assocs =
+ !(defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ ($this->store && $this->store->isDumb()));
+
+ $this->fetcher = Services_Yadis_Yadis::getHTTPFetcher();
+ }
+
+ function begin($service_endpoint)
+ {
+ $nonce = $this->_createNonce();
+ $assoc = $this->_getAssociation($service_endpoint->server_url);
+ $r = new Auth_OpenID_AuthRequest($assoc, $service_endpoint);
+ $r->return_to_args['nonce'] = $nonce;
+ return $r;
+ }
+
+ function complete($query, $endpoint)
+ {
+ $mode = Auth_OpenID::arrayGet($query, 'openid.mode',
+ '<no mode specified>');
+
+ if ($mode == Auth_OpenID_CANCEL) {
+ return new Auth_OpenID_CancelResponse($endpoint);
+ } else if ($mode == 'error') {
+ $error = Auth_OpenID::arrayGet($query, 'openid.error');
+ return new Auth_OpenID_FailureResponse($endpoint, $error);
+ } else if ($mode == 'id_res') {
+ if ($endpoint->identity_url === null) {
+ return new Auth_OpenID_FailureResponse($identity_url,
+ "No session state found");
+ }
+
+ $response = $this->_doIdRes($query, $endpoint);
+
+ if ($response === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "HTTP request failed");
+ }
+ if ($response->status == Auth_OpenID_SUCCESS) {
+ return $this->_checkNonce($response,
+ Auth_OpenID::arrayGet($query,
+ 'nonce'));
+ } else {
+ return $response;
+ }
+ } else {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf("Invalid openid.mode '%s'",
+ $mode));
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _doIdRes($query, $endpoint)
+ {
+ $user_setup_url = Auth_OpenID::arrayGet($query,
+ 'openid.user_setup_url');
+
+ if ($user_setup_url !== null) {
+ return new Auth_OpenID_SetupNeededResponse($endpoint,
+ $user_setup_url);
+ }
+
+ $return_to = Auth_OpenID::arrayGet($query, 'openid.return_to', null);
+ $server_id2 = Auth_OpenID::arrayGet($query, 'openid.identity', null);
+ $assoc_handle = Auth_OpenID::arrayGet($query,
+ 'openid.assoc_handle', null);
+
+ if (($return_to === null) ||
+ ($server_id2 === null) ||
+ ($assoc_handle === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Missing required field");
+ }
+
+ if ($endpoint->getServerID() != $server_id2) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Server ID (delegate) mismatch");
+ }
+
+ $signed = Auth_OpenID::arrayGet($query, 'openid.signed');
+
+ $assoc = $this->store->getAssociation($endpoint->server_url,
+ $assoc_handle);
+
+ if ($assoc === null) {
+ // It's not an association we know about. Dumb mode is
+ // our only possible path for recovery.
+ if ($this->_checkAuth($query, $endpoint->server_url)) {
+ return new Auth_OpenID_SuccessResponse($endpoint, $query,
+ $signed);
+ } else {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Server denied check_authentication");
+ }
+ }
+
+ if ($assoc->getExpiresIn() <= 0) {
+ $msg = sprintf("Association with %s expired",
+ $endpoint->server_url);
+ return new Auth_OpenID_FailureResponse($endpoint, $msg);
+ }
+
+ // Check the signature
+ $sig = Auth_OpenID::arrayGet($query, 'openid.sig', null);
+ if (($sig === null) ||
+ ($signed === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Missing argument signature");
+ }
+
+ $signed_list = explode(",", $signed);
+
+ //Fail if the identity field is present but not signed
+ if (($endpoint->identity_url !== null) &&
+ (!in_array('identity', $signed_list))) {
+ $msg = '"openid.identity" not signed';
+ return new Auth_OpenID_FailureResponse($endpoint, $msg);
+ }
+
+ $v_sig = $assoc->signDict($signed_list, $query);
+
+ if ($v_sig != $sig) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Bad signature");
+ }
+
+ return Auth_OpenID_SuccessResponse::fromQuery($endpoint,
+ $query, $signed);
+ }
+
+ /**
+ * @access private
+ */
+ function _checkAuth($query, $server_url)
+ {
+ $request = $this->_createCheckAuthRequest($query);
+ if ($request === null) {
+ return false;
+ }
+
+ $response = $this->_makeKVPost($request, $server_url);
+ if ($response == null) {
+ return false;
+ }
+
+ return $this->_processCheckAuthResponse($response, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _createCheckAuthRequest($query)
+ {
+ $signed = Auth_OpenID::arrayGet($query, 'openid.signed', null);
+ if ($signed === null) {
+ return null;
+ }
+
+ $whitelist = array('assoc_handle', 'sig',
+ 'signed', 'invalidate_handle');
+
+ $signed = array_merge(explode(",", $signed), $whitelist);
+
+ $check_args = array();
+
+ foreach ($query as $key => $value) {
+ if (in_array(substr($key, 7), $signed)) {
+ $check_args[$key] = $value;
+ }
+ }
+
+ $check_args['openid.mode'] = 'check_authentication';
+ return $check_args;
+ }
+
+ /**
+ * @access private
+ */
+ function _processCheckAuthResponse($response, $server_url)
+ {
+ $is_valid = Auth_OpenID::arrayGet($response, 'is_valid', 'false');
+
+ $invalidate_handle = Auth_OpenID::arrayGet($response,
+ 'invalidate_handle');
+
+ if ($invalidate_handle !== null) {
+ $this->store->removeAssociation($server_url,
+ $invalidate_handle);
+ }
+
+ if ($is_valid == 'true') {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _makeKVPost($args, $server_url)
+ {
+ $mode = $args['openid.mode'];
+
+ $pairs = array();
+ foreach ($args as $k => $v) {
+ $v = urlencode($v);
+ $pairs[] = "$k=$v";
+ }
+
+ $body = implode("&", $pairs);
+
+ $resp = $this->fetcher->post($server_url, $body);
+
+ if ($resp === null) {
+ return null;
+ }
+
+ $response = Auth_OpenID_KVForm::toArray($resp->body);
+
+ if ($resp->status == 400) {
+ return null;
+ } else if ($resp->status != 200) {
+ return null;
+ }
+
+ return $response;
+ }
+
+ /**
+ * @access private
+ */
+ function _checkNonce($response, $nonce)
+ {
+ $parsed_url = parse_url($response->getReturnTo());
+ $query_str = @$parsed_url['query'];
+ $query = array();
+ parse_str($query_str, $query);
+
+ $found = false;
+
+ foreach ($query as $k => $v) {
+ if ($k == 'nonce') {
+ if ($v != $nonce) {
+ return new Auth_OpenID_FailureResponse($response,
+ "Nonce mismatch");
+ } else {
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if (!$found) {
+ return new Auth_OpenID_FailureResponse($response,
+ sprintf("Nonce missing from return_to: %s",
+ $response->getReturnTo()));
+ }
+
+ if (!$this->store->useNonce($nonce)) {
+ return new Auth_OpenID_FailureResponse($response,
+ "Nonce missing from store");
+ }
+
+ return $response;
+ }
+
+ /**
+ * @access private
+ */
+ function _createNonce()
+ {
+ $nonce = Auth_OpenID_CryptUtil::randomString($this->nonce_len,
+ $this->nonce_chrs);
+ $this->store->storeNonce($nonce);
+ return $nonce;
+ }
+
+ /**
+ * @access protected
+ */
+ function _createDiffieHellman()
+ {
+ return new Auth_OpenID_DiffieHellman();
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($server_url)
+ {
+ if (!$this->_use_assocs) {
+ return null;
+ }
+
+ $assoc = $this->store->getAssociation($server_url);
+
+ if (($assoc === null) ||
+ ($assoc->getExpiresIn() <= 0)) {
+
+ $parts = $this->_createAssociateRequest($server_url);
+
+ if ($parts === null) {
+ return null;
+ }
+
+ list($assoc_session, $args) = $parts;
+
+ $response = $this->_makeKVPost($args, $server_url);
+
+ if ($response === null) {
+ $assoc = null;
+ } else {
+ $assoc = $this->_parseAssociation($response, $assoc_session,
+ $server_url);
+ }
+ }
+
+ return $assoc;
+ }
+
+ function _createAssociateRequest($server_url)
+ {
+ $parts = parse_url($server_url);
+
+ if ($parts === false) {
+ return null;
+ }
+
+ if (array_key_exists('scheme', $parts)) {
+ $proto = $parts['scheme'];
+ } else {
+ $proto = 'http';
+ }
+
+ if ($proto == 'https') {
+ $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ $assoc_session = new Auth_OpenID_DiffieHellmanConsumerSession();
+ }
+
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.assoc_type' => 'HMAC-SHA1');
+
+ if ($assoc_session->session_type !== null) {
+ $args['openid.session_type'] = $assoc_session->session_type;
+ }
+
+ $args = array_merge($args, $assoc_session->getRequest());
+ return array($assoc_session, $args);
+ }
+
+ /**
+ * @access private
+ */
+ function _parseAssociation($results, $assoc_session, $server_url)
+ {
+ $required_keys = array('assoc_type', 'assoc_handle',
+ 'expires_in');
+
+ foreach ($required_keys as $key) {
+ if (!array_key_exists($key, $results)) {
+ return null;
+ }
+ }
+
+ $assoc_type = $results['assoc_type'];
+ $assoc_handle = $results['assoc_handle'];
+ $expires_in_str = $results['expires_in'];
+
+ if ($assoc_type != 'HMAC-SHA1') {
+ return null;
+ }
+
+ $expires_in = intval($expires_in_str);
+
+ if ($expires_in <= 0) {
+ return null;
+ }
+
+ $session_type = Auth_OpenID::arrayGet($results, 'session_type');
+ if ($session_type != $assoc_session->session_type) {
+ if ($session_type === null) {
+ $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ return null;
+ }
+ }
+
+ $secret = $assoc_session->extractSecret($results);
+
+ if (!$secret) {
+ return null;
+ }
+
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $expires_in, $assoc_handle, $secret, $assoc_type);
+ $this->store->storeAssociation($server_url, $assoc);
+
+ return $assoc;
+ }
+}
+
+/**
+ * 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($assoc, $endpoint)
+ {
+ $this->assoc = $assoc;
+ $this->endpoint = $endpoint;
+ $this->extra_args = array();
+ $this->return_to_args = array();
+ }
+
+ /**
+ * 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)
+ {
+ $arg_name = implode('.', array('openid', $namespace, $key));
+ $this->extra_args[$arg_name] = $value;
+ }
+
+ /**
+ * Compute the appropriate redirection URL for this request based
+ * on a specified trust root and return-to.
+ *
+ * @param string $trust_root The trust root URI for your
+ * application.
+ *
+ * @param string$ $return_to The return-to URL to be used when the
+ * OpenID server redirects the user back to your site.
+ *
+ * @return string $redirect_url The resulting redirect URL that
+ * you should send to the user agent.
+ */
+ function redirectURL($trust_root, $return_to, $immediate=false)
+ {
+ if ($immediate) {
+ $mode = 'checkid_immediate';
+ } else {
+ $mode = 'checkid_setup';
+ }
+
+ $return_to = Auth_OpenID::appendArgs($return_to, $this->return_to_args);
+
+ $redir_args = array(
+ 'openid.mode' => $mode,
+ 'openid.identity' => $this->endpoint->getServerID(),
+ 'openid.return_to' => $return_to,
+ 'openid.trust_root' => $trust_root);
+
+ if ($this->assoc) {
+ $redir_args['openid.assoc_handle'] = $this->assoc->handle;
+ }
+
+ $redir_args = array_merge($redir_args, $this->extra_args);
+
+ return Auth_OpenID::appendArgs($this->endpoint->server_url,
+ $redir_args);
+ }
+}
+
+/**
+ * The base class for responses from the Auth_OpenID_Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ConsumerResponse {
+ var $status = 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:
+ *
+ * identity_url - 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, $signed_args)
+ {
+ $this->endpoint = $endpoint;
+ $this->identity_url = $endpoint->identity_url;
+ $this->signed_args = $signed_args;
+ }
+
+ /**
+ * @access private
+ */
+ function fromQuery($endpoint, $query, $signed)
+ {
+ $signed_args = array();
+ foreach (explode(",", $signed) as $field_name) {
+ $field_name = 'openid.' . $field_name;
+ $signed_args[$field_name] = Auth_OpenID::arrayGet($query,
+ $field_name, '');
+ }
+ return new Auth_OpenID_SuccessResponse($endpoint, $signed_args);
+ }
+
+ /**
+ * Extract signed extension data from the server's response.
+ *
+ * @param string $prefix The extension namespace from which to
+ * extract the extension data.
+ */
+ function extensionResponse($prefix)
+ {
+ $response = array();
+ $prefix = sprintf('openid.%s.', $prefix);
+ $prefix_len = strlen($prefix);
+ foreach ($this->signed_args as $k => $v) {
+ if (strpos($k, $prefix) === 0) {
+ $response_key = substr($k, $prefix_len);
+ $response[$response_key] = $v;
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * 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 Auth_OpenID::arrayGet($this->signed_args, 'openid.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:
+ *
+ * identity_url - 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)
+ {
+ $this->endpoint = $endpoint;
+ if ($endpoint !== null) {
+ $this->identity_url = $endpoint->identity_url;
+ } else {
+ $this->identity_url = null;
+ }
+ $this->message = $message;
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_CANCEL. Indicates that the
+ * user cancelled the OpenID authentication request. This has two
+ * relevant attributes:
+ *
+ * identity_url - 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->endpoint = $endpoint;
+ $this->identity_url = $endpoint->identity_url;
+ }
+}
+
+/**
+ * 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.
+ *
+ * identity_url - 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.
+ *
+ * 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->endpoint = $endpoint;
+ $this->identity_url = $endpoint->identity_url;
+ $this->setup_url = $setup_url;
+ }
+}
+
+?>
diff --git a/lib/Auth/OpenID/CryptUtil.php b/lib/Auth/OpenID/CryptUtil.php
new file mode 100644
index 0000000..8d7e069
--- /dev/null
+++ b/lib/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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+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/lib/Auth/OpenID/DatabaseConnection.php b/lib/Auth/OpenID/DatabaseConnection.php
new file mode 100644
index 0000000..3f4515f
--- /dev/null
+++ b/lib/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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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/lib/Auth/OpenID/DiffieHellman.php b/lib/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 0000000..2b845b7
--- /dev/null
+++ b/lib/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,181 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once 'Auth/OpenID/BigMath.php';
+require_once 'Auth/OpenID/HMACSHA1.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;
+ }
+
+ /**
+ * Generate the arguments for an OpenID Diffie-Hellman association
+ * request
+ */
+ function getAssocArgs()
+ {
+ $cpub = $this->lib->longToBase64($this->getPublicKey());
+ $args = array(
+ 'openid.dh_consumer_public' => $cpub,
+ 'openid.session_type' => 'DH-SHA1'
+ );
+
+ if ($this->lib->cmp($this->mod, Auth_OpenID_getDefaultMod()) ||
+ $this->lib->cmp($this->gen, Auth_OpenID_getDefaultGen())) {
+ $args['openid.dh_modulus'] = $this->lib->longToBase64($this->mod);
+ $args['openid.dh_gen'] = $this->lib->longToBase64($this->gen);
+ }
+
+ return $args;
+ }
+
+ function usingDefaultValues()
+ {
+ return ($this->mod == Auth_OpenID_getDefaultMod() &&
+ $this->gen == Auth_OpenID_getDefaultGen());
+ }
+
+ /**
+ * Perform the server side of the OpenID Diffie-Hellman association
+ */
+ function serverAssociate($consumer_args, $assoc_secret)
+ {
+ $lib =& Auth_OpenID_getMathLib();
+
+ if (isset($consumer_args['openid.dh_modulus'])) {
+ $mod = $lib->base64ToLong($consumer_args['openid.dh_modulus']);
+ } else {
+ $mod = null;
+ }
+
+ if (isset($consumer_args['openid.dh_gen'])) {
+ $gen = $lib->base64ToLong($consumer_args['openid.dh_gen']);
+ } else {
+ $gen = null;
+ }
+
+ $cpub64 = @$consumer_args['openid.dh_consumer_public'];
+ if (!isset($cpub64)) {
+ return false;
+ }
+
+ $dh = new Auth_OpenID_DiffieHellman($mod, $gen);
+ $cpub = $lib->base64ToLong($cpub64);
+ $mac_key = $dh->xorSecret($cpub, $assoc_secret);
+ $enc_mac_key = base64_encode($mac_key);
+ $spub64 = $lib->longToBase64($dh->getPublicKey());
+
+ $server_args = array(
+ 'session_type' => 'DH-SHA1',
+ 'dh_server_public' => $spub64,
+ 'enc_mac_key' => $enc_mac_key
+ );
+
+ return $server_args;
+ }
+
+ function consumerFinish($reply)
+ {
+ $spub = $this->lib->base64ToLong($reply['dh_server_public']);
+ if ($this->lib->cmp($spub, 0) <= 0) {
+ return false;
+ }
+ $enc_mac_key = base64_decode($reply['enc_mac_key']);
+ return $this->xorSecret($spub, $enc_mac_key);
+ }
+
+ function xorSecret($composite, $secret)
+ {
+ $dh_shared = $this->getSharedSecret($composite);
+ $dh_shared_str = $this->lib->longToBinary($dh_shared);
+ $sha1_dh_shared = Auth_OpenID_SHA1($dh_shared_str);
+
+ $xsecret = "";
+ for ($i = 0; $i < strlen($secret); $i++) {
+ $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
+ }
+
+ return $xsecret;
+ }
+}
diff --git a/lib/Auth/OpenID/Discover.php b/lib/Auth/OpenID/Discover.php
new file mode 100644
index 0000000..d87d47d
--- /dev/null
+++ b/lib/Auth/OpenID/Discover.php
@@ -0,0 +1,258 @@
+<?php
+
+/**
+ * The OpenID and Yadis discovery implementation for OpenID 1.2.
+ */
+
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Parse.php";
+require_once "Services/Yadis/XRIRes.php";
+require_once "Services/Yadis/Yadis.php";
+
+define('_OPENID_1_0_NS', 'http://openid.net/xmlns/1.0');
+define('_OPENID_1_2_TYPE', 'http://openid.net/signon/1.2');
+define('_OPENID_1_1_TYPE', 'http://openid.net/signon/1.1');
+define('_OPENID_1_0_TYPE', 'http://openid.net/signon/1.0');
+
+/**
+ * Object representing an OpenID service endpoint.
+ */
+class Auth_OpenID_ServiceEndpoint {
+ function Auth_OpenID_ServiceEndpoint()
+ {
+ $this->identity_url = null;
+ $this->server_url = null;
+ $this->type_uris = array();
+ $this->delegate = null;
+ $this->canonicalID = null;
+ $this->used_yadis = false; // whether this came from an XRDS
+ }
+
+ function usesExtension($extension_uri)
+ {
+ return in_array($extension_uri, $this->type_uris);
+ }
+
+ function parseService($yadis_url, $uri, $type_uris, $service_element)
+ {
+ // Set the state of this object based on the contents of the
+ // service element.
+ $this->type_uris = $type_uris;
+ $this->identity_url = $yadis_url;
+ $this->server_url = $uri;
+ $this->delegate = Auth_OpenID_ServiceEndpoint::findDelegate(
+ $service_element);
+ $this->used_yadis = true;
+ }
+
+ function findDelegate($service)
+ {
+ // Extract a openid:Delegate value from a Yadis Service
+ // element. If no delegate is found, returns null.
+
+ // Try to register new namespace.
+ $service->parser->registerNamespace('openid',
+ 'http://openid.net/xmlns/1.0');
+
+ // XXX: should this die if there is more than one delegate
+ // element?
+ $delegates = $service->getElements("openid:Delegate");
+
+ if ($delegates) {
+ return $service->parser->content($delegates[0]);
+ } else {
+ return null;
+ }
+ }
+
+ function getServerID()
+ {
+ // Return the identifier that should be sent as the
+ // openid.identity_url parameter to the server.
+ if ($this->delegate === null) {
+ if ($this->canonicalID) {
+ return $this->canonicalID;
+ } else {
+ return $this->identity_url;
+ }
+ } else {
+ return $this->delegate;
+ }
+ }
+
+ function fromHTML($uri, $html)
+ {
+ // Parse the given document as HTML looking for an OpenID <link
+ // rel=...>
+ $urls = Auth_OpenID_legacy_discover($html);
+ if ($urls === false) {
+ return null;
+ }
+
+ list($delegate_url, $server_url) = $urls;
+
+ $service = new Auth_OpenID_ServiceEndpoint();
+ $service->identity_url = $uri;
+ $service->delegate = $delegate_url;
+ $service->server_url = $server_url;
+ $service->type_uris = array(_OPENID_1_0_TYPE);
+ return $service;
+ }
+}
+
+function filter_MatchesAnyOpenIDType(&$service)
+{
+ $uris = $service->getTypes();
+
+ foreach ($uris as $uri) {
+ if (in_array($uri,
+ array(_OPENID_1_0_TYPE,
+ _OPENID_1_1_TYPE,
+ _OPENID_1_2_TYPE))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function Auth_OpenID_makeOpenIDEndpoints($uri, $endpoints)
+{
+ $s = array();
+
+ if (!$endpoints) {
+ return $s;
+ }
+
+ foreach ($endpoints 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();
+ $openid_endpoint->parseService($uri,
+ $service_uri,
+ $type_uris,
+ $service);
+
+ $s[] = $openid_endpoint;
+ }
+ }
+ }
+
+ return $s;
+}
+
+function Auth_OpenID_discoverWithYadis($uri, &$fetcher)
+{
+ // 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.
+ $openid_services = array();
+
+ $http_response = null;
+ $response = Services_Yadis_Yadis::discover($uri, $http_response,
+ $fetcher);
+
+ if ($response) {
+ $identity_url = $response->uri;
+ $openid_services =
+ $response->xrds->services(array('filter_MatchesAnyOpenIDType'));
+ }
+
+ if (!$openid_services) {
+ return @Auth_OpenID_discoverWithoutYadis($uri,
+ $fetcher);
+ }
+
+ if (!$openid_services) {
+ $body = $response->body;
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1
+ // <link rel="...">
+ $service = Auth_OpenID_ServiceEndpoint::fromHTML($identity_url,
+ $body);
+
+ if ($service !== null) {
+ $openid_services = array($service);
+ }
+ } else {
+ $openid_services = Auth_OpenID_makeOpenIDEndpoints($response->uri,
+ $openid_services);
+ }
+
+ return array($identity_url, $openid_services, $http_response);
+}
+
+function _Auth_OpenID_discoverServiceList($uri, &$fetcher)
+{
+ list($url, $services, $resp) = Auth_OpenID_discoverWithYadis($uri,
+ $fetcher);
+
+ return $services;
+}
+
+function _Auth_OpenID_discoverXRIServiceList($uri, &$fetcher)
+{
+ list($url, $services, $resp) = _Auth_OpenID_discoverXRI($uri,
+ $fetcher);
+ return $services;
+}
+
+function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher)
+{
+ $http_resp = @$fetcher->get($uri);
+
+ if ($http_resp->status != 200) {
+ return array(null, array(), $http_resp);
+ }
+
+ $identity_url = $http_resp->final_url;
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1 <link
+ // rel="...">
+ $endpoint =& new Auth_OpenID_ServiceEndpoint();
+ $service = $endpoint->fromHTML($identity_url, $http_resp->body);
+ if ($service === null) {
+ $openid_services = array();
+ } else {
+ $openid_services = array($service);
+ }
+
+ return array($identity_url, $openid_services, $http_resp);
+}
+
+function _Auth_OpenID_discoverXRI($iname, &$fetcher)
+{
+ $services = new Services_Yadis_ProxyResolver($fetcher);
+ list($canonicalID, $service_list) = $services->query($iname,
+ array(_OPENID_1_0_TYPE,
+ _OPENID_1_1_TYPE,
+ _OPENID_1_2_TYPE),
+ array('filter_MatchesAnyOpenIDType'));
+
+ $endpoints = Auth_OpenID_makeOpenIDEndpoints($iname, $service_list);
+
+ for ($i = 0; $i < count($endpoints); $i++) {
+ $endpoints[$i]->canonicalID = $canonicalID;
+ }
+
+ // FIXME: returned xri should probably be in some normal form
+ return array($iname, $endpoints, null);
+}
+
+function Auth_OpenID_discover($uri, &$fetcher)
+{
+ return @Auth_OpenID_discoverWithYadis($uri, $fetcher);
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/DumbStore.php b/lib/Auth/OpenID/DumbStore.php
new file mode 100644
index 0000000..d4d8a8b
--- /dev/null
+++ b/lib/Auth/OpenID/DumbStore.php
@@ -0,0 +1,116 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMACSHA1.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;
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ function storeNonce($nonce)
+ {
+ }
+
+ /**
+ * 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($nonce)
+ {
+ return true;
+ }
+
+ /**
+ * This method returns the auth key generated by the constructor.
+ */
+ function getAuthKey()
+ {
+ return $this->auth_key;
+ }
+
+ /**
+ * This store is a dumb mode store, so this method is overridden
+ * to return true.
+ */
+ function isDumb()
+ {
+ return true;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/FileStore.php b/lib/Auth/OpenID/FileStore.php
new file mode 100644
index 0000000..6ce8856
--- /dev/null
+++ b/lib/Auth/OpenID/FileStore.php
@@ -0,0 +1,674 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ *
+ */
+
+/**
+ * Require base class for creating a new interface.
+ */
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMACSHA1.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 and the $directory containing the auth key file.
+ $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp';
+
+ $this->auth_key_name = $directory . DIRECTORY_SEPARATOR . 'auth_key';
+
+ $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(dirname($this->auth_key_name)) &&
+ 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->auth_key_name and $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);
+ }
+ }
+
+ /**
+ * Read the auth key from the auth key file. Will return None if
+ * there is currently no key.
+ *
+ * @return mixed
+ */
+ function readAuthKey()
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $auth_key_file = @fopen($this->auth_key_name, 'rb');
+ if ($auth_key_file === false) {
+ return null;
+ }
+
+ $key = fread($auth_key_file, filesize($this->auth_key_name));
+ fclose($auth_key_file);
+
+ return $key;
+ }
+
+ /**
+ * Generate a new random auth key and safely store it in the
+ * location specified by $this->auth_key_name.
+ *
+ * @return string $key
+ */
+ function createAuthKey()
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $auth_key = Auth_OpenID_CryptUtil::randomString($this->AUTH_KEY_LEN);
+
+ list($file_obj, $tmp) = $this->_mktemp();
+
+ fwrite($file_obj, $auth_key);
+ fflush($file_obj);
+ fclose($file_obj);
+
+ if (function_exists('link')) {
+ // Posix filesystem
+ $saved = link($tmp, $this->auth_key_name);
+ Auth_OpenID_FileStore::_removeIfPresent($tmp);
+ } else {
+ // Windows filesystem
+ $saved = rename($tmp, $this->auth_key_name);
+ }
+
+ if (!$saved) {
+ // The link failed, either because we lack the permission,
+ // or because the file already exists; try to read the key
+ // in case the file already existed.
+ $auth_key = $this->readAuthKey();
+ }
+
+ return $auth_key;
+ }
+
+ /**
+ * Retrieve the auth key from the file specified by
+ * $this->auth_key_name, creating it if it does not exist.
+ *
+ * @return string $key
+ */
+ function getAuthKey()
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $auth_key = $this->readAuthKey();
+ if ($auth_key === null) {
+ $auth_key = $this->createAuthKey();
+
+ if (strlen($auth_key) != $this->AUTH_KEY_LEN) {
+ $fmt = 'Got an invalid auth key from %s. Expected '.
+ '%d-byte string. Got: %s';
+ $msg = sprintf($fmt, $this->auth_key_name, $this->AUTH_KEY_LEN,
+ $auth_key);
+ trigger_error($msg, E_USER_WARNING);
+ return null;
+ }
+ }
+ return $auth_key;
+ }
+
+ /**
+ * 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) {
+ if (strpos($association_file, $name) === 0) {
+ $matching_files[] = $association_file;
+ }
+ }
+
+ $matching_associations = array();
+ // read the matching files and sort by time issued
+ foreach ($matching_files as $name) {
+ $full_name = $this->association_dir . DIRECTORY_SEPARATOR .
+ $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);
+ }
+ }
+
+ /**
+ * Mark this nonce as present.
+ */
+ function storeNonce($nonce)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce;
+ $nonce_file = fopen($filename, 'w');
+ if ($nonce_file === false) {
+ return false;
+ }
+ fclose($nonce_file);
+ return true;
+ }
+
+ /**
+ * Return whether this nonce is present. As a side effect, mark it
+ * as no longer present.
+ *
+ * @return bool $present
+ */
+ function useNonce($nonce)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce;
+ $st = @stat($filename);
+
+ if ($st === false) {
+ return false;
+ }
+
+ // Either it is too old or we are using it. Either way, we
+ // must remove the file.
+ if (!unlink($filename)) {
+ return false;
+ }
+
+ $now = time();
+ $nonce_age = $now - $st[9];
+
+ // We can us it if the age of the file is less than the
+ // expiration time.
+ return $nonce_age <= $this->max_nonce_age;
+ }
+
+ /**
+ * Remove expired entries from the database. This is potentially
+ * expensive, so only run when it is acceptable to take time.
+ */
+ 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) {
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce;
+ $st = @stat($filename);
+
+ if ($st !== false) {
+ // Remove the nonce if it has expired
+ $nonce_age = $now - $st[9];
+ if ($nonce_age > $this->max_nonce_age) {
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ }
+ }
+ }
+
+ $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) {
+ Auth_OpenID_FileStore::_removeIfPresent(
+ $association_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))) {
+ $files[] = $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 = "";
+ for ($i = 0; $i < strlen($str); $i++) {
+ $c = $str[$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);
+ }
+}
+
+?>
diff --git a/lib/Auth/OpenID/HMACSHA1.php b/lib/Auth/OpenID/HMACSHA1.php
new file mode 100644
index 0000000..6daadb5
--- /dev/null
+++ b/lib/Auth/OpenID/HMACSHA1.php
@@ -0,0 +1,72 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * SHA1_BLOCKSIZE is this module's SHA1 blocksize used by the fallback
+ * implementation.
+ */
+define('Auth_OpenID_SHA1_BLOCKSIZE', 64);
+
+if (!function_exists('sha1')) {
+ /**
+ * Return a raw SHA1 hash of the given string
+ *
+ * XXX: include the SHA1 code from Dan Libby's OpenID library
+ */
+ function Auth_OpenID_SHA1($text)
+ {
+ trigger_error('No SHA1 function found', E_USER_ERROR);
+ }
+} else {
+ /**
+ * @ignore
+ */
+ function Auth_OpenID_SHA1($text)
+ {
+ $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;
+ }
+}
+
+/**
+ * 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 (strlen($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;
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/Interface.php b/lib/Auth/OpenID/Interface.php
new file mode 100644
index 0000000..ce9fa1f
--- /dev/null
+++ b/lib/Auth/OpenID/Interface.php
@@ -0,0 +1,188 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ */
+class Auth_OpenID_OpenIDStore {
+ /**
+ * @var integer The length of the auth key that should be returned
+ * by the getAuthKey method.
+ */
+ var $AUTH_KEY_LEN = 20;
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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
+ * that will remain valid for the longest duration.
+ *
+ * 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);
+ }
+
+ /**
+ * Stores a nonce. This is used by the consumer to prevent replay
+ * attacks.
+ *
+ * @param string $nonce The nonce to store.
+ *
+ * @return null
+ */
+ function storeNonce($nonce)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::storeNonce ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This method is called when the library is attempting to use a
+ * nonce. If the nonce is in the store, this method removes it and
+ * returns a value which evaluates as true. Otherwise it returns a
+ * value which evaluates as false.
+ *
+ * This method is allowed and encouraged to treat nonces older
+ * than some period (a very conservative window would be 6 hours,
+ * for example) as no longer existing, and return False and remove
+ * them.
+ *
+ * @param string $nonce The nonce to use.
+ *
+ * @return bool Whether or not the nonce was valid.
+ */
+ function useNonce($nonce)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::useNonce ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This method returns a key used to sign the tokens, to ensure
+ * that they haven't been tampered with in transit. It should
+ * return the same key every time it is called. The key returned
+ * should be {@link AUTH_KEY_LEN} bytes long.
+ *
+ * @return string The key. It should be {@link AUTH_KEY_LEN} bytes in
+ * length, and use the full range of byte values. That is, it
+ * should be treated as a lump of binary data stored in a string.
+ */
+ function getAuthKey()
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::getAuthKey ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This method must return true if the store is a dumb-mode-style
+ * store. Unlike all other methods in this class, this one
+ * provides a default implementation, which returns false.
+ *
+ * In general, any custom subclass of {@link Auth_OpenID_OpenIDStore}
+ * won't override this method, as custom subclasses are only likely to
+ * be created when the store is fully functional.
+ *
+ * @return bool true if the store works fully, false if the
+ * consumer will have to use dumb mode to use this store.
+ */
+ function isDumb()
+ {
+ return false;
+ }
+
+ /**
+ * Removes all entries from the store; implementation is optional.
+ */
+ function reset()
+ {
+ }
+
+}
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/KVForm.php b/lib/Auth/OpenID/KVForm.php
new file mode 100644
index 0000000..6075c44
--- /dev/null
+++ b/lib/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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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/lib/Auth/OpenID/MySQLStore.php b/lib/Auth/OpenID/MySQLStore.php
new file mode 100644
index 0000000..f89afc6
--- /dev/null
+++ b/lib/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 (nonce CHAR(8) UNIQUE PRIMARY KEY, ".
+ "expires INTEGER) TYPE=InnoDB";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url BLOB, handle VARCHAR(255), ".
+ "secret BLOB, issued INTEGER, lifetime INTEGER, ".
+ "assoc_type VARCHAR(64), PRIMARY KEY (server_url(255), handle)) ".
+ "TYPE=InnoDB";
+
+ $this->sql['settings_table'] =
+ "CREATE TABLE %s (setting VARCHAR(128) UNIQUE PRIMARY KEY, ".
+ "value BLOB) TYPE=InnoDB";
+
+ $this->sql['create_auth'] =
+ "INSERT INTO %s VALUES ('auth_key', !)";
+
+ $this->sql['get_auth'] =
+ "SELECT value FROM %s WHERE setting = 'auth_key'";
+
+ $this->sql['set_assoc'] =
+ "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'] =
+ "REPLACE INTO %s (nonce, expires) VALUES (?, ?)";
+
+ $this->sql['get_nonce'] =
+ "SELECT * FROM %s WHERE nonce = ?";
+
+ $this->sql['remove_nonce'] =
+ "DELETE FROM %s WHERE nonce = ?";
+ }
+
+ /**
+ * @access private
+ */
+ function blobEncode($blob)
+ {
+ return "0x" . bin2hex($blob);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/Parse.php b/lib/Auth/OpenID/Parse.php
new file mode 100644
index 0000000..891ca5e
--- /dev/null
+++ b/lib/Auth/OpenID/Parse.php
@@ -0,0 +1,308 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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\/<>]+)';
+
+ 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)
+ {
+ if ($close_tags) {
+ $options = implode("|", array_merge(array($tag_name), $close_tags));
+ $closer = sprintf("(?:%s)", $options);
+ } else {
+ $closer = $tag_name;
+ }
+
+ $expr = sprintf($this->_tag_expr, $tag_name, $closer);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function htmlFind()
+ {
+ return $this->tagMatcher('html');
+ }
+
+ function headFind()
+ {
+ return $this->tagMatcher('head', array('body'));
+ }
+
+ 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);
+
+ // Try to find the <HTML> tag.
+ $html_re = $this->htmlFind();
+ $html_matches = array();
+ if (!preg_match($html_re, $stripped, $html_matches)) {
+ return array();
+ }
+
+ // Try to find the <HEAD> tag.
+ $head_re = $this->headFind();
+ $head_matches = array();
+ if (!preg_match($head_re, $html_matches[0], $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)
+{
+ $p = new Auth_OpenID_Parse();
+
+ $link_attrs = $p->parseLinkAttrs($html_text);
+
+ $server_url = $p->findFirstHref($link_attrs,
+ 'openid.server');
+
+ if ($server_url === null) {
+ return false;
+ } else {
+ $delegate_url = $p->findFirstHref($link_attrs,
+ 'openid.delegate');
+ return array($delegate_url, $server_url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/PostgreSQLStore.php b/lib/Auth/OpenID/PostgreSQLStore.php
new file mode 100644
index 0000000..a415280
--- /dev/null
+++ b/lib/Auth/OpenID/PostgreSQLStore.php
@@ -0,0 +1,136 @@
+<?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 (nonce CHAR(8) UNIQUE PRIMARY KEY, ".
+ "expires INTEGER)";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
+ "secret BYTEA, issued INTEGER, lifetime INTEGER, ".
+ "assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle), ".
+ "CONSTRAINT secret_length_constraint CHECK ".
+ "(LENGTH(secret) <= 128))";
+
+ $this->sql['settings_table'] =
+ "CREATE TABLE %s (setting VARCHAR(128) UNIQUE PRIMARY KEY, ".
+ "value BYTEA, ".
+ "CONSTRAINT value_length_constraint CHECK (LENGTH(value) <= 20))";
+
+ $this->sql['create_auth'] =
+ "INSERT INTO %s VALUES ('auth_key', '!')";
+
+ $this->sql['get_auth'] =
+ "SELECT value FROM %s WHERE setting = 'auth_key'";
+
+ $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'] =
+ array(
+ 'insert_nonce' => "INSERT INTO %s (nonce, expires) VALUES ".
+ "(?, ?)",
+ 'update_nonce' => "UPDATE %s SET expires = ? WHERE nonce = ?"
+ );
+
+ $this->sql['get_nonce'] =
+ "SELECT * FROM %s WHERE nonce = ?";
+
+ $this->sql['remove_nonce'] =
+ "DELETE FROM %s WHERE nonce = ?";
+ }
+
+ /**
+ * @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 _add_nonce($nonce, $expires)
+ {
+ if ($this->_get_nonce($nonce)) {
+ return $this->resultToBool($this->connection->query(
+ $this->sql['add_nonce']['update_nonce'],
+ array($expires, $nonce)));
+ } else {
+ return $this->resultToBool($this->connection->query(
+ $this->sql['add_nonce']['insert_nonce'],
+ array($nonce, $expires)));
+ }
+ }
+
+ /**
+ * @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/lib/Auth/OpenID/SQLStore.php b/lib/Auth/OpenID/SQLStore.php
new file mode 100644
index 0000000..c7bd540
--- /dev/null
+++ b/lib/Auth/OpenID/SQLStore.php
@@ -0,0 +1,658 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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';
+
+/**
+ * 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
+ * settings_table_name, 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 string $settings_table This is an optional parameter to
+ * specify the name of the table used for this store's settings.
+ * The default value is 'oid_settings'.
+ *
+ * @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, $settings_table = null,
+ $associations_table = null,
+ $nonces_table = null)
+ {
+ global $__Auth_OpenID_PEAR_AVAILABLE;
+
+ $this->settings_table_name = "oid_settings";
+ $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 ($settings_table) {
+ $this->settings_table_name = $settings_table;
+ }
+
+ 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("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));
+
+ $this->connection->query(sprintf("DELETE FROM %s",
+ $this->settings_table_name));
+ }
+
+ /**
+ * @access private
+ */
+ function _verifySQL()
+ {
+ $missing = array();
+ $empty = array();
+
+ $required_sql_keys = array(
+ 'nonce_table',
+ 'assoc_table',
+ 'settings_table',
+ 'get_auth',
+ 'create_auth',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc',
+ 'add_nonce',
+ 'get_nonce',
+ 'remove_nonce'
+ );
+
+ 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',
+ 'get_nonce',
+ 'remove_nonce')
+ ),
+ array(
+ 'value' => $this->associations_table_name,
+ 'keys' => array('assoc_table',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc')
+ ),
+ array(
+ 'value' => $this->settings_table_name,
+ 'keys' => array('settings_table',
+ 'get_auth',
+ 'create_auth')
+ )
+ );
+
+ 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();
+ $s = $this->create_settings_table();
+ $this->connection->autoCommit(false);
+
+ if ($n && $a && $s) {
+ 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;
+ }
+
+ function create_settings_table()
+ {
+ if (!$this->tableExists($this->settings_table_name)) {
+ $r = $this->connection->query($this->sql['settings_table']);
+ return $this->resultToBool($r);
+ }
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _get_auth()
+ {
+ return $this->connection->getOne($this->sql['get_auth']);
+ }
+
+ /**
+ * @access private
+ */
+ function _create_auth($str)
+ {
+ return $this->connection->query($this->sql['create_auth'],
+ array($str));
+ }
+
+ function getAuthKey()
+ {
+ $value = $this->_get_auth();
+ if (!$value) {
+ $auth_key =
+ Auth_OpenID_CryptUtil::randomString($this->AUTH_KEY_LEN);
+
+ $auth_key_s = $this->blobEncode($auth_key);
+ $this->_create_auth($auth_key_s);
+ } else {
+ $auth_key_s = $value;
+ $auth_key = $this->blobDecode($auth_key_s);
+ }
+
+ $this->connection->commit();
+
+ if (strlen($auth_key) != $this->AUTH_KEY_LEN) {
+ $fmt = "Expected %d-byte string for auth key. Got key of length %d";
+ trigger_error(sprintf($fmt, $this->AUTH_KEY_LEN, strlen($auth_key)),
+ E_USER_WARNING);
+ return null;
+ }
+
+ return $auth_key;
+ }
+
+ /**
+ * @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($nonce, $expires)
+ {
+ $sql = $this->sql['add_nonce'];
+ $result = $this->connection->query($sql, array($nonce, $expires));
+ return $this->resultToBool($result);
+ }
+
+ /**
+ * @access private
+ */
+ function storeNonce($nonce)
+ {
+ if ($this->_add_nonce($nonce, time())) {
+ $this->connection->commit();
+ } else {
+ $this->connection->rollback();
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _get_nonce($nonce)
+ {
+ $result = $this->connection->getRow($this->sql['get_nonce'],
+ array($nonce));
+
+ if ($this->isError($result)) {
+ return null;
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _remove_nonce($nonce)
+ {
+ $this->connection->query($this->sql['remove_nonce'],
+ array($nonce));
+ }
+
+ function useNonce($nonce)
+ {
+ $row = $this->_get_nonce($nonce);
+
+ if ($row !== null) {
+ $nonce = $row['nonce'];
+ $timestamp = $row['expires'];
+ $nonce_age = time() - $timestamp;
+
+ if ($nonce_age > $this->max_nonce_age) {
+ $present = 0;
+ } else {
+ $present = 1;
+ }
+
+ $this->_remove_nonce($nonce);
+ } else {
+ $present = 0;
+ }
+
+ $this->connection->commit();
+
+ return $present;
+ }
+
+ /**
+ * "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 < strlen($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;
+ }
+}
+
+?>
diff --git a/lib/Auth/OpenID/SQLiteStore.php b/lib/Auth/OpenID/SQLiteStore.php
new file mode 100644
index 0000000..6351df3
--- /dev/null
+++ b/lib/Auth/OpenID/SQLiteStore.php
@@ -0,0 +1,66 @@
+<?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 (nonce CHAR(8) UNIQUE PRIMARY KEY, ".
+ "expires INTEGER)";
+
+ $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['settings_table'] =
+ "CREATE TABLE %s (setting VARCHAR(128) UNIQUE PRIMARY KEY, ".
+ "value BLOB(20))";
+
+ $this->sql['create_auth'] =
+ "INSERT INTO %s VALUES ('auth_key', ?)";
+
+ $this->sql['get_auth'] =
+ "SELECT value FROM %s WHERE setting = 'auth_key'";
+
+ $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 OR REPLACE INTO %s (nonce, expires) VALUES (?, ?)";
+
+ $this->sql['get_nonce'] =
+ "SELECT * FROM %s WHERE nonce = ?";
+
+ $this->sql['remove_nonce'] =
+ "DELETE FROM %s WHERE nonce = ?";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/Server.php b/lib/Auth/OpenID/Server.php
new file mode 100644
index 0000000..b82bb4a
--- /dev/null
+++ b/lib/Auth/OpenID/Server.php
@@ -0,0 +1,1307 @@
+<?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
+ * 'decodeRequest' and 'encodeResponse' methods of the
+ * Auth_OpenID_Server object. 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 Auth_OpenID_CheckIDRequest object,
+ * and if and when you've come to a decision, form a response by
+ * calling Auth_OpenID_CheckIDRequest::answer.
+ *
+ * Other types of requests relate to establishing associations between
+ * client and server and verifing the authenticity of previous
+ * communications. Auth_OpenID_Server contains all the logic and data
+ * necessary to respond to such requests; just pass it to
+ * 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 Auth_OpenID_Request class here. But you'll likely want data
+ * sent back by your extension to be signed. Auth_OpenID_Response
+ * provides methods with which you can add data to it which can be
+ * signed with the other data in the OpenID signature.
+ *
+ * For example:
+ *
+ * // 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')
+ *
+ * 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 Auth_OpenID_FileStore. For stores based on
+ * MySQL, SQLite, or PostgreSQL, see the 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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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";
+
+define('AUTH_OPENID_HTTP_OK', 200);
+define('AUTH_OPENID_HTTP_REDIRECT', 302);
+define('AUTH_OPENID_HTTP_ERROR', 400);
+
+global $_Auth_OpenID_Request_Modes,
+ $_Auth_OpenID_OpenID_Prefix,
+ $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Encode_Url;
+
+/**
+ * @access private
+ */
+$_Auth_OpenID_Request_Modes = array('checkid_setup',
+ 'checkid_immediate');
+
+/**
+ * @access private
+ */
+$_Auth_OpenID_OpenID_Prefix = "openid.";
+
+/**
+ * @access private
+ */
+$_Auth_OpenID_Encode_Kvform = array('kfvorm');
+
+/**
+ * @access private
+ */
+$_Auth_OpenID_Encode_Url = array('URL/redirect');
+
+/**
+ * @access private
+ */
+function _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($query = null, $message = null)
+ {
+ $this->message = $message;
+ $this->query = $query;
+ }
+
+ /**
+ * Returns the return_to URL for the request which caused this
+ * error.
+ */
+ function hasReturnTo()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ if ($this->query) {
+ return array_key_exists($_Auth_OpenID_OpenID_Prefix .
+ 'return_to', $this->query);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 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()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ $return_to = Auth_OpenID::arrayGet($this->query,
+ $_Auth_OpenID_OpenID_Prefix .
+ 'return_to');
+ if (!$return_to) {
+ return new Auth_OpenID_ServerError(null, "no return_to URL");
+ }
+
+ return Auth_OpenID::appendArgs($return_to,
+ array('openid.mode' => 'error',
+ 'openid.error' => $this->toString()));
+ }
+
+ /**
+ * 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()));
+ }
+
+ /**
+ * 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_Encode_Url,
+ $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Request_Modes;
+
+ if ($this->hasReturnTo()) {
+ return $_Auth_OpenID_Encode_Url;
+ }
+
+ $mode = Auth_OpenID::arrayGet($this->query, 'openid.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->message) {
+ return $this->message;
+ } else {
+ return get_class($this) . " error";
+ }
+ }
+}
+
+/**
+ * An error indicating that the return_to URL is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedReturnURL($query, $return_to)
+ {
+ $this->return_to = $return_to;
+ parent::Auth_OpenID_ServerError($query, "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 toString()
+ {
+ return "Malformed trust root";
+ }
+}
+
+/**
+ * The base class for all server request classes.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_Request {
+ var $mode = null;
+}
+
+/**
+ * A request to verify the validity of a previous response.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
+ var $mode = "check_authentication";
+ var $invalidate_handle = null;
+
+ function Auth_OpenID_CheckAuthRequest($assoc_handle, $sig, $signed,
+ $invalidate_handle = null)
+ {
+ $this->assoc_handle = $assoc_handle;
+ $this->sig = $sig;
+ $this->signed = $signed;
+ if ($invalidate_handle !== null) {
+ $this->invalidate_handle = $invalidate_handle;
+ }
+ }
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $required_keys = array('assoc_handle', 'sig', 'signed');
+
+ foreach ($required_keys as $k) {
+ if (!array_key_exists($_Auth_OpenID_OpenID_Prefix . $k,
+ $query)) {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("%s request missing required parameter %s from \
+ query", "check_authentication", $k));
+ }
+ }
+
+ $assoc_handle = $query[$_Auth_OpenID_OpenID_Prefix . 'assoc_handle'];
+ $sig = $query[$_Auth_OpenID_OpenID_Prefix . 'sig'];
+ $signed_list = $query[$_Auth_OpenID_OpenID_Prefix . 'signed'];
+
+ $signed_list = explode(",", $signed_list);
+ $signed_pairs = array();
+
+ foreach ($signed_list as $field) {
+ if ($field == 'mode') {
+ // XXX KLUDGE HAX WEB PROTOCoL BR0KENNN
+ //
+ // openid.mode is currently check_authentication
+ // because that's the mode of this request. But the
+ // signature was made on something with a different
+ // openid.mode.
+ $value = "id_res";
+ } else {
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
+ } else {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("Couldn't find signed field %r in query %s",
+ $field, var_export($query, true)));
+ }
+ }
+ $signed_pairs[] = array($field, $value);
+ }
+
+ $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $sig,
+ $signed_pairs);
+ $result->invalidate_handle = Auth_OpenID::arrayGet($query,
+ $_Auth_OpenID_OpenID_Prefix . 'invalidate_handle');
+ return $result;
+ }
+
+ function answer(&$signatory)
+ {
+ $is_valid = $signatory->verify($this->assoc_handle, $this->sig,
+ $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['is_valid'] = $is_valid ? "true" : "false";
+
+ if ($this->invalidate_handle) {
+ $assoc = $signatory->getAssociation($this->invalidate_handle,
+ false);
+ if (!$assoc) {
+ $response->fields['invalidate_handle'] =
+ $this->invalidate_handle;
+ }
+ }
+ return $response;
+ }
+}
+
+class Auth_OpenID_PlainTextServerSession {
+ /**
+ * An object that knows how to handle association requests with no
+ * session type.
+ */
+ var $session_type = 'plaintext';
+
+ function fromQuery($unused_request)
+ {
+ return new Auth_OpenID_PlainTextServerSession();
+ }
+
+ function answer($secret)
+ {
+ return array('mac_key' => base64_encode($secret));
+ }
+}
+
+class Auth_OpenID_DiffieHellmanServerSession {
+ /**
+ * An object that knows how to handle association requests with
+ * the Diffie-Hellman session type.
+ */
+
+ var $session_type = 'DH-SHA1';
+
+ function Auth_OpenID_DiffieHellmanServerSession($dh, $consumer_pubkey)
+ {
+ $this->dh = $dh;
+ $this->consumer_pubkey = $consumer_pubkey;
+ }
+
+ function fromQuery($query)
+ {
+ $dh_modulus = Auth_OpenID::arrayGet($query, 'openid.dh_modulus');
+ $dh_gen = Auth_OpenID::arrayGet($query, 'openid.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(
+ '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(
+ $query, "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 = Auth_OpenID::arrayGet($query,
+ 'openid.dh_consumer_public');
+ if ($consumer_pubkey === null) {
+ return new Auth_OpenID_ServerError(
+ '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($query,
+ "dh_consumer_public is not base64");
+ }
+
+ return new Auth_OpenID_DiffieHellmanServerSession($dh,
+ $consumer_pubkey);
+ }
+
+ function answer($secret)
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret);
+ return array(
+ 'dh_server_public' =>
+ $lib->longToBase64($this->dh->public),
+ 'enc_mac_key' => base64_encode($mac_key));
+ }
+}
+
+/**
+ * A request to associate with the server.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
+ var $mode = "associate";
+ var $assoc_type = 'HMAC-SHA1';
+
+ function Auth_OpenID_AssociateRequest(&$session)
+ {
+ $this->session =& $session;
+ }
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $session_classes = array(
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanServerSession',
+ null => 'Auth_OpenID_PlainTextServerSession');
+
+ $session_type = null;
+
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . 'session_type',
+ $query)) {
+ $session_type = $query[$_Auth_OpenID_OpenID_Prefix .
+ 'session_type'];
+ }
+
+ if (!array_key_exists($session_type, $session_classes)) {
+ return new Auth_OpenID_ServerError($query,
+ "Unknown session type $session_type");
+ }
+
+ $session_cls = $session_classes[$session_type];
+ $session = call_user_func_array(array($session_cls, 'fromQuery'),
+ array($query));
+
+ if (($session === null) || (_isError($session))) {
+ return new Auth_OpenID_ServerError($query,
+ "Error parsing $session_type session");
+ }
+
+ return new Auth_OpenID_AssociateRequest($session);
+ }
+
+ function answer($assoc)
+ {
+ $ml =& Auth_OpenID_getMathLib();
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ $response->fields = array('expires_in' => $assoc->getExpiresIn(),
+ 'assoc_type' => 'HMAC-SHA1',
+ 'assoc_handle' => $assoc->handle);
+
+ $r = $this->session->answer($assoc->secret);
+ foreach ($r as $k => $v) {
+ $response->fields[$k] = $v;
+ }
+
+ if ($this->session->session_type != 'plaintext') {
+ $response->fields['session_type'] = $this->session->session_type;
+ }
+
+ return $response;
+ }
+}
+
+/**
+ * A request to confirm the identity of a user.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+ var $mode = "checkid_setup"; // or "checkid_immediate"
+ var $immediate = false;
+ var $trust_root = null;
+
+ function make($query, $identity, $return_to, $trust_root = null,
+ $immediate = false, $assoc_handle = null)
+ {
+ if (!Auth_OpenID_TrustRoot::_parse($return_to)) {
+ return new Auth_OpenID_MalformedReturnURL($query, $return_to);
+ }
+
+ $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root, $immediate,
+ $assoc_handle);
+
+ if (!$r->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL($return_to,
+ $trust_root);
+ } else {
+ return $r;
+ }
+ }
+
+ function Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root = null, $immediate = false,
+ $assoc_handle = null)
+ {
+ $this->identity = $identity;
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ $this->assoc_handle = $assoc_handle;
+
+ if ($immediate) {
+ $this->immediate = true;
+ $this->mode = "checkid_immediate";
+ } else {
+ $this->immediate = false;
+ $this->mode = "checkid_setup";
+ }
+ }
+
+ function fromQuery($query)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $mode = $query[$_Auth_OpenID_OpenID_Prefix . 'mode'];
+ $immediate = null;
+
+ if ($mode == "checkid_immediate") {
+ $immediate = true;
+ $mode = "checkid_immediate";
+ } else {
+ $immediate = false;
+ $mode = "checkid_setup";
+ }
+
+ $required = array('identity',
+ 'return_to');
+
+ $optional = array('trust_root',
+ 'assoc_handle');
+
+ $values = array();
+
+ foreach ($required as $field) {
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix . $field];
+ } else {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("Missing required field %s from request",
+ $field));
+ }
+ $values[$field] = $value;
+ }
+
+ foreach ($optional as $field) {
+ $value = null;
+ if (array_key_exists($_Auth_OpenID_OpenID_Prefix . $field,
+ $query)) {
+ $value = $query[$_Auth_OpenID_OpenID_Prefix. $field];
+ }
+ if ($value) {
+ $values[$field] = $value;
+ }
+ }
+
+ if (!Auth_OpenID_TrustRoot::_parse($values['return_to'])) {
+ return new Auth_OpenID_MalformedReturnURL($query,
+ $values['return_to']);
+ }
+
+ $obj = Auth_OpenID_CheckIDRequest::make($query,
+ $values['identity'],
+ $values['return_to'],
+ Auth_OpenID::arrayGet($values,
+ 'trust_root', null),
+ $immediate);
+
+ if (is_a($obj, 'Auth_OpenID_ServerError')) {
+ return $obj;
+ }
+
+ if (Auth_OpenID::arrayGet($values, 'assoc_handle')) {
+ $obj->assoc_handle = $values['assoc_handle'];
+ }
+
+ return $obj;
+ }
+
+ function trustRootValid()
+ {
+ if (!$this->trust_root) {
+ return true;
+ }
+
+ $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
+ if ($tr === false) {
+ return new Auth_OpenID_MalformedTrustRoot(null, $this->trust_root);
+ }
+
+ return Auth_OpenID_TrustRoot::match($this->trust_root,
+ $this->return_to);
+ }
+
+ function answer($allow, $server_url = null)
+ {
+ if ($allow || $this->immediate) {
+ $mode = 'id_res';
+ } else {
+ $mode = 'cancel';
+ }
+
+ $response = new Auth_OpenID_CheckIDResponse($this, $mode);
+
+ if ($allow) {
+ $response->fields['identity'] = $this->identity;
+ $response->fields['return_to'] = $this->return_to;
+ if (!$this->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL($this->return_to,
+ $this->trust_root);
+ }
+ } else {
+ $response->signed = array();
+ if ($this->immediate) {
+ if (!$server_url) {
+ return new Auth_OpenID_ServerError(null,
+ 'setup_url is required for $allow=false \
+ in immediate mode.');
+ }
+
+ $setup_request =& new Auth_OpenID_CheckIDRequest(
+ $this->identity,
+ $this->return_to,
+ $this->trust_root,
+ false,
+ $this->assoc_handle);
+
+ $setup_url = $setup_request->encodeToURL($server_url);
+
+ $response->fields['user_setup_url'] = $setup_url;
+ }
+ }
+
+ return $response;
+ }
+
+ function encodeToURL($server_url)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ // 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,
+ 'return_to' => $this->return_to);
+
+ if ($this->trust_root) {
+ $q['trust_root'] = $this->trust_root;
+ }
+
+ if ($this->assoc_handle) {
+ $q['assoc_handle'] = $this->assoc_handle;
+ }
+
+ $_q = array();
+
+ foreach ($q as $k => $v) {
+ $_q[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
+ }
+
+ return Auth_OpenID::appendArgs($server_url, $_q);
+ }
+
+ function getCancelURL()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ if ($this->immediate) {
+ return new Auth_OpenID_ServerError(null,
+ "Cancel is not an appropriate \
+ response to immediate mode \
+ requests.");
+ }
+
+ return Auth_OpenID::appendArgs($this->return_to,
+ array($_Auth_OpenID_OpenID_Prefix . 'mode' =>
+ 'cancel'));
+ }
+}
+
+/**
+ * This class encapsulates the response to an OpenID server request.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_ServerResponse($request)
+ {
+ $this->request = $request;
+ $this->fields = array();
+ }
+
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Request_Modes,
+ $_Auth_OpenID_Encode_Url;
+
+ if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
+ return $_Auth_OpenID_Encode_Url;
+ } else {
+ return $_Auth_OpenID_Encode_Kvform;
+ }
+ }
+
+ function encodeToURL()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $fields = array();
+
+ foreach ($this->fields as $k => $v) {
+ $fields[$_Auth_OpenID_OpenID_Prefix . $k] = $v;
+ }
+
+ return Auth_OpenID::appendArgs($this->request->return_to, $fields);
+ }
+
+ function encodeToKVForm()
+ {
+ return Auth_OpenID_KVForm::fromArray($this->fields);
+ }
+}
+
+/**
+ * A response to a checkid request.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_CheckIDResponse extends Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_CheckIDResponse(&$request, $mode = 'id_res')
+ {
+ parent::Auth_OpenID_ServerResponse($request);
+ $this->fields['mode'] = $mode;
+ $this->signed = array();
+
+ if ($mode == 'id_res') {
+ array_push($this->signed, 'mode', 'identity', 'return_to');
+ }
+ }
+
+ function addField($namespace, $key, $value, $signed = true)
+ {
+ if ($namespace) {
+ $key = sprintf('%s.%s', $namespace, $key);
+ }
+ $this->fields[$key] = $value;
+ if ($signed && !in_array($key, $this->signed)) {
+ $this->signed[] = $key;
+ }
+ }
+
+ function addFields($namespace, $fields, $signed = true)
+ {
+ foreach ($fields as $k => $v) {
+ $this->addField($namespace, $k, $v, $signed);
+ }
+ }
+
+ function update($namespace, $other)
+ {
+ $namespaced_fields = array();
+
+ foreach ($other->fields as $k => $v) {
+ $name = sprintf('%s.%s', $namespace, $k);
+
+ $namespaced_fields[$name] = $v;
+ }
+
+ $this->fields = array_merge($this->fields, $namespaced_fields);
+ $this->signed = array_merge($this->signed, $other->signed);
+ }
+}
+
+/**
+ * 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, $sig, $signed_pairs)
+ {
+ $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;
+ }
+
+ $expected_sig = base64_encode($assoc->sign($signed_pairs));
+
+ return $sig == $expected_sig;
+ }
+
+ /**
+ * 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);
+ if (!$assoc) {
+ // fall back to dumb mode
+ $signed_response->fields['invalidate_handle'] = $assoc_handle;
+ $assoc = $this->createAssociation(true);
+ }
+ } else {
+ // dumb mode.
+ $assoc = $this->createAssociation(true);
+ }
+
+ $signed_response->fields['assoc_handle'] = $assoc->handle;
+ $assoc->addSignature($signed_response->signed,
+ $signed_response->fields, '');
+ return $signed_response;
+ }
+
+ /**
+ * Make a new association.
+ */
+ function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
+ {
+ $secret = Auth_OpenID_CryptUtil::getBytes(20);
+ $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)
+ {
+ 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)) {
+ $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 Auth_OpenID_Response to an Auth_OpenID_WebResponse.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Encoder {
+
+ var $responseFactory = 'Auth_OpenID_WebResponse';
+
+ /**
+ * Encode an Auth_OpenID_Response and return an
+ * Auth_OpenID_WebResponse.
+ */
+ function encode(&$response)
+ {
+ global $_Auth_OpenID_Encode_Kvform,
+ $_Auth_OpenID_Encode_Url;
+
+ $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 {
+ return new Auth_OpenID_EncodingError($response);
+ }
+ return $wr;
+ }
+}
+
+/**
+ * Returns true if the given response needs a signature.
+ *
+ * @access private
+ */
+function needsSigning($response)
+{
+ return (in_array($response->request->mode, array('checkid_setup',
+ 'checkid_immediate')) &&
+ $response->signed);
+}
+
+/**
+ * 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 Auth_OpenID_Response and return an
+ * 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') &&
+ needsSigning($response)) {
+
+ if (!$this->signatory) {
+ return new Auth_OpenID_ServerError(null,
+ "Must have a store to sign request");
+ }
+ if (array_key_exists('sig', $response->fields)) {
+ return new Auth_OpenID_AlreadySigned($response);
+ }
+ $response = $this->signatory->sign($response);
+ }
+ return parent::encode($response);
+ }
+}
+
+/**
+ * Decode an incoming Auth_OpenID_WebResponse into an
+ * Auth_OpenID_Request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Decoder {
+
+ function Auth_OpenID_Decoder()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+ $this->prefix = $_Auth_OpenID_OpenID_Prefix;
+
+ $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;
+ }
+
+ $myquery = array();
+
+ foreach ($query as $k => $v) {
+ if (strpos($k, $this->prefix) === 0) {
+ $myquery[$k] = $v;
+ }
+ }
+
+ if (!$myquery) {
+ return null;
+ }
+
+ $mode = Auth_OpenID::arrayGet($myquery, $this->prefix . 'mode');
+ if (!$mode) {
+ return new Auth_OpenID_ServerError($query,
+ sprintf("No %s mode found in query", $this->prefix));
+ }
+
+ $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
+ $this->defaultDecoder($query));
+
+ if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
+ return call_user_func_array(array($handlerCls, 'fromQuery'),
+ array($query));
+ } else {
+ return $handlerCls;
+ }
+ }
+
+ function defaultDecoder($query)
+ {
+ $mode = $query[$this->prefix . 'mode'];
+ return new Auth_OpenID_ServerError($query,
+ sprintf("No decoder for 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($return_to, $trust_root)
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $query = array(
+ $_Auth_OpenID_OpenID_Prefix . 'return_to' => $return_to,
+ $_Auth_OpenID_OpenID_Prefix . 'trust_root' => $trust_root);
+
+ parent::Auth_OpenID_ServerError($query);
+ }
+
+ function toString()
+ {
+ global $_Auth_OpenID_OpenID_Prefix;
+
+ $return_to = $this->query[$_Auth_OpenID_OpenID_Prefix . 'return_to'];
+ $trust_root = $this->query[$_Auth_OpenID_OpenID_Prefix . 'trust_root'];
+
+ return sprintf("return_to %s not under trust_root %s",
+ $return_to, $trust_root);
+ }
+}
+
+/**
+ * An object that implements the OpenID protocol for a single URL.
+ *
+ * Use this object by calling getOpenIDResponse when you get any
+ * request for the server URL.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Server {
+ function Auth_OpenID_Server(&$store)
+ {
+ $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();
+ }
+
+ /**
+ * Handle a request. Given an Auth_OpenID_Request object, call
+ * the appropriate Auth_OpenID_Server method to process the
+ * request and generate a response.
+ *
+ * @param Auth_OpenID_Request $request An Auth_OpenID_Request
+ * returned by Auth_OpenID_Server::decodeRequest.
+ *
+ * @return Auth_OpenID_Response $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.
+ *
+ * @access private
+ */
+ function openid_check_authentication(&$request)
+ {
+ return $request->answer($this->signatory);
+ }
+
+ /**
+ * The callback for 'associate' messages.
+ *
+ * @access private
+ */
+ function openid_associate(&$request)
+ {
+ $assoc = $this->signatory->createAssociation(false);
+ return $request->answer($assoc);
+ }
+
+ /**
+ * 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
+ * Auth_OpenID_Request object.
+ */
+ function decodeRequest(&$query)
+ {
+ return $this->decoder->decode($query);
+ }
+}
+
+?>
diff --git a/lib/Auth/OpenID/ServerRequest.php b/lib/Auth/OpenID/ServerRequest.php
new file mode 100644
index 0000000..0072894
--- /dev/null
+++ b/lib/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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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/lib/Auth/OpenID/TrustRoot.php b/lib/Auth/OpenID/TrustRoot.php
new file mode 100644
index 0000000..88eff29
--- /dev/null
+++ b/lib/Auth/OpenID/TrustRoot.php
@@ -0,0 +1,243 @@
+<?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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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',
+ '/\.(com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|ac|' .
+ 'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|' .
+ 'bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|' .
+ 'cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|' .
+ 'fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|' .
+ 'gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|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|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|' .
+ 'nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|' .
+ 'ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|' .
+ 'so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|' .
+ 'ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$/');
+
+/**
+ * A wrapper for trust-root related functions
+ */
+class Auth_OpenID_TrustRoot {
+ /**
+ * 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)
+ {
+ $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;
+ }
+
+ // Return false if the original trust root value has more than
+ // one port specification.
+ if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+ 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, -1) != '/') {
+ $path .= '/';
+ }
+ } else {
+ $path = '/';
+ }
+ $parts['path'] = $path;
+ if (!isset($parts['port'])) {
+ $parts['port'] = false;
+ }
+ 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;
+ }
+
+ // 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];
+
+ // Require at least two levels of specificity for non-country
+ // tlds and three levels for country tlds.
+ $elements = explode('.', $parts['host']);
+ $n = count($elements);
+ if ($parts['wildcard']) {
+ $n -= 1;
+ }
+ if (strlen($tld) == 2) {
+ $n -= 1;
+ }
+ if ($n <= 1) {
+ return false;
+ }
+ 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 (substr($path, 0, strlen($base_path)) != $base_path) {
+ 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']);
+ }
+}
+?> \ No newline at end of file
diff --git a/lib/Auth/OpenID/URINorm.php b/lib/Auth/OpenID/URINorm.php
new file mode 100644
index 0000000..d1c653e
--- /dev/null
+++ b/lib/Auth/OpenID/URINorm.php
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * URI normalization routines.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once 'Services/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})/';
+}
+
+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(Services_Yadis_getUCSChars(),
+ Services_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 (Services_Yadis_startswith($path, '../')) {
+ $path = substr($path, 3);
+ } else if (Services_Yadis_startswith($path, './')) {
+ $path = substr($path, 2);
+ } else if (Services_Yadis_startswith($path, '/./')) {
+ $path = substr($path, 2);
+ } else if ($path == '/.') {
+ $path = '/';
+ } else if (Services_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[] = '';
+ }
+ }
+
+ $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/lib/Services/Yadis/HTTPFetcher.php b/lib/Services/Yadis/HTTPFetcher.php
new file mode 100644
index 0000000..97940a4
--- /dev/null
+++ b/lib/Services/Yadis/HTTPFetcher.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Services_Yadis_HTTPResponse {
+ function Services_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 Yadis
+ */
+class Services_Yadis_HTTPFetcher {
+
+ var $timeout = 20; // timeout in seconds.
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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($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)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/Manager.php b/lib/Services/Yadis/Manager.php
new file mode 100644
index 0000000..524eea2
--- /dev/null
+++ b/lib/Services/Yadis/Manager.php
@@ -0,0 +1,496 @@
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package Yadis
+ */
+
+/**
+ * The base session class used by the Services_Yadis_Manager. This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package Yadis
+ */
+class Services_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.
+ */
+class Services_Yadis_SessionLoader {
+ /**
+ * Override this.
+ */
+ 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.
+ */
+ 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).
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ function prepareForSave($obj)
+ {
+ return array();
+ }
+}
+
+class Auth_OpenID_ServiceEndpointLoader extends Services_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']);
+ }
+}
+
+class Services_Yadis_ManagerLoader extends Services_Yadis_SessionLoader {
+ function requiredKeys()
+ {
+ return array('starting_url',
+ 'yadis_url',
+ 'services',
+ 'session_key',
+ '_current',
+ 'stale');
+ }
+
+ function newObject($data)
+ {
+ return new Services_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 Yadis
+ */
+class Services_Yadis_Manager {
+
+ /**
+ * Intialize a new yadis service manager.
+ *
+ * @access private
+ */
+ function Services_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 .finish() to clean up the
+ * session state.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Discovery {
+
+ /**
+ * @access private
+ */
+ var $DEFAULT_SUFFIX = 'auth';
+
+ /**
+ * @access private
+ */
+ var $PREFIX = '_yadis_services_';
+
+ /**
+ * Initialize a discovery object.
+ *
+ * @param Services_Yadis_PHPSession $session An object which
+ * implements the Services_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 Services_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();
+ $http_response = array();
+
+ $services = call_user_func($discover_cb, $this->url,
+ $fetcher);
+
+ $manager = $this->createManager($services, $this->url);
+ }
+
+ if ($manager) {
+ $loader = new Services_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.
+ */
+ function cleanup()
+ {
+ $manager = $this->getManager();
+ if ($manager) {
+ $service = $manager->current();
+ $this->destroyManager();
+ } 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
+ */
+ function &getManager()
+ {
+ // 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 Services_Yadis_ManagerLoader();
+ $manager = $loader->fromSession(unserialize($manager_str));
+ }
+
+ if ($manager && $manager->forURL($this->url)) {
+ 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 Services_Yadis_ManagerLoader();
+ $manager = new Services_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
+ */
+ function destroyManager()
+ {
+ if ($this->getManager() !== null) {
+ $key = $this->getSessionKey();
+ $this->session->del($key);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/Misc.php b/lib/Services/Yadis/Misc.php
new file mode 100644
index 0000000..794b62e
--- /dev/null
+++ b/lib/Services/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 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+function Services_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 Services_Yadis_getIPrivateChars()
+{
+ return array(
+ array(0xE000, 0xF8FF),
+ array(0xF0000, 0xFFFFD),
+ array(0x100000, 0x10FFFD)
+ );
+}
+
+function Services_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 Services_Yadis_startswith($s, $stuff)
+{
+ return strpos($s, $stuff) === 0;
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/ParanoidHTTPFetcher.php b/lib/Services/Yadis/ParanoidHTTPFetcher.php
new file mode 100644
index 0000000..e14799f
--- /dev/null
+++ b/lib/Services/Yadis/ParanoidHTTPFetcher.php
@@ -0,0 +1,177 @@
+<?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 Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Interface import
+ */
+require_once "Services/Yadis/HTTPFetcher.php";
+
+/**
+ * A paranoid {@link Services_Yadis_HTTPFetcher} class which uses CURL
+ * for fetching.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_ParanoidHTTPFetcher extends Services_Yadis_HTTPFetcher {
+ function Services_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)
+ {
+ $this->data .= $data;
+ return strlen($data);
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ $redir = true;
+
+ while ($redir && ($off > 0)) {
+ $this->reset();
+
+ $c = curl_init();
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ if (!$this->allowedURL($url)) {
+ trigger_error(sprintf("Fetching URL not allowed: %s", $url),
+ E_USER_WARNING);
+ 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);
+ }
+
+ 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) {
+ 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 (preg_match("/:/", $header)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+ }
+
+ return new Services_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+
+ $off = $stop - time();
+ }
+
+ trigger_error(sprintf("Timed out fetching: %s", $url),
+ E_USER_WARNING);
+
+ return null;
+ }
+
+ function post($url, $body)
+ {
+ $this->reset();
+
+ if (!$this->allowedURL($url)) {
+ trigger_error(sprintf("Fetching URL not allowed: %s", $url),
+ E_USER_WARNING);
+ return null;
+ }
+
+ $c = curl_init();
+
+ 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) {
+ trigger_error("No HTTP code returned", E_USER_WARNING);
+ return null;
+ }
+
+ $body = $this->data;
+
+ curl_close($c);
+
+ $new_headers = array();
+
+ foreach ($this->headers as $header) {
+ if (preg_match("/:/", $header)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ return new Services_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/ParseHTML.php b/lib/Services/Yadis/ParseHTML.php
new file mode 100644
index 0000000..ca8c364
--- /dev/null
+++ b/lib/Services/Yadis/ParseHTML.php
@@ -0,0 +1,258 @@
+<?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 Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * 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 Yadis
+ */
+class Services_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 Services_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/lib/Services/Yadis/PlainHTTPFetcher.php b/lib/Services/Yadis/PlainHTTPFetcher.php
new file mode 100644
index 0000000..6b4b143
--- /dev/null
+++ b/lib/Services/Yadis/PlainHTTPFetcher.php
@@ -0,0 +1,245 @@
+<?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 Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Interface import
+ */
+require_once "Services/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 Yadis
+ */
+class Services_Yadis_PlainHTTPFetcher extends Services_Yadis_HTTPFetcher {
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->allowedURL($url)) {
+ trigger_error("Bad URL scheme in url: " . $url,
+ E_USER_WARNING);
+ 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 {
+ trigger_error("fetcher post method doesn't support " .
+ " scheme '" . $parts['scheme'] .
+ "', no default port available",
+ E_USER_WARNING);
+ return null;
+ }
+ }
+
+ $host = $parts['host'];
+
+ if ($parts['scheme'] == 'https') {
+ $host = 'ssl://' . $host;
+ }
+
+ $user_agent = "PHP Yadis Library Fetcher";
+
+ $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 = "";
+ while (!feof($sock)) {
+ $data .= fgets($sock, 1024);
+ }
+
+ 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)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ return new Services_Yadis_HTTPResponse($url, $code, $new_headers, $body);
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->allowedURL($url)) {
+ trigger_error("Bad URL scheme in url: " . $url,
+ E_USER_WARNING);
+ 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 {
+ trigger_error("fetcher post method doesn't support scheme '" .
+ $parts['scheme'] .
+ "', no default port available",
+ E_USER_WARNING);
+ 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) {
+ trigger_error("Could not connect to " . $parts['host'] .
+ " port " . $parts['port'],
+ E_USER_WARNING);
+ 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 Services_Yadis_HTTPResponse($url, $code,
+ $headers, $response_body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/XML.php b/lib/Services/Yadis/XML.php
new file mode 100644
index 0000000..8e1d237
--- /dev/null
+++ b/lib/Services/Yadis/XML.php
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
+ * and 5, respectively.
+ *
+ * @package Yadis
+ */
+
+/**
+ * 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 Services_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 Yadis
+ */
+class Services_Yadis_XMLParser {
+ /**
+ * Initialize an instance of Services_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 Services_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 Services_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_domxml extends Services_Yadis_XMLParser {
+ function Services_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->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 Services_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 Services_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_dom extends Services_Yadis_XMLParser {
+ function Services_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();
+
+ 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 $__Services_Yadis_defaultParser;
+$__Services_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 Services_Yadis_XMLParser $parser An instance of a
+ * Services_Yadis_XMLParser subclass.
+ */
+function Services_Yadis_setDefaultParser(&$parser)
+{
+ global $__Services_Yadis_defaultParser;
+ $__Services_Yadis_defaultParser =& $parser;
+}
+
+function Services_Yadis_getSupportedExtensions()
+{
+ return array(
+ 'dom' => array('classname' => 'Services_Yadis_dom',
+ 'libname' => array('dom.so', 'dom.dll')),
+ 'domxml' => array('classname' => 'Services_Yadis_domxml',
+ 'libname' => array('domxml.so', 'php_domxml.dll')),
+ );
+}
+
+/**
+ * Returns an instance of a Services_Yadis_XMLParser subclass based on
+ * the availability of PHP extensions for XML parsing. If
+ * Services_Yadis_setDefaultParser has been called, the parser used in
+ * that call will be returned instead.
+ */
+function &Services_Yadis_getXMLParser()
+{
+ global $__Services_Yadis_defaultParser;
+
+ if (isset($__Services_Yadis_defaultParser)) {
+ return $__Services_Yadis_defaultParser;
+ }
+
+ $p = null;
+ $classname = null;
+
+ $extensions = Services_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 {
+ Services_Yadis_setDefaultParser($p);
+ }
+
+ return $p;
+}
+
+?>
diff --git a/lib/Services/Yadis/XRDS.php b/lib/Services/Yadis/XRDS.php
new file mode 100644
index 0000000..bc82a20
--- /dev/null
+++ b/lib/Services/Yadis/XRDS.php
@@ -0,0 +1,425 @@
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Services/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Services_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 Services_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));
+
+function Services_Yadis_getNSMap()
+{
+ return array('xrds' => 'xri://$xrds',
+ 'xrd' => 'xri://$xrd*($v*2.0)');
+}
+
+/**
+ * @access private
+ */
+function Services_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
+ * Services_Yadis_XRDS::services() and
+ * Services_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 Services_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Service {
+
+ /**
+ * Creates an empty service object.
+ */
+ function Services_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;
+ }
+
+ /**
+ * 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 = Services_Yadis_array_scramble($uris[$k]);
+ $result = array_merge($result, $new_uris);
+ }
+
+ $result = array_merge($result,
+ Services_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 Services_Yadis_XMLParser}).
+ */
+ function getElements($name)
+ {
+ return $this->parser->evalXPath($name, $this->element);
+ }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre> $xrds = Services_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Services_Yadis_XRDS will be returned. Otherwise, null will be
+ * returned. This class is used by the Services_Yadis_Yadis::discover
+ * method.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_XRDS {
+
+ /**
+ * Instantiate a Services_Yadis_XRDS object. Requires an XPath
+ * instance which has been used to parse a valid XRDS document.
+ */
+ function Services_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
+ * Services_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 Services_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 = Services_Yadis_getXMLParser();
+
+ $ns_map = Services_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'] != 'xri://$xrd*($v*2.0)') {
+ return $_null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != 'xri://$xrd*($v*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 Services_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 Services_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
+ * Services_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
+ * Services_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/lib/Services/Yadis/XRI.php b/lib/Services/Yadis/XRI.php
new file mode 100644
index 0000000..91d385e
--- /dev/null
+++ b/lib/Services/Yadis/XRI.php
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * Routines for XRI resolution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once 'Services/Yadis/Misc.php';
+require_once 'Services/Yadis/Yadis.php';
+require_once 'Auth/OpenID.php';
+
+function Services_Yadis_getDefaultProxy()
+{
+ return 'http://proxy.xri.net/';
+}
+
+function Services_Yadis_getXRIAuthorities()
+{
+ return array('!', '=', '@', '+', '$', '(');
+}
+
+function Services_Yadis_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Services_Yadis_getUCSChars(),
+ Services_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('/[%s]/', implode('', $parts));
+}
+
+function Services_Yadis_getXrefRE()
+{
+ return '/\((.*?)\)/';
+}
+
+function Services_Yadis_identifierScheme($identifier)
+{
+ if (Services_Yadis_startswith($identifier, 'xri://') ||
+ (in_array($identifier[0], Services_Yadis_getXRIAuthorities()))) {
+ return "XRI";
+ } else {
+ return "URI";
+ }
+}
+
+function Services_Yadis_toIRINormal($xri)
+{
+ if (!Services_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+
+ return Services_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 Services_Yadis_escapeForIRI($xri)
+{
+ $xri = str_replace('%', '%25', $xri);
+ $xri = preg_replace_callback(Services_Yadis_getXrefRE(),
+ '_escape_xref', $xri);
+ return $xri;
+}
+
+function Services_Yadis_toURINormal($xri)
+{
+ return Services_Yadis_iriToURI(Services_Yadis_toIRINormal($xri));
+}
+
+function Services_Yadis_iriToURI($iri)
+{
+ if (1) {
+ return $iri;
+ } else {
+ // According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
+ return preg_replace_callback(Services_Yadis_getEscapeRE(),
+ 'Services_Yadis_pct_escape_unicode', $iri);
+ }
+}
+
+
+function Services_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 Services_Yadis_providerIsAuthoritative($providerID, $canonicalID)
+{
+ $lastbang = strrpos($canonicalID, '!');
+ $p = substr($canonicalID, 0, $lastbang);
+ return $p == $providerID;
+}
+
+function Services_Yadis_rootAuthority($xri)
+{
+ // Return the root authority for an XRI.
+
+ $root = null;
+
+ if (Services_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], Services_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 Services_Yadis_XRI($root);
+}
+
+function Services_Yadis_XRI($xri)
+{
+ if (!Services_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+ return $xri;
+}
+
+function Services_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[count($canonicalID_nodes) - 1];
+ $canonicalID = Services_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, '!'));
+ $parent_list = array();
+
+ foreach ($parser->evalXPath('xrd:CanonicalID', $xrd) as $c) {
+ $parent_list[] = Services_Yadis_XRI($parser->content($c));
+ }
+
+ if (!in_array($parent_sought, $parent_list)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ $childID = $parent_sought;
+ }
+
+ $root = Services_Yadis_rootAuthority($iname);
+ if (!Services_Yadis_providerIsAuthoritative($root, $childID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ return $canonicalID;
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/XRIRes.php b/lib/Services/Yadis/XRIRes.php
new file mode 100644
index 0000000..b87cf04
--- /dev/null
+++ b/lib/Services/Yadis/XRIRes.php
@@ -0,0 +1,68 @@
+<?php
+
+require_once 'Services/Yadis/XRDS.php';
+require_once 'Services/Yadis/XRI.php';
+
+class Services_Yadis_ProxyResolver {
+ function Services_Yadis_ProxyResolver(&$fetcher, $proxy_url = null)
+ {
+ $this->fetcher =& $fetcher;
+ $this->proxy_url = $proxy_url;
+ if (!$this->proxy_url) {
+ $this->proxy_url = Services_Yadis_getDefaultProxy();
+ }
+ }
+
+ function queryURL($xri, $service_type = null)
+ {
+ // trim off the xri:// prefix
+ $qxri = substr(Services_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 = Services_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) {
+ continue;
+ }
+ $xrds = Services_Yadis_XRDS::parseXRDS($response->body);
+ if (!$xrds) {
+ continue;
+ }
+ $canonicalID = Services_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);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/lib/Services/Yadis/Yadis.php b/lib/Services/Yadis/Yadis.php
new file mode 100644
index 0000000..338bb3a
--- /dev/null
+++ b/lib/Services/Yadis/Yadis.php
@@ -0,0 +1,313 @@
+<?php
+
+/**
+ * The core PHP Yadis implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Need both fetcher types so we can use the right one based on the
+ * presence or absence of CURL.
+ */
+require_once "Services/Yadis/PlainHTTPFetcher.php";
+require_once "Services/Yadis/ParanoidHTTPFetcher.php";
+
+/**
+ * Need this for parsing HTML (looking for META tags).
+ */
+require_once "Services/Yadis/ParseHTML.php";
+
+/**
+ * Need this to parse the XRDS document during Yadis discovery.
+ */
+require_once "Services/Yadis/XRDS.php";
+
+/**
+ * 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 "Services/Yadis/Yadis.php";</pre>
+ *
+ * To perform Yadis discovery, first call the "discover" method
+ * statically with a URI parameter:
+ *
+ * <pre> $http_response = array();
+ * $fetcher = Services_Yadis_Yadis::getHTTPFetcher();
+ * $yadis_object = Services_Yadis_Yadis::discover($uri,
+ * $http_response, $fetcher);</pre>
+ *
+ * If the discovery succeeds, $yadis_object will be an instance of
+ * {@link Services_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 Services_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 Services_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
+ * Services_Yadis_Service}.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Yadis {
+
+ /**
+ * Returns an HTTP fetcher object. If the CURL extension is
+ * present, an instance of {@link Services_Yadis_ParanoidHTTPFetcher}
+ * is returned. If not, an instance of
+ * {@link Services_Yadis_PlainHTTPFetcher} is returned.
+ */
+ function getHTTPFetcher($timeout = 20)
+ {
+ if (Services_Yadis_Yadis::curlPresent()) {
+ $fetcher = new Services_Yadis_ParanoidHTTPFetcher($timeout);
+ } else {
+ $fetcher = new Services_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
+ * Services_Yadis_HTTPResponse}.
+ *
+ * @param Services_Yadis_HTTPFetcher $fetcher An instance of a
+ * Services_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
+ * Services_Yadis_Yadis, depending on whether the discovery
+ * succeeded.
+ */
+ function discover($uri, &$http_response, &$fetcher,
+ $extra_ns_map = null, $timeout = 20)
+ {
+ if (!$uri) {
+ return null;
+ }
+
+ $request_uri = $uri;
+ $headers = array("Accept: application/xrds+xml");
+
+ if (!$fetcher) {
+ $fetcher = Services_Yadis_Yadis::getHTTPFetcher($timeout);
+ }
+
+ $response = $fetcher->get($uri, $headers);
+ $http_response = $response;
+
+ if (!$response) {
+ return null;
+ }
+
+ if ($response->status != 200) {
+ return null;
+ }
+
+ $xrds_uri = $response->final_url;
+ $uri = $response->final_url;
+ $body = $response->body;
+
+ $xrds_header_uri = Services_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('x-xrds-location',
+ 'x-yadis-location'));
+
+ $content_type = Services_Yadis_Yadis::_getHeader($response->headers,
+ array('content-type'));
+
+ if ($xrds_header_uri) {
+ $xrds_uri = $xrds_header_uri;
+ $response = $fetcher->get($xrds_uri);
+ $http_response = $response;
+ if (!$response) {
+ return null;
+ } else {
+ $body = $response->body;
+ $headers = $response->headers;
+ $content_type = Services_Yadis_Yadis::_getHeader($headers,
+ array('content-type'));
+ }
+ }
+
+ if (Services_Yadis_Yadis::_getContentType($content_type) !=
+ 'application/xrds+xml') {
+ // Treat the body as HTML and look for a META tag.
+ $parser = new Services_Yadis_ParseHTML();
+ $new_uri = $parser->getHTTPEquiv($body);
+ $xrds_uri = null;
+ if ($new_uri) {
+ $response = $fetcher->get($new_uri);
+ if ($response->status != 200) {
+ return null;
+ }
+ $http_response = $response;
+ $body = $response->body;
+ $xrds_uri = $new_uri;
+ $content_type = Services_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+ }
+ }
+
+ $xrds = Services_Yadis_XRDS::parseXRDS($body, $extra_ns_map);
+
+ if ($xrds !== null) {
+ $y = new Services_Yadis_Yadis();
+
+ $y->request_uri = $request_uri;
+ $y->xrds = $xrds;
+ $y->uri = $uri;
+ $y->xrds_uri = $xrds_uri;
+ $y->body = $body;
+ $y->content_type = $content_type;
+
+ return $y;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Instantiates an empty Services_Yadis_Yadis object. This
+ * constructor should not be used by any user of the library.
+ * This constructor results in a completely useless object which
+ * must be populated with valid discovery information. Instead of
+ * using this constructor, call
+ * Services_Yadis_Yadis::discover($uri).
+ */
+ function Services_Yadis_Yadis()
+ {
+ $this->request_uri = null;
+ $this->uri = null;
+ $this->xrds = null;
+ $this->xrds_uri = null;
+ $this->body = null;
+ $this->content_type = null;
+ }
+
+ /**
+ * 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 Services_Yadis_Service}
+ * objects
+ */
+ function services()
+ {
+ if ($this->xrds) {
+ return $this->xrds->services();
+ }
+
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/templates/default/en/openid-about.php b/templates/default/en/openid-about.php
new file mode 100644
index 0000000..f72fc2b
--- /dev/null
+++ b/templates/default/en/openid-about.php
@@ -0,0 +1,60 @@
+<?php $this->includeAtTemplateBase('includes/header.php'); ?>
+
+ <div id="header">
+ <h1>simpleSAMLphp OpenID</h1>
+ <div id="poweredby"><img src="/<?php echo $data['baseurlpath']; ?>resources/icons/bino.png" alt="Bino" /></div>
+ </div>
+
+ <div id="content">
+
+ <?php if (isset($data['header'])) { echo '<h2>' . $data['header'] . '</h2>'; } ?>
+
+ <p>[ <a href="/<?php echo $data['baseurlpath']; ?>/openid/provider/server.php/sites">List of trusted sites</a> |
+ About simpleSAMLphp OpenID ]</p>
+
+
+ <p>Welcome to the simpleSAMLphp OpenID provider.</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="<?php echo $data['openidserver']; ?>" /&gt;
+&lt;link rel="openid.delegation" href="<?php echo $data['openiddelegation']; ?>" /&gt;
+
+ </pre>
+
+
+ <p><?php
+
+ if (isset($data['userid'])) {
+ echo 'You are now logged in as ' . $data['userid'];
+ } else {
+ echo '<a href="' . $data['initssourl'] . '">Login</a>';
+ }
+
+ ?>
+
+ <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>
+
+
+ <h2>About simpleSAMLphp</h2>
+ <p>Hey! This simpleSAMLphp thing is pretty cool, where can I read more about it?
+ You can find more information about simpleSAMLphp at <a href="http://rnd.feide.no">the Feide RnD blog</a> over at <a href="http://uninett.no">UNINETT</a>.</p>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?>
+
diff --git a/templates/default/en/openid-sites.php b/templates/default/en/openid-sites.php
new file mode 100644
index 0000000..024d9e7
--- /dev/null
+++ b/templates/default/en/openid-sites.php
@@ -0,0 +1,81 @@
+<?php $this->includeAtTemplateBase('includes/header.php'); ?>
+
+ <div id="header">
+ <h1>simpleSAMLphp OpenID</h1>
+ <div id="poweredby"><img src="/<?php echo $data['baseurlpath']; ?>resources/icons/bino.png" alt="Bino" /></div>
+ </div>
+
+ <div id="content">
+
+ <?php if (isset($data['header'])) { echo '<h2>' . $data['header'] . '</h2>'; } ?>
+
+
+ <p>[ List of trusted sites |
+ <a href="/<?php echo $data['baseurlpath']; ?>/openid/provider/server.php/about">About simpleSAMLphp OpenID</a> ]</p>
+
+
+ <p>These decisions have been remembered for this session. All decisions will be forgotten when the session ends.</p>
+
+
+ <?php if (isset($data['sites'])) { ?>
+
+ <div class="form">
+ <form method="post" action="<?php echo '/' . $data['baseurlpath'] . 'openid/provider/server.php/sites'; ?>">
+ <table>
+ <tbody>
+ <?php
+
+ $trusted_sites = array();
+ $untrusted_sites = array();
+ foreach ($data['sites'] as $site => $trusted) {
+ if ($trusted) {
+ $trusted_sites[] = $site;
+ } else {
+ $untrusted_sites[] = $site;
+ }
+ }
+
+ $i = 0;
+ foreach (array('Trusted Sites' => $trusted_sites,
+ 'Untrusted Sites' => $untrusted_sites) as
+ $name => $sites) {
+ if ($sites) {
+ echo '<tr><th colspan="2">'. $name . '</th></tr>';
+ foreach ($sites as $site) {
+ $siteid = 'site' . $i;
+ echo '<tr>
+ <td><input type="checkbox" name="' . $siteid . '" value="' .
+ htmlspecialchars($site, ENT_QUOTES) . '" id="' . $siteid . '" /></td>
+ <td><label for="' . $siteid . '"><code>' . htmlspecialchars($site, ENT_QUOTES) . '</code></label></td>
+ </tr>';
+ $i += 1;
+ }
+ }
+ }
+
+
+ ?>
+ </tbody>
+ </table>
+ <input type="submit" name="remove" value="Remove Selected" />
+ <input type="submit" name="refresh" value="Refresh List" />
+ <input type="submit" name="forget" value="Forget All" />
+ </form>
+ </div>
+
+ <?php } else { ?>
+
+ <p>No sites are remembered for this session. When you authenticate with a site,
+ you can choose to add it to this list by choosing <q>Remember this decision</q>.
+ </p>
+
+ <?php } ?>
+
+
+ <h2>About simpleSAMLphp</h2>
+
+ <p>Hey! This simpleSAMLphp thing is pretty cool, where can I read more about it?
+ You can find more information about simpleSAMLphp at <a href="http://rnd.feide.no">the Feide RnD blog</a> over at <a href="http://uninett.no">UNINETT</a>.</p>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?>
+
diff --git a/templates/default/en/openid-trust.php b/templates/default/en/openid-trust.php
new file mode 100644
index 0000000..2e1266f
--- /dev/null
+++ b/templates/default/en/openid-trust.php
@@ -0,0 +1,33 @@
+<?php $this->includeAtTemplateBase('includes/header.php'); ?>
+
+ <div id="header">
+ <h1>simpleSAMLphp OpenID</h1>
+ <div id="poweredby"><img src="/<?php echo $data['baseurlpath']; ?>resources/icons/bino.png" alt="Bino" /></div>
+ </div>
+
+ <div id="content">
+
+ <?php if (isset($data['header'])) { echo '<h2>' . $data['header'] . '</h2>'; } ?>
+
+
+ <p>[ <a href="/<?php echo $data['baseurlpath']; ?>/openid/provider/server.php/sites">List of trusted sites</a> |
+ <a href="/<?php echo $data['baseurlpath']; ?>/openid/provider/server.php/about">About simpleSAMLphp OpenID</a> ]</p>
+
+ <div class="form">
+ <p>Do you wish to confirm your identity URL (<code><?php echo $data['openidurl']; ?></code>)
+ with <code><?php echo $data['siteurl']; ?></code>?</p>
+ <form method="post" action="<?php echo $data['trusturl']; ?>">
+ <input type="checkbox" name="remember" value="on" id="remember"><label
+ for="remember">Remember this decision</label>
+ <br />
+ <input type="submit" name="trust" value="Confirm" />
+ <input type="submit" value="Do not confirm" />
+ </form>
+ </div>
+
+
+ <h2>About simpleSAMLphp</h2>
+ <p>Hey! This simpleSAMLphp thing is pretty cool, where can I read more about it?
+ You can find more information about simpleSAMLphp at <a href="http://rnd.feide.no">the Feide RnD blog</a> over at <a href="http://uninett.no">UNINETT</a>.</p>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?> \ No newline at end of file
diff --git a/www/openid/provider/server.php b/www/openid/provider/server.php
new file mode 100644
index 0000000..3508a68
--- /dev/null
+++ b/www/openid/provider/server.php
@@ -0,0 +1,676 @@
+<?php
+
+
+
+
+
+require_once('../../_include.php');
+
+// Include simpleSAMLphp libraries
+require_once('SimpleSAML/Utilities.php');
+require_once('SimpleSAML/Session.php');
+require_once('SimpleSAML/Logger.php');
+require_once('SimpleSAML/XML/MetaDataStore.php');
+require_once('SimpleSAML/XML/AttributeFilter.php');
+require_once('SimpleSAML/XHTML/Template.php');
+
+// Include openid libs
+require_once 'lib/session.php';
+require_once 'lib/actions.php';
+
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Server.php";
+require_once "Auth/OpenID/HMACSHA1.php";
+require_once "Auth/OpenID/FileStore.php";
+
+session_start();
+
+
+
+
+
+
+
+/*
+ * CONFIGURATION
+ */
+
+
+
+/**
+ * Initialize an OpenID store
+ *
+ * @return object $store an instance of OpenID store (see the
+ * documentation for how to create one)
+ */
+function getOpenIDStore()
+{
+
+ $config = SimpleSAML_Configuration::getInstance();
+ return new Auth_OpenID_FileStore($config->getValue('openid.filestore'));
+}
+
+/**
+ * Trusted sites is an array of trust roots.
+ *
+ * Sites in this list will not have to be approved by the user in
+ * order to be used. It is OK to leave this value as-is.
+ *
+ * In a more robust server, this should be a per-user setting.
+ */
+$trusted_sites = array(
+);
+
+
+
+
+
+
+/*
+ * ACTIONS
+ */
+
+
+
+/**
+ * Handle a standard OpenID server request
+ */
+function action_default()
+{
+ $server =& getServer();
+ $method = $_SERVER['REQUEST_METHOD'];
+ $request = null;
+ if ($method == 'GET') {
+ $request = $_GET;
+ } else {
+ $request = $_POST;
+ }
+
+ $request = Auth_OpenID::fixArgs($request);
+ $request = $server->decodeRequest($request);
+
+ if (!$request) {
+
+ $config = SimpleSAML_Configuration::getInstance();
+ $metadata = new SimpleSAML_XML_MetaDataStore($config);
+
+ $t = new SimpleSAML_XHTML_Template($config, 'openid-about.php');
+ $t->data['openidserver'] = $metadata->getGenerated('server', 'openid-provider');
+
+
+ $session = SimpleSAML_Session::getInstance(true);
+
+ $useridfield = $config->getValue('openid.userid_attributename');
+ $delegationprefix = $config->getValue('openid.delegation_prefix');
+
+ $username = 'your_username';
+ if (isset($session) && $session->isValid() ) {
+ $attributes = $session->getAttributes();
+ $username = $attributes[$useridfield][0];
+ $t->data['userid'] = $username;
+ }
+ $idpmeta = $metadata->getMetaDataCurrent('openid-provider');
+
+ $relaystate = SimpleSAML_Utilities::selfURLNoQuery() . '?RelayState=' . urlencode($_GET['RelayState']) .
+ '&RequestID=' . urlencode($requestid);
+ $authurl = SimpleSAML_Utilities::addURLparameter('/' . $config->getValue('baseurlpath') . $idpmeta['auth'],
+ 'RelayState=' . urlencode($relaystate));
+
+ $t->data['initssourl'] = $authurl;
+ $t->data['openiddelegation'] = $delegationprefix . $username;
+
+
+
+ $t->show();
+ exit(0);
+ }
+
+ setRequestInfo($request);
+
+ if (in_array($request->mode,
+ array('checkid_immediate', 'checkid_setup'))) {
+
+ if (isTrusted($request->identity, $request->trust_root)) {
+ $response =& $request->answer(true);
+ $sreg = getSreg($request->identity);
+ if (is_array($sreg)) {
+ foreach ($sreg as $k => $v) {
+ $response->addField('sreg', $k, $v);
+ }
+ }
+ } else if ($request->immediate) {
+ $response =& $request->answer(false, getServerURL());
+ } else {
+ if (!getLoggedInUser()) {
+ // TODO Login
+ //return login_render();
+ check_authenticated_user();
+ }
+
+ $config = SimpleSAML_Configuration::getInstance();
+ $t = new SimpleSAML_XHTML_Template($config, 'openid-trust.php');
+
+ $t->data['openidurl'] = getLoggedInUser();
+ $t->data['siteurl'] = htmlspecialchars($request->trust_root);;
+ $t->data['trusturl'] = buildURL('trust', true);
+
+ $t->show();
+ exit(0);
+
+ //return trust_render($request);
+ }
+ } else {
+ $response =& $server->handleRequest($request);
+ }
+
+ $webresponse =& $server->encodeResponse($response);
+
+ 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 (!isset($input['password'])) {
+ $errors[] = 'Enter a password to continue';
+ }
+ if (count($errors) == 0) {
+ $openid_url = $input['openid_url'];
+ $openid_url = Auth_OpenID::normalizeUrl($openid_url);
+ $password = $input['password'];
+ if (!checkLogin($openid_url, $password)) {
+ $errors[] = 'The entered password does not match the ' .
+ 'entered identity URL.';
+ }
+ }
+ return array($errors, $openid_url);
+}
+
+
+
+
+function check_authenticated_user() {
+
+ //session_start();
+
+ $config = SimpleSAML_Configuration::getInstance();
+ $metadata = new SimpleSAML_XML_MetaDataStore($config);
+ $session = SimpleSAML_Session::getInstance(true);
+
+ $logger = new SimpleSAML_Logger();
+
+ $idpentityid = $metadata->getMetaDataCurrentEntityID('openid-provider');
+ $idpmeta = $metadata->getMetaDataCurrent('openid-provider');
+
+
+ /* Check if valid local session exists.. */
+ if (!isset($session) || !$session->isValid() ) {
+
+
+
+ $relaystate = SimpleSAML_Utilities::selfURLNoQuery() . '/login';
+ $authurl = SimpleSAML_Utilities::addURLparameter('/' . $config->getValue('baseurlpath') . $idpmeta['auth'],
+ 'RelayState=' . urlencode($relaystate));
+
+
+ header('Location: ' . $authurl);
+ exit(0);
+ }
+
+ $attributes = $session->getAttributes();
+ $info = getRequestInfo();
+
+
+ $useridfield = $config->getValue('openid.userid_attributename');
+ $delegationprefix = $config->getValue('openid.delegation_prefix');
+
+ $username = $attributes[$useridfield][0];
+
+
+ $openid_url = $delegationprefix . $username;
+
+ error_log('set logged in user to be [' .$delegationprefix. '][' . $username . ']' );
+ setLoggedInUser($openid_url);
+
+}
+
+
+/**
+ * Log in a user and potentially continue the requested identity approval
+ */
+function action_login()
+{
+
+ error_log('action login');
+
+ //session_start();
+
+ check_authenticated_user();
+
+ $info = getRequestInfo();
+
+ return doAuth($info);
+
+}
+
+
+
+
+
+/**
+ * Ask the user whether he wants to trust this site
+ */
+function action_trust()
+{
+ $info = getRequestInfo();
+ $trusted = isset($_POST['trust']);
+ if ($info && isset($_POST['remember'])) {
+ $sites = getSessionSites();
+ $sites[$info->trust_root] = $trusted;
+ setSessionSites($sites);
+ }
+ return doAuth($info, $trusted, true);
+}
+
+function action_sites()
+{
+ $sites = getSessionSites();
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ if (isset($_POST['forget'])) {
+ $sites = null;
+ setSessionSites($sites);
+ } elseif (isset($_POST['remove'])) {
+ foreach ($_POST as $k => $v) {
+ if (preg_match('/^site[0-9]+$/', $k) && isset($sites[$v])) {
+ unset($sites[$v]);
+ }
+ }
+ setSessionSites($sites);
+ }
+ }
+
+ $config = SimpleSAML_Configuration::getInstance();
+ $t = new SimpleSAML_XHTML_Template($config, 'openid-sites.php');
+
+ $t->data['openidurl'] = getLoggedInUser();
+ $t->data['sites'] = $sites;
+
+ $t->show();
+ exit(0);
+
+
+ // TODO Render sites
+ //return sites_render($sites);
+}
+
+
+
+/**
+ * Return an HTTP redirect response
+ */
+function redirect_render($redir_url)
+{
+ /*
+ $headers = array(http_found,
+ header_content_text,
+ header_connection_close,
+ 'Location: ' . $redir_url,
+ );
+ */
+ header('Location: ' . $redir_url);
+
+// $body = sprintf(redirect_message, $redir_url);
+ // return array($headers, $body);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/*
+ * SESSION
+ */
+
+
+
+/**
+ * Get the URL of the current script
+ */
+function getServerURL()
+{
+ $path = $_SERVER['SCRIPT_NAME'];
+ $host = $_SERVER['HTTP_HOST'];
+ $port = $_SERVER['SERVER_PORT'];
+ $s = $_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());
+ }
+ return $server;
+}
+
+/**
+ * Return whether the trust root is currently trusted
+ */
+function isTrusted($identity_url, $trust_root)
+{
+ // from config.php
+ global $trusted_sites;
+
+ if ($identity_url != getLoggedInUser()) {
+ return false;
+ }
+
+ if (in_array($trust_root, $trusted_sites)) {
+ return true;
+ }
+
+ $sites = getSessionSites();
+ return isset($sites[$trust_root]) && $sites[$trust_root];
+}
+
+/**
+ * Return a hashed form of the user's password
+ */
+function hashPassword($password)
+{
+ return bin2hex(Auth_OpenID_SHA1($password));
+}
+
+/**
+ * Check the user's login information
+ */
+function checkLogin($openid_url, $password)
+{
+ // from config.php
+ global $openid_users;
+ $hash = hashPassword($password);
+
+ return isset($openid_users[$openid_url])
+ && $hash == $openid_users[$openid_url];
+}
+
+/**
+ * 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 setSessionSites($sites=null)
+{
+ if (!isset($sites)) {
+ unset($_SESSION['session_sites']);
+ } else {
+ $_SESSION['session_sites'] = serialize($sites);
+ }
+}
+
+function getSessionSites()
+{
+ return isset($_SESSION['session_sites'])
+ ? unserialize($_SESSION['session_sites'])
+ : false;
+}
+
+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];
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/*
+ * OpenID Transactions
+ */
+
+
+
+function authCancel($info)
+{
+ if ($info) {
+ setRequestInfo();
+ $url = $info->getCancelURL();
+ } else {
+ $url = getServerURL();
+ }
+ redirect_render($url);
+}
+
+function doAuth($info, $trusted=null, $fail_cancels=false)
+{
+ if (!$info) {
+ // There is no authentication information, so bail
+ authCancel(null);
+ }
+
+ $req_url = $info->identity;
+ $user = getLoggedInUser();
+ setRequestInfo($info);
+
+ if ($req_url != $user) {
+ error_log('simpleSAMLphp doauth():' . 'Your identity ' . $user .
+ ' does not match the requested identity from the OpenID consumer, which was: ' . $req_url);
+ $config = SimpleSAML_Configuration::getInstance();
+ $t = new SimpleSAML_XHTML_Template($config, 'error.php');
+
+ $t->data['header'] = 'OpenID identity mismatch';
+ $t->data['message'] = 'Your identity ' . $user . ' does not match the requested identity from the
+ OpenID consumer, which was: ' . $req_url;
+ $t->data['e'] = new Exception('OpenID Error');
+
+ $t->show();
+ exit(0);
+
+ }
+
+ $sites = getSessionSites();
+ $trust_root = $info->trust_root;
+ $fail_cancels = $fail_cancels || isset($sites[$trust_root]);
+ $trusted = isset($trusted) ? $trusted : isTrusted($req_url, $trust_root);
+
+ if ($trusted) {
+ setRequestInfo();
+ $server =& getServer();
+ $response =& $info->answer(true);
+ $webresponse =& $server->encodeResponse($response);
+
+ $new_headers = array();
+
+ foreach ($webresponse->headers as $k => $v) {
+ $new_headers[] = $k.": ".$v;
+ }
+
+
+ array_walk($new_headers, 'header');
+ header(header_connection_close);
+ print $webresponse->body;
+
+
+ } elseif ($fail_cancels) {
+ authCancel($info);
+ } else {
+
+ $config = SimpleSAML_Configuration::getInstance();
+ $t = new SimpleSAML_XHTML_Template($config, 'openid-trust.php');
+
+ $t->data['openidurl'] = getLoggedInUser();
+ $t->data['siteurl'] = htmlspecialchars($request->trust_root);;
+ $t->data['trusturl'] = buildURL('trust', true);
+
+ $t->show();
+ exit(0);
+
+
+ }
+}
+
+
+
+
+/*
+ * Handle actions
+ */
+
+
+//init();
+$action = getAction();
+if (!function_exists($action)) {
+ $action = 'action_default';
+}
+$action();
+
+
+
+?> \ No newline at end of file