4 * Extension argument processing code
10 * Import tools needed to deal with messages.
12 require_once 'Auth/OpenID.php';
13 require_once 'Auth/OpenID/KVForm.php';
14 require_once 'Auth/Yadis/XML.php';
15 require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse
17 // This doesn't REALLY belong here, but where is better?
18 define('Auth_OpenID_IDENTIFIER_SELECT',
19 "http://specs.openid.net/auth/2.0/identifier_select");
21 // URI for Simple Registration extension, the only commonly deployed
22 // OpenID 1.x extension, and so a special case
23 define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');
25 // The OpenID 1.X namespace URI
26 define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
27 define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');
29 function Auth_OpenID_isOpenID1($ns)
31 return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
32 ($ns == Auth_OpenID_OPENID1_NS);
35 // The OpenID 2.0 namespace URI
36 define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');
38 // The namespace consisting of pairs with keys that are prefixed with
39 // "openid." but not in another namespace.
40 define('Auth_OpenID_NULL_NAMESPACE', 'Null namespace');
42 // The null namespace, when it is an allowed OpenID namespace
43 define('Auth_OpenID_OPENID_NS', 'OpenID namespace');
45 // The top-level namespace, excluding all pairs with keys that start
47 define('Auth_OpenID_BARE_NS', 'Bare namespace');
49 // Sentinel for Message implementation to indicate that getArg should
50 // return null instead of returning a default.
51 define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');
53 // Limit, in bytes, of identity provider and return_to URLs, including
54 // response payload. See OpenID 1.1 specification, Appendix D.
55 define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);
57 // All OpenID protocol fields. Used to check namespace aliases.
58 global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
59 $Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
60 'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
61 'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
62 'dh_consumer_public', 'claimed_id', 'identity', 'realm',
63 'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
64 'assoc_handle', 'trust_root', 'openid');
66 // Global namespace / alias registration map. See
67 // Auth_OpenID_registerNamespaceAlias.
68 global $Auth_OpenID_registered_aliases;
69 $Auth_OpenID_registered_aliases = array();
72 * Registers a (namespace URI, alias) mapping in a global namespace
73 * alias map. Raises NamespaceAliasRegistrationError if either the
74 * namespace URI or alias has already been registered with a different
75 * value. This function is required if you want to use a namespace
76 * with an OpenID 1 message.
78 function Auth_OpenID_registerNamespaceAlias($namespace_uri, $alias)
80 global $Auth_OpenID_registered_aliases;
82 if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
83 $alias) == $namespace_uri) {
87 if (in_array($namespace_uri,
88 array_values($Auth_OpenID_registered_aliases))) {
92 if (in_array($alias, array_keys($Auth_OpenID_registered_aliases))) {
96 $Auth_OpenID_registered_aliases[$alias] = $namespace_uri;
101 * Removes a (namespace_uri, alias) registration from the global
102 * namespace alias map. Returns true if the removal succeeded; false
103 * if not (if the mapping did not exist).
105 function Auth_OpenID_removeNamespaceAlias($namespace_uri, $alias)
107 global $Auth_OpenID_registered_aliases;
109 if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
110 $alias) === $namespace_uri) {
111 unset($Auth_OpenID_registered_aliases[$alias]);
119 * An Auth_OpenID_Mapping maintains a mapping from arbitrary keys to
120 * arbitrary values. (This is unlike an ordinary PHP array, whose
121 * keys may be only simple scalars.)
125 class Auth_OpenID_Mapping {
127 * Initialize a mapping. If $classic_array is specified, its keys
128 * and values are used to populate the mapping.
130 function Auth_OpenID_Mapping($classic_array = null)
132 $this->keys = array();
133 $this->values = array();
135 if (is_array($classic_array)) {
136 foreach ($classic_array as $key => $value) {
137 $this->set($key, $value);
143 * Returns true if $thing is an Auth_OpenID_Mapping object; false
146 static function isA($thing)
148 return (is_object($thing) &&
149 strtolower(get_class($thing)) == 'auth_openid_mapping');
153 * Returns an array of the keys in the mapping.
161 * Returns an array of values in the mapping.
165 return $this->values;
169 * Returns an array of (key, value) pairs in the mapping.
175 for ($i = 0; $i < count($this->keys); $i++) {
176 $temp[] = array($this->keys[$i],
183 * Returns the "length" of the mapping, or the number of keys.
187 return count($this->keys);
191 * Sets a key-value pair in the mapping. If the key already
192 * exists, its value is replaced with the new value.
194 function set($key, $value)
196 $index = array_search($key, $this->keys);
198 if ($index !== false) {
199 $this->values[$index] = $value;
201 $this->keys[] = $key;
202 $this->values[] = $value;
207 * Gets a specified value from the mapping, associated with the
208 * specified key. If the key does not exist in the mapping,
209 * $default is returned instead.
211 function get($key, $default = null)
213 $index = array_search($key, $this->keys);
215 if ($index !== false) {
216 return $this->values[$index];
227 // PHP is broken yet again. Sort the arrays to remove the
228 // hole in the numeric indexes that make up the array.
229 $old_keys = $this->keys;
230 $old_values = $this->values;
232 $this->keys = array();
233 $this->values = array();
235 foreach ($old_keys as $k) {
239 foreach ($old_values as $v) {
240 $this->values[] = $v;
245 * Deletes a key-value pair from the mapping with the specified
250 $index = array_search($key, $this->keys);
252 if ($index !== false) {
253 unset($this->keys[$index]);
254 unset($this->values[$index]);
262 * Returns true if the specified value has a key in the mapping;
265 function contains($value)
267 return (array_search($value, $this->keys) !== false);
272 * Maintains a bijective map between namespace uris and aliases.
276 class Auth_OpenID_NamespaceMap {
277 function Auth_OpenID_NamespaceMap()
279 $this->alias_to_namespace = new Auth_OpenID_Mapping();
280 $this->namespace_to_alias = new Auth_OpenID_Mapping();
281 $this->implicit_namespaces = array();
284 function getAlias($namespace_uri)
286 return $this->namespace_to_alias->get($namespace_uri);
289 function getNamespaceURI($alias)
291 return $this->alias_to_namespace->get($alias);
294 function iterNamespaceURIs()
296 // Return an iterator over the namespace URIs
297 return $this->namespace_to_alias->keys();
300 function iterAliases()
302 // Return an iterator over the aliases"""
303 return $this->alias_to_namespace->keys();
308 return $this->namespace_to_alias->items();
311 function isImplicit($namespace_uri)
313 return in_array($namespace_uri, $this->implicit_namespaces);
316 function addAlias($namespace_uri, $desired_alias, $implicit=false)
318 // Add an alias from this namespace URI to the desired alias
319 global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
321 // Check that desired_alias is not an openid protocol field as
323 if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) {
324 Auth_OpenID::log("\"%s\" is not an allowed namespace alias",
329 // Check that desired_alias does not contain a period as per
331 if (strpos($desired_alias, '.') !== false) {
332 Auth_OpenID::log('"%s" must not contain a dot', $desired_alias);
336 // Check that there is not a namespace already defined for the
338 $current_namespace_uri =
339 $this->alias_to_namespace->get($desired_alias);
341 if (($current_namespace_uri !== null) &&
342 ($current_namespace_uri != $namespace_uri)) {
343 Auth_OpenID::log('Cannot map "%s" because previous mapping exists',
348 // Check that there is not already a (different) alias for
349 // this namespace URI
350 $alias = $this->namespace_to_alias->get($namespace_uri);
352 if (($alias !== null) && ($alias != $desired_alias)) {
353 Auth_OpenID::log('Cannot map %s to alias %s. ' .
354 'It is already mapped to alias %s',
355 $namespace_uri, $desired_alias, $alias);
359 assert((Auth_OpenID_NULL_NAMESPACE === $desired_alias) ||
360 is_string($desired_alias));
362 $this->alias_to_namespace->set($desired_alias, $namespace_uri);
363 $this->namespace_to_alias->set($namespace_uri, $desired_alias);
365 array_push($this->implicit_namespaces, $namespace_uri);
368 return $desired_alias;
371 function add($namespace_uri)
373 // Add this namespace URI to the mapping, without caring what
374 // alias it ends up with
376 // See if this namespace is already mapped to an alias
377 $alias = $this->namespace_to_alias->get($namespace_uri);
379 if ($alias !== null) {
383 // Fall back to generating a numerical alias
386 $alias = 'ext' . strval($i);
387 if ($this->addAlias($namespace_uri, $alias) === null) {
394 // Should NEVER be reached!
398 function contains($namespace_uri)
400 return $this->isDefined($namespace_uri);
403 function isDefined($namespace_uri)
405 return $this->namespace_to_alias->contains($namespace_uri);
410 * In the implementation of this object, null represents the global
411 * namespace as well as a namespace with no key.
415 class Auth_OpenID_Message {
417 function Auth_OpenID_Message($openid_namespace = null)
419 // Create an empty Message
420 $this->allowed_openid_namespaces = array(
421 Auth_OpenID_OPENID1_NS,
422 Auth_OpenID_THE_OTHER_OPENID1_NS,
423 Auth_OpenID_OPENID2_NS);
425 $this->args = new Auth_OpenID_Mapping();
426 $this->namespaces = new Auth_OpenID_NamespaceMap();
427 if ($openid_namespace === null) {
428 $this->_openid_ns_uri = null;
430 $implicit = Auth_OpenID_isOpenID1($openid_namespace);
431 $this->setOpenIDNamespace($openid_namespace, $implicit);
437 return Auth_OpenID_isOpenID1($this->getOpenIDNamespace());
442 return $this->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS;
445 static function fromPostArgs($args)
447 // Construct a Message containing a set of POST arguments
448 $obj = new Auth_OpenID_Message();
450 // Partition into "openid." args and bare args
451 $openid_args = array();
452 foreach ($args as $key => $value) {
454 if (is_array($value)) {
458 $parts = explode('.', $key, 2);
460 if (count($parts) == 2) {
461 list($prefix, $rest) = $parts;
466 if ($prefix != 'openid') {
467 $obj->args->set(array(Auth_OpenID_BARE_NS, $key), $value);
469 $openid_args[$rest] = $value;
473 if ($obj->_fromOpenIDArgs($openid_args)) {
480 static function fromOpenIDArgs($openid_args)
484 // Construct a Message from a parsed KVForm message
485 $obj = new Auth_OpenID_Message();
486 if ($obj->_fromOpenIDArgs($openid_args)) {
496 function _fromOpenIDArgs($openid_args)
498 global $Auth_OpenID_registered_aliases;
500 // Takes an Auth_OpenID_Mapping instance OR an array.
502 if (!Auth_OpenID_Mapping::isA($openid_args)) {
503 $openid_args = new Auth_OpenID_Mapping($openid_args);
508 // Resolve namespaces
509 foreach ($openid_args->items() as $pair) {
510 list($rest, $value) = $pair;
512 $parts = explode('.', $rest, 2);
514 if (count($parts) == 2) {
515 list($ns_alias, $ns_key) = $parts;
517 $ns_alias = Auth_OpenID_NULL_NAMESPACE;
521 if ($ns_alias == 'ns') {
522 if ($this->namespaces->addAlias($value, $ns_key) === null) {
525 } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) &&
528 if ($this->setOpenIDNamespace($value, false) === false) {
532 $ns_args[] = array($ns_alias, $ns_key, $value);
536 if (!$this->getOpenIDNamespace()) {
537 if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) ===
543 // Actually put the pairs into the appropriate namespaces
544 foreach ($ns_args as $triple) {
545 list($ns_alias, $ns_key, $value) = $triple;
546 $ns_uri = $this->namespaces->getNamespaceURI($ns_alias);
547 if ($ns_uri === null) {
548 $ns_uri = $this->_getDefaultNamespace($ns_alias);
549 if ($ns_uri === null) {
551 $ns_uri = Auth_OpenID_OPENID_NS;
552 $ns_key = sprintf('%s.%s', $ns_alias, $ns_key);
554 $this->namespaces->addAlias($ns_uri, $ns_alias, true);
558 $this->setArg($ns_uri, $ns_key, $value);
564 function _getDefaultNamespace($mystery_alias)
566 global $Auth_OpenID_registered_aliases;
567 if ($this->isOpenID1()) {
568 return @$Auth_OpenID_registered_aliases[$mystery_alias];
573 function setOpenIDNamespace($openid_ns_uri, $implicit)
575 if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) {
576 Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri);
580 $succeeded = $this->namespaces->addAlias($openid_ns_uri,
581 Auth_OpenID_NULL_NAMESPACE,
583 if ($succeeded === false) {
587 $this->_openid_ns_uri = $openid_ns_uri;
592 function getOpenIDNamespace()
594 return $this->_openid_ns_uri;
597 static function fromKVForm($kvform_string)
599 // Create a Message from a KVForm string
600 return Auth_OpenID_Message::fromOpenIDArgs(
601 Auth_OpenID_KVForm::toArray($kvform_string));
609 function toPostArgs()
611 // Return all arguments with openid. in front of namespaced
616 // Add namespace definitions to the output
617 foreach ($this->namespaces->iteritems() as $pair) {
618 list($ns_uri, $alias) = $pair;
619 if ($this->namespaces->isImplicit($ns_uri)) {
622 if ($alias == Auth_OpenID_NULL_NAMESPACE) {
623 $ns_key = 'openid.ns';
625 $ns_key = 'openid.ns.' . $alias;
627 $args[$ns_key] = $ns_uri;
630 foreach ($this->args->items() as $pair) {
631 list($ns_parts, $value) = $pair;
632 list($ns_uri, $ns_key) = $ns_parts;
633 $key = $this->getKey($ns_uri, $ns_key);
634 $args[$key] = $value;
642 // Return all namespaced arguments, failing if any
643 // non-namespaced arguments exist.
644 $post_args = $this->toPostArgs();
646 foreach ($post_args as $k => $v) {
647 if (strpos($k, 'openid.') !== 0) {
649 // 'This message can only be encoded as a POST, because it '
650 // 'contains arguments that are not prefixed with "openid."')
653 $kvargs[substr($k, 7)] = $v;
660 function toFormMarkup($action_url, $form_tag_attrs = null,
661 $submit_text = "Continue")
663 $form = "<form accept-charset=\"UTF-8\" ".
664 "enctype=\"application/x-www-form-urlencoded\"";
666 if (!$form_tag_attrs) {
667 $form_tag_attrs = array();
670 $form_tag_attrs['action'] = $action_url;
671 $form_tag_attrs['method'] = 'post';
673 unset($form_tag_attrs['enctype']);
674 unset($form_tag_attrs['accept-charset']);
676 if ($form_tag_attrs) {
677 foreach ($form_tag_attrs as $name => $attr) {
678 $form .= sprintf(" %s=\"%s\"", $name, $attr);
684 foreach ($this->toPostArgs() as $name => $value) {
686 "<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
687 $name, urldecode($value));
690 $form .= sprintf("<input type=\"submit\" value=\"%s\" />\n",
693 $form .= "</form>\n";
698 function toURL($base_url)
700 // Generate a GET URL with the parameters in this message
701 // attached as query parameters.
702 return Auth_OpenID::appendArgs($base_url, $this->toPostArgs());
707 // Generate a KVForm string that contains the parameters in
708 // this message. This will fail if the message contains
709 // arguments outside of the 'openid.' prefix.
710 return Auth_OpenID_KVForm::fromArray($this->toArgs());
713 function toURLEncoded()
715 // Generate an x-www-urlencoded string
718 foreach ($this->toPostArgs() as $k => $v) {
719 $args[] = array($k, $v);
723 return Auth_OpenID::httpBuildQuery($args);
729 function _fixNS($namespace)
731 // Convert an input value into the internally used values of
734 if ($namespace == Auth_OpenID_OPENID_NS) {
735 if ($this->_openid_ns_uri === null) {
736 return new Auth_OpenID_FailureResponse(null,
737 'OpenID namespace not set');
739 $namespace = $this->_openid_ns_uri;
743 if (($namespace != Auth_OpenID_BARE_NS) &&
744 (!is_string($namespace))) {
746 $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ".
747 "Auth_OpenID_OPENID_NS or a string. got %s",
748 print_r($namespace, true));
749 return new Auth_OpenID_FailureResponse(null, $err_msg);
752 if (($namespace != Auth_OpenID_BARE_NS) &&
753 (strpos($namespace, ':') === false)) {
754 // fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
755 // warnings.warn(fmt % (namespace,), DeprecationWarning)
757 if ($namespace == 'sreg') {
758 // fmt = 'Using %r instead of "sreg" as namespace'
759 // warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
760 return Auth_OpenID_SREG_URI;
767 function hasKey($namespace, $ns_key)
769 $namespace = $this->_fixNS($namespace);
770 if (Auth_OpenID::isFailure($namespace)) {
774 return $this->args->contains(array($namespace, $ns_key));
778 function getKey($namespace, $ns_key)
780 // Get the key for a particular namespaced argument
781 $namespace = $this->_fixNS($namespace);
782 if (Auth_OpenID::isFailure($namespace)) {
785 if ($namespace == Auth_OpenID_BARE_NS) {
789 $ns_alias = $this->namespaces->getAlias($namespace);
791 // No alias is defined, so no key can exist
792 if ($ns_alias === null) {
796 if ($ns_alias == Auth_OpenID_NULL_NAMESPACE) {
799 $tail = sprintf('%s.%s', $ns_alias, $ns_key);
802 return 'openid.' . $tail;
805 function getArg($namespace, $key, $default = null)
807 // Get a value for a namespaced key.
808 $namespace = $this->_fixNS($namespace);
810 if (Auth_OpenID::isFailure($namespace)) {
813 if ((!$this->args->contains(array($namespace, $key))) &&
814 ($default == Auth_OpenID_NO_DEFAULT)) {
815 $err_msg = sprintf("Namespace %s missing required field %s",
817 return new Auth_OpenID_FailureResponse(null, $err_msg);
819 return $this->args->get(array($namespace, $key), $default);
824 function getArgs($namespace)
826 // Get the arguments that are defined for this namespace URI
828 $namespace = $this->_fixNS($namespace);
829 if (Auth_OpenID::isFailure($namespace)) {
833 foreach ($this->args->items() as $pair) {
834 list($key, $value) = $pair;
835 list($pair_ns, $ns_key) = $key;
836 if ($pair_ns == $namespace) {
837 $stuff[$ns_key] = $value;
845 function updateArgs($namespace, $updates)
847 // Set multiple key/value pairs in one call
849 $namespace = $this->_fixNS($namespace);
851 if (Auth_OpenID::isFailure($namespace)) {
854 foreach ($updates as $k => $v) {
855 $this->setArg($namespace, $k, $v);
861 function setArg($namespace, $key, $value)
863 // Set a single argument in this namespace
864 $namespace = $this->_fixNS($namespace);
866 if (Auth_OpenID::isFailure($namespace)) {
869 $this->args->set(array($namespace, $key), $value);
870 if ($namespace !== Auth_OpenID_BARE_NS) {
871 $this->namespaces->add($namespace);
877 function delArg($namespace, $key)
879 $namespace = $this->_fixNS($namespace);
881 if (Auth_OpenID::isFailure($namespace)) {
884 return $this->args->del(array($namespace, $key));
888 function getAliasedArg($aliased_key, $default = null)
890 if ($aliased_key == 'ns') {
891 // Return the namespace URI for the OpenID namespace
892 return $this->getOpenIDNamespace();
895 $parts = explode('.', $aliased_key, 2);
897 if (count($parts) != 2) {
900 list($alias, $key) = $parts;
902 if ($alias == 'ns') {
903 // Return the namespace URI for a namespace alias
905 return $this->namespaces->getNamespaceURI($key);
907 $ns = $this->namespaces->getNamespaceURI($alias);
913 $ns = $this->getOpenIDNamespace();
916 return $this->getArg($ns, $key, $default);