summaryrefslogtreecommitdiffstats
path: root/Auth/OpenID/Consumer.php
diff options
context:
space:
mode:
authorJosh Hoyt <josh@janrain.com>2006-02-10 22:32:06 +0000
committerJosh Hoyt <josh@janrain.com>2006-02-10 22:32:06 +0000
commit7b027571294dd9787b8afc57b19e35b27b85a61c (patch)
tree02a8f8889c313ab435b2bbfd81b288ca758a6858 /Auth/OpenID/Consumer.php
parent6f7f603b7ac9a96dabd5c01ccdd0c610389dae5c (diff)
downloadphp-openid-7b027571294dd9787b8afc57b19e35b27b85a61c.zip
php-openid-7b027571294dd9787b8afc57b19e35b27b85a61c.tar.gz
php-openid-7b027571294dd9787b8afc57b19e35b27b85a61c.tar.bz2
[project @ Move Consumer.php out of its subdirectory]
Diffstat (limited to 'Auth/OpenID/Consumer.php')
-rw-r--r--Auth/OpenID/Consumer.php853
1 files changed, 853 insertions, 0 deletions
diff --git a/Auth/OpenID/Consumer.php b/Auth/OpenID/Consumer.php
new file mode 100644
index 0000000..39b2edf
--- /dev/null
+++ b/Auth/OpenID/Consumer.php
@@ -0,0 +1,853 @@
+<?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_AuthenticationRequest instance. Otherwise it will
+ * provide some useful information for giving the user an error
+ * message.
+ *
+ * Now that you have the Auth_OpenID_AuthenticationRequest 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_AuthenticationRequest 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/HMACSHA1.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/AuthenticationRequest.php";
+require_once "Auth/OpenID/Consumer/Fetchers.php";
+require_once "Auth/OpenID/Consumer/Parse.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/Util.php";
+
+/**
+ * This is the status code returned when either the of the beginAuth
+ * or completeAuth methods return successfully.
+ */
+define('Auth_OpenID_SUCCESS', 'success');
+
+/**
+ * This is the status code completeAuth returns when the value it
+ * received indicated an invalid login.
+ */
+define('Auth_OpenID_FAILURE', 'failure');
+
+/**
+ * This is the status code completeAuth returns when the
+ * Auth_OpenID_Consumer instance is in immediate mode, and the identity
+ * server sends back a URL to send the user to to complete his or her
+ * login.
+ */
+define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
+
+/**
+ * This is the status code beginAuth returns when it is unable to
+ * fetch the OpenID URL the user entered.
+ */
+define('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.
+*/
+define('Auth_OpenID_PARSE_ERROR', 'parse error');
+
+/**
+ * This is the characters that the nonces are made from.
+ */
+define('Auth_OpenID_DEFAULT_NONCE_CHRS',"abcdefghijklmnopqrstuvwxyz" .
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+/**
+ * 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.
+ */
+define('Auth_OpenID_DEFAULT_TOKEN_LIFETIME', 60 * 5); // five minutes
+
+/**
+ * This is the number of characters in the generated nonce for each
+ * transaction.
+ */
+define('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 consumer's store object.
+ */
+ var $store;
+
+ /**
+ * @access private
+ */
+ var $_use_assocs;
+
+ /**
+ * This consumer's HTTP fetcher object.
+ */
+ var $fetcher;
+
+ /**
+ * The consumer's mode.
+ */
+ var $mode;
+
+ /**
+ * What characters are allowed in nonces
+ */
+ var $nonce_chrs = Auth_OpenID_DEFAULT_NONCE_CHRS;
+
+ /**
+ * How long should an authentication session stay good?
+ *
+ * In units of sections. Shorter times mean less opportunity for
+ * attackers, longer times mean less chance of a user's session
+ * timing out.
+ */
+ var $token_lifetime = Auth_OpenID_DEFAULT_TOKEN_LIFETIME;
+
+ /**
+ * 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)
+ {
+ if ($store === null) {
+ trigger_error("Must supply non-null store to create consumer",
+ E_USER_ERROR);
+ return null;
+ }
+
+ $this->store =& $store;
+ $this->_use_assocs =
+ !(defined('Auth_OpenID_NO_MATH_SUPPORT') ||
+ $this->store->isDumb());
+
+ if ($fetcher === null) {
+ $this->fetcher = Auth_OpenID_getHTTPFetcher();
+ } else {
+ $this->fetcher =& $fetcher;
+ }
+ }
+
+ /**
+ * 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_AuthenticationRequest. 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)
+ {
+ list($status, $info) = $this->fetcher->findIdentityInfo($user_url);
+ if ($status != Auth_OpenID_SUCCESS) {
+ return array($status, $info);
+ }
+
+ list($user_url, $server_id, $server_url) = $info;
+ $nonce = $this->_generateNonce();
+ $token = $this->_genToken($nonce, $user_url, $server_id, $server_url);
+
+ $req = new Auth_OpenID_AuthenticationRequest
+ ($token, $server_id, $server_url, $nonce);
+
+ return array(Auth_OpenID_SUCCESS, $req);
+ }
+
+ /**
+ * 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_AuthenticationRequest $auth_request This
+ * must be a Auth_OpenID_AuthenticationRequest 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,
+ $immediate = false)
+ {
+ $assoc = $this->_getAssociation($auth_request->server_url,
+ $replace = 1);
+
+ if ($assoc === null && $this->_use_assocs) {
+ $msg = "Could not get association for redirection";
+ trigger_error($msg, E_USER_WARNING);
+ return null;
+ }
+
+ $mode = $immediate ? 'checkid_immediate' : 'checkid_setup';
+ $redir_args = array(
+ 'openid.identity' => $auth_request->server_id,
+ 'openid.return_to' => $return_to,
+ 'openid.trust_root' => $trust_root,
+ 'openid.mode' => $mode,
+ );
+
+ if ($assoc !== null) {
+ $redir_args['openid.assoc_handle'] = $assoc->handle;
+ }
+
+ $this->store->storeNonce($auth_request->nonce);
+ return Auth_OpenID_appendArgs($auth_request->server_url, $redir_args);
+ }
+
+ /**
+ * 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)
+ {
+ $query = Auth_OpenID_fixArgs($query);
+
+ $mode = Auth_OpenID_arrayGet($query, 'openid.mode', '');
+
+ if ($mode == 'cancel') {
+ return array(Auth_OpenID_SUCCESS, null);
+ } elseif ($mode == 'error') {
+
+ $error = Auth_OpenID_arrayGet($query, 'openid.error', null);
+
+ if ($error !== null) {
+ trigger_error("In OpenID completeAuth: $error", E_USER_NOTICE);
+ } else {
+ trigger_error("Error response from server", E_USER_NOTICE);
+ }
+ return array(Auth_OpenID_FAILURE, null);
+ } elseif ($mode == 'id_res') {
+ return $this->_doIdRes($token, $query);
+ } else {
+ trigger_error("No openid.mode in response from server",
+ E_USER_NOTICE);
+ return array(Auth_OpenID_FAILURE, null);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _doIdRes($token, $query)
+ {
+ $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_arrayGet($query, 'openid.return_to', null);
+ $server_id2 = Auth_OpenID_arrayGet($query, 'openid.identity', null);
+ $assoc_handle = Auth_OpenID_arrayGet($query,
+ 'openid.assoc_handle', null);
+
+ if (($return_to === null) ||
+ ($server_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_arrayGet($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_arrayGet($query, 'openid.sig', null);
+ $signed = Auth_OpenID_arrayGet($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)
+ {
+ $signed = Auth_OpenID_arrayGet($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_kvToArray($ret[2]);
+ $is_valid = Auth_OpenID_arrayGet($results, 'is_valid', 'false');
+
+ if ($is_valid == 'true') {
+ $invalidate_handle = Auth_OpenID_arrayGet($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_arrayGet($results, 'error', null);
+ if ($error !== null) {
+ $msg = sprintf("Error message from server during " .
+ "check_authentication: %s", $error);
+ trigger_error($msg, E_USER_NOTICE);
+ }
+
+ return Auth_OpenID_FAILURE;
+ }
+
+ /**
+ * @access protected
+ */
+ function _createDiffieHellman()
+ {
+ return new Auth_OpenID_DiffieHellman();
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($server_url, $replace = false)
+ {
+ if (!$this->_use_assocs) {
+ return null;
+ }
+
+ $assoc = $this->store->getAssociation($server_url);
+
+ if (($assoc === null) ||
+ ($replace && ($assoc->getExpiresIn() < $this->token_lifetime))) {
+
+ $args = array(
+ 'openid.mode' => 'associate',
+ 'openid.assoc_type' => 'HMAC-SHA1',
+ );
+
+ $dh = $this->_createDiffieHellman();
+ $args = array_merge($args, $dh->getAssocArgs());
+ $body = Auth_OpenID_http_build_query($args);
+
+ $assoc = $this->_fetchAssociation($dh, $server_url, $body);
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * @static
+ * @access private
+ */
+ function _generateNonce()
+ {
+ return Auth_OpenID_randomString(Auth_OpenID_NONCE_LEN,
+ $this->nonce_chrs);
+ }
+
+ /**
+ * @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_HMACSHA1($this->store->getAuthKey(),
+ $joined);
+
+ return base64_encode($sig . $joined);
+ }
+
+ /**
+ * @access private
+ */
+ function _splitToken($token)
+ {
+ $token = base64_decode($token);
+ if (strlen($token) < 20) {
+ return null;
+ }
+
+ $sig = substr($token, 0, 20);
+ $joined = substr($token, 20);
+ $check_sig = Auth_OpenID_HMACSHA1($this->store->getAuthKey(), $joined);
+ if ($check_sig != $sig) {
+ return null;
+ }
+
+ $split = explode("\x00", $joined);
+ if (count($split) != 5) {
+ return null;
+ }
+
+ $ts = intval($split[0]);
+ if ($ts == 0) {
+ return null;
+ }
+
+ if ($ts + $this->token_lifetime < time()) {
+ return null;
+ }
+
+ return array_slice($split, 1);
+ }
+
+ /**
+ * @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';
+ trigger_error(sprintf($fmt, $server_url), E_USER_NOTICE);
+ return null;
+ }
+
+ list($http_code, $url, $data) = $ret;
+ $results = Auth_OpenID_kvToArray($data);
+ if ($http_code == 400) {
+ $error = Auth_OpenID_arrayGet($results, 'error',
+ '<no message from server>');
+
+ $fmt = 'Getting association: error returned from server %s: %s';
+ trigger_error(sprintf($fmt, $server_url, $error), E_USER_NOTICE);
+ return null;
+ } else if ($http_code != 200) {
+ $fmt = 'Getting association: bad status code from server %s: %s';
+ $msg = sprintf($fmt, $server_url, $http_code);
+ trigger_error($msg, E_USER_NOTICE);
+ return null;
+ }
+
+ $results = Auth_OpenID_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)) {
+ $fmt = "associate: missing key in response from %s: %s";
+ $msg = sprintf($fmt, $server_url, $key);
+ trigger_error($msg, E_USER_NOTICE);
+ return null;
+ }
+ }
+
+ $assoc_type = $results['assoc_type'];
+ if ($assoc_type != 'HMAC-SHA1') {
+ $fmt = 'Unsupported assoc_type returned from server %s: %s';
+ $msg = sprintf($fmt, $server_url, $assoc_type);
+ trigger_error($msg, E_USER_NOTICE);
+ return null;
+ }
+
+ $assoc_handle = $results['assoc_handle'];
+ $expires_in = intval(Auth_OpenID_arrayGet($results, 'expires_in',
+ '0'));
+
+ $session_type = Auth_OpenID_arrayGet($results, 'session_type', null);
+ if ($session_type === null) {
+ $secret = base64_decode($results['mac_key']);
+ } else {
+ $fmt = 'Unsupported session_type returned from server %s: %s';
+ if ($session_type != 'DH-SHA1') {
+ $msg = sprintf($fmt, $server_url, $session_type);
+ trigger_error($msg, E_USER_NOTICE);
+ return null;
+ }
+
+ $secret = $dh->consumerFinish($results);
+ }
+
+ $assoc = Auth_OpenID_Association::fromExpiresIn($expires_in,
+ $assoc_handle,
+ $secret,
+ $assoc_type);
+
+ $this->store->storeAssociation($server_url, $assoc);
+ return $assoc;
+ }
+}
+
+?>