code:
<?php

/**
 * Extension argument processing code
 *
 * @package OpenID
 */

/**
 * Import tools needed to deal with messages.
 */
require_once 'Auth/OpenID.php';
require_once 'Auth/OpenID/KVForm.php';
require_once 'Auth/Yadis/XML.php';
require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse

// This doesn't REALLY belong here, but where is better?
define('Auth_OpenID_IDENTIFIER_SELECT',
       "http://specs.openid.net/auth/2.0/identifier_select");

// URI for Simple Registration extension, the only commonly deployed
// OpenID 1.x extension, and so a special case
define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');

// The OpenID 1.X namespace URI
define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');

function Auth_OpenID_isOpenID1($ns)
{
    return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
        ($ns == Auth_OpenID_OPENID1_NS);
}

// The OpenID 2.0 namespace URI
define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');

// The namespace consisting of pairs with keys that are prefixed with
// "openid."  but not in another namespace.
define('Auth_OpenID_NULL_NAMESPACE', 'Null namespace');

// The null namespace, when it is an allowed OpenID namespace
define('Auth_OpenID_OPENID_NS', 'OpenID namespace');

// The top-level namespace, excluding all pairs with keys that start
// with "openid."
define('Auth_OpenID_BARE_NS', 'Bare namespace');

// Sentinel for Message implementation to indicate that getArg should
// return null instead of returning a default.
define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');

// Limit, in bytes, of identity provider and return_to URLs, including
// response payload.  See OpenID 1.1 specification, Appendix D.
define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);

// All OpenID protocol fields.  Used to check namespace aliases.
global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
$Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
    'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
    'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
    'dh_consumer_public', 'claimed_id', 'identity', 'realm',
    'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
    'assoc_handle', 'trust_root', 'openid');

// Global namespace / alias registration map.  See
// Auth_OpenID_registerNamespaceAlias.
global $Auth_OpenID_registered_aliases;
$Auth_OpenID_registered_aliases = array();

/**
 * Registers a (namespace URI, alias) mapping in a global namespace
 * alias map.  Raises NamespaceAliasRegistrationError if either the
 * namespace URI or alias has already been registered with a different
 * value.  This function is required if you want to use a namespace
 * with an OpenID 1 message.
 */
function Auth_OpenID_registerNamespaceAlias($namespace_uri, $alias)
{
    global $Auth_OpenID_registered_aliases;

    if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
                              $alias) == $namespace_uri) {
        return true;
    }

    if (in_array($namespace_uri,
                 array_values($Auth_OpenID_registered_aliases))) {
        return false;
    }

    if (in_array($alias, array_keys($Auth_OpenID_registered_aliases))) {
        return false;
    }

    $Auth_OpenID_registered_aliases[$alias] = $namespace_uri;
    return true;
}

/**
 * Removes a (namespace_uri, alias) registration from the global
 * namespace alias map.  Returns true if the removal succeeded; false
 * if not (if the mapping did not exist).
 */
function Auth_OpenID_removeNamespaceAlias($namespace_uri, $alias)
{
    global $Auth_OpenID_registered_aliases;

    if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
                              $alias) === $namespace_uri) {
        unset($Auth_OpenID_registered_aliases[$alias]);
        return true;
    }

    return false;
}

/**
 * An Auth_OpenID_Mapping maintains a mapping from arbitrary keys to
 * arbitrary values.  (This is unlike an ordinary PHP array, whose
 * keys may be only simple scalars.)
 *
 * @package OpenID
 */
class Auth_OpenID_Mapping {
    /**
     * Initialize a mapping.  If $classic_array is specified, its keys
     * and values are used to populate the mapping.
     */
    function Auth_OpenID_Mapping($classic_array = null)
    {
        $this->keys = array();
        $this->values = array();

        if (is_array($classic_array)) {
            foreach ($classic_array as $key => $value) {
                $this->set($key, $value);
            }
        }
    }

    /**
     * Returns true if $thing is an Auth_OpenID_Mapping object; false
     * if not.
     */
    static function isA($thing)
    {
        return (is_object($thing) &&
                strtolower(get_class($thing)) == 'auth_openid_mapping');
    }

    /**
     * Returns an array of the keys in the mapping.
     */
    function keys()
    {
        return $this->keys;
    }

    /**
     * Returns an array of values in the mapping.
     */
    function values()
    {
        return $this->values;
    }

    /**
     * Returns an array of (key, value) pairs in the mapping.
     */
    function items()
    {
        $temp = array();

        for ($i = 0; $i < count($this->keys); $i++) {
            $temp[] = array($this->keys[$i],
                            $this->values[$i]);
        }
        return $temp;
    }

    /**
     * Returns the "length" of the mapping, or the number of keys.
     */
    function len()
    {
        return count($this->keys);
    }

    /**
     * Sets a key-value pair in the mapping.  If the key already
     * exists, its value is replaced with the new value.
     */
    function set($key, $value)
    {
        $index = array_search($key, $this->keys);

        if ($index !== false) {
            $this->values[$index] = $value;
        } else {
            $this->keys[] = $key;
            $this->values[] = $value;
        }
    }

    /**
     * Gets a specified value from the mapping, associated with the
     * specified key.  If the key does not exist in the mapping,
     * $default is returned instead.
     */
    function get($key, $default = null)
    {
        $index = array_search($key, $this->keys);

        if ($index !== false) {
            return $this->values[$index];
        } else {
            return $default;
        }
    }

    /**
     * @access private
     */
    function _reflow()
    {
        // PHP is broken yet again.  Sort the arrays to remove the
        // hole in the numeric indexes that make up the array.
        $old_keys = $this->keys;
        $old_values = $this->values;

        $this->keys = array();
        $this->values = array();

        foreach ($old_keys as $k) {
            $this->keys[] = $k;
        }

        foreach ($old_values as $v) {
            $this->values[] = $v;
        }
    }

    /**
     * Deletes a key-value pair from the mapping with the specified
     * key.
     */
    function del($key)
    {
        $index = array_search($key, $this->keys);

        if ($index !== false) {
            unset($this->keys[$index]);
            unset($this->values[$index]);
            $this->_reflow();
            return true;
        }
        return false;
    }

    /**
     * Returns true if the specified value has a key in the mapping;
     * false if not.
     */
    function contains($value)
    {
        return (array_search($value, $this->keys) !== false);
    }
}

/**
 * Maintains a bijective map between namespace uris and aliases.
 *
 * @package OpenID
 */
class Auth_OpenID_NamespaceMap {
    function Auth_OpenID_NamespaceMap()
    {
        $this->alias_to_namespace = new Auth_OpenID_Mapping();
        $this->namespace_to_alias = new Auth_OpenID_Mapping();
        $this->implicit_namespaces = array();
    }

    function getAlias($namespace_uri)
    {
        return $this->namespace_to_alias->get($namespace_uri);
    }

    function getNamespaceURI($alias)
    {
        return $this->alias_to_namespace->get($alias);
    }

    function iterNamespaceURIs()
    {
        // Return an iterator over the namespace URIs
        return $this->namespace_to_alias->keys();
    }

    function iterAliases()
    {
        // Return an iterator over the aliases"""
        return $this->alias_to_namespace->keys();
    }

    function iteritems()
    {
        return $this->namespace_to_alias->items();
    }

    function isImplicit($namespace_uri)
    {
        return in_array($namespace_uri, $this->implicit_namespaces);
    }

    function addAlias($namespace_uri, $desired_alias, $implicit=false)
    {
        // Add an alias from this namespace URI to the desired alias
        global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;

        // Check that desired_alias is not an openid protocol field as
        // per the spec.
        if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) {
            Auth_OpenID::log("\"%s\" is not an allowed namespace alias",
                            $desired_alias);
            return null;
        }

        // Check that desired_alias does not contain a period as per
        // the spec.
        if (strpos($desired_alias, '.') !== false) {
            Auth_OpenID::log('"%s" must not contain a dot', $desired_alias);
            return null;
        }

        // Check that there is not a namespace already defined for the
        // desired alias
        $current_namespace_uri =
            $this->alias_to_namespace->get($desired_alias);

        if (($current_namespace_uri !== null) &&
            ($current_namespace_uri != $namespace_uri)) {
            Auth_OpenID::log('Cannot map "%s" because previous mapping exists',
                            $namespace_uri);
            return null;
        }

        // Check that there is not already a (different) alias for
        // this namespace URI
        $alias = $this->namespace_to_alias->get($namespace_uri);

        if (($alias !== null) && ($alias != $desired_alias)) {
            Auth_OpenID::log('Cannot map %s to alias %s. ' .
                            'It is already mapped to alias %s',
                            $namespace_uri, $desired_alias, $alias);
            return null;
        }

        assert((Auth_OpenID_NULL_NAMESPACE === $desired_alias) ||
               is_string($desired_alias));

        $this->alias_to_namespace->set($desired_alias, $namespace_uri);
        $this->namespace_to_alias->set($namespace_uri, $desired_alias);
        if ($implicit) {
            array_push($this->implicit_namespaces, $namespace_uri);
        }

        return $desired_alias;
    }

    function add($namespace_uri)
    {
        // Add this namespace URI to the mapping, without caring what
        // alias it ends up with

        // See if this namespace is already mapped to an alias
        $alias = $this->namespace_to_alias->get($namespace_uri);

        if ($alias !== null) {
            return $alias;
        }

        // Fall back to generating a numerical alias
        $i = 0;
        while (1) {
            $alias = 'ext' . strval($i);
            if ($this->addAlias($namespace_uri, $alias) === null) {
                $i += 1;
            } else {
                return $alias;
            }
        }

        // Should NEVER be reached!
        return null;
    }

    function contains($namespace_uri)
    {
        return $this->isDefined($namespace_uri);
    }

    function isDefined($namespace_uri)
    {
        return $this->namespace_to_alias->contains($namespace_uri);
    }
}

/**
 * In the implementation of this object, null represents the global
 * namespace as well as a namespace with no key.
 *
 * @package OpenID
 */
class Auth_OpenID_Message {

    function Auth_OpenID_Message($openid_namespace = null)
    {
        // Create an empty Message
        $this->allowed_openid_namespaces = array(
                               Auth_OpenID_OPENID1_NS,
                               Auth_OpenID_THE_OTHER_OPENID1_NS,
                               Auth_OpenID_OPENID2_NS);

        $this->args = new Auth_OpenID_Mapping();
        $this->namespaces = new Auth_OpenID_NamespaceMap();
        if ($openid_namespace === null) {
            $this->_openid_ns_uri = null;
        } else {
            $implicit = Auth_OpenID_isOpenID1($openid_namespace);
            $this->setOpenIDNamespace($openid_namespace, $implicit);
        }
    }

    function isOpenID1()
    {
        return Auth_OpenID_isOpenID1($this->getOpenIDNamespace());
    }

    function isOpenID2()
    {
        return $this->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS;
    }

    static function fromPostArgs($args)
    {
        // Construct a Message containing a set of POST arguments
        $obj = new Auth_OpenID_Message();

        // Partition into "openid." args and bare args
        $openid_args = array();
        foreach ($args as $key => $value) {

            if (is_array($value)) {
                return null;
            }

            $parts = explode('.', $key, 2);

            if (count($parts) == 2) {
                list($prefix, $rest) = $parts;
            } else {
                $prefix = null;
            }

            if ($prefix != 'openid') {
                $obj->args->set(array(Auth_OpenID_BARE_NS, $key), $value);
            } else {
                $openid_args[$rest] = $value;
            }
        }

        if ($obj->_fromOpenIDArgs($openid_args)) {
            return $obj;
        } else {
            return null;
        }
    }

    static function fromOpenIDArgs($openid_args)
    {
        // Takes an array.

        // Construct a Message from a parsed KVForm message
        $obj = new Auth_OpenID_Message();
        if ($obj->_fromOpenIDArgs($openid_args)) {
            return $obj;
        } else {
            return null;
        }
    }

    /**
     * @access private
     */
    function _fromOpenIDArgs($openid_args)
    {
        global $Auth_OpenID_registered_aliases;

        // Takes an Auth_OpenID_Mapping instance OR an array.

        if (!Auth_OpenID_Mapping::isA($openid_args)) {
            $openid_args = new Auth_OpenID_Mapping($openid_args);
        }

        $ns_args = array();

        // Resolve namespaces
        foreach ($openid_args->items() as $pair) {
            list($rest, $value) = $pair;

            $parts = explode('.', $rest, 2);

            if (count($parts) == 2) {
                list($ns_alias, $ns_key) = $parts;
            } else {
                $ns_alias = Auth_OpenID_NULL_NAMESPACE;
                $ns_key = $rest;
            }

            if ($ns_alias == 'ns') {
                if ($this->namespaces->addAlias($value, $ns_key) === null) {
                    return false;
                }
            } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) &&
                       ($ns_key == 'ns')) {
                // null namespace
                if ($this->setOpenIDNamespace($value, false) === false) {
                    return false;
                }
            } else {
                $ns_args[] = array($ns_alias, $ns_key, $value);
            }
        }

        if (!$this->getOpenIDNamespace()) {
            if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) ===
                false) {
                return false;
            }
        }

        // Actually put the pairs into the appropriate namespaces
        foreach ($ns_args as $triple) {
            list($ns_alias, $ns_key, $value) = $triple;
            $ns_uri = $this->namespaces->getNamespaceURI($ns_alias);
            if ($ns_uri === null) {
                $ns_uri = $this->_getDefaultNamespace($ns_alias);
                if ($ns_uri === null) {

                    $ns_uri = Auth_OpenID_OPENID_NS;
                    $ns_key = sprintf('%s.%s', $ns_alias, $ns_key);
                } else {
                    $this->namespaces->addAlias($ns_uri, $ns_alias, true);
                }
            }

            $this->setArg($ns_uri, $ns_key, $value);
        }

        return true;
    }

    function _getDefaultNamespace($mystery_alias)
    {
        global $Auth_OpenID_registered_aliases;
        if ($this->isOpenID1()) {
            return @$Auth_OpenID_registered_aliases[$mystery_alias];
        }
        return null;
    }

    function setOpenIDNamespace($openid_ns_uri, $implicit)
    {
        if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) {
            Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri);
            return false;
        }

        $succeeded = $this->namespaces->addAlias($openid_ns_uri,
                                                 Auth_OpenID_NULL_NAMESPACE,
                                                 $implicit);
        if ($succeeded === false) {
            return false;
        }

        $this->_openid_ns_uri = $openid_ns_uri;

        return true;
    }

    function getOpenIDNamespace()
    {
        return $this->_openid_ns_uri;
    }

    static function fromKVForm($kvform_string)
    {
        // Create a Message from a KVForm string
        return Auth_OpenID_Message::fromOpenIDArgs(
                     Auth_OpenID_KVForm::toArray($kvform_string));
    }

    function copy()
    {
        return $this;
    }

    function toPostArgs()
    {
        // Return all arguments with openid. in front of namespaced
        // arguments.

        $args = array();

        // Add namespace definitions to the output
        foreach ($this->namespaces->iteritems() as $pair) {
            list($ns_uri, $alias) = $pair;
            if ($this->namespaces->isImplicit($ns_uri)) {
                continue;
            }
            if ($alias == Auth_OpenID_NULL_NAMESPACE) {
                $ns_key = 'openid.ns';
            } else {
                $ns_key = 'openid.ns.' . $alias;
            }
            $args[$ns_key] = $ns_uri;
        }

        foreach ($this->args->items() as $pair) {
            list($ns_parts, $value) = $pair;
            list($ns_uri, $ns_key) = $ns_parts;
            $key = $this->getKey($ns_uri, $ns_key);
            $args[$key] = $value;
        }

        return $args;
    }

    function toArgs()
    {
        // Return all namespaced arguments, failing if any
        // non-namespaced arguments exist.
        $post_args = $this->toPostArgs();
        $kvargs = array();
        foreach ($post_args as $k => $v) {
            if (strpos($k, 'openid.') !== 0) {
                // raise ValueError(
                //   'This message can only be encoded as a POST, because it '
                //   'contains arguments that are not prefixed with "openid."')
                return null;
            } else {
                $kvargs[substr($k, 7)] = $v;
            }
        }

        return $kvargs;
    }

    function toFormMarkup($action_url, $form_tag_attrs = null,
                          $submit_text = "Continue")
    {
        $form = "<form accept-charset=\"UTF-8\" ".
            "enctype=\"application/x-www-form-urlencoded\"";

        if (!$form_tag_attrs) {
            $form_tag_attrs = array();
        }

        $form_tag_attrs['action'] = $action_url;
        $form_tag_attrs['method'] = 'post';

        unset($form_tag_attrs['enctype']);
        unset($form_tag_attrs['accept-charset']);

        if ($form_tag_attrs) {
            foreach ($form_tag_attrs as $name => $attr) {
                $form .= sprintf(" %s=\"%s\"", $name, htmlspecialchars($attr));
            }
        }

        $form .= ">\n";

        foreach ($this->toPostArgs() as $name => $value) {
            $form .= sprintf(
                        "<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
                        htmlspecialchars($name), htmlspecialchars($value));
        }

        $form .= sprintf("<input type=\"submit\" value=\"%s\" />\n",
                         htmlspecialchars($submit_text));

        $form .= "</form>\n";

        return $form;
    }

    function toURL($base_url)
    {
        // Generate a GET URL with the parameters in this message
        // attached as query parameters.
        return Auth_OpenID::appendArgs($base_url, $this->toPostArgs());
    }

    function toKVForm()
    {
        // Generate a KVForm string that contains the parameters in
        // this message. This will fail if the message contains
        // arguments outside of the 'openid.' prefix.
        return Auth_OpenID_KVForm::fromArray($this->toArgs());
    }

    function toURLEncoded()
    {
        // Generate an x-www-urlencoded string
        $args = array();

        foreach ($this->toPostArgs() as $k => $v) {
            $args[] = array($k, $v);
        }

        sort($args);
        return Auth_OpenID::httpBuildQuery($args);
    }

    /**
     * @access private
     */
    function _fixNS($namespace)
    {
        // Convert an input value into the internally used values of
        // this object

        if ($namespace == Auth_OpenID_OPENID_NS) {
            if ($this->_openid_ns_uri === null) {
                return new Auth_OpenID_FailureResponse(null,
                    'OpenID namespace not set');
            } else {
                $namespace = $this->_openid_ns_uri;
            }
        }

        if (($namespace != Auth_OpenID_BARE_NS) &&
              (!is_string($namespace))) {
            //TypeError
            $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ".
                              "Auth_OpenID_OPENID_NS or a string. got %s",
                              print_r($namespace, true));
            return new Auth_OpenID_FailureResponse(null, $err_msg);
        }

        if (($namespace != Auth_OpenID_BARE_NS) &&
            (strpos($namespace, ':') === false)) {
            // fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
            // warnings.warn(fmt % (namespace,), DeprecationWarning)

            if ($namespace == 'sreg') {
                // fmt = 'Using %r instead of "sreg" as namespace'
                // warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
                return Auth_OpenID_SREG_URI;
            }
        }

        return $namespace;
    }

    function hasKey($namespace, $ns_key)
    {
        $namespace = $this->_fixNS($namespace);
        if (Auth_OpenID::isFailure($namespace)) {
            // XXX log me
            return false;
        } else {
            return $this->args->contains(array($namespace, $ns_key));
        }
    }

    function getKey($namespace, $ns_key)
    {
        // Get the key for a particular namespaced argument
        $namespace = $this->_fixNS($namespace);
        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        }
        if ($namespace == Auth_OpenID_BARE_NS) {
            return $ns_key;
        }

        $ns_alias = $this->namespaces->getAlias($namespace);

        // No alias is defined, so no key can exist
        if ($ns_alias === null) {
            return null;
        }

        if ($ns_alias == Auth_OpenID_NULL_NAMESPACE) {
            $tail = $ns_key;
        } else {
            $tail = sprintf('%s.%s', $ns_alias, $ns_key);
        }

        return 'openid.' . $tail;
    }

    function getArg($namespace, $key, $default = null)
    {
        // Get a value for a namespaced key.
        $namespace = $this->_fixNS($namespace);

        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        } else {
            if ((!$this->args->contains(array($namespace, $key))) &&
              ($default == Auth_OpenID_NO_DEFAULT)) {
                $err_msg = sprintf("Namespace %s missing required field %s",
                                   $namespace, $key);
                return new Auth_OpenID_FailureResponse(null, $err_msg);
            } else {
                return $this->args->get(array($namespace, $key), $default);
            }
        }
    }

    function getArgs($namespace)
    {
        // Get the arguments that are defined for this namespace URI

        $namespace = $this->_fixNS($namespace);
        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        } else {
            $stuff = array();
            foreach ($this->args->items() as $pair) {
                list($key, $value) = $pair;
                list($pair_ns, $ns_key) = $key;
                if ($pair_ns == $namespace) {
                    $stuff[$ns_key] = $value;
                }
            }

            return $stuff;
        }
    }

    function updateArgs($namespace, $updates)
    {
        // Set multiple key/value pairs in one call

        $namespace = $this->_fixNS($namespace);

        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        } else {
            foreach ($updates as $k => $v) {
                $this->setArg($namespace, $k, $v);
            }
            return true;
        }
    }

    function setArg($namespace, $key, $value)
    {
        // Set a single argument in this namespace
        $namespace = $this->_fixNS($namespace);

        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        } else {
            $this->args->set(array($namespace, $key), $value);
            if ($namespace !== Auth_OpenID_BARE_NS) {
                $this->namespaces->add($namespace);
            }
            return true;
        }
    }

    function delArg($namespace, $key)
    {
        $namespace = $this->_fixNS($namespace);

        if (Auth_OpenID::isFailure($namespace)) {
            return $namespace;
        } else {
            return $this->args->del(array($namespace, $key));
        }
    }

    function getAliasedArg($aliased_key, $default = null)
    {
        if ($aliased_key == 'ns') {
            // Return the namespace URI for the OpenID namespace
            return $this->getOpenIDNamespace();
        }

        $parts = explode('.', $aliased_key, 2);

        if (count($parts) != 2) {
            $ns = null;
        } else {
            list($alias, $key) = $parts;

            if ($alias == 'ns') {
              // Return the namespace URI for a namespace alias
              // parameter.
              return $this->namespaces->getNamespaceURI($key);
            } else {
              $ns = $this->namespaces->getNamespaceURI($alias);
            }
        }

        if ($ns === null) {
            $key = $aliased_key;
            $ns = $this->getOpenIDNamespace();
        }

        return $this->getArg($ns, $key, $default);
    }
}


code:
<?php

/**
 * A MySQL store.
 *
 * @package OpenID
 */

/**
 * Require the base class file.
 */
require_once "Auth/OpenID/SQLStore.php";

/**
 * An SQL store that uses MySQL as its backend.
 *
 * @package OpenID
 */
class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
    /**
     * @access private
     */
    function setSQL()
    {
        $this->sql['nonce_table'] =
            "CREATE TABLE %s (\n".
            "  server_url VARCHAR(2047) NOT NULL,\n".
            "  timestamp INTEGER NOT NULL,\n".
            "  salt CHAR(40) NOT NULL,\n".
            "  UNIQUE (server_url(255), timestamp, salt)\n".
            ") ENGINE=InnoDB";

        $this->sql['assoc_table'] =
            "CREATE TABLE %s (\n".
            "  server_url VARCHAR(2047) NOT NULL,\n".
            "  handle VARCHAR(255) NOT NULL,\n".
            "  secret BLOB NOT NULL,\n".
            "  issued INTEGER NOT NULL,\n".
            "  lifetime INTEGER NOT NULL,\n".
            "  assoc_type VARCHAR(64) NOT NULL,\n".
            "  PRIMARY KEY (server_url(255), handle)\n".
            ") ENGINE=InnoDB";

        $this->sql['set_assoc'] =
            "REPLACE INTO %s (server_url, handle, secret, issued,\n".
            "  lifetime, assoc_type) VALUES (?, ?, !, ?, ?, ?)";

        $this->sql['get_assocs'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ?";

        $this->sql['get_assoc'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ? AND handle = ?";

        $this->sql['remove_assoc'] =
            "DELETE FROM %s WHERE server_url = ? AND handle = ?";

        $this->sql['add_nonce'] =
            "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";

        $this->sql['clean_nonce'] =
            "DELETE FROM %s WHERE timestamp < ?";

        $this->sql['clean_assoc'] =
            "DELETE FROM %s WHERE issued + lifetime < ?";
    }

    /**
     * @access private
     */
    function blobEncode($blob)
    {
        return "0x" . bin2hex($blob);
    }
}

license: mit


code:
<?php

/**
 * Nonce-related functionality.
 *
 * @package OpenID
 */

/**
 * Need CryptUtil to generate random strings.
 */
require_once 'Auth/OpenID/CryptUtil.php';

/**
 * This is the characters that the nonces are made from.
 */
define('Auth_OpenID_Nonce_CHRS',"abcdefghijklmnopqrstuvwxyz" .
       "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

// Keep nonces for five hours (allow five hours for the combination of
// request time and clock skew). This is probably way more than is
// necessary, but there is not much overhead in storing nonces.
global $Auth_OpenID_SKEW;
$Auth_OpenID_SKEW = 60 * 60 * 5;

define('Auth_OpenID_Nonce_REGEX',
       '/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/');

define('Auth_OpenID_Nonce_TIME_FMT',
       '%Y-%m-%dT%H:%M:%SZ');

function Auth_OpenID_splitNonce($nonce_string)
{
    // Extract a timestamp from the given nonce string
    $result = preg_match(Auth_OpenID_Nonce_REGEX, $nonce_string, $matches);
    if ($result != 1 || count($matches) != 8) {
        return null;
    }

    list($unused,
         $tm_year,
         $tm_mon,
         $tm_mday,
         $tm_hour,
         $tm_min,
         $tm_sec,
         $uniquifier) = $matches;

    $timestamp =
        @gmmktime($tm_hour, $tm_min, $tm_sec, $tm_mon, $tm_mday, $tm_year);

    if ($timestamp === false || $timestamp < 0) {
        return null;
    }

    return array($timestamp, $uniquifier);
}

function Auth_OpenID_checkTimestamp($nonce_string,
                                    $allowed_skew = null,
                                    $now = null)
{
    // Is the timestamp that is part of the specified nonce string
    // within the allowed clock-skew of the current time?
    global $Auth_OpenID_SKEW;

    if ($allowed_skew === null) {
        $allowed_skew = $Auth_OpenID_SKEW;
    }

    $parts = Auth_OpenID_splitNonce($nonce_string);
    if ($parts == null) {
        return false;
    }

    if ($now === null) {
        $now = time();
    }

    $stamp = $parts[0];

    // Time after which we should not use the nonce
    $past = $now - $allowed_skew;

    // Time that is too far in the future for us to allow
    $future = $now + $allowed_skew;

    // the stamp is not too far in the future and is not too far
    // in the past
    return (($past <= $stamp) && ($stamp <= $future));
}

function Auth_OpenID_mkNonce($when = null)
{
    // Generate a nonce with the current timestamp
    $salt = Auth_OpenID_CryptUtil::randomString(
        6, Auth_OpenID_Nonce_CHRS);
    if ($when === null) {
        // It's safe to call time() with no arguments; it returns a
        // GMT unix timestamp on PHP 4 and PHP 5.  gmmktime() with no
        // args returns a local unix timestamp on PHP 4, so don't use
        // that.
        $when = time();
    }
    $time_str = gmstrftime(Auth_OpenID_Nonce_TIME_FMT, $when);
    return $time_str . $salt;
}


code:
<?php

/**
 * An implementation of the OpenID Provider Authentication Policy
 *  Extension 1.0
 *
 * See:
 * http://openid.net/developers/specs/
 */

require_once "Auth/OpenID/Extension.php";

define('Auth_OpenID_PAPE_NS_URI',
       "http://specs.openid.net/extensions/pape/1.0");

define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
       'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
define('PAPE_AUTH_MULTI_FACTOR',
       'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
define('PAPE_AUTH_PHISHING_RESISTANT',
       'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');

define('PAPE_TIME_VALIDATOR',
      '/^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$/');
/**
 * A Provider Authentication Policy request, sent from a relying party
 * to a provider
 *
 * preferred_auth_policies: The authentication policies that
 * the relying party prefers
 *
 * max_auth_age: The maximum time, in seconds, that the relying party
 * wants to allow to have elapsed before the user must re-authenticate
 */
class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {

    var $ns_alias = 'pape';
    var $ns_uri = Auth_OpenID_PAPE_NS_URI;

    function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
                                      $max_auth_age=null)
    {
        if ($preferred_auth_policies === null) {
            $preferred_auth_policies = array();
        }

        $this->preferred_auth_policies = $preferred_auth_policies;
        $this->max_auth_age = $max_auth_age;
    }

    /**
     * Add an acceptable authentication policy URI to this request
     *
     * This method is intended to be used by the relying party to add
     * acceptable authentication types to the request.
     *
     * policy_uri: The identifier for the preferred type of
     * authentication.
     */
    function addPolicyURI($policy_uri)
    {
        if (!in_array($policy_uri, $this->preferred_auth_policies)) {
            $this->preferred_auth_policies[] = $policy_uri;
        }
    }

    function getExtensionArgs()
    {
        $ns_args = array(
                         'preferred_auth_policies' =>
                           implode(' ', $this->preferred_auth_policies)
                         );

        if ($this->max_auth_age !== null) {
            $ns_args['max_auth_age'] = strval($this->max_auth_age);
        }

        return $ns_args;
    }

    /**
     * Instantiate a Request object from the arguments in a checkid_*
     * OpenID message
     */
    static function fromOpenIDRequest($request)
    {
        $obj = new Auth_OpenID_PAPE_Request();
        $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);

        if ($args === null || $args === array()) {
            return null;
        }

        $obj->parseExtensionArgs($args);
        return $obj;
    }

    /**
     * Set the state of this request to be that expressed in these
     * PAPE arguments
     *
     * @param args: The PAPE arguments without a namespace
     */
    function parseExtensionArgs($args)
    {
        // preferred_auth_policies is a space-separated list of policy
        // URIs
        $this->preferred_auth_policies = array();

        $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
        if ($policies_str) {
            foreach (explode(' ', $policies_str) as $uri) {
                if (!in_array($uri, $this->preferred_auth_policies)) {
                    $this->preferred_auth_policies[] = $uri;
                }
            }
        }

        // max_auth_age is base-10 integer number of seconds
        $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
        if ($max_auth_age_str) {
            $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
        } else {
            $this->max_auth_age = null;
        }
    }

    /**
     * Given a list of authentication policy URIs that a provider
     * supports, this method returns the subsequence of those types
     * that are preferred by the relying party.
     *
     * @param supported_types: A sequence of authentication policy
     * type URIs that are supported by a provider
     *
     * @return array The sub-sequence of the supported types that are
     * preferred by the relying party. This list will be ordered in
     * the order that the types appear in the supported_types
     * sequence, and may be empty if the provider does not prefer any
     * of the supported authentication types.
     */
    function preferredTypes($supported_types)
    {
        $result = array();

        foreach ($supported_types as $st) {
            if (in_array($st, $this->preferred_auth_policies)) {
                $result[] = $st;
            }
        }
        return $result;
    }
}

/**
 * A Provider Authentication Policy response, sent from a provider to
 * a relying party
 */
class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {

    var $ns_alias = 'pape';
    var $ns_uri = Auth_OpenID_PAPE_NS_URI;

    function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null,
                                       $nist_auth_level=null)
    {
        if ($auth_policies) {
            $this->auth_policies = $auth_policies;
        } else {
            $this->auth_policies = array();
        }

        $this->auth_time = $auth_time;
        $this->nist_auth_level = $nist_auth_level;
    }

    /**
     * Add a authentication policy to this response
     *
     * This method is intended to be used by the provider to add a
     * policy that the provider conformed to when authenticating the
     * user.
     *
     * @param policy_uri: The identifier for the preferred type of
     * authentication.
     */
    function addPolicyURI($policy_uri)
    {
        if (!in_array($policy_uri, $this->auth_policies)) {
            $this->auth_policies[] = $policy_uri;
        }
    }

    /**
     * Create an Auth_OpenID_PAPE_Response object from a successful
     * OpenID library response.
     *
     * @param success_response $success_response A SuccessResponse
     * from Auth_OpenID_Consumer::complete()
     *
     * @returns: A provider authentication policy response from the
     * data that was supplied with the id_res response.
     */
    static function fromSuccessResponse($success_response)
    {
        $obj = new Auth_OpenID_PAPE_Response();

        // PAPE requires that the args be signed.
        $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);

        if ($args === null || $args === array()) {
            return null;
        }

        $result = $obj->parseExtensionArgs($args);

        if ($result === false) {
            return null;
        } else {
            return $obj;
        }
    }

    /**
     * Parse the provider authentication policy arguments into the
     *  internal state of this object
     *
     * @param args: unqualified provider authentication policy
     * arguments
     *
     * @param strict: Whether to return false when bad data is
     * encountered
     *
     * @return null The data is parsed into the internal fields of
     * this object.
    */
    function parseExtensionArgs($args, $strict=false)
    {
        $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
        if ($policies_str && $policies_str != "none") {
            $this->auth_policies = explode(" ", $policies_str);
        }

        $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
        if ($nist_level_str !== null) {
            $nist_level = Auth_OpenID::intval($nist_level_str);

            if ($nist_level === false) {
                if ($strict) {
                    return false;
                } else {
                    $nist_level = null;
                }
            }

            if (0 <= $nist_level && $nist_level < 5) {
                $this->nist_auth_level = $nist_level;
            } else if ($strict) {
                return false;
            }
        }

        $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
        if ($auth_time !== null) {
            if (preg_match(PAPE_TIME_VALIDATOR, $auth_time)) {
                $this->auth_time = $auth_time;
            } else if ($strict) {
                return false;
            }
        }
    }

    function getExtensionArgs()
    {
        $ns_args = array();
        if (count($this->auth_policies) > 0) {
            $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
        } else {
            $ns_args['auth_policies'] = 'none';
        }

        if ($this->nist_auth_level !== null) {
            if (!in_array($this->nist_auth_level, range(0, 4), true)) {
                return false;
            }
            $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
        }

        if ($this->auth_time !== null) {
            if (!preg_match(PAPE_TIME_VALIDATOR, $this->auth_time)) {
                return false;
            }

            $ns_args['auth_time'] = $this->auth_time;
        }

        return $ns_args;
    }
}


code:
<?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 &,
 *   <, >, and ".
 *
 * The parser ignores SGML comments and <![CDATA[blocks]]>. Both kinds
 * of quoting are allowed for attributes.
 *
 * The parser deals with invalid markup in these ways:
 *
 * - Tag names are not case-sensitive
 *
 * - The <html> tag is accepted even when it is not at the top level
 *
 * - The <head> tag is accepted even when it is not a direct child of
 *   the <html> tag, but a <html> tag must be an ancestor of the
 *   <head> tag
 *
 * - <link> tags are accepted even when they are not direct children
 *   of the <head> tag, but a <head> tag must be an ancestor of the
 *   <link> tag
 *
 * - If there is no closing tag for an open <html> or <head> tag, the
 *   remainder of the document is viewed as being inside of the
 *   tag. If there is no closing tag for a <link> tag, the link tag is
 *   treated as a short tag. Exceptions to this rule are that <html>
 *   closes <html> and <body> or <head> closes <head>
 *
 * - Attributes of the <link> tag are not required to be quoted.
 *
 * - In the case of duplicated attribute names, the attribute coming
 *   last in the tag will be the value returned.
 *
 * - Any text that does not parse as an attribute within a link tag
 *   will be ignored. (e.g. <link pumpkin rel='openid.server' /> will
 *   ignore pumpkin)
 *
 * - If there are more than one <html> or <head> tag, the parser only
 *   looks inside of the first one.
 *
 * - The contents of <script> tags are ignored entirely, except
 *   unclosed <script> tags. Unclosed <script> tags are ignored.
 *
 * - Any other invalid markup is ignored, including unclosed SGML
 *   comments and unclosed <![CDATA[blocks.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @access private
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * Require Auth_OpenID::arrayGet().
 */
require_once "Auth/OpenID.php";

class Auth_OpenID_Parse {

    /**
     * Specify some flags for use with regex matching.
     */
    var $_re_flags = "si";

    /**
     * Stuff to remove before we start looking for tags
     */
    var $_removed_re =
           "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";

    /**
     * Starts with the tag name at a word boundary, where the tag name
     * is not a namespace
     */
    var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*)(?:<\/?%s\s*>|\Z))";

    var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';

    var $_open_tag_expr = "<%s\b";
    var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";

    function Auth_OpenID_Parse()
    {
        $this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
                                    $this->_re_flags);

        $this->_entity_replacements = array(
                                            'amp' => '&',
                                            'lt' => '<',
                                            'gt' => '>',
                                            'quot' => '"'
                                            );

        $this->_attr_find = sprintf("/%s/%s",
                                    $this->_attr_find,
                                    $this->_re_flags);

        $this->_removed_re = sprintf("/%s/%s",
                                     $this->_removed_re,
                                     $this->_re_flags);

        $this->_ent_replace =
            sprintf("&(%s);", implode("|",
                                      $this->_entity_replacements));
    }

    /**
     * Returns a regular expression that will match a given tag in an
     * SGML string.
     */
    function tagMatcher($tag_name, $close_tags = null)
    {
        $expr = $this->_tag_expr;

        if ($close_tags) {
            $options = implode("|", array_merge(array($tag_name), $close_tags));
            $closer = sprintf("(?:%s)", $options);
        } else {
            $closer = $tag_name;
        }

        $expr = sprintf($expr, $tag_name, $closer);
        return sprintf("/%s/%s", $expr, $this->_re_flags);
    }

    function openTag($tag_name)
    {
        $expr = sprintf($this->_open_tag_expr, $tag_name);
        return sprintf("/%s/%s", $expr, $this->_re_flags);
    }

    function closeTag($tag_name)
    {
        $expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
        return sprintf("/%s/%s", $expr, $this->_re_flags);
    }

    function htmlBegin($s)
    {
        $matches = array();
        $result = preg_match($this->openTag('html'), $s,
                             $matches, PREG_OFFSET_CAPTURE);
        if ($result === false || !$matches) {
            return false;
        }
        // Return the offset of the first match.
        return $matches[0][1];
    }

    function htmlEnd($s)
    {
        $matches = array();
        $result = preg_match($this->closeTag('html'), $s,
                             $matches, PREG_OFFSET_CAPTURE);
        if ($result === false || !$matches) {
            return false;
        }
        // Return the offset of the first match.
        return $matches[count($matches) - 1][1];
    }

    function headFind()
    {
        return $this->tagMatcher('head', array('body', 'html'));
    }

    function replaceEntities($str)
    {
        foreach ($this->_entity_replacements as $old => $new) {
            $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
        }
        return $str;
    }

    function removeQuotes($str)
    {
        $matches = array();
        $double = '/^"(.*)"$/';
        $single = "/^\'(.*)\'$/";

        if (preg_match($double, $str, $matches)) {
            return $matches[1];
        } else if (preg_match($single, $str, $matches)) {
            return $matches[1];
        } else {
            return $str;
        }
    }

    function match($regexp, $text, &$match)
    {
        if (!is_callable('mb_ereg_search_init')) {
            if (!preg_match($regexp, $text, $match)) {
                return false;
            }
            $match = $match[0];
            return true;
        }

        $regexp = substr($regexp, 1, strlen($regexp) - 2 - strlen($this->_re_flags));
        mb_ereg_search_init($text);
        if (!mb_ereg_search($regexp)) {
            return false;
        }
        $match = mb_ereg_search_getregs();
        return true;
    }

    /**
     * Find all link tags in a string representing a HTML document and
     * return a list of their attributes.
     *
     * @todo This is quite ineffective and may fail with the default
     *       pcre.backtrack_limit of 100000 in PHP 5.2, if $html is big.
     *       It should rather use stripos (in PHP5) or strpos()+strtoupper()
     *       in PHP4 to manage this.
     *
     * @param string $html The text to parse
     * @return array $list An array of arrays of attributes, one for each
     * link tag
     */
    function parseLinkAttrs($html)
    {
        $stripped = preg_replace($this->_removed_re,
                                 "",
                                 $html);

        $html_begin = $this->htmlBegin($stripped);
        $html_end = $this->htmlEnd($stripped);

        if ($html_begin === false) {
            return array();
        }

        if ($html_end === false) {
            $html_end = strlen($stripped);
        }

        $stripped = substr($stripped, $html_begin,
                           $html_end - $html_begin);

        // Workaround to prevent PREG_BACKTRACK_LIMIT_ERROR:
        $old_btlimit = ini_set( 'pcre.backtrack_limit', -1 );

        // Try to find the <HEAD> tag.
        $head_re = $this->headFind();
        $head_match = array();
        if (!$this->match($head_re, $stripped, $head_match)) {
                     ini_set( 'pcre.backtrack_limit', $old_btlimit );
                     return array();
        }

        $link_data = array();
        $link_matches = array();

        if (!preg_match_all($this->_link_find, $head_match[0],
                            $link_matches)) {
            ini_set( 'pcre.backtrack_limit', $old_btlimit );
            return array();
        }

        foreach ($link_matches[0] as $link) {
            $attr_matches = array();
            preg_match_all($this->_attr_find, $link, $attr_matches);
            $link_attrs = array();
            foreach ($attr_matches[0] as $index => $full_match) {
                $name = $attr_matches[1][$index];
                $value = $this->replaceEntities(
                              $this->removeQuotes($attr_matches[2][$index]));

                $link_attrs[strtolower($name)] = $value;
            }
            $link_data[] = $link_attrs;
        }

        ini_set( 'pcre.backtrack_limit', $old_btlimit );
        return $link_data;
    }

    function relMatches($rel_attr, $target_rel)
    {
        // Does this target_rel appear in the rel_str?
        // XXX: TESTME
        $rels = preg_split("/\s+/", trim($rel_attr));
        foreach ($rels as $rel) {
            $rel = strtolower($rel);
            if ($rel == $target_rel) {
                return 1;
            }
        }

        return 0;
    }

    function linkHasRel($link_attrs, $target_rel)
    {
        // Does this link have target_rel as a relationship?
        // XXX: TESTME
        $rel_attr = Auth_OpeniD::arrayGet($link_attrs, 'rel', null);
        return ($rel_attr && $this->relMatches($rel_attr,
                                               $target_rel));
    }

    function findLinksRel($link_attrs_list, $target_rel)
    {
        // Filter the list of link attributes on whether it has
        // target_rel as a relationship.
        // XXX: TESTME
        $result = array();
        foreach ($link_attrs_list as $attr) {
            if ($this->linkHasRel($attr, $target_rel)) {
                $result[] = $attr;
            }
        }

        return $result;
    }

    function findFirstHref($link_attrs_list, $target_rel)
    {
        // Return the value of the href attribute for the first link
        // tag in the list that has target_rel as a relationship.
        // XXX: TESTME
        $matches = $this->findLinksRel($link_attrs_list,
                                       $target_rel);
        if (!$matches) {
            return null;
        }
        $first = $matches[0];
        return Auth_OpenID::arrayGet($first, 'href', null);
    }
}

function Auth_OpenID_legacy_discover($html_text, $server_rel,
                                     $delegate_rel)
{
    $p = new Auth_OpenID_Parse();

    $link_attrs = $p->parseLinkAttrs($html_text);

    $server_url = $p->findFirstHref($link_attrs,
                                    $server_rel);

    if ($server_url === null) {
        return false;
    } else {
        $delegate_url = $p->findFirstHref($link_attrs,
                                          $delegate_rel);
        return array($delegate_url, $server_url);
    }
}


code:
<?php

/**
 * A PostgreSQL store.
 *
 * @package OpenID
 */

/**
 * Require the base class file.
 */
require_once "Auth/OpenID/SQLStore.php";

/**
 * An SQL store that uses PostgreSQL as its backend.
 *
 * @package OpenID
 */
class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
    /**
     * @access private
     */
    function setSQL()
    {
        $this->sql['nonce_table'] =
            "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
                             "timestamp INTEGER NOT NULL, ".
                             "salt CHAR(40) NOT NULL, ".
                "UNIQUE (server_url, timestamp, salt))";

        $this->sql['assoc_table'] =
            "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
                             "handle VARCHAR(255) NOT NULL, ".
                             "secret BYTEA NOT NULL, ".
                             "issued INTEGER NOT NULL, ".
                             "lifetime INTEGER NOT NULL, ".
                             "assoc_type VARCHAR(64) NOT NULL, ".
            "PRIMARY KEY (server_url, handle), ".
            "CONSTRAINT secret_length_constraint CHECK ".
            "(LENGTH(secret) <= 128))";

        $this->sql['set_assoc'] =
            array(
                  'insert_assoc' => "INSERT INTO %s (server_url, handle, ".
                  "secret, issued, lifetime, assoc_type) VALUES ".
                  "(?, ?, '!', ?, ?, ?)",
                  'update_assoc' => "UPDATE %s SET secret = '!', issued = ?, ".
                  "lifetime = ?, assoc_type = ? WHERE server_url = ? AND ".
                  "handle = ?"
                  );

        $this->sql['get_assocs'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ?";

        $this->sql['get_assoc'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ? AND handle = ?";

        $this->sql['remove_assoc'] =
            "DELETE FROM %s WHERE server_url = ? AND handle = ?";

        $this->sql['add_nonce'] =
                  "INSERT INTO %s (server_url, timestamp, salt) VALUES ".
                  "(?, ?, ?)"
                  ;

        $this->sql['clean_nonce'] =
            "DELETE FROM %s WHERE timestamp < ?";

        $this->sql['clean_assoc'] =
            "DELETE FROM %s WHERE issued + lifetime < ?";
    }

    /**
     * @access private
     */
    function _set_assoc($server_url, $handle, $secret, $issued, $lifetime,
                        $assoc_type)
    {
        $result = $this->_get_assoc($server_url, $handle);
        if ($result) {
            // Update the table since this associations already exists.
            $this->connection->query($this->sql['set_assoc']['update_assoc'],
                                     array($secret, $issued, $lifetime,
                                           $assoc_type, $server_url, $handle));
        } else {
            // Insert a new record because this association wasn't
            // found.
            $this->connection->query($this->sql['set_assoc']['insert_assoc'],
                                     array($server_url, $handle, $secret,
                                           $issued, $lifetime, $assoc_type));
        }
    }

    /**
     * @access private
     */
    function blobEncode($blob)
    {
        return $this->_octify($blob);
    }

    /**
     * @access private
     */
    function blobDecode($blob)
    {
        return $this->_unoctify($blob);
    }
}


code:
<?php

/**
 * Supplies Redis server store backend for OpenID servers and consumers.
 * Uses Predis library {@see https://github.com/nrk/predis}.
 * Requires PHP >= 5.3.
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author Ville Mattila <ville@eventio.fi>
 * @copyright 2008 JanRain Inc., 2013 Eventio Oy / Ville Mattila
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 * Contributed by Eventio Oy <http://www.eventio.fi/>
 */

/**
 * Import the interface for creating a new store class.
 */
require_once 'Auth/OpenID/Interface.php';

/**
 * Supplies Redis server store backend for OpenID servers and consumers.
 * Uses Predis library {@see https://github.com/nrk/predis}.
 * Requires PHP >= 5.3.
 *
 * @package OpenID
 */
class Auth_OpenID_PredisStore extends Auth_OpenID_OpenIDStore {

    /**
     * @var \Predis\Client
     */
    protected $redis;

    /**
     * Prefix for Redis keys
     * @var string
     */
    protected $prefix;

    /**
     * Initializes a new {@link Auth_OpenID_PredisStore} instance.
     *
     * @param \Predis\Client $redis  Predis client object
     * @param string         $prefix Prefix for all keys stored to the Redis
     */
    function Auth_OpenID_PredisStore(\Predis\Client $redis, $prefix = '')
    {
        $this->prefix = $prefix;
        $this->redis = $redis;
    }

    /**
     * Store association until its expiration time in Redis server.
     * Overwrites any existing association with same server_url and
     * handle. Handles list of associations for every server.
     */
    function storeAssociation($server_url, $association)
    {
        // create Redis keys for association itself
        // and list of associations for this server
        $associationKey = $this->associationKey($server_url,
            $association->handle);
        $serverKey = $this->associationServerKey($server_url);

        // save association to server's associations' keys list
        $this->redis->lpush(
            $serverKey,
            $associationKey
        );

        // Will touch the association list expiration, to avoid filling up
        $newExpiration = ($association->issued + $association->lifetime);

        $expirationKey = $serverKey.'_expires_at';
        $expiration = $this->redis->get($expirationKey);
        if (!$expiration || $newExpiration > $expiration) {
            $this->redis->set($expirationKey, $newExpiration);
            $this->redis->expireat($serverKey, $newExpiration);
            $this->redis->expireat($expirationKey, $newExpiration);
        }

        // save association itself, will automatically expire
        $this->redis->setex(
            $associationKey,
            $newExpiration - time(),
            serialize($association)
        );
    }

    /**
     * Read association from Redis. If no handle given
     * and multiple associations found, returns latest issued
     */
    function getAssociation($server_url, $handle = null)
    {
        // simple case: handle given
        if ($handle !== null) {
            return $this->getAssociationFromServer(
                $this->associationKey($server_url, $handle)
            );
        }

        // no handle given, receiving the latest issued
        $serverKey = $this->associationServerKey($server_url);
        $lastKey = $this->redis->lpop($serverKey);
        if (!$lastKey) { return null; }

        // get association, return null if failed
        return $this->getAssociationFromServer($lastKey);
    }

    /**
     * Function to actually receive and unserialize the association
     * from the server.
     */
    private function getAssociationFromServer($associationKey)
    {
        $association = $this->redis->get($associationKey);
        return $association ? unserialize($association) : null;
    }

    /**
     * Immediately delete association from Redis.
     */
    function removeAssociation($server_url, $handle)
    {
        // create Redis keys
        $serverKey = $this->associationServerKey($server_url);
        $associationKey = $this->associationKey($server_url,
            $handle);

        // Removing the association from the server's association list
        $removed = $this->redis->lrem($serverKey, 0, $associationKey);
        if ($removed < 1) {
            return false;
        }

        // Delete the association itself
        return $this->redis->del($associationKey);
    }

    /**
     * Create nonce for server and salt, expiring after
     * $Auth_OpenID_SKEW seconds.
     */
    function useNonce($server_url, $timestamp, $salt)
    {
        global $Auth_OpenID_SKEW;

        // save one request to memcache when nonce obviously expired
        if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
            return false;
        }

        // SETNX will set the value only of the key doesn't exist yet.
        $nonceKey = $this->nonceKey($server_url, $salt);
        $added = $this->predis->setnx($nonceKey);
        if ($added) {
            // Will set expiration
            $this->predis->expire($nonceKey, $Auth_OpenID_SKEW);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Build up nonce key
     */
    private function nonceKey($server_url, $salt)
    {
        return $this->prefix .
               'openid_nonce_' .
               sha1($server_url) . '_' . sha1($salt);
    }

    /**
     * Key is prefixed with $prefix and 'openid_association_' string
     */
    function associationKey($server_url, $handle = null)
    {
        return $this->prefix .
               'openid_association_' .
               sha1($server_url) . '_' . sha1($handle);
    }

    /**
     * Key is prefixed with $prefix and 'openid_association_server_' string
     */
    function associationServerKey($server_url)
    {
        return $this->prefix .
               'openid_association_server_' .
               sha1($server_url);
    }

    /**
     * Report that this storage doesn't support cleanup
     */
    function supportsCleanup()
    {
        return false;
    }

}


code:
<?php

/**
 * SQL-backed OpenID stores.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * @access private
 */
require_once 'Auth/OpenID/Interface.php';
require_once 'Auth/OpenID/Nonce.php';

/**
 * @access private
 */
require_once 'Auth/OpenID.php';

/**
 * @access private
 */
require_once 'Auth/OpenID/Nonce.php';

/**
 * This is the parent class for the SQL stores, which contains the
 * logic common to all of the SQL stores.
 *
 * The table names used are determined by the class variables
 * associations_table_name and nonces_table_name.  To change the name
 * of the tables used, pass new table names into the constructor.
 *
 * To create the tables with the proper schema, see the createTables
 * method.
 *
 * This class shouldn't be used directly.  Use one of its subclasses
 * instead, as those contain the code necessary to use a specific
 * database.  If you're an OpenID integrator and you'd like to create
 * an SQL-driven store that wraps an application's database
 * abstraction, be sure to create a subclass of
 * {@link Auth_OpenID_DatabaseConnection} that calls the application's
 * database abstraction calls.  Then, pass an instance of your new
 * database connection class to your SQLStore subclass constructor.
 *
 * All methods other than the constructor and createTables should be
 * considered implementation details.
 *
 * @package OpenID
 */
class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {

    /**
     * This creates a new SQLStore instance.  It requires an
     * established database connection be given to it, and it allows
     * overriding the default table names.
     *
     * @param connection $connection This must be an established
     * connection to a database of the correct type for the SQLStore
     * subclass you're using.  This must either be an PEAR DB
     * connection handle or an instance of a subclass of
     * Auth_OpenID_DatabaseConnection.
     *
     * @param associations_table: This is an optional parameter to
     * specify the name of the table used for storing associations.
     * The default value is 'oid_associations'.
     *
     * @param nonces_table: This is an optional parameter to specify
     * the name of the table used for storing nonces.  The default
     * value is 'oid_nonces'.
     */
    function Auth_OpenID_SQLStore($connection,
                                  $associations_table = null,
                                  $nonces_table = null)
    {
        $this->associations_table_name = "oid_associations";
        $this->nonces_table_name = "oid_nonces";

        // Check the connection object type to be sure it's a PEAR
        // database connection.
        if (!(is_object($connection) &&
              (is_subclass_of($connection, 'db_common') ||
               is_subclass_of($connection,
                              'auth_openid_databaseconnection')))) {
            trigger_error("Auth_OpenID_SQLStore expected PEAR connection " .
                          "object (got ".get_class($connection).")",
                          E_USER_ERROR);
            return;
        }

        $this->connection = $connection;

        // Be sure to set the fetch mode so the results are keyed on
        // column name instead of column index.  This is a PEAR
        // constant, so only try to use it if PEAR is present.  Note
        // that Auth_Openid_Databaseconnection instances need not
        // implement ::setFetchMode for this reason.
        if (is_subclass_of($this->connection, 'db_common')) {
            $this->connection->setFetchMode(DB_FETCHMODE_ASSOC);
        }

        if ($associations_table) {
            $this->associations_table_name = $associations_table;
        }

        if ($nonces_table) {
            $this->nonces_table_name = $nonces_table;
        }

        $this->max_nonce_age = 6 * 60 * 60;

        // Be sure to run the database queries with auto-commit mode
        // turned OFF, because we want every function to run in a
        // transaction, implicitly.  As a rule, methods named with a
        // leading underscore will NOT control transaction behavior.
        // Callers of these methods will worry about transactions.
        $this->connection->autoCommit(false);

        // Create an empty SQL strings array.
        $this->sql = array();

        // Call this method (which should be overridden by subclasses)
        // to populate the $this->sql array with SQL strings.
        $this->setSQL();

        // Verify that all required SQL statements have been set, and
        // raise an error if any expected SQL strings were either
        // absent or empty.
        list($missing, $empty) = $this->_verifySQL();

        if ($missing) {
            trigger_error("Expected keys in SQL query list: " .
                          implode(", ", $missing),
                          E_USER_ERROR);
            return;
        }

        if ($empty) {
            trigger_error("SQL list keys have no SQL strings: " .
                          implode(", ", $empty),
                          E_USER_ERROR);
            return;
        }

        // Add table names to queries.
        $this->_fixSQL();
    }

    function tableExists($table_name)
    {
        return !$this->isError(
                      $this->connection->query(
                          sprintf("SELECT * FROM %s LIMIT 0",
                                  $table_name)));
    }

    /**
     * Returns true if $value constitutes a database error; returns
     * false otherwise.
     */
    function isError($value)
    {
        return @PEAR::isError($value);
    }

    /**
     * Converts a query result to a boolean.  If the result is a
     * database error according to $this->isError(), this returns
     * false; otherwise, this returns true.
     */
    function resultToBool($obj)
    {
        if ($this->isError($obj)) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * This method should be overridden by subclasses.  This method is
     * called by the constructor to set values in $this->sql, which is
     * an array keyed on sql name.
     */
    function setSQL()
    {
    }

    /**
     * Resets the store by removing all records from the store's
     * tables.
     */
    function reset()
    {
        $this->connection->query(sprintf("DELETE FROM %s",
                                         $this->associations_table_name));

        $this->connection->query(sprintf("DELETE FROM %s",
                                         $this->nonces_table_name));
    }

    /**
     * @access private
     */
    function _verifySQL()
    {
        $missing = array();
        $empty = array();

        $required_sql_keys = array(
                                   'nonce_table',
                                   'assoc_table',
                                   'set_assoc',
                                   'get_assoc',
                                   'get_assocs',
                                   'remove_assoc'
                                   );

        foreach ($required_sql_keys as $key) {
            if (!array_key_exists($key, $this->sql)) {
                $missing[] = $key;
            } else if (!$this->sql[$key]) {
                $empty[] = $key;
            }
        }

        return array($missing, $empty);
    }

    /**
     * @access private
     */
    function _fixSQL()
    {
        $replacements = array(
                              array(
                                    'value' => $this->nonces_table_name,
                                    'keys' => array('nonce_table',
                                                    'add_nonce',
                                                    'clean_nonce')
                                    ),
                              array(
                                    'value' => $this->associations_table_name,
                                    'keys' => array('assoc_table',
                                                    'set_assoc',
                                                    'get_assoc',
                                                    'get_assocs',
                                                    'remove_assoc',
                                                    'clean_assoc')
                                    )
                              );

        foreach ($replacements as $item) {
            $value = $item['value'];
            $keys = $item['keys'];

            foreach ($keys as $k) {
                if (is_array($this->sql[$k])) {
                    foreach ($this->sql[$k] as $part_key => $part_value) {
                        $this->sql[$k][$part_key] = sprintf($part_value,
                                                            $value);
                    }
                } else {
                    $this->sql[$k] = sprintf($this->sql[$k], $value);
                }
            }
        }
    }

    function blobDecode($blob)
    {
        return $blob;
    }

    function blobEncode($str)
    {
        return $str;
    }

    function createTables()
    {
        $this->connection->autoCommit(true);
        $n = $this->create_nonce_table();
        $a = $this->create_assoc_table();
        $this->connection->autoCommit(false);

        if ($n && $a) {
            return true;
        } else {
            return false;
        }
    }

    function create_nonce_table()
    {
        if (!$this->tableExists($this->nonces_table_name)) {
            $r = $this->connection->query($this->sql['nonce_table']);
            return $this->resultToBool($r);
        }
        return true;
    }

    function create_assoc_table()
    {
        if (!$this->tableExists($this->associations_table_name)) {
            $r = $this->connection->query($this->sql['assoc_table']);
            return $this->resultToBool($r);
        }
        return true;
    }

    /**
     * @access private
     */
    function _set_assoc($server_url, $handle, $secret, $issued,
                        $lifetime, $assoc_type)
    {
        return $this->connection->query($this->sql['set_assoc'],
                                        array(
                                              $server_url,
                                              $handle,
                                              $secret,
                                              $issued,
                                              $lifetime,
                                              $assoc_type));
    }

    function storeAssociation($server_url, $association)
    {
        if ($this->resultToBool($this->_set_assoc(
                                            $server_url,
                                            $association->handle,
                                            $this->blobEncode(
                                                  $association->secret),
                                            $association->issued,
                                            $association->lifetime,
                                            $association->assoc_type
                                            ))) {
            $this->connection->commit();
        } else {
            $this->connection->rollback();
        }
    }

    /**
     * @access private
     */
    function _get_assoc($server_url, $handle)
    {
        $result = $this->connection->getRow($this->sql['get_assoc'],
                                            array($server_url, $handle));
        if ($this->isError($result)) {
            return null;
        } else {
            return $result;
        }
    }

    /**
     * @access private
     */
    function _get_assocs($server_url)
    {
        $result = $this->connection->getAll($this->sql['get_assocs'],
                                            array($server_url));

        if ($this->isError($result)) {
            return array();
        } else {
            return $result;
        }
    }

    function removeAssociation($server_url, $handle)
    {
        if ($this->_get_assoc($server_url, $handle) == null) {
            return false;
        }

        if ($this->resultToBool($this->connection->query(
                              $this->sql['remove_assoc'],
                              array($server_url, $handle)))) {
            $this->connection->commit();
        } else {
            $this->connection->rollback();
        }

        return true;
    }

    function getAssociation($server_url, $handle = null)
    {
        if ($handle !== null) {
            $assoc = $this->_get_assoc($server_url, $handle);

            $assocs = array();
            if ($assoc) {
                $assocs[] = $assoc;
            }
        } else {
            $assocs = $this->_get_assocs($server_url);
        }

        if (!$assocs || (count($assocs) == 0)) {
            return null;
        } else {
            $associations = array();

            foreach ($assocs as $assoc_row) {
                $assoc = new Auth_OpenID_Association($assoc_row['handle'],
                                                     $assoc_row['secret'],
                                                     $assoc_row['issued'],
                                                     $assoc_row['lifetime'],
                                                     $assoc_row['assoc_type']);

                $assoc->secret = $this->blobDecode($assoc->secret);

                if ($assoc->getExpiresIn() == 0) {
                    $this->removeAssociation($server_url, $assoc->handle);
                } else {
                    $associations[] = array($assoc->issued, $assoc);
                }
            }

            if ($associations) {
                $issued = array();
                $assocs = array();
                foreach ($associations as $key => $assoc) {
                    $issued[$key] = $assoc[0];
                    $assocs[$key] = $assoc[1];
                }

                array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
                                $associations);

                // return the most recently issued one.
                list($issued, $assoc) = $associations[0];
                return $assoc;
            } else {
                return null;
            }
        }
    }

    /**
     * @access private
     */
    function _add_nonce($server_url, $timestamp, $salt)
    {
        $sql = $this->sql['add_nonce'];
        $result = $this->connection->query($sql, array($server_url,
                                                       $timestamp,
                                                       $salt));
        if ($this->isError($result)) {
            $this->connection->rollback();
        } else {
            $this->connection->commit();
        }
        return $this->resultToBool($result);
    }

    function useNonce($server_url, $timestamp, $salt)
    {
        global $Auth_OpenID_SKEW;

        if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
            return false;
        }

        return $this->_add_nonce($server_url, $timestamp, $salt);
    }

    /**
     * "Octifies" a binary string by returning a string with escaped
     * octal bytes.  This is used for preparing binary data for
     * PostgreSQL BYTEA fields.
     *
     * @access private
     */
    function _octify($str)
    {
        $result = "";
        for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) {
            $ch = substr($str, $i, 1);
            if ($ch == "\\") {
                $result .= "\\\\\\\\";
            } else if (ord($ch) == 0) {
                $result .= "\\\\000";
            } else {
                $result .= "\\" . strval(decoct(ord($ch)));
            }
        }
        return $result;
    }

    /**
     * "Unoctifies" octal-escaped data from PostgreSQL and returns the
     * resulting ASCII (possibly binary) string.
     *
     * @access private
     */
    function _unoctify($str)
    {
        $result = "";
        $i = 0;
        while ($i < strlen($str)) {
            $char = $str[$i];
            if ($char == "\\") {
                // Look to see if the next char is a backslash and
                // append it.
                if ($str[$i + 1] != "\\") {
                    $octal_digits = substr($str, $i + 1, 3);
                    $dec = octdec($octal_digits);
                    $char = chr($dec);
                    $i += 4;
                } else {
                    $char = "\\";
                    $i += 2;
                }
            } else {
                $i += 1;
            }

            $result .= $char;
        }

        return $result;
    }

    function cleanupNonces()
    {
        global $Auth_OpenID_SKEW;
        $v = time() - $Auth_OpenID_SKEW;

        $this->connection->query($this->sql['clean_nonce'], array($v));
        $num = $this->connection->affectedRows();
        $this->connection->commit();
        return $num;
    }

    function cleanupAssociations()
    {
        $this->connection->query($this->sql['clean_assoc'],
                                 array(time()));
        $num = $this->connection->affectedRows();
        $this->connection->commit();
        return $num;
    }
}


code:
<?php

/**
 * An SQLite store.
 *
 * @package OpenID
 */

/**
 * Require the base class file.
 */
require_once "Auth/OpenID/SQLStore.php";

/**
 * An SQL store that uses SQLite as its backend.
 *
 * @package OpenID
 */
class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
    function setSQL()
    {
        $this->sql['nonce_table'] =
            "CREATE TABLE %s (server_url VARCHAR(2047), timestamp INTEGER, ".
            "salt CHAR(40), UNIQUE (server_url, timestamp, salt))";

        $this->sql['assoc_table'] =
            "CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
            "secret BLOB(128), issued INTEGER, lifetime INTEGER, ".
            "assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle))";

        $this->sql['set_assoc'] =
            "INSERT OR REPLACE INTO %s VALUES (?, ?, ?, ?, ?, ?)";

        $this->sql['get_assocs'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ?";

        $this->sql['get_assoc'] =
            "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
            "WHERE server_url = ? AND handle = ?";

        $this->sql['remove_assoc'] =
            "DELETE FROM %s WHERE server_url = ? AND handle = ?";

        $this->sql['add_nonce'] =
            "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";

        $this->sql['clean_nonce'] =
            "DELETE FROM %s WHERE timestamp < ?";

        $this->sql['clean_assoc'] =
            "DELETE FROM %s WHERE issued + lifetime < ?";
    }

    /**
     * @access private
     */
    function _add_nonce($server_url, $timestamp, $salt)
    {
        // PECL SQLite extensions 1.0.3 and older (1.0.3 is the
        // current release at the time of this writing) have a broken
        // sqlite_escape_string function that breaks when passed the
        // empty string. Prefixing all strings with one character
        // keeps them unique and avoids this bug. The nonce table is
        // write-only, so we don't have to worry about updating other
        // functions with this same bad hack.
        return parent::_add_nonce('x' . $server_url, $timestamp, $salt);
    }
}


code:
<?php

/**
 * Simple registration request and response parsing and object
 * representation.
 *
 * This module contains objects representing simple registration
 * requests and responses that can be used with both OpenID relying
 * parties and OpenID providers.
 *
 * 1. The relying party creates a request object and adds it to the
 * {@link Auth_OpenID_AuthRequest} object before making the
 * checkid request to the OpenID provider:
 *
 *   $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
 *   $auth_request->addExtension($sreg_req);
 *
 * 2. The OpenID provider extracts the simple registration request
 * from the OpenID request using {@link
 * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
 * approval and data, creates an {@link Auth_OpenID_SRegResponse}
 * object and adds it to the id_res response:
 *
 *   $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
 *                                  $checkid_request);
 *   // [ get the user's approval and data, informing the user that
 *   //   the fields in sreg_response were requested ]
 *   $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
 *                                  $sreg_req, $user_data);
 *   $sreg_resp->toMessage($openid_response->fields);
 *
 * 3. The relying party uses {@link
 * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
 * from the OpenID response:
 *
 *   $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
 *                                  $success_response);
 *
 * @package OpenID
 */

/**
 * Import message and extension internals.
 */
require_once 'Auth/OpenID/Message.php';
require_once 'Auth/OpenID/Extension.php';

// The data fields that are listed in the sreg spec
global $Auth_OpenID_sreg_data_fields;
$Auth_OpenID_sreg_data_fields = array(
                                      'fullname' => 'Full Name',
                                      'nickname' => 'Nickname',
                                      'dob' => 'Date of Birth',
                                      'email' => 'E-mail Address',
                                      'gender' => 'Gender',
                                      'postcode' => 'Postal Code',
                                      'country' => 'Country',
                                      'language' => 'Language',
                                      'timezone' => 'Time Zone');

/**
 * Check to see that the given value is a valid simple registration
 * data field name.  Return true if so, false if not.
 */
function Auth_OpenID_checkFieldName($field_name)
{
    global $Auth_OpenID_sreg_data_fields;

    if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
        return false;
    }
    return true;
}

// URI used in the wild for Yadis documents advertising simple
// registration support
define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');

// URI in the draft specification for simple registration 1.1
// <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');

// This attribute will always hold the preferred URI to use when
// adding sreg support to an XRDS file or in an OpenID namespace
// declaration.
define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);

Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');

/**
 * Does the given endpoint advertise support for simple
 * registration?
 *
 * $endpoint: The endpoint object as returned by OpenID discovery.
 * returns whether an sreg type was advertised by the endpoint
 */
function Auth_OpenID_supportsSReg($endpoint)
{
    return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
            $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
}

/**
 * A base class for classes dealing with Simple Registration protocol
 * messages.
 *
 * @package OpenID
 */
class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
    /**
     * Extract the simple registration namespace URI from the given
     * OpenID message. Handles OpenID 1 and 2, as well as both sreg
     * namespace URIs found in the wild, as well as missing namespace
     * definitions (for OpenID 1)
     *
     * $message: The OpenID message from which to parse simple
     * registration fields. This may be a request or response message.
     *
     * Returns the sreg namespace URI for the supplied message. The
     * message may be modified to define a simple registration
     * namespace.
     *
     * @access private
     */
    static function _getSRegNS($message)
    {
        $alias = null;
        $found_ns_uri = null;

        // See if there exists an alias for one of the two defined
        // simple registration types.
        foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
                       Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
            $alias = $message->namespaces->getAlias($sreg_ns_uri);
            if ($alias !== null) {
                $found_ns_uri = $sreg_ns_uri;
                break;
            }
        }

        if ($alias === null) {
            // There is no alias for either of the types, so try to
            // add one. We default to using the modern value (1.1)
            $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
            if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
                                               'sreg') === null) {
                // An alias for the string 'sreg' already exists, but
                // it's defined for something other than simple
                // registration
                return null;
            }
        }

        return $found_ns_uri;
    }
}

/**
 * An object to hold the state of a simple registration request.
 *
 * required: A list of the required fields in this simple registration
 * request
 *
 * optional: A list of the optional fields in this simple registration
 * request
 *
 * @package OpenID
 */
class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {

    var $ns_alias = 'sreg';

    /**
     * Initialize an empty simple registration request.
     */
    static function build($required=null, $optional=null,
                   $policy_url=null,
                   $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
                   $cls='Auth_OpenID_SRegRequest')
    {
        $obj = new $cls();

        $obj->required = array();
        $obj->optional = array();
        $obj->policy_url = $policy_url;
        $obj->ns_uri = $sreg_ns_uri;

        if ($required) {
            if (!$obj->requestFields($required, true, true)) {
                return null;
            }
        }

        if ($optional) {
            if (!$obj->requestFields($optional, false, true)) {
                return null;
            }
        }

        return $obj;
    }

    /**
     * Create a simple registration request that contains the fields
     * that were requested in the OpenID request with the given
     * arguments
     *
     * $request: The OpenID authentication request from which to
     * extract an sreg request.
     *
     * $cls: name of class to use when creating sreg request object.
     * Used for testing.
     *
     * Returns the newly created simple registration request
     */
    static function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
    {

        $obj = call_user_func_array(array($cls, 'build'),
                 array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));

        // Since we're going to mess with namespace URI mapping, don't
        // mutate the object that was passed in.
        $m = $request->message;

        $obj->ns_uri = $obj->_getSRegNS($m);
        $args = $m->getArgs($obj->ns_uri);

        if ($args === null || Auth_OpenID::isFailure($args)) {
            return null;
        }

        $obj->parseExtensionArgs($args);

        return $obj;
    }

    /**
     * Parse the unqualified simple registration request parameters
     * and add them to this object.
     *
     * This method is essentially the inverse of
     * getExtensionArgs. This method restores the serialized simple
     * registration request fields.
     *
     * If you are extracting arguments from a standard OpenID
     * checkid_* request, you probably want to use fromOpenIDRequest,
     * which will extract the sreg namespace and arguments from the
     * OpenID request. This method is intended for cases where the
     * OpenID server needs more control over how the arguments are
     * parsed than that method provides.
     *
     * $args == $message->getArgs($ns_uri);
     * $request->parseExtensionArgs($args);
     *
     * $args: The unqualified simple registration arguments
     *
     * strict: Whether requests with fields that are not defined in
     * the simple registration specification should be tolerated (and
     * ignored)
     */
    function parseExtensionArgs($args, $strict=false)
    {
        foreach (array('required', 'optional') as $list_name) {
            $required = ($list_name == 'required');
            $items = Auth_OpenID::arrayGet($args, $list_name);
            if ($items) {
                foreach (explode(',', $items) as $field_name) {
                    if (!$this->requestField($field_name, $required, $strict)) {
                        if ($strict) {
                            return false;
                        }
                    }
                }
            }
        }

        $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');

        return true;
    }

    /**
     * A list of all of the simple registration fields that were
     * requested, whether they were required or optional.
     */
    function allRequestedFields()
    {
        return array_merge($this->required, $this->optional);
    }

    /**
     * Have any simple registration fields been requested?
     */
    function wereFieldsRequested()
    {
        return count($this->allRequestedFields());
    }

    /**
     * Was this field in the request?
     */
    function contains($field_name)
    {
        return (in_array($field_name, $this->required) ||
                in_array($field_name, $this->optional));
    }

    /**
     * Request the specified field from the OpenID user
     *
     * $field_name: the unqualified simple registration field name
     *
     * required: whether the given field should be presented to the
     * user as being a required to successfully complete the request
     *
     * strict: whether to raise an exception when a field is added to
     * a request more than once
     */
    function requestField($field_name,
                          $required=false, $strict=false)
    {
        if (!Auth_OpenID_checkFieldName($field_name)) {
            return false;
        }

        if ($strict) {
            if ($this->contains($field_name)) {
                return false;
            }
        } else {
            if (in_array($field_name, $this->required)) {
                return true;
            }

            if (in_array($field_name, $this->optional)) {
                if ($required) {
                    unset($this->optional[array_search($field_name,
                                                       $this->optional)]);
                } else {
                    return true;
                }
            }
        }

        if ($required) {
            $this->required[] = $field_name;
        } else {
            $this->optional[] = $field_name;
        }

        return true;
    }

    /**
     * Add the given list of fields to the request
     *
     * field_names: The simple registration data fields to request
     *
     * required: Whether these values should be presented to the user
     * as required
     *
     * strict: whether to raise an exception when a field is added to
     * a request more than once
     */
    function requestFields($field_names, $required=false, $strict=false)
    {
        if (!is_array($field_names)) {
            return false;
        }

        foreach ($field_names as $field_name) {
            if (!$this->requestField($field_name, $required, $strict=$strict)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get a dictionary of unqualified simple registration arguments
     * representing this request.
     *
     * This method is essentially the inverse of
     * C{L{parseExtensionArgs}}. This method serializes the simple
     * registration request fields.
     */
    function getExtensionArgs()
    {
        $args = array();

        if ($this->required) {
            $args['required'] = implode(',', $this->required);
        }

        if ($this->optional) {
            $args['optional'] = implode(',', $this->optional);
        }

        if ($this->policy_url) {
            $args['policy_url'] = $this->policy_url;
        }

        return $args;
    }
}

/**
 * Represents the data returned in a simple registration response
 * inside of an OpenID C{id_res} response. This object will be created
 * by the OpenID server, added to the C{id_res} response object, and
 * then extracted from the C{id_res} message by the Consumer.
 *
 * @package OpenID
 */
class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {

    var $ns_alias = 'sreg';

    function Auth_OpenID_SRegResponse($data=null,
                                      $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
    {
        if ($data === null) {
            $this->data = array();
        } else {
            $this->data = $data;
        }

        $this->ns_uri = $sreg_ns_uri;
    }

    /**
     * Take a C{L{SRegRequest}} and a dictionary of simple
     * registration values and create a C{L{SRegResponse}} object
     * containing that data.
     *
     * request: The simple registration request object
     *
     * data: The simple registration data for this response, as a
     * dictionary from unqualified simple registration field name to
     * string (unicode) value. For instance, the nickname should be
     * stored under the key 'nickname'.
     */
    static function extractResponse($request, $data)
    {
        $obj = new Auth_OpenID_SRegResponse();
        $obj->ns_uri = $request->ns_uri;

        foreach ($request->allRequestedFields() as $field) {
            $value = Auth_OpenID::arrayGet($data, $field);
            if ($value !== null) {
                $obj->data[$field] = $value;
            }
        }

        return $obj;
    }

    /**
     * Create a C{L{SRegResponse}} object from a successful OpenID
     * library response
     * (C{L{openid.consumer.consumer.SuccessResponse}}) response
     * message
     *
     * success_response: A SuccessResponse from consumer.complete()
     *
     * signed_only: Whether to process only data that was
     * signed in the id_res message from the server.
     *
     * Returns a simple registration response containing the data that
     * was supplied with the C{id_res} response.
     */
    static function fromSuccessResponse($success_response, $signed_only=true)
    {
        global $Auth_OpenID_sreg_data_fields;

        $obj = new Auth_OpenID_SRegResponse();
        $obj->ns_uri = $obj->_getSRegNS($success_response->message);

        if ($signed_only) {
            $args = $success_response->getSignedNS($obj->ns_uri);
        } else {
            $args = $success_response->message->getArgs($obj->ns_uri);
        }

        if ($args === null || Auth_OpenID::isFailure($args)) {
            return null;
        }

        foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
            if (in_array($field_name, array_keys($args))) {
                $obj->data[$field_name] = $args[$field_name];
            }
        }

        return $obj;
    }

    function getExtensionArgs()
    {
        return $this->data;
    }

    // Read-only dictionary interface
    function get($field_name, $default=null)
    {
        if (!Auth_OpenID_checkFieldName($field_name)) {
            return null;
        }

        return Auth_OpenID::arrayGet($this->data, $field_name, $default);
    }

    function contents()
    {
        return $this->data;
    }
}