<?php
/**
 * OpenID server protocol and logic.
 * 
 * Overview
 *
 * An OpenID server must perform three tasks:
 *
 *  1. Examine the incoming request to determine its nature and validity.
 *  2. Make a decision about how to respond to this request.
 *  3. Format the response according to the protocol.
 * 
 * The first and last of these tasks may performed by the {@link
 * Auth_OpenID_Server::decodeRequest()} and {@link
 * Auth_OpenID_Server::encodeResponse} methods.  Who gets to do the
 * intermediate task -- deciding how to respond to the request -- will
 * depend on what type of request it is.
 *
 * If it's a request to authenticate a user (a 'checkid_setup' or
 * 'checkid_immediate' request), you need to decide if you will assert
 * that this user may claim the identity in question.  Exactly how you
 * do that is a matter of application policy, but it generally
 * involves making sure the user has an account with your system and
 * is logged in, checking to see if that identity is hers to claim,
 * and verifying with the user that she does consent to releasing that
 * information to the party making the request.
 *
 * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
 * object, and if and when you've come to a decision, form a response
 * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
 *
 * Other types of requests relate to establishing associations between
 * client and server and verifing the authenticity of previous
 * communications.  {@link Auth_OpenID_Server} contains all the logic
 * and data necessary to respond to such requests; just pass it to
 * {@link Auth_OpenID_Server::handleRequest()}.
 *
 * OpenID Extensions
 * 
 * Do you want to provide other information for your users in addition
 * to authentication?  Version 1.2 of the OpenID protocol allows
 * consumers to add extensions to their requests.  For example, with
 * sites using the Simple Registration
 * Extension
 * (http://www.openidenabled.com/openid/simple-registration-extension/),
 * a user can agree to have their nickname and e-mail address sent to
 * a site when they sign up.
 *
 * Since extensions do not change the way OpenID authentication works,
 * code to handle extension requests may be completely separate from
 * the {@link Auth_OpenID_Request} class here.  But you'll likely want
 * data sent back by your extension to be signed.  {@link
 * Auth_OpenID_ServerResponse} provides methods with which you can add
 * data to it which can be signed with the other data in the OpenID
 * signature.
 *
 * For example:
 *
 * <pre>  // when request is a checkid_* request
 *  $response = $request->answer(true);
 *  // this will a signed 'openid.sreg.timezone' parameter to the response
 *  response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
 *
 * Stores
 *
 * The OpenID server needs to maintain state between requests in order
 * to function.  Its mechanism for doing this is called a store.  The
 * store interface is defined in Interface.php.  Additionally, several
 * concrete store implementations are provided, so that most sites
 * won't need to implement a custom store.  For a store backed by flat
 * files on disk, see {@link Auth_OpenID_FileStore}.  For stores based
 * on MySQL, SQLite, or PostgreSQL, see the {@link
 * Auth_OpenID_SQLStore} subclasses.
 *
 * Upgrading
 *
 * The keys by which a server looks up associations in its store have
 * changed in version 1.2 of this library.  If your store has entries
 * created from version 1.0 code, you should empty it.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * Required imports
 */
require_once "Auth/OpenID.php";
require_once "Auth/OpenID/Association.php";
require_once "Auth/OpenID/CryptUtil.php";
require_once "Auth/OpenID/BigMath.php";
require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/KVForm.php";
require_once "Auth/OpenID/TrustRoot.php";
require_once "Auth/OpenID/ServerRequest.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/Nonce.php";

define('AUTH_OPENID_HTTP_OK', 200);
define('AUTH_OPENID_HTTP_REDIRECT', 302);
define('AUTH_OPENID_HTTP_ERROR', 400);

/**
 * @access private
 */
global $_Auth_OpenID_Request_Modes;
$_Auth_OpenID_Request_Modes = array('checkid_setup', 'checkid_immediate');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_URL', 'URL/redirect');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');

/**
 * @access private
 */
function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError') {
  return is_a($obj, $cls);
}

/**
 * An error class which gets instantiated and returned whenever an
 * OpenID protocol error occurs.  Be prepared to use this in place of
 * an ordinary server response.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerError {

  /**
   * @access private
   */
  function Auth_OpenID_ServerError($message = null, $text = null, $reference = null, $contact = null) {
    $this->message = $message;
    $this->text = $text;
    $this->contact = $contact;
    $this->reference = $reference;
  }

  function getReturnTo() {
    if ($this->message && $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
      return $this->message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
    } else {
      return null;
    }
  }

  /**
   * Returns the return_to URL for the request which caused this
   * error.
   */
  function hasReturnTo() {
    return $this->getReturnTo() !== null;
  }

  /**
   * Encodes this error's response as a URL suitable for
   * redirection.  If the response has no return_to, another
   * Auth_OpenID_ServerError is returned.
   */
  function encodeToURL() {
    if (! $this->message) {
      return null;
    }
    
    $msg = $this->toMessage();
    return $msg->toURL($this->getReturnTo());
  }

  /**
   * Encodes the response to key-value form.  This is a
   * machine-readable format used to respond to messages which came
   * directly from the consumer and not through the user-agent.  See
   * the OpenID specification.
   */
  function encodeToKVForm() {
    return Auth_OpenID_KVForm::fromArray(array('mode' => 'error', 'error' => $this->toString()));
  }

  function toFormMarkup($form_tag_attrs = null) {
    $msg = $this->toMessage();
    return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
  }

  function toHTML($form_tag_attrs = null) {
    return Auth_OpenID::autoSubmitHTML($this->toFormMarkup($form_tag_attrs));
  }

  function toMessage() {
    // Generate a Message object for sending to the relying party,
    // after encoding.
    $namespace = $this->message->getOpenIDNamespace();
    $reply = new Auth_OpenID_Message($namespace);
    $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
    $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());
    
    if ($this->contact !== null) {
      $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
    }
    
    if ($this->reference !== null) {
      $reply->setArg(Auth_OpenID_OPENID_NS, 'reference', $this->reference);
    }
    
    return $reply;
  }

  /**
   * Returns one of Auth_OpenID_ENCODE_URL,
   * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
   * encoding expected for this error's payload.
   */
  function whichEncoding() {
    global $_Auth_OpenID_Request_Modes;
    
    if ($this->hasReturnTo()) {
      if ($this->message->isOpenID2() && (strlen($this->encodeToURL()) > Auth_OpenID_OPENID1_URL_LIMIT)) {
        return Auth_OpenID_ENCODE_HTML_FORM;
      } else {
        return Auth_OpenID_ENCODE_URL;
      }
    }
    
    if (! $this->message) {
      return null;
    }
    
    $mode = $this->message->getArg(Auth_OpenID_OPENID_NS, 'mode');
    
    if ($mode) {
      if (! in_array($mode, $_Auth_OpenID_Request_Modes)) {
        return Auth_OpenID_ENCODE_KVFORM;
      }
    }
    return null;
  }

  /**
   * Returns this error message.
   */
  function toString() {
    if ($this->text) {
      return $this->text;
    } else {
      return get_class($this) . " error";
    }
  }
}

/**
 * Error returned by the server code when a return_to is absent from a
 * request.
 *
 * @package OpenID
 */
class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {

  function Auth_OpenID_NoReturnToError($message = null, $text = "No return_to URL available") {
    parent::Auth_OpenID_ServerError($message, $text);
  }

  function toString() {
    return "No return_to available";
  }
}

/**
 * An error indicating that the return_to URL is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {

  function Auth_OpenID_MalformedReturnURL($message, $return_to) {
    $this->return_to = $return_to;
    parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
  }
}

/**
 * This error is returned when the trust_root value is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {

  function Auth_OpenID_MalformedTrustRoot($message = null, $text = "Malformed trust root") {
    parent::Auth_OpenID_ServerError($message, $text);
  }

  function toString() {
    return "Malformed trust root";
  }
}

/**
 * The base class for all server request classes.
 *
 * @package OpenID
 */
class Auth_OpenID_Request {
  var $mode = null;
}

/**
 * A request to verify the validity of a previous response.
 *
 * @package OpenID
 */
class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
  var $mode = "check_authentication";
  var $invalidate_handle = null;

  function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed, $invalidate_handle = null) {
    $this->assoc_handle = $assoc_handle;
    $this->signed = $signed;
    if ($invalidate_handle !== null) {
      $this->invalidate_handle = $invalidate_handle;
    }
    $this->namespace = Auth_OpenID_OPENID2_NS;
    $this->message = null;
  }

  function fromMessage($message, $server = null) {
    $required_keys = array('assoc_handle', 'sig', 'signed');
    
    foreach ($required_keys as $k) {
      if (! $message->getArg(Auth_OpenID_OPENID_NS, $k)) {
        return new Auth_OpenID_ServerError($message, sprintf("%s request missing required parameter %s from \
                            query", "check_authentication", $k));
      }
    }
    
    $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
    $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');
    
    $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
    $signed_list = explode(",", $signed_list);
    
    $signed = $message;
    if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
      $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
    }
    
    $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
    $result->message = $message;
    $result->sig = $sig;
    $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'invalidate_handle');
    return $result;
  }

  function answer(&$signatory) {
    $is_valid = $signatory->verify($this->assoc_handle, $this->signed);
    
    // Now invalidate that assoc_handle so it this checkAuth
    // message cannot be replayed.
    $signatory->invalidate($this->assoc_handle, true);
    $response = new Auth_OpenID_ServerResponse($this);
    
    $response->fields->setArg(Auth_OpenID_OPENID_NS, 'is_valid', ($is_valid ? "true" : "false"));
    
    if ($this->invalidate_handle) {
      $assoc = $signatory->getAssociation($this->invalidate_handle, false);
      if (! $assoc) {
        $response->fields->setArg(Auth_OpenID_OPENID_NS, 'invalidate_handle', $this->invalidate_handle);
      }
    }
    return $response;
  }
}

/**
 * A class implementing plaintext server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_PlainTextServerSession {
  /**
   * An object that knows how to handle association requests with no
   * session type.
   */
  var $session_type = 'no-encryption';
  var $needs_math = false;
  var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');

  function fromMessage($unused_request) {
    return new Auth_OpenID_PlainTextServerSession();
  }

  function answer($secret) {
    return array('mac_key' => base64_encode($secret));
  }
}

/**
 * A class implementing DH-SHA1 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA1ServerSession {
  /**
   * An object that knows how to handle association requests with
   * the Diffie-Hellman session type.
   */
  
  var $session_type = 'DH-SHA1';
  var $needs_math = true;
  var $allowed_assoc_types = array('HMAC-SHA1');
  var $hash_func = 'Auth_OpenID_SHA1';

  function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey) {
    $this->dh = $dh;
    $this->consumer_pubkey = $consumer_pubkey;
  }

  function getDH($message) {
    $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
    $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');
    
    if ((($dh_modulus === null) && ($dh_gen !== null)) || (($dh_gen === null) && ($dh_modulus !== null))) {
      
      if ($dh_modulus === null) {
        $missing = 'modulus';
      } else {
        $missing = 'generator';
      }
      
      return new Auth_OpenID_ServerError($message, 'If non-default modulus or generator is ' . 'supplied, both must be supplied.  Missing ' . $missing);
    }
    
    $lib = & Auth_OpenID_getMathLib();
    
    if ($dh_modulus || $dh_gen) {
      $dh_modulus = $lib->base64ToLong($dh_modulus);
      $dh_gen = $lib->base64ToLong($dh_gen);
      if ($lib->cmp($dh_modulus, 0) == 0 || $lib->cmp($dh_gen, 0) == 0) {
        return new Auth_OpenID_ServerError($message, "Failed to parse dh_mod or dh_gen");
      }
      $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
    } else {
      $dh = new Auth_OpenID_DiffieHellman();
    }
    
    $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_consumer_public');
    if ($consumer_pubkey === null) {
      return new Auth_OpenID_ServerError($message, 'Public key for DH-SHA1 session ' . 'not found in query');
    }
    
    $consumer_pubkey = $lib->base64ToLong($consumer_pubkey);
    
    if ($consumer_pubkey === false) {
      return new Auth_OpenID_ServerError($message, "dh_consumer_public is not base64");
    }
    
    return array($dh, $consumer_pubkey);
  }

  function fromMessage($message) {
    $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
    
    if (is_a($result, 'Auth_OpenID_ServerError')) {
      return $result;
    } else {
      list($dh, $consumer_pubkey) = $result;
      return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey);
    }
  }

  function answer($secret) {
    $lib = & Auth_OpenID_getMathLib();
    $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret, $this->hash_func);
    return array('dh_server_public' => $lib->longToBase64($this->dh->public), 
        'enc_mac_key' => base64_encode($mac_key));
  }
}

/**
 * A class implementing DH-SHA256 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA256ServerSession extends Auth_OpenID_DiffieHellmanSHA1ServerSession {
  
  var $session_type = 'DH-SHA256';
  var $hash_func = 'Auth_OpenID_SHA256';
  var $allowed_assoc_types = array('HMAC-SHA256');

  function fromMessage($message) {
    $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
    
    if (is_a($result, 'Auth_OpenID_ServerError')) {
      return $result;
    } else {
      list($dh, $consumer_pubkey) = $result;
      return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh, $consumer_pubkey);
    }
  }
}

/**
 * A request to associate with the server.
 *
 * @package OpenID
 */
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
  var $mode = "associate";

  function getSessionClasses() {
    return array('no-encryption' => 'Auth_OpenID_PlainTextServerSession', 
        'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession', 
        'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
  }

  function Auth_OpenID_AssociateRequest(&$session, $assoc_type) {
    $this->session = & $session;
    $this->namespace = Auth_OpenID_OPENID2_NS;
    $this->assoc_type = $assoc_type;
  }

  function fromMessage($message, $server = null) {
    if ($message->isOpenID1()) {
      $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 'session_type');
      
      if ($session_type == 'no-encryption') {
        // oidutil.log('Received OpenID 1 request with a no-encryption '
      //             'assocaition session type. Continuing anyway.')
      } else if (! $session_type) {
        $session_type = 'no-encryption';
      }
    } else {
      $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 'session_type');
      if ($session_type === null) {
        return new Auth_OpenID_ServerError($message, "session_type missing from request");
      }
    }
    
    $session_class = Auth_OpenID::arrayGet(Auth_OpenID_AssociateRequest::getSessionClasses(), $session_type);
    
    if ($session_class === null) {
      return new Auth_OpenID_ServerError($message, "Unknown session type " . $session_type);
    }
    
    $session = call_user_func(array($session_class, 'fromMessage'), $message);
    if (is_a($session, 'Auth_OpenID_ServerError')) {
      return $session;
    }
    
    $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_type', 'HMAC-SHA1');
    
    if (! in_array($assoc_type, $session->allowed_assoc_types)) {
      $fmt = "Session type %s does not support association type %s";
      return new Auth_OpenID_ServerError($message, sprintf($fmt, $session_type, $assoc_type));
    }
    
    $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
    $obj->message = $message;
    $obj->namespace = $message->getOpenIDNamespace();
    return $obj;
  }

  function answer($assoc) {
    $response = new Auth_OpenID_ServerResponse($this);
    $response->fields->updateArgs(Auth_OpenID_OPENID_NS, array(
        'expires_in' => sprintf('%d', $assoc->getExpiresIn()), 
        'assoc_type' => $this->assoc_type, 'assoc_handle' => $assoc->handle));
    
    $response->fields->updateArgs(Auth_OpenID_OPENID_NS, $this->session->answer($assoc->secret));
    
    if (! ($this->session->session_type == 'no-encryption' && $this->message->isOpenID1())) {
      $response->fields->setArg(Auth_OpenID_OPENID_NS, 'session_type', $this->session->session_type);
    }
    
    return $response;
  }

  function answerUnsupported($text_message, $preferred_association_type = null, $preferred_session_type = null) {
    if ($this->message->isOpenID1()) {
      return new Auth_OpenID_ServerError($this->message);
    }
    
    $response = new Auth_OpenID_ServerResponse($this);
    $response->fields->setArg(Auth_OpenID_OPENID_NS, 'error_code', 'unsupported-type');
    $response->fields->setArg(Auth_OpenID_OPENID_NS, 'error', $text_message);
    
    if ($preferred_association_type) {
      $response->fields->setArg(Auth_OpenID_OPENID_NS, 'assoc_type', $preferred_association_type);
    }
    
    if ($preferred_session_type) {
      $response->fields->setArg(Auth_OpenID_OPENID_NS, 'session_type', $preferred_session_type);
    }
    
    return $response;
  }
}

/**
 * A request to confirm the identity of a user.
 *
 * @package OpenID
 */
class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
  /**
   * Return-to verification callback.  Default is
   * Auth_OpenID_verifyReturnTo from TrustRoot.php.
   */
  var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
  
  /**
   * The mode of this request.
   */
  var $mode = "checkid_setup"; // or "checkid_immediate"
  

  /**
   * Whether this request is for immediate mode.
   */
  var $immediate = false;
  
  /**
   * The trust_root value for this request.
   */
  var $trust_root = null;
  
  /**
   * The OpenID namespace for this request.
   * deprecated since version 2.0.2
   */
  var $namespace;

  function make(&$message, $identity, $return_to, $trust_root = null, $immediate = false, $assoc_handle = null, $server = null) {
    if ($server === null) {
      return new Auth_OpenID_ServerError($message, "server must not be null");
    }
    
    if ($return_to && ! Auth_OpenID_TrustRoot::_parse($return_to)) {
      return new Auth_OpenID_MalformedReturnURL($message, $return_to);
    }
    
    $r = new Auth_OpenID_CheckIDRequest($identity, $return_to, $trust_root, $immediate, $assoc_handle, $server);
    
    $r->namespace = $message->getOpenIDNamespace();
    $r->message = & $message;
    
    if (! $r->trustRootValid()) {
      return new Auth_OpenID_UntrustedReturnURL($message, $return_to, $trust_root);
    } else {
      return $r;
    }
  }

  function Auth_OpenID_CheckIDRequest($identity, $return_to, $trust_root = null, $immediate = false, $assoc_handle = null, $server = null, $claimed_id = null) {
    $this->namespace = Auth_OpenID_OPENID2_NS;
    $this->assoc_handle = $assoc_handle;
    $this->identity = $identity;
    if ($claimed_id === null) {
      $this->claimed_id = $identity;
    } else {
      $this->claimed_id = $claimed_id;
    }
    $this->return_to = $return_to;
    $this->trust_root = $trust_root;
    $this->server = & $server;
    
    if ($immediate) {
      $this->immediate = true;
      $this->mode = "checkid_immediate";
    } else {
      $this->immediate = false;
      $this->mode = "checkid_setup";
    }
  }

  function equals($other) {
    return ((is_a($other, 'Auth_OpenID_CheckIDRequest')) && ($this->namespace == $other->namespace) && ($this->assoc_handle == $other->assoc_handle) && ($this->identity == $other->identity) && ($this->claimed_id == $other->claimed_id) && ($this->return_to == $other->return_to) && ($this->trust_root == $other->trust_root));
  }

  /*
     * Does the relying party publish the return_to URL for this
     * response under the realm? It is up to the provider to set a
     * policy for what kinds of realms should be allowed. This
     * return_to URL verification reduces vulnerability to data-theft
     * attacks based on open proxies, corss-site-scripting, or open
     * redirectors.
     *
     * This check should only be performed after making sure that the
     * return_to URL matches the realm.
     *
     * @return true if the realm publishes a document with the
     * return_to URL listed, false if not or if discovery fails
     */
  function returnToVerified() {
    return call_user_func_array($this->verifyReturnTo, array($this->trust_root, $this->return_to));
  }

  function fromMessage(&$message, $server) {
    $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
    $immediate = null;
    
    if ($mode == "checkid_immediate") {
      $immediate = true;
      $mode = "checkid_immediate";
    } else {
      $immediate = false;
      $mode = "checkid_setup";
    }
    
    $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
    
    if (($message->isOpenID1()) && (! $return_to)) {
      $fmt = "Missing required field 'return_to' from checkid request";
      return new Auth_OpenID_ServerError($message, $fmt);
    }
    
    $identity = $message->getArg(Auth_OpenID_OPENID_NS, 'identity');
    $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
    if ($message->isOpenID1()) {
      if ($identity === null) {
        $s = "OpenID 1 message did not contain openid.identity";
        return new Auth_OpenID_ServerError($message, $s);
      }
    } else {
      if ($identity && ! $claimed_id) {
        $s = "OpenID 2.0 message contained openid.identity but not " . "claimed_id";
        return new Auth_OpenID_ServerError($message, $s);
      } else if ($claimed_id && ! $identity) {
        $s = "OpenID 2.0 message contained openid.claimed_id " . "but not identity";
        return new Auth_OpenID_ServerError($message, $s);
      }
    }
    
    // There's a case for making self.trust_root be a TrustRoot
    // here.  But if TrustRoot isn't currently part of the
    // "public" API, I'm not sure it's worth doing.
    if ($message->isOpenID1()) {
      $trust_root_param = 'trust_root';
    } else {
      $trust_root_param = 'realm';
    }
    $trust_root = $message->getArg(Auth_OpenID_OPENID_NS, $trust_root_param);
    if (! $trust_root) {
      $trust_root = $return_to;
    }
    
    if (! $message->isOpenID1() && ($return_to === null) && ($trust_root === null)) {
      return new Auth_OpenID_ServerError($message, "openid.realm required when openid.return_to absent");
    }
    
    $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
    
    $obj = Auth_OpenID_CheckIDRequest::make($message, $identity, $return_to, $trust_root, $immediate, $assoc_handle, $server);
    
    if (is_a($obj, 'Auth_OpenID_ServerError')) {
      return $obj;
    }
    
    $obj->claimed_id = $claimed_id;
    
    return $obj;
  }

  function idSelect() {
    // Is the identifier to be selected by the IDP?
    // So IDPs don't have to import the constant
    return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
  }

  function trustRootValid() {
    if (! $this->trust_root) {
      return true;
    }
    
    $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
    if ($tr === false) {
      return new Auth_OpenID_MalformedTrustRoot($this->message, $this->trust_root);
    }
    
    if ($this->return_to !== null) {
      return Auth_OpenID_TrustRoot::match($this->trust_root, $this->return_to);
    } else {
      return true;
    }
  }

  /**
   * Respond to this request.  Return either an
   * {@link Auth_OpenID_ServerResponse} or
   * {@link Auth_OpenID_ServerError}.
   *
   * @param bool $allow Allow this user to claim this identity, and
   * allow the consumer to have this information?
   *
   * @param string $server_url DEPRECATED.  Passing $op_endpoint to
   * the {@link Auth_OpenID_Server} constructor makes this optional.
   *
   * When an OpenID 1.x immediate mode request does not succeed, it
   * gets back a URL where the request may be carried out in a
   * not-so-immediate fashion.  Pass my URL in here (the fully
   * qualified address of this server's endpoint, i.e.
   * http://example.com/server), and I will use it as a base for the
   * URL for a new request.
   *
   * Optional for requests where {@link $immediate} is false or
   * $allow is true.
   *
   * @param string $identity The OP-local identifier to answer with.
   * Only for use when the relying party requested identifier
   * selection.
   *
   * @param string $claimed_id The claimed identifier to answer
   * with, for use with identifier selection in the case where the
   * claimed identifier and the OP-local identifier differ,
   * i.e. when the claimed_id uses delegation.
   *
   * If $identity is provided but this is not, $claimed_id will
   * default to the value of $identity.  When answering requests
   * that did not ask for identifier selection, the response
   * $claimed_id will default to that of the request.
   *
   * This parameter is new in OpenID 2.0.
   *
   * @return mixed
   */
  function answer($allow, $server_url = null, $identity = null, $claimed_id = null) {
    if (! $this->return_to) {
      return new Auth_OpenID_NoReturnToError();
    }
    
    if (! $server_url) {
      if ((! $this->message->isOpenID1()) && (! $this->server->op_endpoint)) {
        return new Auth_OpenID_ServerError(null, "server should be constructed with op_endpoint to " . "respond to OpenID 2.0 messages.");
      }
      
      $server_url = $this->server->op_endpoint;
    }
    
    if ($allow) {
      $mode = 'id_res';
    } else if ($this->message->isOpenID1()) {
      if ($this->immediate) {
        $mode = 'id_res';
      } else {
        $mode = 'cancel';
      }
    } else {
      if ($this->immediate) {
        $mode = 'setup_needed';
      } else {
        $mode = 'cancel';
      }
    }
    
    if (! $this->trustRootValid()) {
      return new Auth_OpenID_UntrustedReturnURL(null, $this->return_to, $this->trust_root);
    }
    
    $response = new Auth_OpenID_ServerResponse($this);
    
    if ($claimed_id && ($this->message->isOpenID1())) {
      return new Auth_OpenID_ServerError(null, "claimed_id is new in OpenID 2.0 and not " . "available for " . $this->namespace);
    }
    
    if ($identity && ! $claimed_id) {
      $claimed_id = $identity;
    }
    
    if ($allow) {
      
      if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
        if (! $identity) {
          return new Auth_OpenID_ServerError(null, "This request uses IdP-driven identifier selection.  " . "You must supply an identifier in the response.");
        }
        
        $response_identity = $identity;
        $response_claimed_id = $claimed_id;
      
      } else if ($this->identity) {
        if ($identity && ($this->identity != $identity)) {
          $fmt = "Request was for %s, cannot reply with identity %s";
          return new Auth_OpenID_ServerError(null, sprintf($fmt, $this->identity, $identity));
        }
        
        $response_identity = $this->identity;
        $response_claimed_id = $this->claimed_id;
      } else {
        if ($identity) {
          return new Auth_OpenID_ServerError(null, "This request specified no identity and " . "you supplied " . $identity);
        }
        
        $response_identity = null;
      }
      
      if (($this->message->isOpenID1()) && ($response_identity === null)) {
        return new Auth_OpenID_ServerError(null, "Request was an OpenID 1 request, so response must " . "include an identifier.");
      }
      
      $response->fields->updateArgs(Auth_OpenID_OPENID_NS, array('mode' => $mode, 
          'return_to' => $this->return_to, 
          'response_nonce' => Auth_OpenID_mkNonce()));
      
      if (! $this->message->isOpenID1()) {
        $response->fields->setArg(Auth_OpenID_OPENID_NS, 'op_endpoint', $server_url);
      }
      
      if ($response_identity !== null) {
        $response->fields->setArg(Auth_OpenID_OPENID_NS, 'identity', $response_identity);
        if ($this->message->isOpenID2()) {
          $response->fields->setArg(Auth_OpenID_OPENID_NS, 'claimed_id', $response_claimed_id);
        }
      }
    
    } else {
      $response->fields->setArg(Auth_OpenID_OPENID_NS, 'mode', $mode);
      
      if ($this->immediate) {
        if (($this->message->isOpenID1()) && (! $server_url)) {
          return new Auth_OpenID_ServerError(null, 'setup_url is required for $allow=false \
                                  in OpenID 1.x immediate mode.');
        }
        
        $setup_request = new Auth_OpenID_CheckIDRequest($this->identity, $this->return_to, $this->trust_root, false, $this->assoc_handle, $this->server, $this->claimed_id);
        $setup_request->message = $this->message;
        
        $setup_url = $setup_request->encodeToURL($server_url);
        
        if ($setup_url === null) {
          return new Auth_OpenID_NoReturnToError();
        }
        
        $response->fields->setArg(Auth_OpenID_OPENID_NS, 'user_setup_url', $setup_url);
      }
    }
    
    return $response;
  }

  function encodeToURL($server_url) {
    if (! $this->return_to) {
      return new Auth_OpenID_NoReturnToError();
    }
    
    // Imported from the alternate reality where these classes are
    // used in both the client and server code, so Requests are
    // Encodable too.  That's right, code imported from alternate
    // realities all for the love of you, id_res/user_setup_url.
    

    $q = array('mode' => $this->mode, 'identity' => $this->identity, 'claimed_id' => $this->claimed_id, 
        'return_to' => $this->return_to);
    
    if ($this->trust_root) {
      if ($this->message->isOpenID1()) {
        $q['trust_root'] = $this->trust_root;
      } else {
        $q['realm'] = $this->trust_root;
      }
    }
    
    if ($this->assoc_handle) {
      $q['assoc_handle'] = $this->assoc_handle;
    }
    
    $response = new Auth_OpenID_Message($this->message->getOpenIDNamespace());
    $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
    return $response->toURL($server_url);
  }

  function getCancelURL() {
    if (! $this->return_to) {
      return new Auth_OpenID_NoReturnToError();
    }
    
    if ($this->immediate) {
      return new Auth_OpenID_ServerError(null, "Cancel is not an appropriate \
                                               response to immediate mode \
                                               requests.");
    }
    
    $response = new Auth_OpenID_Message($this->message->getOpenIDNamespace());
    $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
    return $response->toURL($this->return_to);
  }
}

/**
 * This class encapsulates the response to an OpenID server request.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerResponse {

  function Auth_OpenID_ServerResponse(&$request) {
    $this->request = & $request;
    $this->fields = new Auth_OpenID_Message($this->request->namespace);
  }

  function whichEncoding() {
    global $_Auth_OpenID_Request_Modes;
    
    if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
      if ($this->fields->isOpenID2() && (strlen($this->encodeToURL()) > Auth_OpenID_OPENID1_URL_LIMIT)) {
        return Auth_OpenID_ENCODE_HTML_FORM;
      } else {
        return Auth_OpenID_ENCODE_URL;
      }
    } else {
      return Auth_OpenID_ENCODE_KVFORM;
    }
  }

  /*
     * Returns the form markup for this response.
     *
     * @return str
     */
  function toFormMarkup($form_tag_attrs = null) {
    return $this->fields->toFormMarkup($this->request->return_to, $form_tag_attrs);
  }

  /*
     * Returns an HTML document containing the form markup for this
     * response that autosubmits with javascript.
     */
  function toHTML() {
    return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
  }

  /*
     * Returns True if this response's encoding is ENCODE_HTML_FORM.
     * Convenience method for server authors.
     *
     * @return bool
     */
  function renderAsForm() {
    return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
  }

  function encodeToURL() {
    return $this->fields->toURL($this->request->return_to);
  }

  function addExtension($extension_response) {
    $extension_response->toMessage($this->fields);
  }

  function needsSigning() {
    return $this->fields->getArg(Auth_OpenID_OPENID_NS, 'mode') == 'id_res';
  }

  function encodeToKVForm() {
    return $this->fields->toKVForm();
  }
}

/**
 * A web-capable response object which you can use to generate a
 * user-agent response.
 *
 * @package OpenID
 */
class Auth_OpenID_WebResponse {
  var $code = AUTH_OPENID_HTTP_OK;
  var $body = "";

  function Auth_OpenID_WebResponse($code = null, $headers = null, $body = null) {
    if ($code) {
      $this->code = $code;
    }
    
    if ($headers !== null) {
      $this->headers = $headers;
    } else {
      $this->headers = array();
    }
    
    if ($body !== null) {
      $this->body = $body;
    }
  }
}

/**
 * Responsible for the signature of query data and the verification of
 * OpenID signature values.
 *
 * @package OpenID
 */
class Auth_OpenID_Signatory {
  
  // = 14 * 24 * 60 * 60; # 14 days, in seconds
  var $SECRET_LIFETIME = 1209600;
  
  // keys have a bogus server URL in them because the filestore
  // really does expect that key to be a URL.  This seems a little
  // silly for the server store, since I expect there to be only one
  // server URL.
  var $normal_key = 'http://localhost/|normal';
  var $dumb_key = 'http://localhost/|dumb';

  /**
   * Create a new signatory using a given store.
   */
  function Auth_OpenID_Signatory(&$store) {
    // assert store is not None
    $this->store = & $store;
  }

  /**
   * Verify, using a given association handle, a signature with
   * signed key-value pairs from an HTTP request.
   */
  function verify($assoc_handle, $message) {
    $assoc = $this->getAssociation($assoc_handle, true);
    if (! $assoc) {
      // oidutil.log("failed to get assoc with handle %r to verify sig %r"
      //             % (assoc_handle, sig))
      return false;
    }
    
    return $assoc->checkMessageSignature($message);
  }

  /**
   * Given a response, sign the fields in the response's 'signed'
   * list, and insert the signature into the response.
   */
  function sign($response) {
    $signed_response = $response;
    $assoc_handle = $response->request->assoc_handle;
    
    if ($assoc_handle) {
      // normal mode
      $assoc = $this->getAssociation($assoc_handle, false, false);
      if (! $assoc || ($assoc->getExpiresIn() <= 0)) {
        // fall back to dumb mode
        $signed_response->fields->setArg(Auth_OpenID_OPENID_NS, 'invalidate_handle', $assoc_handle);
        $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');
        
        if ($assoc && ($assoc->getExpiresIn() <= 0)) {
          $this->invalidate($assoc_handle, false);
        }
        
        $assoc = $this->createAssociation(true, $assoc_type);
      }
    } else {
      // dumb mode.
      $assoc = $this->createAssociation(true);
    }
    
    $signed_response->fields = $assoc->signMessage($signed_response->fields);
    return $signed_response;
  }

  /**
   * Make a new association.
   */
  function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1') {
    $secret = Auth_OpenID_CryptUtil::getBytes(Auth_OpenID_getSecretSize($assoc_type));
    
    $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
    $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
    
    $assoc = Auth_OpenID_Association::fromExpiresIn($this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
    
    if ($dumb) {
      $key = $this->dumb_key;
    } else {
      $key = $this->normal_key;
    }
    
    $this->store->storeAssociation($key, $assoc);
    return $assoc;
  }

  /**
   * Given an association handle, get the association from the
   * store, or return a ServerError or null if something goes wrong.
   */
  function getAssociation($assoc_handle, $dumb, $check_expiration = true) {
    if ($assoc_handle === null) {
      return new Auth_OpenID_ServerError(null, "assoc_handle must not be null");
    }
    
    if ($dumb) {
      $key = $this->dumb_key;
    } else {
      $key = $this->normal_key;
    }
    
    $assoc = $this->store->getAssociation($key, $assoc_handle);
    
    if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
      if ($check_expiration) {
        $this->store->removeAssociation($key, $assoc_handle);
        $assoc = null;
      }
    }
    
    return $assoc;
  }

  /**
   * Invalidate a given association handle.
   */
  function invalidate($assoc_handle, $dumb) {
    if ($dumb) {
      $key = $this->dumb_key;
    } else {
      $key = $this->normal_key;
    }
    $this->store->removeAssociation($key, $assoc_handle);
  }
}

/**
 * Encode an {@link Auth_OpenID_ServerResponse} to an
 * {@link Auth_OpenID_WebResponse}.
 *
 * @package OpenID
 */
class Auth_OpenID_Encoder {
  
  var $responseFactory = 'Auth_OpenID_WebResponse';

  /**
   * Encode an {@link Auth_OpenID_ServerResponse} and return an
   * {@link Auth_OpenID_WebResponse}.
   */
  function encode(&$response) {
    $cls = $this->responseFactory;
    
    $encode_as = $response->whichEncoding();
    if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
      $wr = new $cls(null, null, $response->encodeToKVForm());
      if (is_a($response, 'Auth_OpenID_ServerError')) {
        $wr->code = AUTH_OPENID_HTTP_ERROR;
      }
    } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
      $location = $response->encodeToURL();
      $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT, array('location' => $location));
    } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
      $wr = new $cls(AUTH_OPENID_HTTP_OK, array(), $response->toFormMarkup());
    } else {
      return new Auth_OpenID_EncodingError($response);
    }
    return $wr;
  }
}

/**
 * An encoder which also takes care of signing fields when required.
 *
 * @package OpenID
 */
class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {

  function Auth_OpenID_SigningEncoder(&$signatory) {
    $this->signatory = & $signatory;
  }

  /**
   * Sign an {@link Auth_OpenID_ServerResponse} and return an
   * {@link Auth_OpenID_WebResponse}.
   */
  function encode(&$response) {
    // the isinstance is a bit of a kludge... it means there isn't
    // really an adapter to make the interfaces quite match.
    if (! is_a($response, 'Auth_OpenID_ServerError') && $response->needsSigning()) {
      
      if (! $this->signatory) {
        return new Auth_OpenID_ServerError(null, "Must have a store to sign request");
      }
      
      if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
        return new Auth_OpenID_AlreadySigned($response);
      }
      $response = $this->signatory->sign($response);
    }
    
    return parent::encode($response);
  }
}

/**
 * Decode an incoming query into an Auth_OpenID_Request.
 *
 * @package OpenID
 */
class Auth_OpenID_Decoder {

  function Auth_OpenID_Decoder(&$server) {
    $this->server = & $server;
    
    $this->handlers = array('checkid_setup' => 'Auth_OpenID_CheckIDRequest', 
        'checkid_immediate' => 'Auth_OpenID_CheckIDRequest', 
        'check_authentication' => 'Auth_OpenID_CheckAuthRequest', 
        'associate' => 'Auth_OpenID_AssociateRequest');
  }

  /**
   * Given an HTTP query in an array (key-value pairs), decode it
   * into an Auth_OpenID_Request object.
   */
  function decode($query) {
    if (! $query) {
      return null;
    }
    
    $message = Auth_OpenID_Message::fromPostArgs($query);
    
    if ($message === null) {
      /*
             * It's useful to have a Message attached to a
             * ProtocolError, so we override the bad ns value to build
             * a Message out of it.  Kinda kludgy, since it's made of
             * lies, but the parts that aren't lies are more useful
             * than a 'None'.
             */
      $old_ns = $query['openid.ns'];
      
      $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
      $message = Auth_OpenID_Message::fromPostArgs($query);
      return new Auth_OpenID_ServerError($message, sprintf("Invalid OpenID namespace URI: %s", $old_ns));
    }
    
    $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
    if (! $mode) {
      return new Auth_OpenID_ServerError($message, "No mode value in message");
    }
    
    if (Auth_OpenID::isFailure($mode)) {
      return new Auth_OpenID_ServerError($message, $mode->message);
    }
    
    $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode, $this->defaultDecoder($message));
    
    if (! is_a($handlerCls, 'Auth_OpenID_ServerError')) {
      return call_user_func_array(array($handlerCls, 'fromMessage'), array($message, 
          $this->server));
    } else {
      return $handlerCls;
    }
  }

  function defaultDecoder($message) {
    $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
    
    if (Auth_OpenID::isFailure($mode)) {
      return new Auth_OpenID_ServerError($message, $mode->message);
    }
    
    return new Auth_OpenID_ServerError($message, sprintf("Unrecognized OpenID mode %s", $mode));
  }
}

/**
 * An error that indicates an encoding problem occurred.
 *
 * @package OpenID
 */
class Auth_OpenID_EncodingError {

  function Auth_OpenID_EncodingError(&$response) {
    $this->response = & $response;
  }
}

/**
 * An error that indicates that a response was already signed.
 *
 * @package OpenID
 */
class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
  // This response is already signed.
}

/**
 * An error that indicates that the given return_to is not under the
 * given trust_root.
 *
 * @package OpenID
 */
class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {

  function Auth_OpenID_UntrustedReturnURL($message, $return_to, $trust_root) {
    parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
    $this->return_to = $return_to;
    $this->trust_root = $trust_root;
  }

  function toString() {
    return sprintf("return_to %s not under trust_root %s", $this->return_to, $this->trust_root);
  }
}

/**
 * I handle requests for an OpenID server.
 *
 * Some types of requests (those which are not checkid requests) may
 * be handed to my {@link handleRequest} method, and I will take care
 * of it and return a response.
 *
 * For your convenience, I also provide an interface to {@link
 * Auth_OpenID_Decoder::decode()} and {@link
 * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
 * decodeRequest} and {@link encodeResponse}.
 *
 * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
 *
 * Example:
 *
 * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
 *                                   "http://example.com/op");
 * $request = $oserver->decodeRequest();
 * if (in_array($request->mode, array('checkid_immediate',
 *                                    'checkid_setup'))) {
 *     if ($app->isAuthorized($request->identity, $request->trust_root)) {
 *         $response = $request->answer(true);
 *     } else if ($request->immediate) {
 *         $response = $request->answer(false);
 *     } else {
 *         $app->showDecidePage($request);
 *         return;
 *     }
 * } else {
 *     $response = $oserver->handleRequest($request);
 * }
 *
 * $webresponse = $oserver->encode($response);</pre>
 *
 * @package OpenID
 */
class Auth_OpenID_Server {

  function Auth_OpenID_Server(&$store, $op_endpoint = null) {
    $this->store = & $store;
    $this->signatory = new Auth_OpenID_Signatory($this->store);
    $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
    $this->decoder = new Auth_OpenID_Decoder($this);
    $this->op_endpoint = $op_endpoint;
    $this->negotiator = & Auth_OpenID_getDefaultNegotiator();
  }

  /**
   * Handle a request.  Given an {@link Auth_OpenID_Request} object,
   * call the appropriate {@link Auth_OpenID_Server} method to
   * process the request and generate a response.
   *
   * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
   * returned by {@link Auth_OpenID_Server::decodeRequest()}.
   *
   * @return Auth_OpenID_ServerResponse $response A response object
   * capable of generating a user-agent reply.
   */
  function handleRequest($request) {
    if (method_exists($this, "openid_" . $request->mode)) {
      $handler = array($this, "openid_" . $request->mode);
      return call_user_func($handler, $request);
    }
    return null;
  }

  /**
   * The callback for 'check_authentication' messages.
   */
  function openid_check_authentication(&$request) {
    return $request->answer($this->signatory);
  }

  /**
   * The callback for 'associate' messages.
   */
  function openid_associate(&$request) {
    $assoc_type = $request->assoc_type;
    $session_type = $request->session->session_type;
    if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
      $assoc = $this->signatory->createAssociation(false, $assoc_type);
      return $request->answer($assoc);
    } else {
      $message = sprintf('Association type %s is not supported with ' . 'session type %s', $assoc_type, $session_type);
      list($preferred_assoc_type, $preferred_session_type) = $this->negotiator->getAllowedType();
      return $request->answerUnsupported($message, $preferred_assoc_type, $preferred_session_type);
    }
  }

  /**
   * Encodes as response in the appropriate format suitable for
   * sending to the user agent.
   */
  function encodeResponse(&$response) {
    return $this->encoder->encode($response);
  }

  /**
   * Decodes a query args array into the appropriate
   * {@link Auth_OpenID_Request} object.
   */
  function decodeRequest($query = null) {
    if ($query === null) {
      $query = Auth_OpenID::getQuery();
    }
    
    return $this->decoder->decode($query);
  }
}
