summaryrefslogtreecommitdiffstats
path: root/Auth
diff options
context:
space:
mode:
Diffstat (limited to 'Auth')
-rw-r--r--Auth/OpenID/Association.php284
-rw-r--r--Auth/OpenID/Consumer/Consumer.php973
-rw-r--r--Auth/OpenID/Consumer/Fetchers.php374
-rw-r--r--Auth/OpenID/Consumer/Parse.php288
-rw-r--r--Auth/OpenID/CryptUtil.php855
-rw-r--r--Auth/OpenID/DiffieHellman.php109
-rw-r--r--Auth/OpenID/HMACSHA1.php58
-rw-r--r--Auth/OpenID/KVForm.php102
-rw-r--r--Auth/OpenID/OIDUtil.php283
-rw-r--r--Auth/OpenID/Store/DumbStore.php117
-rw-r--r--Auth/OpenID/Store/FileStore.php652
-rw-r--r--Auth/OpenID/Store/Interface.php179
-rw-r--r--Auth/OpenID/Store/SQLStore.php16
13 files changed, 4290 insertions, 0 deletions
diff --git a/Auth/OpenID/Association.php b/Auth/OpenID/Association.php
new file mode 100644
index 0000000..638e835
--- /dev/null
+++ b/Auth/OpenID/Association.php
@@ -0,0 +1,284 @@
+<?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
+ */
+
+/**
+ * Includes for utility functions.
+ */
+require_once('CryptUtil.php');
+require_once('KVForm.php');
+require_once('OIDUtil.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 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.
+ */
+ var $SIG_LENGTH = 20;
+
+ /**
+ * The ordering and name of keys as stored by serialize.
+ */
+ var $assoc_keys = array(
+ 'version',
+ 'handle',
+ 'secret',
+ 'issued',
+ 'lifetime',
+ 'assoc_type'
+ );
+
+ /**
+ * This is an alternate constructor used by the OpenID consumer
+ * library to create associations. OpenIDStore implementations
+ * shouldn't use this constructor.
+ *
+ * @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.
+ */
+ 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.
+ *
+ * @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 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));
+ }
+
+ /**
+ * This checks to see if two Auth_OpenID_Association instances
+ * represent different associations.
+ *
+ * @return bool $result true if the two instances represent
+ * different associations, false otherwise.
+ */
+ function not_equal($other)
+ {
+ return !($this->equal($other));
+ }
+
+ /**
+ * 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' => Auth_OpenID_toBase64($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::arrayToKV($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::kvToArray($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'];
+ if ($keys != $class_assoc_keys) {
+ trigger_error('Unexpected key values: ' . strval($keys),
+ E_USER_WARNING);
+ return null;
+ }
+
+ list($version, $handle, $secret, $issued, $lifetime, $assoc_type) =
+ $values;
+
+ if ($version != '2') {
+ trigger_error('Unknown version: ' . $version, E_USER_WARNING);
+ return null;
+ }
+
+ $issued = intval($issued);
+ $lifetime = intval($lifetime);
+ $secret = Auth_OpenID_fromBase64($secret);
+
+ return new $class_name(
+ $handle, $secret, $issued, $lifetime, $assoc_type);
+ }
+
+ /**
+ * Generate a signature for a sequence of (key, value) pairs
+ *
+ * @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)
+ {
+ assert($this->assoc_type == 'HMAC-SHA1');
+ $kv = Auth_OpenID_KVForm::arrayToKV($pairs);
+ return Auth_OpenID_hmacSha1($this->secret, $kv);
+ }
+
+ /**
+ * Generate a signature for some fields in a dictionary
+ *
+ * @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 Auth_OpenID_toBase64($this->sign($pairs));
+ }
+
+ function addSignature($fields, &$data, $prefix = 'openid.')
+ {
+ $sig = $this->signDict($fields, $data, $prefix);
+ $signed = implode(",", $fields);
+ $data[$prefix . 'sig'] = $sig;
+ $data[$prefix . 'signed'] = $signed;
+ }
+
+ 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/Auth/OpenID/Consumer/Consumer.php b/Auth/OpenID/Consumer/Consumer.php
new file mode 100644
index 0000000..7ac3ab0
--- /dev/null
+++ b/Auth/OpenID/Consumer/Consumer.php
@@ -0,0 +1,973 @@
+<?php
+
+/**
+ * This module documents the main interface with the OpenID consumer
+ * libary. 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
+ * OpenIDConsumer instance. More on the abstract store type and
+ * concrete implementations of it that are provided in the
+ * documentation for the constructor of the OpenIDConsumer class.
+ *
+ * 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 checks that the entered URL describes an
+ * OpenID page by fetching it and looking for appropriate link tags
+ * in the head section.
+ * 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 a 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 concrete
+ * implementations that are provided allow the consumer site to store
+ * the necessary data in several different ways: in the filesystem, in
+ * a MySQL database, or in an SQLite database.
+ *
+ * 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
+ * identity 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.
+ *
+ * When your site receives that request, it should create an
+ * Auth_OpenID_Consumer instance, and call beginAuth on it. If
+ * beginAuth completes successfully, it will return an
+ * Auth_OpenID_AuthRequest instance. Otherwise it will provide some
+ * useful information for giving the user an error message.
+ *
+ * Now that you have the Auth_OpenID_AuthRequest object, you need to
+ * preserve the value in its $token field for lookup on the user's
+ * next request from your site. There are several approaches for
+ * doing this which will work. If your environment has any kind of
+ * session-tracking system, storing the token in the session is a good
+ * approach. If it doesn't you can store the token in either a cookie
+ * or in the return_to url provided in the next step.
+ *
+ * The next step is to call the constructRedirect method on the
+ * Auth_OpenID_Consumer object. Pass it the Auth_OpenID_AuthRequest
+ * object returned by the previous call to beginAuth along with the
+ * return_to and trust_root URLs. The return_to URL 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.
+ *
+ * Next, send the user a redirect to the URL generated by
+ * constructRedirect.
+ *
+ * That's the first half of the process. The second half of the
+ * process is done after the user's ID server sends the user 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 constructRedirect 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.
+ *
+ * When handling this request, the first thing to do is check the
+ * 'openid.return_to' parameter. If it doesn't match the URL that
+ * the request was actually sent to (the URL the request was actually
+ * sent to will contain the openid parameters in addition to any in
+ * the return_to URL, but they should be identical other than that),
+ * that is clearly suspicious, and the request shouldn't be allowed to
+ * proceed.
+
+ * Otherwise, the next step is to extract the token value set in the
+ * first half of the OpenID login. Create a Auth_OpenID_Consumer
+ * object, and call its completeAuth method with that token and a
+ * dictionary of all the query arguments. This call will return a
+ * status code and some additional information describing the the
+ * server's response. See the documentation for completeAuth for a
+ * full explanation of the possible responses.
+ *
+ * At this point, you have an identity URL that you know belongs to
+ * the user who made that request. Some sites will use that URL
+ * directly as the user name. Other sites will want to map that URL
+ * to a username in the site's traditional namespace. At this point,
+ * you can take whichever action makes the most sense.
+ *
+ * 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/CryptUtil.php");
+require_once("Auth/OpenID/KVForm.php");
+require_once("Auth/OpenID/OIDUtil.php");
+require_once("Auth/OpenID/Association.php");
+require_once("Auth/OpenID/DiffieHellman.php");
+require_once("Auth/OpenID/Consumer/Parse.php");
+require_once("Auth/OpenID/Consumer/Fetchers.php");
+
+/**
+ * This is the status code returned when either the of the beginAuth
+ * or completeAuth methods return successfully.
+ */
+$Auth_OpenID_SUCCESS = 'success';
+
+/**
+ * This is the status code completeAuth returns when the value it
+ * received indicated an invalid login.
+ */
+$Auth_OpenID_FAILURE = 'failure';
+
+/**
+ * This is the status code completeAuth returns when the
+ * 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.
+ */
+$Auth_OpenID_SETUP_NEEDED = 'setup needed';
+
+/**
+ * This is the status code beginAuth returns when it is unable to
+ * fetch the OpenID URL the user entered.
+ */
+$Auth_OpenID_HTTP_FAILURE = 'http failure';
+
+/**
+ * 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.
+*/
+$Auth_OpenID_PARSE_ERROR = 'parse error';
+
+/**
+ * This is the characters that the nonces are made from.
+ */
+$_Auth_OpenID_NONCE_CHRS = $GLOBALS['_Auth_OpenID_letters'] .
+ $GLOBALS['_Auth_OpenID_digits'];
+
+/**
+ * This is the number of seconds the tokens generated by this library
+ * will be valid for. If you want to change the lifetime of a token,
+ * set this value to the desired lifespan, in seconds.
+ */
+$_Auth_OpenID_TOKEN_LIFETIME = 60 * 5; // five minutes
+
+/**
+ * This is the number of characters in the generated nonce for each
+ * transaction.
+ */
+$_Auth_OpenID_NONCE_LEN = 8;
+
+/**
+ * This class is the interface to the OpenID consumer logic.
+ * Instances of it maintain no per-request state, so they can be
+ * reused (or even used by multiple threads concurrently) as needed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Consumer {
+
+ /**
+ * This method initializes a new Auth_OpenID_Consumer instance to
+ * access the library.
+ *
+ * @param Auth_OpenID_OpenIDStore $store This must be an object
+ * that implements the interface in Auth_OpenID_Store. Several
+ * concrete implementations are provided, to cover most common use
+ * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see
+ * the Auth_OpenID_SQLStore class and its sublcasses. For a
+ * filesystem-backed store, see the Auth_OpenID_FileStore module.
+ * As a last resort, if it isn't possible for the server to store
+ * state at all, an instance of Auth_OpenID_DumbStore can be used.
+ * This should be an absolute last resort, though, as it makes the
+ * consumer vulnerable to replay attacks over the lifespan of the
+ * tokens the library creates.
+ *
+ * @param Auth_OpenID_HTTPFetcher $fetcher This is an optional
+ * reference to an instance of Auth_OpenID_HTTPFetcher. If
+ * present, the provided fetcher is used by the library to fetch
+ * users' identity pages and make direct requests to the identity
+ * server. If it is not present, a default fetcher is used. The
+ * default fetcher uses curl if the Curl bindings are available,
+ * and uses a raw socket POST if not.
+ *
+ * @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_Consumer(&$store, $fetcher = null, $immediate = false)
+ {
+ if ($store === null) {
+ trigger_error("Must supply non-null store to create consumer",
+ E_USER_ERROR);
+ return null;
+ }
+
+ $this->store =& $store;
+
+ if ($fetcher === null) {
+ $this->fetcher = Auth_OpenID_getHTTPFetcher();
+ } else {
+ $this->fetcher =& $fetcher;
+ }
+
+ if ($immediate) {
+ $this->mode = 'checkid_immediate';
+ } else {
+ $this->mode = 'checkid_setup';
+ }
+
+ $this->immediate = $immediate;
+ }
+
+ /**
+ * This method is called to start the OpenID login process.
+ *
+ * First, the user's claimed identity page is fetched, to
+ * determine their identity server. If the page cannot be fetched
+ * or if the page does not have the necessary link tags in it,
+ * this method returns one of $Auth_OpenID_HTTP_FAILURE or
+ * $Auth_OpenID_PARSE_ERROR, depending on where the process failed.
+ *
+ * Second, unless the store provided is a dumb store, it checks to
+ * see if it has an association with that identity server, and
+ * creates and stores one if not.
+ *
+ * Third, it generates a signed token for this authentication
+ * transaction, which contains a timestamp, a nonce, and the
+ * information needed in Step 4 (above) in the module overview.
+ * The token is used by the library to make handling the various
+ * pieces of information needed in Step 4 (above) easy and secure.
+ *
+ * The token generated must be preserved until Step 4 (above),
+ * which is after the redirect to the OpenID server takes place.
+ * This means that the token must be preserved across http
+ * requests. There are three basic approaches that might be used
+ * for storing the token. First, the token could be put in the
+ * return_to URL passed into the constructRedirect method.
+ * Second, the token could be stored in a cookie. Third, in an
+ * environment that supports user sessions, the session is a good
+ * spot to store the token.
+ *
+ * @param string $user_url This is the url the user entered as
+ * their OpenID. This call takes care of normalizing it and
+ * resolving any redirects the server might issue.
+ *
+ * @return array $array This method returns an array containing a
+ * status code and additional information about the code.
+ *
+ * If there was a problem fetching the identity page the user
+ * gave, the status code is set to $Auth_OpenID_HTTP_FAILURE, and
+ * the additional information value is either set to null if the
+ * HTTP transaction failed or the HTTP return code, which will be
+ * in the 400-500 range. This additional information value may
+ * change in a future release.
+ *
+ * If the identity page fetched successfully, but didn't include
+ * the correct link tags, the status code is set to
+ * $Auth_OpenID_PARSE_ERROR, and the additional information value
+ * is currently set to null. The additional information value may
+ * change in a future release.
+ *
+ * Otherwise, the status code is set to $Auth_OpenID_SUCCESS, and
+ * the additional information is an instance of
+ * Auth_OpenID_AuthRequest. The $token attribute contains the
+ * token to be preserved for the next HTTP request. The
+ * $server_url might also be of interest, if you wish to blacklist
+ * or whitelist OpenID servers. The other contents of the object
+ * are information needed in the constructRedirect call.
+ */
+ function beginAuth($user_url)
+ {
+ global $Auth_OpenID_SUCCESS;
+
+ list($status, $info) = $this->_findIdentityInfo($user_url);
+ if ($status != $Auth_OpenID_SUCCESS) {
+ return array($status, $info);
+ }
+
+ list($consumer_id, $server_id, $server_url) = $info;
+ return $this->_gotIdentityInfo($consumer_id, $server_id, $server_url);
+ }
+
+ /**
+ * This method is called to construct the redirect URL sent to the
+ * browser to ask the server to verify its identity. This is
+ * called in Step 3 (above) of the flow described in the overview.
+ * The generated redirect should be sent to the browser which
+ * initiated the authorization request.
+ *
+ * @param Auth_OpenID_AuthRequest $auth_request This must be a
+ * Auth_OpenID_AuthRequest instance which was returned from a
+ * previous call to beginAuth. It contains information found
+ * during the beginAuth call which is needed to build the redirect
+ * URL.
+ *
+ * @param string $return_to This is the URL that will be included
+ * in the generated redirect as the URL the OpenID server will
+ * send its response to. The URL passed in must handle OpenID
+ * authentication responses.
+ *
+ * @param string $trust_root This is a URL that will be sent to
+ * the server to identify this site. The OpenID spec at
+ * http://www.openid.net/specs.bml#mode-checkid_immediate has more
+ * information on what the trust_root value is for and what its
+ * form can be. While the trust root is officially optional in
+ * the OpenID specification, this implementation requires that it
+ * be set. Nothing is actually gained by leaving out the trust
+ * root, as you can get identical behavior by specifying the
+ * return_to URL as the trust root.
+ *
+ * @return string $url This method returns a string containing the
+ * URL to redirect to when such a URL is successfully constructed.
+ */
+ function constructRedirect($auth_request, $return_to, $trust_root)
+ {
+ $assoc = $this->_getAssociation($auth_request->server_url,
+ $replace = 1);
+ // Because _getAssociation is asynchronous if the association is
+ // not already in the store.
+
+ if ($assoc === null) {
+ trigger_error("Could not get association for redirection",
+ E_USER_WARNING);
+ return null;
+ }
+
+ return $this->_constructRedirect($assoc, $auth_request,
+ $return_to, $trust_root);
+ }
+
+ /**
+ * Given an array of CGI data from PHP, this method replaces
+ * "openid_" with "openid." in the CGI key strings (NOT the
+ * values). This is to work around the fact that PHP will mangle
+ * the CGI key strings to protect against register_globals
+ * problems.
+ */
+ function fixResponse($arr)
+ {
+ // Depending on PHP settings, the query data received may have
+ // been modified so that incoming "." values in the keys have
+ // been replaced with underscores. Look specifically for
+ // "openid_" and replace it with "openid.".
+ $result = array();
+
+ foreach ($arr as $key => $value) {
+ $new_key = str_replace("openid_", "openid.", $key);
+ $result[$new_key] = $value;
+ }
+
+ return $result;
+ }
+
+ /**
+ * This method is called to interpret the server's response to an
+ * OpenID request. It is called in Step 4 of the flow described
+ * in the overview.
+ *
+ * The return value is a pair, consisting of a status and
+ * additional information. The status values are strings, but
+ * should be referred to by their symbolic values:
+ * $Auth_OpenID_SUCCESS, $Auth_OpenID_FAILURE, and
+ * $Auth_OpenID_SETUP_NEEDED.
+ *
+ * When $Auth_OpenID_SUCCESS is returned, the additional
+ * information returned is either null or a string. If it is
+ * null, it means the user cancelled the login, and no further
+ * information can be determined. If the additional information
+ * is a string, it is the identity that has been verified as
+ * belonging to the user making this request.
+ *
+ * When $Auth_OpenID_FAILURE is returned, the additional
+ * information is either null or a string. In either case, this
+ * code means that the identity verification failed. If it can be
+ * determined, the identity that failed to verify is returned.
+ * Otherwise null is returned.
+ *
+ * When $Auth_OpenID_SETUP_NEEDED is returned, the additional
+ * information is the user setup URL. This is a URL returned only
+ * as a response to requests made with openid.mode=immediate,
+ * which indicates that the login was unable to proceed, and the
+ * user should be sent to that URL if they wish to proceed with
+ * the login.
+ *
+ * @param string $token This is the token for this authentication
+ * transaction, generated by the call to beginAuth.
+ *
+ * @param array $query This is a dictionary-like object containing
+ * the query parameters the OpenID server included in its redirect
+ * back to the return_to URL. The keys and values should both be
+ * url-unescaped.
+ *
+ * @return array $array Returns the status of the response and any
+ * additional information, as described above.
+ */
+ function completeAuth($token, $query)
+ {
+ global $Auth_OpenID_SUCCESS, $Auth_OpenID_FAILURE;
+
+ $query = $this->fixResponse($query);
+
+ $mode = Auth_OpenID_array_get($query, 'openid.mode', '');
+
+ if ($mode == 'cancel') {
+ return array($Auth_OpenID_SUCCESS, null);
+ } else if ($mode == 'error') {
+
+ $error = Auth_OpenID_array_get($query, 'openid.error', null);
+
+ if ($error !== null) {
+ Auth_OpenID_log($error);
+ }
+ return array($Auth_OpenID_FAILURE, null);
+ } else if ($mode == 'id_res') {
+ return $this->_doIdRes($token, $query);
+ } else {
+ return array($Auth_OpenID_FAILURE, null);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _gotIdentityInfo($consumer_id, $server_id, $server_url)
+ {
+ global $Auth_OpenID_SUCCESS, $_Auth_OpenID_NONCE_CHRS,
+ $_Auth_OpenID_NONCE_LEN;
+
+ $nonce = Auth_OpenID_CryptUtil::randomString($_Auth_OpenID_NONCE_LEN,
+ $_Auth_OpenID_NONCE_CHRS);
+
+ $token = $this->_genToken($nonce, $consumer_id,
+ $server_id, $server_url);
+ return array($Auth_OpenID_SUCCESS,
+ new Auth_OpenID_AuthRequest($token, $server_id,
+ $server_url, $nonce));
+ }
+
+ /**
+ * @access private
+ */
+ function _constructRedirect($assoc, $auth_req, $return_to, $trust_root)
+ {
+ $redir_args = array(
+ 'openid.identity' => $auth_req->server_id,
+ 'openid.return_to' => $return_to,
+ 'openid.trust_root' => $trust_root,
+ 'openid.mode' => $this->mode,
+ );
+
+ if ($assoc !== null) {
+ $redir_args['openid.assoc_handle'] = $assoc->handle;
+ }
+
+ $this->store->storeNonce($auth_req->nonce);
+ return strval(Auth_OpenID_appendArgs($auth_req->server_url,
+ $redir_args));
+ }
+
+ /**
+ * @access private
+ */
+ function _doIdRes($token, $query)
+ {
+ global $Auth_OpenID_FAILURE, $Auth_OpenID_SETUP_NEEDED,
+ $Auth_OpenID_SUCCESS;
+
+ $ret = $this->_splitToken($token);
+ if ($ret === null) {
+ return array($Auth_OpenID_FAILURE, null);
+ }
+
+ list($nonce, $consumer_id, $server_id, $server_url) = $ret;
+
+ $return_to = Auth_OpenID_array_get($query, 'openid.return_to', null);
+ $server_id2 = Auth_OpenID_array_get($query, 'openid.identity', null);
+ $assoc_handle = Auth_OpenID_array_get($query,
+ 'openid.assoc_handle', null);
+
+ if (($return_to === null) ||
+ ($server_id === null) ||
+ ($assoc_handle === null)) {
+ return array($Auth_OpenID_FAILURE, $consumer_id);
+ }
+
+ if ($server_id != $server_id2) {
+ return array($Auth_OpenID_FAILURE, $consumer_id);
+ }
+
+ $user_setup_url = Auth_OpenID_array_get($query,
+ 'openid.user_setup_url', null);
+
+ if ($user_setup_url !== null) {
+ return array($Auth_OpenID_SETUP_NEEDED, $user_setup_url);
+ }
+
+ $assoc = $this->store->getAssociation($server_url);
+
+ if (($assoc === null) ||
+ ($assoc->handle != $assoc_handle) ||
+ ($assoc->getExpiresIn() <= 0)) {
+ // It's not an association we know about. Dumb mode is
+ // our only possible path for recovery.
+ return array($this->_checkAuth($nonce, $query, $server_url),
+ $consumer_id);
+ }
+
+ // Check the signature
+ $sig = Auth_OpenID_array_get($query, 'openid.sig', null);
+ $signed = Auth_OpenID_array_get($query, 'openid.signed', null);
+ if (($sig === null) ||
+ ($signed === null)) {
+ return array($Auth_OpenID_FAILURE, $consumer_id);
+ }
+
+ $signed_list = explode(",", $signed);
+ $v_sig = $assoc->signDict($signed_list, $query);
+
+ if ($v_sig != $sig) {
+ return array($Auth_OpenID_FAILURE, $consumer_id);
+ }
+
+ if (!$this->store->useNonce($nonce)) {
+ return array($Auth_OpenID_FAILURE, $consumer_id);
+ }
+
+ return array($Auth_OpenID_SUCCESS, $consumer_id);
+ }
+
+ /**
+ * @access private
+ */
+ function _checkAuth($nonce, $query, $server_url)
+ {
+ global $Auth_OpenID_FAILURE, $Auth_OpenID_SUCCESS;
+
+ // XXX: send only those arguments that were signed?
+ $signed = Auth_OpenID_array_get($query, 'openid.signed', null);
+ if ($signed === null) {
+ return $Auth_OpenID_FAILURE;
+ }
+
+ $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';
+ $post_data = Auth_OpenID_http_build_query($check_args);
+
+ $ret = $this->fetcher->post($server_url, $post_data);
+ if ($ret === null) {
+ return $Auth_OpenID_FAILURE;
+ }
+
+ $results = Auth_OpenID_KVForm::kvToArray($ret[2]);
+ $is_valid = Auth_OpenID_array_get($results, 'is_valid', 'false');
+
+ if ($is_valid == 'true') {
+ $invalidate_handle = Auth_OpenID_array_get($results,
+ 'invalidate_handle',
+ null);
+ if ($invalidate_handle !== null) {
+ $this->store->removeAssociation($server_url,
+ $invalidate_handle);
+ }
+
+ if (!$this->store->useNonce($nonce)) {
+ return $Auth_OpenID_FAILURE;
+ }
+
+ return $Auth_OpenID_SUCCESS;
+ }
+
+ $error = Auth_OpenID_array_get($results, 'error', null);
+ if ($error !== null) {
+ Auth_OpenID_log(sprintf("Error message from server during " .
+ "check_authentication: %s", error));
+ }
+
+ return $Auth_OpenID_FAILURE;
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($server_url, $replace = false)
+ {
+ global $_Auth_OpenID_TOKEN_LIFETIME;
+
+ if ($this->store->isDumb()) {
+ return null;
+ }
+
+ $assoc = $this->store->getAssociation($server_url);
+
+ if (($assoc === null) ||
+ ($replace && ($assoc->getExpiresIn() <
+ $_Auth_OpenID_TOKEN_LIFETIME))) {
+ $dh = new Auth_OpenID_DiffieHellman();
+ $body = $this->_createAssociateRequest($dh);
+ $assoc = $this->_fetchAssociation($dh, $server_url, $body);
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * @access private
+ */
+ function _genToken($nonce, $consumer_id, $server_id, $server_url)
+ {
+ $timestamp = strval(time());
+ $elements = array($timestamp, $nonce,
+ $consumer_id, $server_id, $server_url);
+
+ $joined = implode("\x00", $elements);
+ $sig = Auth_OpenID_CryptUtil::hmacSha1($this->store->getAuthKey(),
+ $joined);
+
+ return Auth_OpenID_toBase64($sig . $joined);
+ }
+
+ /**
+ * @access private
+ */
+ function _splitToken($token)
+ {
+ global $_Auth_OpenID_TOKEN_LIFETIME;
+
+ $token = Auth_OpenID_fromBase64($token);
+ if (strlen($token) < 20) {
+ return null;
+ }
+
+ $sig = substr($token, 0, 20);
+ $joined = substr($token, 20);
+ if (Auth_OpenID_CryptUtil::hmacSha1(
+ $this->store->getAuthKey(), $joined) != $sig) {
+ return null;
+ }
+
+ $split = explode("\x00", $joined);
+ if (count($split) != 5) {
+ return null;
+ }
+
+ $ts = intval($split[0]);
+ if ($ts == 0) {
+ return null;
+ }
+
+ if ($ts + $_Auth_OpenID_TOKEN_LIFETIME < time()) {
+ return null;
+ }
+
+ return array_slice($split, 1);
+ }
+
+ /**
+ * @access private
+ */
+ function _findIdentityInfo($identity_url)
+ {
+ global $Auth_OpenID_HTTP_FAILURE;
+
+ $url = Auth_OpenID_normalizeUrl($identity_url);
+ $ret = $this->fetcher->get($url);
+ if ($ret === null) {
+ return array($Auth_OpenID_HTTP_FAILURE, null);
+ }
+
+ list($http_code, $consumer_id, $data) = $ret;
+ if ($http_code != 200) {
+ return array($Auth_OpenID_HTTP_FAILURE, $http_code);
+ }
+
+ // This method is split in two this way to allow for
+ // asynchronous implementations of _findIdentityInfo.
+ return $this->_parseIdentityInfo($data, $consumer_id);
+ }
+
+ /**
+ * @access private
+ */
+ function _parseIdentityInfo($data, $consumer_id)
+ {
+ global $Auth_OpenID_PARSE_ERROR, $Auth_OpenID_SUCCESS;
+
+ $link_attrs = Auth_OpenID_parseLinkAttrs($data);
+ $server = Auth_OpenID_findFirstHref($link_attrs, 'openid.server');
+ $delegate = Auth_OpenID_findFirstHref($link_attrs, 'openid.delegate');
+
+ if ($server === null) {
+ return array($Auth_OpenID_PARSE_ERROR, null);
+ }
+
+ if ($delegate !== null) {
+ $server_id = $delegate;
+ } else {
+ $server_id = $consumer_id;
+ }
+
+ $urls = array($consumer_id, $server_id, $server);
+
+ $normalized = array();
+
+ foreach ($urls as $url) {
+ $normalized[] = Auth_OpenID_normalizeUrl($url);
+ }
+
+ return array($Auth_OpenID_SUCCESS, $normalized);
+ }
+
+ /**
+ * @access private
+ */
+ function _createAssociateRequest($dh, $args = null)
+ {
+ global $_Auth_OpenID_DEFAULT_MOD, $_Auth_OpenID_DEFAULT_GEN;
+
+ if ($args === null) {
+ $args = array();
+ }
+
+ $cpub = Auth_OpenID_CryptUtil::longToBase64($dh->public);
+
+ $args = array_merge($args, array(
+ 'openid.mode' => 'associate',
+ 'openid.assoc_type' => 'HMAC-SHA1',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.dh_consumer_public' => $cpub
+ ));
+
+ if (($dh->mod != $_Auth_OpenID_DEFAULT_MOD) ||
+ ($dh->gen != $_Auth_OpenID_DEFAULT_GEN)) {
+ $args = array_merge($args,
+ array(
+ 'openid.dh_modulus' =>
+ Auth_OpenID_CryptUtil::longToBase64($dh->modulus),
+ 'openid.dh_gen' =>
+ Auth_OpenID_CryptUtil::longToBase64($dh->generator)
+ ));
+ }
+
+ return Auth_OpenID_http_build_query($args);
+ }
+
+ /**
+ * @access private
+ */
+ function _fetchAssociation($dh, $server_url, $body)
+ {
+ $ret = $this->fetcher->post($server_url, $body);
+ if ($ret === null) {
+ $fmt = 'Getting association: failed to fetch URL: %s';
+ Auth_OpenID_log(sprintf($fmt, $server_url));
+ return null;
+ }
+
+ list($http_code, $url, $data) = $ret;
+ $results = Auth_OpenID_KVForm::kvToArray($data);
+ if ($http_code == 400) {
+ $server_error = Auth_OpenID_array_get($results, 'error',
+ '<no message from server>');
+
+ $fmt = 'Getting association: error returned from server %s: %s';
+ Auth_OpenID_log(sprintf($fmt, $server_url, $server_error));
+ return null;
+ } else if ($http_code != 200) {
+ $fmt = 'Getting association: bad status code from server %s: %s';
+ Auth_OpenID_log(sprintf($fmt, $server_url, $http_code));
+ return null;
+ }
+
+ $results = Auth_OpenID_KVForm::kvToArray($data);
+
+ return $this->_parseAssociation($results, $dh, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _parseAssociation($results, $dh, $server_url)
+ {
+ $required_keys = array('assoc_type', 'assoc_handle',
+ 'dh_server_public', 'enc_mac_key');
+
+ foreach ($required_keys as $key) {
+ if (!array_key_exists($key, $results)) {
+ Auth_OpenID_log(sprintf("Getting association: missing key in ".
+ "response from %s: %s",
+ $server_url, $key),
+ E_USER_WARNING);
+ return null;
+ }
+ }
+
+ $assoc_type = $results['assoc_type'];
+ if ($assoc_type != 'HMAC-SHA1') {
+ $fmt = 'Unsupported assoc_type returned from server %s: %s';
+ Auth_OpenID_log(sprintf($fmt, $server_url, $assoc_type));
+ return null;
+ }
+
+ $assoc_handle = $results['assoc_handle'];
+ $expires_in = intval(Auth_OpenID_array_get($results, 'expires_in',
+ '0'));
+
+ $session_type = Auth_OpenID_array_get($results, 'session_type', null);
+ if ($session_type === null) {
+ $secret = Auth_OpenID_fromBase64($results['mac_key']);
+ } else {
+ $fmt = 'Unsupported session_type returned from server %s: %s';
+ if ($session_type != 'DH-SHA1') {
+ Auth_OpenID_log(sprintf($fmt, $server_url, $session_type));
+ return null;
+ }
+
+ $spub = Auth_OpenID_CryptUtil::base64ToLong(
+ $results['dh_server_public']);
+
+ $enc_mac_key = Auth_OpenID_CryptUtil::fromBase64(
+ $results['enc_mac_key']);
+
+ $secret = $dh->xorSecret($spub, $enc_mac_key);
+ }
+
+ $assoc = Auth_OpenID_Association::fromExpiresIn($expires_in,
+ $assoc_handle,
+ $secret,
+ $assoc_type);
+
+ $this->store->storeAssociation($server_url, $assoc);
+ return $assoc;
+ }
+}
+
+/**
+ * This class represents an in-progress OpenID authentication request.
+ * It exists to make transferring information between the beginAuth
+ * and constructRedirect methods easier. Users of the OpenID consumer
+ * library will need to be aware of the $token value, and may care
+ * about the $server_url value. All other fields are internal
+ * information for the library which the user of the library shouldn't
+ * touch at all.
+ *
+ * The 'token' is the token generated by the library. It must be
+ * saved until the user's return request, via whatever mechanism works
+ * best for this consumer application.
+ *
+ * The 'server_url' is the URL of the identity server that will be
+ * used. It isn't necessary to do anything with this value, but it is
+ * available for consumers that wish to either blacklist or whitelist
+ * OpenID servers.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AuthRequest {
+ function Auth_OpenID_AuthRequest($token, $server_id, $server_url, $nonce)
+ {
+ $this->token = $token;
+ $this->server_id = $server_id;
+ $this->server_url = $server_url;
+ $this->nonce = $nonce;
+ }
+}
+
+?>
diff --git a/Auth/OpenID/Consumer/Fetchers.php b/Auth/OpenID/Consumer/Fetchers.php
new file mode 100644
index 0000000..6918fc5
--- /dev/null
+++ b/Auth/OpenID/Consumer/Fetchers.php
@@ -0,0 +1,374 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface and several
+ * 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
+ */
+
+/**
+ * Specify a socket timeout setting, in seconds.
+ */
+$_Auth_OpenID_socket_timeout = 20;
+
+/**
+ * Specify allowed URL schemes for fetching.
+ */
+$_Auth_OpenID_allowed_schemes = array('http', 'https');
+
+/**
+ * This class is the interface for HTTP fetchers the OpenID consumer
+ * library uses. This interface is only important if you need to
+ * write a new fetcher for some reason.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_HTTPFetcher {
+
+ /**
+ * This performs an HTTP get, following redirects along the way.
+ *
+ * @return array $tuple This returns a three-tuple on success.
+ * The first value is the http return code. The second value is
+ * the final url that was fetched, after following any redirects.
+ * The third value is the data that was retrieved from the site.
+ * If the fetch didn't succeed, return null.
+ */
+ function get($url)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This performs an HTTP post. If it makes sense, it will follow
+ * redirects along the way.
+ *
+ * @return array $tuple This returns a three-tuple on success.
+ * The first value is the http return code. The second value is
+ * the final url that was fetched, after following any redirects.
+ * The third value is the data that was retrieved from the site.
+ * If the fetch didn't succeed, return null.
+ */
+ function post($url, $body)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+}
+
+/**
+ * Detect the presence of Curl and set a flag accordingly.
+ */
+$_Auth_OpenID_curl_found = false;
+if (function_exists('curl_init')) {
+ $_Auth_OpenID_curl_found = true;
+}
+
+function Auth_OpenID_getHTTPFetcher()
+{
+ global $_Auth_OpenID_curl_found;
+ if (!$_Auth_OpenID_curl_found) {
+ $fetcher = new Auth_OpenID_PlainHTTPFetcher();
+ } else {
+ $fetcher = new Auth_OpenID_ParanoidHTTPFetcher();
+ }
+
+ return $fetcher;
+}
+
+function Auth_OpenID_allowedURL($url)
+{
+ global $_Auth_OpenID_allowed_schemes;
+ foreach ($_Auth_OpenID_allowed_schemes as $scheme) {
+ if (strpos($url, sprintf("%s://", $scheme)) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * This class implements a plain, hand-built socket-based fetcher
+ * which will be used in the event that CURL is unavailable.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainHTTPFetcher extends Auth_OpenID_HTTPFetcher {
+ /**
+ * @access private
+ */
+ function _fetch($url)
+ {
+ $data = @file_get_contents($url);
+
+ if ($data !== false) {
+ return array(200, $url, $data);
+ } else {
+ return null;
+ }
+ }
+
+ function get($url)
+ {
+ if (!Auth_OpenID_allowedURL($url)) {
+ trigger_error("Bad URL scheme in url: " . $url,
+ E_USER_WARNING);
+ return null;
+ }
+
+ return $this->_fetch($url);
+ }
+
+ function post($url, $body)
+ {
+ global $_Auth_OpenID_socket_timeout;
+
+ if (!Auth_OpenID_allowedURL($url)) {
+ trigger_error("Bad URL scheme in url: " . $url,
+ E_USER_WARNING);
+ return null;
+ }
+
+ $parts = parse_url($url);
+
+ $headers = array();
+
+ $headers[] = "POST ".$parts['path']." HTTP/1.1";
+ $headers[] = "Host: " . $parts['host'];
+ $headers[] = "Content-type: application/x-www-form-urlencoded";
+ $headers[] = "Content-length: " . strval(strlen($body));
+
+ // 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;
+ }
+ }
+
+ // Connect to the remote server.
+ $sock = fsockopen($parts['host'], $parts['port']);
+ stream_set_timeout($sock, $_Auth_OpenID_socket_timeout);
+
+ if ($sock === false) {
+ trigger_error("Could not connect to " . $parts['host'] .
+ " port " . $parts['port'],
+ E_USER_WARNING);
+ return null;
+ }
+
+ // 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];
+
+ return array($code, $url, $response_body);
+ }
+}
+
+/**
+ * An array to store headers and data from Curl calls.
+ */
+$_Auth_OpenID_curl_data = array();
+
+/**
+ * A function to prepare a "slot" in the global $_Auth_OpenID_curl_data
+ * array so curl data can be stored there by curl callbacks in the
+ * paranoid fetcher.
+ */
+function _initResponseSlot($ch)
+{
+ global $_Auth_OpenID_curl_data;
+ $key = strval($ch);
+ if (!array_key_exists($key, $_Auth_OpenID_curl_data)) {
+ $_Auth_OpenID_curl_data[$key] = array('headers' => array(),
+ 'body' => "");
+ }
+ return $key;
+}
+
+/**
+ * A callback function for curl so headers can be stored.
+ */
+function _writeHeaders($ch, $data)
+{
+ global $_Auth_OpenID_curl_data;
+ $key = _initResponseSlot($ch);
+ $_Auth_OpenID_curl_data[$key]['headers'][] = rtrim($data);
+ return strlen($data);
+}
+
+/**
+ * A callback function for curl so page data can be stored.
+ */
+function _writeData($ch, $data)
+{
+ global $_Auth_OpenID_curl_data;
+ $key = _initResponseSlot($ch);
+ $_Auth_OpenID_curl_data[$key]['body'] .= $data;
+ return strlen($data);
+}
+
+
+/**
+ * A paranoid Auth_OpenID_HTTPFetcher class which uses CURL for
+ * fetching.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ParanoidHTTPFetcher extends Auth_OpenID_HTTPFetcher {
+ function Auth_OpenID_ParanoidHTTPFetcher()
+ {
+ global $_Auth_OpenID_curl_found;
+ if (!$_Auth_OpenID_curl_found) {
+ trigger_error("Cannot use this class; CURL extension not found",
+ E_USER_ERROR);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _findRedirect($headers)
+ {
+ foreach ($headers as $line) {
+ if (strpos($line, "Location: ") == 0) {
+ $parts = explode(" ", $line, 2);
+ return $parts[1];
+ }
+ }
+ return null;
+ }
+
+ function get($url)
+ {
+ global $_Auth_OpenID_socket_timeout;
+ global $_Auth_OpenID_curl_data;
+
+ $c = curl_init();
+
+ $curl_key = _initResponseSlot($c);
+
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+
+ $stop = time() + $_Auth_OpenID_socket_timeout;
+ $off = $_Auth_OpenID_socket_timeout;
+
+ while ($off > 0) {
+ if (!Auth_OpenID_allowedURL($url)) {
+ trigger_error(sprintf("Fetching URL not allowed: %s", $url),
+ E_USER_WARNING);
+ return null;
+ }
+
+ curl_setopt($c, CURLOPT_WRITEFUNCTION, "_writeData");
+ curl_setopt($c, CURLOPT_HEADERFUNCTION, "_writeHeaders");
+ curl_setopt($c, CURLOPT_TIMEOUT, $off);
+ curl_setopt($c, CURLOPT_URL, $url);
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+ $body = $_Auth_OpenID_curl_data[$curl_key]['body'];
+ $headers = $_Auth_OpenID_curl_data[$curl_key]['headers'];
+
+ if (!$code) {
+ trigger_error("No HTTP code returned", E_USER_WARNING);
+ return null;
+ }
+
+ if (in_array($code, array(301, 302, 303, 307))) {
+ $url = $this->_findRedirect($headers);
+ } else {
+ curl_close($c);
+ return array($code, $url, $body);
+ }
+
+ $off = $stop - time();
+ }
+
+ trigger_error(sprintf("Timed out fetching: %s", $url),
+ E_USER_WARNING);
+
+ return null;
+ }
+
+ function post($url, $body)
+ {
+ global $_Auth_OpenID_socket_timeout;
+ global $_Auth_OpenID_curl_data;
+
+ if (!Auth_OpenID_allowedURL($url)) {
+ trigger_error(sprintf("Fetching URL not allowed: %s", $url),
+ E_USER_WARNING);
+ return null;
+ }
+
+ $c = curl_init();
+
+ $curl_key = _initResponseSlot($c);
+
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ curl_setopt($c, CURLOPT_POST, true);
+ curl_setopt($c, CURLOPT_POSTFIELDS, $body);
+ curl_setopt($c, CURLOPT_TIMEOUT, $_Auth_OpenID_socket_timeout);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION, "_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 = $_Auth_OpenID_curl_data[$curl_key]['body'];
+
+ curl_close($c);
+ return array($code, $url, $body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Consumer/Parse.php b/Auth/OpenID/Consumer/Parse.php
new file mode 100644
index 0000000..69f386f
--- /dev/null
+++ b/Auth/OpenID/Consumer/Parse.php
@@ -0,0 +1,288 @@
+<?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.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Specify some flags for use with regex matching.
+ */
+$_Auth_OpenID_re_flags = "si";
+
+/**
+ * Stuff to remove before we start looking for tags
+ */
+$_Auth_OpenID_removed_re = "<!--.*?-->|" .
+ "<!\[CDATA\[.*?\]\]>|" .
+ "<script\b(?!:)[^>]*>.*?<\/script>";
+
+/**
+ * Starts with the tag name at a word boundary, where the tag name is
+ * not a namespace
+ */
+$_Auth_OpenID_tag_expr = "<%s\b(?!:)([^>]*?)" .
+ "(?:\/>|>(.*?)" .
+ "(?:<\/?%s\s*>|\Z))";
+
+/**
+ * Returns a regular expression that will match a given tag in an SGML
+ * string.
+ */
+function Auth_OpenID_tagMatcher($tag_name, $close_tags = null)
+{
+ global $_Auth_OpenID_tag_expr, $_Auth_OpenID_re_flags;
+
+ if ($close_tags) {
+ $options = implode("|", array_merge(array($tag_name), $close_tags));
+ $closer = sprintf("(?:%s)", $options);
+ } else {
+ $closer = $tag_name;
+ }
+
+ $expr = sprintf($_Auth_OpenID_tag_expr, $tag_name, $closer);
+ return sprintf("/%s/%s", $expr, $_Auth_OpenID_re_flags);
+}
+
+function Auth_OpenID_html_find()
+{
+ return Auth_OpenID_tagMatcher('html');
+}
+
+function Auth_OpenID_head_find()
+{
+ return Auth_OpenID_tagMatcher('head', array('body'));
+}
+
+$_Auth_OpenID_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
+
+$_Auth_OpenID_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
+ $_Auth_OpenID_re_flags);
+
+$_Auth_OpenID_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+$_Auth_OpenID_attr_find = sprintf("/%s/%s",
+ $_Auth_OpenID_attr_find,
+ $_Auth_OpenID_re_flags);
+
+$_Auth_OpenID_removed_re = sprintf("/%s/%s",
+ $_Auth_OpenID_removed_re,
+ $_Auth_OpenID_re_flags);
+
+$_Auth_OpenID_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $_Auth_OpenID_entity_replacements));
+
+function Auth_OpenID_replace_entities($str)
+{
+ global $_Auth_OpenID_entity_replacements;
+ foreach ($_Auth_OpenID_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+ return $str;
+}
+
+function Auth_OpenID_remove_quotes($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 Auth_OpenID_parseLinkAttrs($html)
+{
+
+ global $_Auth_OpenID_removed_re,
+ $_Auth_OpenID_link_find,
+ $_Auth_OpenID_attr_find;
+
+ $stripped = preg_replace($_Auth_OpenID_removed_re,
+ "",
+ $html);
+
+ // Try to find the <HTML> tag.
+ $html_re = Auth_OpenID_html_find();
+ $html_matches = array();
+ if (!preg_match($html_re, $stripped, $html_matches)) {
+ return array();
+ }
+
+ // Try to find the <HEAD> tag.
+ $head_re = Auth_OpenID_head_find();
+ $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($_Auth_OpenID_link_find, $head_matches[0],
+ $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($_Auth_OpenID_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = Auth_OpenID_replace_entities(
+ Auth_OpenID_remove_quotes(
+ $attr_matches[2][$index]));
+
+ $link_attrs[$name] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+}
+
+function Auth_OpenID_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 Auth_OpenID_linkHasRel($link_attrs, $target_rel)
+{
+ // Does this link have target_rel as a relationship?
+ // XXX: TESTME
+ $rel_attr = Auth_OpenID_array_get($link_attrs, 'rel', null);
+ return ($rel_attr && Auth_OpenID_relMatches($rel_attr, $target_rel));
+}
+
+function Auth_OpenID_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 (Auth_OpenID_linkHasRel($attr, $target_rel)) {
+ $result[] = $attr;
+ }
+ }
+
+ return $result;
+}
+
+function Auth_OpenID_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 = Auth_OpenID_findLinksRel($link_attrs_list, $target_rel);
+ if (!$matches) {
+ return null;
+ }
+ $first = $matches[0];
+ return Auth_OpenID_array_get($first, 'href', null);
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/CryptUtil.php b/Auth/OpenID/CryptUtil.php
new file mode 100644
index 0000000..a20a5ed
--- /dev/null
+++ b/Auth/OpenID/CryptUtil.php
@@ -0,0 +1,855 @@
+<?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.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require the HMAC/SHA-1 implementation for creating such hashes.
+ */
+require_once('HMACSHA1.php');
+
+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');
+}
+
+/**
+ * Auth_OpenID_CryptUtil houses static utility functions.
+ *
+ * @package OpenID
+ * @static
+ */
+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_USE_INSECURE_RAND</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)
+ {
+ $bytes = '';
+ $f = @fopen(Auth_OpenID_RAND_SOURCE, "r");
+ if ($f === false) {
+ if (!defined('Auth_OpenID_USE_INSECURE_RAND')) {
+ trigger_error('Set Auth_OpenID_USE_INSECURE_RAND to ' .
+ 'continue with insecure random.',
+ E_USER_ERROR);
+ }
+ $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);
+ fclose($f);
+ }
+ return $bytes;
+ }
+
+ /**
+ * Computes the maximum integer value for this PHP installation.
+ *
+ * @return int $max_int_value The maximum integer value for this
+ * PHP installation
+ */
+ function maxint()
+ {
+ /**
+ * quick-and-dirty function for PHP int size -- assumes
+ * largest integer is of form 2^n - 1
+ */
+ $to_test = pow(2, 16);
+ while (1) {
+ $last = $to_test;
+ $to_test = 2 * $to_test;
+ if (($to_test < $last) || (!is_int($to_test))) {
+ return($last + ($last - 1));
+ }
+ }
+ }
+
+ /**
+ * Computes the SHA1 hash.
+ *
+ * @param string $str The input string.
+ * @return string The resulting SHA1 hash, in binary form.
+ */
+ function sha1($str)
+ {
+ return Auth_OpenID_sha1_raw($str);
+ }
+
+ /**
+ * Computes an HMAC-SHA1 digest.
+ *
+ * @param string $key The key used to generate the HMAC-SHA1 digest
+ * @param string $text The text to be hashed
+ * @return string $digest The raw HMAC-SHA1 digest
+ */
+ function hmacSha1($key, $text)
+ {
+ return Auth_OpenID_HMACSHA1($key, $text);
+ }
+
+ /**
+ * Converts a base64-encoded string to its raw binary equivalent.
+ *
+ * @param string $str The base64-encoded string to decode
+ * @return string $raw The decoded binary data
+ */
+ function fromBase64($str)
+ {
+ return base64_decode($str);
+ }
+
+ /**
+ * Converts a raw binary string to its base64-encoded equivalent.
+ *
+ * @param string $str The raw binary data to encode
+ * @return string $raw The base64-encoded version of $str
+ */
+ function toBase64($str)
+ {
+ return base64_encode($str);
+ }
+
+ /**
+ * 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)
+ {
+
+ $lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+
+ $cmp = $lib->cmp($long, 0);
+ if ($cmp < 0) {
+ print "longToBytes takes only positive integers.";
+ return null;
+ }
+
+ if ($cmp == 0) {
+ return "\x00";
+ }
+
+ $bytes = array();
+
+ while ($lib->cmp($long, 0) > 0) {
+ array_unshift($bytes, $lib->mod($long, 256));
+ $long = $lib->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 long integer, returns the number converted to a binary
+ * string. This function accepts "long" numbers within the PHP
+ * integer range (usually 32 bits).
+ *
+ * @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_platform($long)
+ {
+
+ if ($long < 0) {
+ print "longToBytes_platform takes only positive integers.";
+ return null;
+ }
+
+ return pack('N', $long);
+ }
+
+ /**
+ * 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)
+ {
+ $lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+
+ 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 = $lib->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 = $lib->mul($n, pow(2, 8));
+ $n = $lib->add($n, $byte);
+ }
+
+ return $n;
+ }
+
+ /**
+ * 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_platform($str)
+ {
+ if ($str === null) {
+ return null;
+ }
+
+ $data = unpack('Nx', $str);
+ return $data['x'];
+ }
+
+ /**
+ * Converts a base64-encoded string to a long number.
+ *
+ * @param string $str A base64-encoded string
+ * @return integer $long A long number
+ */
+ function base64ToLong($str)
+ {
+ return Auth_OpenID_CryptUtil::binaryToLong(
+ Auth_OpenID_CryptUtil::fromBase64($str));
+ }
+
+ /**
+ * Converts a long number to its base64-encoded representation.
+ *
+ * @param integer $long The long number to be converted
+ * @return string $str The base64-encoded version of $long
+ */
+ function longToBase64($long)
+ {
+ return Auth_OpenID_CryptUtil::toBase64(
+ Auth_OpenID_CryptUtil::longToBinary($long));
+ }
+
+ /**
+ * Given two strings of equal length, computes the exclusive-OR of
+ * the two strings' ordinal values and returns the resulting
+ * string.
+ *
+ * @param string $x A string
+ * @param string $y A string
+ * @return string $result The result of $x XOR $y
+ */
+ function strxor($x, $y)
+ {
+ if (strlen($x) != strlen($y)) {
+ return null;
+ }
+
+ $str = "";
+ for ($i = 0; $i < strlen($x); $i++) {
+ $str .= chr(ord($x[$i]) ^ ord($y[$i]));
+ }
+
+ return $str;
+ }
+
+ /**
+ * Reverses a string or array.
+ *
+ * @param mixed $list A string or an array
+ * @return mixed $result The reversed string or array
+ */
+ function reversed($list)
+ {
+ if (is_string($list)) {
+ return strrev($list);
+ } else if (is_array($list)) {
+ return array_reverse($list);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 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 randrange($start, $stop = null, $step = 1)
+ {
+
+ static $Auth_OpenID_CryptUtil_duplicate_cache = array();
+ $lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+
+ if ($stop == null) {
+ $stop = $start;
+ $start = 0;
+ }
+
+ $r = $lib->div($lib->sub($stop, $start), $step);
+
+ // DO NOT MODIFY THIS VALUE.
+ $rbytes = Auth_OpenID_CryptUtil::longToBinary($r);
+
+ if (array_key_exists($rbytes, $Auth_OpenID_CryptUtil_duplicate_cache)) {
+ list($duplicate, $nbytes) =
+ $Auth_OpenID_CryptUtil_duplicate_cache[$rbytes];
+ } else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = strlen($rbytes) - 1;
+ } else {
+ $nbytes = strlen($rbytes);
+ }
+
+ $mxrand = $lib->pow(256, $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = $lib->mod($mxrand, $r);
+
+ if (count($Auth_OpenID_CryptUtil_duplicate_cache) > 10) {
+ $Auth_OpenID_CryptUtil_duplicate_cache = array();
+ }
+
+ $Auth_OpenID_CryptUtil_duplicate_cache[$rbytes] =
+ array($duplicate, $nbytes);
+ }
+
+ while (1) {
+ $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
+ $n = Auth_OpenID_CryptUtil::binaryToLong($bytes);
+ // Keep looping if this value is in the low duplicated
+ // range
+ if ($lib->cmp($n, $duplicate) >= 0) {
+ break;
+ }
+ }
+
+ return $lib->add($start, $lib->mul($lib->mod($n, $r), $step));
+ }
+
+ /**
+ * Returns a random number in the specified range. This function
+ * accepts $start, $stop, and $step values within the platform
+ * integer range.
+ *
+ * @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 randrange_platform($start, $stop = null, $step = 1)
+ {
+
+ static $Auth_OpenID_CryptUtil_duplicate_cache = array();
+
+ if ($stop == null) {
+ $stop = $start;
+ $start = 0;
+ }
+
+ $r = ($stop - $start) / $step;
+
+ // DO NOT MODIFY THIS VALUE.
+ $rbytes = Auth_OpenID_CryptUtil::longToBinary_platform($r);
+
+ if (array_key_exists($rbytes, $Auth_OpenID_CryptUtil_duplicate_cache)) {
+ list($duplicate, $nbytes) =
+ $Auth_OpenID_CryptUtil_duplicate_cache[$rbytes];
+ } else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = strlen($rbytes) - 1;
+ } else {
+ $nbytes = strlen($rbytes);
+ }
+
+ $mxrand = pow(256, $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = $mxrand % $r;
+
+ if (count($Auth_OpenID_CryptUtil_duplicate_cache) > 10) {
+ $Auth_OpenID_CryptUtil_duplicate_cache = array();
+ }
+
+ $Auth_OpenID_CryptUtil_duplicate_cache[$rbytes] =
+ array($duplicate, $nbytes);
+ }
+
+ while (1) {
+ $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
+ $n = Auth_OpenID_CryptUtil::binaryToLong_platform($bytes);
+ // Keep looping if this value is in the low duplicated
+ // range
+ if ($n >= $duplicate) {
+ break;
+ }
+ }
+
+ return $start + ($n % $r) * $step;
+ }
+
+ /**
+ * 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, $chrs = null)
+ {
+ if ($chrs === null) {
+ return Auth_OpenID_CryptUtil::getBytes($length);
+ } else {
+ $n = strlen($chrs);
+ $str = "";
+ for ($i = 0; $i < $length; $i++) {
+ $str .= $chrs[Auth_OpenID_CryptUtil::randrange_platform($n)];
+ }
+ return $str;
+ }
+ }
+}
+
+/**
+ * Exposes math library functionality.
+ *
+ * Auth_OpenID_MathWrapper is a base class that defines the interface
+ * to a math library like GMP or BCmath. This library will attempt to
+ * use an available long number implementation. If a library like GMP
+ * is found, the appropriate Auth_OpenID_MathWrapper subclass will be
+ * instantiated and used for mathematics operations on large numbers.
+ * This base class wraps only native PHP functionality. See
+ * Auth_OpenID_MathWrapper subclasses for access to particular long
+ * number implementations.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MathWrapper {
+ /**
+ * The type of the Auth_OpenID_MathWrapper class. This value
+ * describes the library or module being wrapped. Users of
+ * Auth_OpenID_MathWrapper instances should check this value if
+ * they care about the type of math functionality being exposed.
+ */
+ var $type = 'dumb';
+
+ /**
+ * Returns a random number in the specified range.
+ */
+ function random($min, $max)
+ {
+ return mt_rand($min, $max);
+ }
+
+ /**
+ * Returns $base raised to the $exponent power.
+ */
+ function pow($base, $exponent)
+ {
+ return pow($base, $exponent);
+ }
+
+ /**
+ * Returns the sum of $x and $y.
+ */
+ function add($x, $y)
+ {
+ return $x + $y;
+ }
+
+ /**
+ * Returns -1 if $x < $y, 0 if $x == $y, and 1 if $x > $y.
+ */
+ function cmp($x, $y)
+ {
+ if ($x > $y) {
+ return 1;
+ } else if ($x < $y) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * "Initializes" a new number. This may simply return the
+ * specified number or it may call a library function for this
+ * purpose. The base may be ignored depending on the
+ * implementation.
+ */
+ function init($number, $base = 10)
+ {
+ return $number;
+ }
+
+ /**
+ * Returns the result of $base mod $modulus.
+ */
+ function mod($base, $modulus)
+ {
+ return $base % $modulus;
+ }
+
+ /**
+ * Returns the product of $x and $y.
+ */
+ function mul($x, $y)
+ {
+ return $x * $y;
+ }
+
+ /**
+ * Returns the difference of $x and $y.
+ */
+ function sub($x, $y)
+ {
+ return $x - $y;
+ }
+
+ /**
+ * Returns $x / $y.
+ */
+ function div($x, $y)
+ {
+ return $x / $y;
+ }
+
+ /**
+ * Returns ($base to the $exponent power) mod $modulus. In some
+ * long number implementations, this may be optimized. This
+ * placeholder implementation performs it manually.
+ */
+ 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;
+ }
+}
+
+/**
+ * Exposes BCmath math library functionality.
+ *
+ * Auth_OpenID_BcMathWrapper implements the Auth_OpenID_MathWrapper
+ * interface and wraps the functionality provided by the BCMath
+ * library.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathWrapper {
+ var $type = 'bcmath';
+
+ function random($min, $max)
+ {
+ return mt_rand($min, $max);
+ }
+
+ 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);
+ }
+
+ function powmod($base, $exponent, $modulus)
+ {
+ if (false && function_exists('bcpowmod')) {
+ return bcpowmod($base, $exponent, $modulus);
+ } else {
+ return parent::powmod($base, $exponent, $modulus);
+ }
+ }
+
+}
+
+/**
+ * Exposes GMP math library functionality.
+ *
+ * Auth_OpenID_GmpMathWrapper implements the Auth_OpenID_MathWrapper
+ * interface and wraps the functionality provided by the GMP library.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathWrapper {
+ var $type = 'gmp';
+
+ function random($min, $max)
+ {
+ return gmp_random($max);
+ }
+
+ 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);
+ }
+}
+
+$_Auth_OpenID___mathLibrary = null;
+
+/**
+ * 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
+ * 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.
+ */
+$_Auth_OpenID_supported_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')
+ );
+
+ /**
+ * Auth_OpenID_MathLibrary checks for the presence of long number
+ * extension modules and returns an instance of Auth_OpenID_MathWrapper
+ * which exposes the module's functionality.
+ *
+ * @static
+ * @package OpenID
+ */
+class Auth_OpenID_MathLibrary {
+
+ /**
+ * A method to access an available long number implementation.
+ *
+ * Checks for the existence of an extension module described by
+ * the local Auth_OpenID_supported_extensions array and returns an
+ * instance of a wrapper for that extension module. If no
+ * extension module is found, an instance of
+ * Auth_OpenID_MathWrapper is returned, which wraps the native PHP
+ * integer implementation. The proper calling convention for this
+ * method is $lib =& Auth_OpenID_MathLibrary::getLibWrapper().
+ *
+ * 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
+ * Auth_OpenID_MathWrapper or one of its subclasses
+ */
+ function &getLibWrapper()
+ {
+ // 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.
+ global $_Auth_OpenID___mathLibrary;
+
+ if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $_Auth_OpenID___mathLibrary = null;
+ return $_Auth_OpenID___mathLibrary;
+ }
+
+ global $_Auth_OpenID_supported_extensions;
+
+ // If this method has not been called before, look at
+ // $Auth_OpenID_supported_extensions and try to find an
+ // extension that works.
+ if (!$_Auth_OpenID___mathLibrary) {
+ $loaded = false;
+ $tried = array();
+
+ foreach ($_Auth_OpenID_supported_extensions as $extension) {
+ $tried[] = $extension['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) {
+ $classname = $extension['class'];
+ $_Auth_OpenID___mathLibrary = new $classname();
+ break;
+ }
+ }
+
+ // If no extensions were found, fall back to
+ // Auth_OpenID_MathWrapper so at least some platform-size
+ // math can be performed.
+ if (!$_Auth_OpenID___mathLibrary) {
+ $triedstr = implode(", ", $tried);
+ $msg = 'This PHP installation has no big integer math ' .
+ 'library. Define Auth_OpenID_NO_MATH_SUPPORT to use ' .
+ 'this library in dumb mode. Tried: ' . $triedstr;
+ trigger_error($msg, E_USER_ERROR);
+ }
+ }
+
+ return $_Auth_OpenID___mathLibrary;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/DiffieHellman.php b/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 0000000..f34ab3c
--- /dev/null
+++ b/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * The OpenID library's Diffie-Hellman implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require CryptUtil because we need to get a Auth_OpenID_MathWrapper
+ * object.
+ */
+require_once('CryptUtil.php');
+
+$_Auth_OpenID_DEFAULT_MOD = '155172898181473697471232257763715539915724801'.
+'966915404479707795314057629378541917580651227423698188993727816152646631'.
+'438561595825688188889951272158842675419950341258706556549803580104870537'.
+'681476726513255747040765857479291291572334510643245094715007229621094194'.
+'349783925984760375594985848253359305585439638443';
+
+$_Auth_OpenID_DEFAULT_GEN = '2';
+
+/**
+ * The Diffie-Hellman key exchange class. This class relies on
+ * Auth_OpenID_MathLibrary to perform large number operations.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellman {
+
+ var $mod;
+ var $gen;
+ var $private;
+ var $lib = null;
+
+ function fromBase64($mod, $gen)
+ {
+ if ($mod !== null) {
+ $mod = Auth_OpenID_CryptUtil::base64ToLong($mod);
+ }
+
+ if ($gen !== null) {
+ $gen = Auth_OpenID_CryptUtil::base64ToLong($gen);
+ }
+
+ return new Auth_OpenID_DiffieHellman($mod, $gen);
+ }
+
+ function Auth_OpenID_DiffieHellman($mod = null, $gen = null,
+ $private = null)
+ {
+ global $_Auth_OpenID_DEFAULT_MOD,
+ $_Auth_OpenID_DEFAULT_GEN;
+
+ $this->lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+
+ if (!$this->lib) {
+ // This should NEVER occur because even if no math
+ // extensions can be found, we should get an instance of
+ // Auth_OpenID_MathWrapper, but if there's a bug in
+ // Auth_OpenID_MathLibrary::getLibWrapper, it might.
+ trigger_error("Big integer fallback implementation unavailable.",
+ E_USER_ERROR);
+ }
+
+ if ($mod === null) {
+ $this->mod = $this->lib->init($_Auth_OpenID_DEFAULT_MOD);
+ } else {
+ $this->mod = $mod;
+ }
+
+ if ($gen === null) {
+ $this->gen = $this->lib->init($_Auth_OpenID_DEFAULT_GEN);
+ } else {
+ $this->gen = $gen;
+ }
+
+ $this->private =
+ ($private === null) ? $this->lib->random(1, $this->mod) : $private;
+
+ $this->public = $this->lib->powmod($this->gen, $this->private,
+ $this->mod);
+ }
+
+ function getSharedSecret($composite)
+ {
+ return $this->lib->powmod($composite, $this->private, $this->mod);
+ }
+
+ function getPublicKey()
+ {
+ return $this->public;
+ }
+
+ function xorSecret($composite, $secret)
+ {
+ $dh_shared = $this->getSharedSecret($composite);
+ $sha1_dh_shared = Auth_OpenID_CryptUtil::sha1(
+ Auth_OpenID_CryptUtil::longToBinary($dh_shared));
+ return Auth_OpenID_CryptUtil::strxor($secret, $sha1_dh_shared);
+ }
+}
diff --git a/Auth/OpenID/HMACSHA1.php b/Auth/OpenID/HMACSHA1.php
new file mode 100644
index 0000000..9873f9a
--- /dev/null
+++ b/Auth/OpenID/HMACSHA1.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This is the HMACSHA1 implementation for the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 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('SHA1_BLOCKSIZE', 64);
+
+if (!function_exists('sha1')) {
+ // XXX: include the SHA1 code from Dan Libby's OpenID library
+ trigger_error('No SHA1 function found', E_USER_ERROR);
+} else {
+ function Auth_OpenID_sha1_raw($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.
+ *
+ * @ignore
+ */
+function Auth_OpenID_HMACSHA1($key, $text)
+{
+ if (strlen($key) > SHA1_BLOCKSIZE) {
+ $key = Auth_OpenID_sha1_raw($key, true);
+ }
+
+ $key = str_pad($key, SHA1_BLOCKSIZE, chr(0x00));
+ $ipad = str_repeat(chr(0x36), SHA1_BLOCKSIZE);
+ $opad = str_repeat(chr(0x5c), SHA1_BLOCKSIZE);
+ $hash1 = Auth_OpenID_sha1_raw(($key ^ $ipad) . $text, true);
+ $hmac = Auth_OpenID_sha1_raw(($key ^ $opad) . $hash1, true);
+ return $hmac;
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/KVForm.php b/Auth/OpenID/KVForm.php
new file mode 100644
index 0000000..1b690e5
--- /dev/null
+++ b/Auth/OpenID/KVForm.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * This is the KVForm module.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * The Auth_OpenID_KVForm class.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_KVForm {
+ function arrayToKV($values)
+ {
+ if ($values === null) {
+ return null;
+ }
+
+ $serialized = '';
+ foreach ($values as $key => $value) {
+ if (is_array($value)) {
+ list($key, $value) = $value;
+ }
+
+ if (strpos($key, ':') !== false) {
+ trigger_error('":" in key:' . addslashes($key),
+ E_USER_WARNING);
+ return null;
+ }
+
+ if (strpos($key, "\n") !== false) {
+ trigger_error('"\n" in key:' . addslashes($key),
+ E_USER_WARNING);
+ return null;
+ }
+
+ if (strpos($value, "\n") !== false) {
+ trigger_error('"\n" in value:' . addslashes($value),
+ E_USER_WARNING);
+ return null;
+ }
+ $serialized .= "$key:$value\n";
+ }
+ return $serialized;
+ }
+
+ function kvToArray($kvs)
+ {
+ $lines = explode("\n", $kvs);
+
+ $last = array_pop($lines);
+ if ($last !== '') {
+ trigger_error('No newline at end of kv string:' . addslashes($kvs),
+ E_USER_WARNING);
+ array_push($lines, $last);
+ }
+
+ $values = array();
+
+ for ($lineno = 0; $lineno < count($lines); $lineno++) {
+ $line = $lines[$lineno];
+ $kv = explode(':', $line, 2);
+ if (count($kv) != 2) {
+ $esc = addslashes($line);
+ trigger_error("No colon on line $lineno: $esc",
+ E_USER_WARNING);
+ continue;
+ }
+
+ $key = $kv[0];
+ $tkey = trim($key);
+ if ($tkey != $key) {
+ $esc = addslashes($key);
+ trigger_error("Whitespace in key on line $lineno: '$esc'",
+ E_USER_WARNING);
+ }
+
+ $value = $kv[1];
+ $tval = trim($value);
+ if ($tval != $value) {
+ $esc = addslashes($value);
+ trigger_error("Whitespace in value on line $lineno: '$esc'",
+ E_USER_WARNING);
+ }
+
+ $values[$tkey] = $tval;
+ }
+
+ return $values;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/OIDUtil.php b/Auth/OpenID/OIDUtil.php
new file mode 100644
index 0000000..5a8c3b6
--- /dev/null
+++ b/Auth/OpenID/OIDUtil.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * OIDUtil: URL manipulation utility functions for the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Some constants for string checking.
+ */
+$_Auth_OpenID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+$_Auth_OpenID_digits = "0123456789";
+$_Auth_OpenID_punct = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+
+/**
+ * Convenience function for getting array values.
+ */
+function Auth_OpenID_array_get($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_array_get expected " .
+ "array as first parameter", E_USER_WARNING);
+ return false;
+ }
+}
+
+
+/**
+ * Prints the specified message using trigger_error(E_USER_NOTICE).
+ */
+function Auth_OpenID_log($message, $unused_level = 0)
+{
+ trigger_error($message, E_USER_NOTICE);
+}
+
+/**
+ * Implements the PHP 5 'http_build_query' functionality.
+ *
+ * @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 Auth_OpenID_http_build_query($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 Auth_OpenID_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_http_build_query($args);
+}
+
+/**
+ * Converts the specified string to a base64 representation.
+ */
+function Auth_OpenID_toBase64($s)
+{
+ return base64_encode($s);
+}
+
+/**
+ * Returns the original string representation of the specified
+ * base64-encoded string.
+ */
+function Auth_OpenID_fromBase64($s)
+{
+ return base64_decode($s);
+}
+
+/**
+ * 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
+ */
+function Auth_OpenID_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.
+ *
+ * @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 Auth_OpenID_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.
+ *
+ * @param string $url The URL to be normalized.
+ * @return mixed $new_url The URL after normalization, or null if $url
+ * was malformed.
+ */
+function Auth_OpenID_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('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/Auth/OpenID/Store/DumbStore.php b/Auth/OpenID/Store/DumbStore.php
new file mode 100644
index 0000000..b7ef9c2
--- /dev/null
+++ b/Auth/OpenID/Store/DumbStore.php
@@ -0,0 +1,117 @@
+<?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('Interface.php');
+require_once('Auth/OpenID/CryptUtil.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 (though only within the
+ * lifespan of the tokens), 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 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 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_CryptUtil::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 during the lifespan of the token.
+ */
+ 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/Auth/OpenID/Store/FileStore.php b/Auth/OpenID/Store/FileStore.php
new file mode 100644
index 0000000..9505dc9
--- /dev/null
+++ b/Auth/OpenID/Store/FileStore.php
@@ -0,0 +1,652 @@
+<?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('Interface.php');
+require_once('Auth/OpenID/OIDUtil.php');
+
+function Auth_OpenID_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_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;
+ }
+}
+
+function Auth_OpenID_mkstemp($dir)
+{
+ foreach (range(0, 4) as $i) {
+ $name = tempnam($dir, "php_openid_filestore_");
+
+ if ($name !== false) {
+ return $name;
+ }
+ }
+ return false;
+}
+
+function Auth_OpenID_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;
+}
+
+function Auth_OpenID_listdir($dir)
+{
+ $handle = opendir($dir);
+ $files = array();
+ while (false !== ($filename = readdir($handle))) {
+ $files[] = $filename;
+ }
+ return $files;
+}
+
+function _isFilenameSafe($char)
+{
+ global $_Auth_OpenID_letters, $_Auth_OpenID_digits;
+ $_Auth_OpenID_filename_allowed = $_Auth_OpenID_letters .
+ $_Auth_OpenID_digits . ".";
+ return (strpos($_Auth_OpenID_filename_allowed, $char) !== false);
+}
+
+function _safe64($str)
+{
+ $h64 = Auth_OpenID_toBase64(Auth_OpenID_CryptUtil::sha1($str));
+ $h64 = str_replace('+', '_', $h64);
+ $h64 = str_replace('/', '.', $h64);
+ $h64 = str_replace('=', '', $h64);
+ return $h64;
+}
+
+function _filenameEscape($str)
+{
+ $filename = "";
+ for ($i = 0; $i < strlen($str); $i++) {
+ $c = $str[$i];
+ if (_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.
+ *
+ * @return bool $result True if the file was present, false if not.
+ */
+function _removeIfPresent($filename)
+{
+ return @unlink($filename);
+}
+
+/**
+ * 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.
+ */
+function _ensureDir($dir_name)
+{
+ if (@mkdir($dir_name) || is_dir($dir_name)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 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.
+ *
+ * Methods of this object can raise OSError if unexpected filesystem
+ * conditions, such as bad permissions or missing directories, occur.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Initializes a new FileOpenIDStore. 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)
+ {
+ $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
+
+ $this->_setup();
+ }
+
+ function destroy()
+ {
+ Auth_OpenID_rmtree($this->directory);
+ $this->active = false;
+ }
+
+ /**
+ * Make sure that the directories in which we store our data
+ * exist.
+ *
+ * @access private
+ */
+ function _setup()
+ {
+ _ensureDir(dirname($this->auth_key_name));
+ _ensureDir($this->nonce_dir);
+ _ensureDir($this->association_dir);
+ _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_mkstemp($dir = $this->temp_dir);
+ $file_obj = @fopen($name, 'wb');
+ if ($file_obj !== false) {
+ return array($file_obj, $name);
+ } else {
+ _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);
+
+ if (!link($tmp, $this->auth_key_name)) {
+ // 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();
+
+ if (!$auth_key) {
+ return null;
+ } else {
+ _removeIfPresent($tmp);
+ }
+ }
+
+ 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 = _filenameEscape($parts[0]);
+ $url_hash = _safe64($server_url);
+ if ($handle) {
+ $handle_hash = _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 null;
+ }
+
+ $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 null;
+ }
+
+ fwrite($tmp_file, $association_s);
+
+ fflush($tmp_file);
+
+ fclose($tmp_file);
+
+ if (!rename($tmp, $filename)) {
+ // We only expect EEXIST to happen only on Windows. It's
+ // possible that we will succeed in unlinking the existing
+ // file, but not in putting the temporary file in place.
+ unlink($filename);
+
+ // Now the target should not exist. Try renaming again,
+ // giving up if it fails.
+ if (!rename($tmp, $filename)) {
+ _removeIfPresent($tmp);
+ return null;
+ }
+ }
+
+ // If there was an error, don't leave the temporary file
+ // around.
+ _removeIfPresent($tmp);
+ }
+
+ /**
+ * Retrieve an association. If no handle is specified, return the
+ * association with the latest expiration.
+ *
+ * @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_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) {
+ _removeIfPresent($filename);
+ return null;
+ }
+
+ if ($association->getExpiresIn() == 0) {
+ _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 _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_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) {
+ _removeIfPresent($filename);
+ }
+ }
+ }
+
+ $association_filenames = Auth_OpenID_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) {
+ _removeIfPresent($association_filename);
+ } else {
+ if ($association->getExpiresIn() == 0) {
+ _removeIfPresent($association_filename);
+ }
+ }
+ }
+ }
+ }
+}
+
+?>
diff --git a/Auth/OpenID/Store/Interface.php b/Auth/OpenID/Store/Interface.php
new file mode 100644
index 0000000..0500e44
--- /dev/null
+++ b/Auth/OpenID/Store/Interface.php
@@ -0,0 +1,179 @@
+<?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.
+ *
+ * @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 $AUTH_KEY_LEN bytes long.
+ *
+ * @return string The key. It should be $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 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;
+ }
+}
+?> \ No newline at end of file
diff --git a/Auth/OpenID/Store/SQLStore.php b/Auth/OpenID/Store/SQLStore.php
new file mode 100644
index 0000000..04945ed
--- /dev/null
+++ b/Auth/OpenID/Store/SQLStore.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * A base class for 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
+ */
+
+?>