<?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. */ 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; } 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; } } 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; } 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, $attr); } } $form .= ">\n"; foreach ($this->toPostArgs() as $name => $value) { $form .= sprintf( "<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n", $name, $value); } $form .= sprintf("<input type=\"submit\" value=\"%s\" />\n", $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) { $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); } } ?>