summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Auth/OpenID.php20
-rw-r--r--Auth/OpenID/AX.php849
-rw-r--r--Tests/Auth/OpenID/AX.php567
-rw-r--r--Tests/TestDriver.php1
4 files changed, 1437 insertions, 0 deletions
diff --git a/Auth/OpenID.php b/Auth/OpenID.php
index c74c252..51f655e 100644
--- a/Auth/OpenID.php
+++ b/Auth/OpenID.php
@@ -531,5 +531,25 @@ class Auth_OpenID {
return $parts;
}
}
+
+ function filter($callback, &$sequence)
+ {
+ $result = array();
+
+ foreach ($sequence as $item) {
+ if (call_user_func_array($callback, array($item))) {
+ $result[] = $item;
+ }
+ }
+
+ return $result;
+ }
+
+ function update(&$dest, &$src)
+ {
+ foreach ($src as $k => $v) {
+ $dest[$k] = $v;
+ }
+ }
}
?> \ No newline at end of file
diff --git a/Auth/OpenID/AX.php b/Auth/OpenID/AX.php
new file mode 100644
index 0000000..69b1ab0
--- /dev/null
+++ b/Auth/OpenID/AX.php
@@ -0,0 +1,849 @@
+<?php
+
+/**
+ * Implements the OpenID attribute exchange specification, version 1.0
+ * as of svn revision 295 from openid.net svn.
+ */
+
+require_once "Auth/OpenID/Extension.php";
+require_once "Auth/OpenID/Message.php";
+
+define('Auth_OpenID_AX_NS_URI',
+ 'http://openid.net/srv/ax/1.0');
+
+class Auth_OpenID_AX {
+ function isError($thing)
+ {
+ return is_a($thing, 'Auth_OpenID_AX_Error');
+ }
+}
+
+/*
+ * Results from data that does not meet the attribute exchange 1.0
+ * specification
+ */
+class Auth_OpenID_AX_Error {
+ function Auth_OpenID_AX_Error($message=null)
+ {
+ $this->message = $message;
+ }
+}
+
+/*
+ * Abstract class containing common code for attribute exchange messages
+ */
+class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
+ /*
+ * ns_alias: The preferred namespace alias for attribute exchange
+ * messages
+ */
+ var $ns_alias = 'ax';
+
+ /*
+ * mode: The type of this attribute exchange message. This must be
+ * overridden in subclasses.
+ */
+ var $mode = null;
+
+ var $ns_uri = Auth_OpenID_AX_NS_URI;
+
+ /*
+ * Return Auth_OpenID_AX_Error if the mode in the attribute
+ * exchange arguments does not match what is expected for this
+ * class; true otherwise.
+ */
+ function _checkMode($ax_args)
+ {
+ $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
+ if ($mode != $this->mode) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "Expected mode '%s'; got '%s'",
+ $this->mode, $mode));
+ }
+
+ return true;
+ }
+
+ /*
+ * Return a set of attribute exchange arguments containing the
+ * basic information that must be in every attribute exchange
+ * message.
+ */
+ function _newArgs()
+ {
+ return array('mode' => $this->mode);
+ }
+}
+
+/*
+ * Represents a single attribute in an attribute exchange
+ * request. This should be added to an AXRequest object in order to
+ * request the attribute.
+ */
+class Auth_OpenID_AX_AttrInfo {
+ function Auth_OpenID_AX_AttrInfo($type_uri, $count=1, $required=false,
+ $alias=null)
+ {
+ /*
+ * required: Whether the attribute will be marked as required
+ * when presented to the subject of the attribute exchange
+ * request.
+ */
+ $this->required = $required;
+
+ /*
+ * count: How many values of this type to request from the
+ * subject. Defaults to one.
+ */
+ $this->count = $count;
+
+ /*
+ * type_uri: The identifier that determines what the attribute
+ * represents and how it is serialized. For example, one type
+ * URI representing dates could represent a Unix timestamp in
+ * base 10 and another could represent a human-readable
+ * string.
+ */
+ $this->type_uri = $type_uri;
+
+ /*
+ * alias: The name that should be given to this alias in the
+ * request. If it is not supplied, a generic name will be
+ * assigned. For example, if you want to call a Unix timestamp
+ * value 'tstamp', set its alias to that value. If two
+ * attributes in the same message request to use the same
+ * alias, the request will fail to be generated.
+ */
+ $this->alias = $alias;
+ }
+}
+
+/*
+ * Given a namespace mapping and a string containing a comma-separated
+ * list of namespace aliases, return a list of type URIs that
+ * correspond to those aliases.
+ *
+ * @param $namespace_map The mapping from namespace URI to alias
+ * @param $alias_list_s The string containing the comma-separated
+ * list of aliases. May also be None for convenience.
+ *
+ * @return $seq The list of namespace URIs that corresponds to the
+ * supplied list of aliases. If the string was zero-length or None, an
+ * empty list will be returned.
+ *
+ * return null If an alias is present in the list of aliases but
+ * is not present in the namespace map.
+ */
+function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
+{
+ $uris = array();
+
+ if ($alias_list_s) {
+ foreach (explode(',', $alias_list_s) as $alias) {
+ $type_uri = $namespace_map->getNamespaceURI($alias);
+ if ($type_uri === null) {
+ // raise KeyError(
+ // 'No type is defined for attribute name %r' % (alias,))
+ return new Auth_OpenID_AX_Error(
+ sprintf('No type is defined for attribute name %s',
+ $alias)
+ );
+ } else {
+ $uris[] = $type_uri;
+ }
+ }
+ }
+
+ return $uris;
+}
+
+/*
+ * An attribute exchange 'fetch_request' message. This message is sent
+ * by a relying party when it wishes to obtain attributes about the
+ * subject of an OpenID authentication request.
+ */
+class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
+
+ var $mode = 'fetch_request';
+
+ function Auth_OpenID_AX_FetchRequest($update_url=null)
+ {
+ /*
+ * requested_attributes: The attributes that have been
+ * requested thus far, indexed by the type URI.
+ */
+ $this->requested_attributes = array();
+
+ /*
+ * update_url: A URL that will accept responses for this
+ * attribute exchange request, even in the absence of the user
+ * who made this request.
+ */
+ $this->update_url = $update_url;
+ }
+
+ /*
+ * Add an attribute to this attribute exchange request.
+ *
+ * @param attribute: The attribute that is being requested
+ * @return true on success, false when the requested attribute is
+ * already present in this fetch request.
+ */
+ function add($attribute)
+ {
+ if ($this->contains($attribute->type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("The attribute %s has already been requested",
+ $attribute->type_uri));
+ }
+
+ $this->requested_attributes[$attribute->type_uri] = $attribute;
+
+ return true;
+ }
+
+ /*
+ * Get the serialized form of this attribute fetch request.
+ *
+ * @returns: The fetch request message parameters
+ */
+ function getExtensionArgs()
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ $required = array();
+ $if_available = array();
+
+ $ax_args = $this->_newArgs();
+
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->alias === null) {
+ $alias = $aliases->add($type_uri);
+ } else {
+ $alias = $aliases->addAlias($type_uri, $attribute->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attribute->alias, $type_uri
+ ));
+ }
+ }
+
+ if ($attribute->required) {
+ $required[] = $alias;
+ } else {
+ $if_available[] = $alias;
+ }
+
+ if ($attribute->count != 1) {
+ $ax_args['count.' . $alias] = strval($attribute->count);
+ }
+
+ $ax_args['type.' . $alias] = $type_uri;
+ }
+
+ if ($required) {
+ $ax_args['required'] = implode(',', $required);
+ }
+
+ if ($if_available) {
+ $ax_args['if_available'] = implode(',', $if_available);
+ }
+
+ return $ax_args;
+ }
+
+ /*
+ * Get the type URIs for all attributes that have been marked as
+ * required.
+ *
+ * @return A list of the type URIs for attributes that have been
+ * marked as required.
+ */
+ function getRequiredAttrs()
+ {
+ $required = array();
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->required) {
+ $required[] = $type_uri;
+ }
+ }
+
+ return $required;
+ }
+
+ /*
+ * Extract a FetchRequest from an OpenID message
+ *
+ * @param message: The OpenID message containing the attribute
+ * fetch request
+ *
+ * @returns: Auth_OpenID_AX_Error or the FetchRequest extracted
+ * from the message if successful
+ */
+ function &fromOpenIDRequest($message)
+ {
+ $obj = new Auth_OpenID_AX_FetchRequest();
+ $ax_args = $message->getArgs($obj->ns_uri);
+
+ $result = $obj->parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ return $obj;
+ }
+
+ /*
+ * Given attribute exchange arguments, populate this FetchRequest.
+ *
+ * @return $result Auth_OpenID_AX_Error if the data to be parsed
+ * does not follow the attribute exchange specification. At least
+ * when 'if_available' or 'required' is not specified for a
+ * particular attribute type. Returns true otherwise.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $alias = substr($key, 5);
+ $type_uri = $value;
+
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+
+ $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
+ if ($count_s) {
+ $count = Auth_OpenID::intval($count_s);
+ } else {
+ $count = 1;
+ }
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count.' . $alias, $count_s));
+ }
+
+ $this->add(new Auth_OpenID_AX_AttrInfo($type_uri, $count,
+ false, $alias));
+ }
+ }
+
+ $required = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'required'));
+
+ foreach ($required as $type_uri) {
+ $attrib =& $this->requested_attributes[$type_uri];
+ $attrib->required = true;
+ }
+
+ $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'if_available'));
+
+ $all_type_uris = array_merge($required, $if_available);
+
+ foreach ($aliases->iterNamespaceURIs() as $type_uri) {
+ if (!in_array($type_uri, $all_type_uris)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf('Type URI %s was in the request but not ' .
+ 'present in "required" or "if_available"',
+ $type_uri));
+
+ }
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /*
+ * Iterate over the AttrInfo objects that are contained in this
+ * fetch_request.
+ */
+ function iterAttrs()
+ {
+ return array_values($this->requested_attributes);
+ }
+
+ function iterTypes()
+ {
+ return array_keys($this->requested_attributes);
+ }
+
+ /*
+ * Is the given type URI present in this fetch_request?
+ */
+ function contains($type_uri)
+ {
+ return in_array($type_uri, $this->iterTypes());
+ }
+}
+
+/*
+ * An abstract class that implements a message that has attribute keys
+ * and values. It contains the common code between fetch_response and
+ * store_request.
+ */
+class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
+
+ function Auth_OpenID_AX_KeyValueMessage()
+ {
+ $this->data = array();
+ }
+
+ /*
+ * Add a single value for the given attribute type to the
+ * message. If there are already values specified for this type,
+ * this value will be sent in addition to the values already
+ * specified.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param value: The value to add to the response to the relying
+ * party for this attribute
+ * @return null
+ */
+ function addValue($type_uri, $value)
+ {
+ if (!array_key_exists($type_uri, $this->data)) {
+ $this->data[$type_uri] = array();
+ }
+
+ $values =& $this->data[$type_uri];
+ $values[] = $value;
+ }
+
+ /*
+ * Set the values for the given attribute type. This replaces any
+ * values that have already been set for this attribute.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param values: A list of values to send for this attribute.
+ */
+ function setValues($type_uri, &$values)
+ {
+ $this->data[$type_uri] =& $values;
+ }
+
+ /*
+ * Get the extension arguments for the key/value pairs contained
+ * in this message.
+ *
+ * @param aliases: An alias mapping. Set to None if you don't care
+ * about the aliases for this request.
+ */
+ function _getExtensionKVArgs(&$aliases)
+ {
+ if ($aliases === null) {
+ $aliases = new Auth_OpenID_NamespaceMap();
+ }
+
+ $ax_args = array();
+
+ foreach ($this->data as $type_uri => $values) {
+ $alias = $aliases->add($type_uri);
+
+ $ax_args['type.' . $alias] = $type_uri;
+
+ if (count($values) == 1) {
+ $ax_args['value.' . $alias] = $values[0];
+ } else {
+ $ax_args['count.' . $alias] = strval(count($values));
+
+ foreach ($values as $i => $value) {
+ $key = sprintf('value.%s.%d', $alias, $i + 1);
+ $ax_args[$key] = $value;
+ }
+ }
+ }
+
+ return $ax_args;
+ }
+
+ /*
+ * Parse attribiute exchange key/value arguments into this object.
+ *
+ * @param ax_args: The attribute exchange fetch_response
+ * arguments, with namespacing removed.
+ *
+ * @return Auth_OpenID_AX_Error or true
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $type_uri = $value;
+ $alias = substr($key, 5);
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+ }
+ }
+
+ foreach ($aliases->iteritems() as $pair) {
+ list($type_uri, $alias) = $pair;
+
+ if (array_key_exists('count.' . $alias, $ax_args)) {
+ $count_s = $ax_args['count.' . $alias];
+
+ $count = Auth_OpenID::intval($count_s);
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count.' . $alias, $count_s)
+ );
+ }
+
+ $values = array();
+ for ($i = 1; $i < $count + 1; $i++) {
+ $value_key = sprintf('value.%s.%d', $alias, $i);
+
+ if (!array_key_exists($value_key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $value_key));
+ }
+
+ $value = $ax_args[$value_key];
+ $values[] = $value;
+ }
+ } else {
+ $key = 'value.' . $alias;
+
+ if (!array_key_exists($key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $key));
+ }
+
+ $value = $ax_args['value.' . $alias];
+
+ if ($value == '') {
+ $values = array();
+ } else {
+ $values = array($value);
+ }
+ }
+
+ $this->data[$type_uri] = $values;
+ }
+
+ return true;
+ }
+
+ /*
+ * Get a single value for an attribute. If no value was sent for
+ * this attribute, use the supplied default. If there is more than
+ * one value for this attribute, this method will fail.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param default: The value to return if the attribute was not
+ * sent in the fetch_response.
+ *
+ * @return $value Auth_OpenID_AX_Error on failure or the value of
+ * the attribute in the fetch_response message, or the default
+ * supplied
+ */
+ function getSingle($type_uri, $default=null)
+ {
+ $values = Auth_OpenID::arrayGet($this->data, $type_uri);
+ if (!$values) {
+ return $default;
+ } else if (count($values) == 1) {
+ return $values[0];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf('More than one value present for %s',
+ $type_uri)
+ );
+ }
+ }
+
+ /*
+ * Get the list of values for this attribute in the
+ * fetch_response.
+ *
+ * XXX: what to do if the values are not present? default
+ * parameter? this is funny because it's always supposed to return
+ * a list, so the default may break that, though it's provided by
+ * the user's code, so it might be okay. If no default is
+ * supplied, should the return be None or []?
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @return $values The list of values for this attribute in the
+ * response. May be an empty list. If the attribute was not sent
+ * in the response, returns Auth_OpenID_AX_Error.
+ */
+ function get($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return $this->data[$type_uri];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+
+ /*
+ * Get the number of responses for a particular attribute in this
+ * fetch_response message.
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @returns: The number of values sent for this attribute. If the
+ * attribute was not sent in the response, returns
+ * Auth_OpenID_AX_Error.
+ */
+ function count($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return count($this->get($type_uri));
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+}
+
+/*
+ * A fetch_response attribute exchange message
+ */
+class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'fetch_response';
+
+ function Auth_OpenID_AX_FetchResponse($update_url=null)
+ {
+ $this->Auth_OpenID_AX_KeyValueMessage();
+ $this->update_url = $update_url;
+ }
+
+ /*
+ * Serialize this object into arguments in the attribute exchange
+ * namespace
+ *
+ * @return $args The dictionary of unqualified attribute exchange
+ * arguments that represent this fetch_response, or
+ * Auth_OpenID_AX_Error on error.
+ */
+ function getExtensionArgs(&$request)
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ if ($request !== null) {
+ // Validate the data in the context of the request (the
+ // same attributes should be present in each, and the
+ // counts in the response must be no more than the counts
+ // in the request)
+
+ foreach ($this->data as $type_uri => $unused) {
+ if (!$request->contains($type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Response attribute not present in request: %s",
+ $type_uri)
+ );
+ }
+ }
+
+ foreach ($request->iterAttrs() as $attr_info) {
+ // Copy the aliases from the request so that reading
+ // the response in light of the request is easier
+ if ($attr_info->alias === null) {
+ $aliases->add($attr_info->type_uri);
+ } else {
+ $alias = $aliases->addAlias($attr_info->type_uri,
+ $attr_info->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attr_info->alias, $attr_info->type_uri)
+ );
+ }
+ }
+
+ if (array_key_exists($attr_info->type_uri, $this->data)) {
+ $values = $this->data[$attr_info->type_uri];
+ } else {
+ $values = array();
+ }
+
+ if ($attr_info->count < count($values)) {
+ // raise AXError(
+ // 'More than the number of requested values were '
+ // 'specified for %r' % (attr_info.type_uri,))
+ return new Auth_OpenID_AX_Error(
+ sprintf("More than the number of requested values " .
+ "were specified for %s",
+ $attr_info->type_uri)
+ );
+ }
+ }
+ }
+
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+
+ // Add the KV args into the response with the args that are
+ // unique to the fetch_response
+ $ax_args = $this->_newArgs();
+
+ if ($this->update_url) {
+ $ax_args['update_url'] = $this->update_url;
+ }
+
+ Auth_OpenID::update(&$ax_args, $kv_args);
+
+ return $ax_args;
+ }
+
+ /*
+ * @return $result Auth_OpenID_AX_Error on failure or true on
+ * success.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = parent::parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /*
+ * Construct a FetchResponse object from an OpenID library
+ * SuccessResponse object.
+ *
+ * @param success_response: A successful id_res response object
+ *
+ * @param signed: Whether non-signed args should be processsed. If
+ * True (the default), only signed arguments will be processsed.
+ *
+ * @return $response A FetchResponse containing the data from the
+ * OpenID message
+ */
+ function &fromSuccessResponse($success_response, $signed=true)
+ {
+ $obj = new Auth_OpenID_AX_FetchResponse();
+ if ($signed) {
+ $ax_args = $success_response->getSignedNS($obj->ns_uri);
+ } else {
+ $ax_args = $success_response->message->getArgs($obj->ns_uri);
+ }
+
+ return $obj->parseExtensionArgs($ax_args);
+ }
+}
+
+/*
+ * A store request attribute exchange message representation
+ */
+class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'store_request';
+
+ /*
+ * @param $aliases The namespace aliases to use when making this
+ * store response. Leave as None to use defaults.
+ */
+ function getExtensionArgs($aliases=null)
+ {
+ $ax_args = $this->_newArgs();
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+ Auth_OpenID::update(&$ax_args, $kv_args);
+ return $ax_args;
+ }
+}
+
+/*
+ * An indication that the store request was processed along with this
+ * OpenID transaction. Use make(), NOT the constructor, to create
+ * response objects.
+ */
+class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
+ var $SUCCESS_MODE = 'store_response_success';
+ var $FAILURE_MODE = 'store_response_failure';
+
+ /*
+ * Returns Auth_OpenID_AX_Error on error or an
+ * Auth_OpenID_AX_StoreResponse object on success.
+ */
+ function &make($succeeded=true, $error_message=null)
+ {
+ if (($succeeded) && ($error_message !== null)) {
+ return new Auth_OpenID_AX_Error('An error message may only be '.
+ 'included in a failing fetch response');
+ }
+
+ return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
+ }
+
+ function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
+ {
+ if ($succeeded) {
+ $this->mode = $this->SUCCESS_MODE;
+ } else {
+ $this->mode = $this->FAILURE_MODE;
+ }
+
+ $this->error_message = $error_message;
+ }
+
+ /*
+ * Was this response a success response?
+ */
+ function succeeded()
+ {
+ return $this->mode == $this->SUCCESS_MODE;
+ }
+
+ function getExtensionArgs()
+ {
+ $ax_args = $this->_newArgs();
+ if ((!$this->succeeded()) && $this->error_message) {
+ $ax_args['error'] = $this->error_message;
+ }
+
+ return $ax_args;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/AX.php b/Tests/Auth/OpenID/AX.php
new file mode 100644
index 0000000..ed2045b
--- /dev/null
+++ b/Tests/Auth/OpenID/AX.php
@@ -0,0 +1,567 @@
+<?php
+
+/*
+ * Tests for the attribute exchange extension module
+ */
+
+require_once "PHPUnit.php";
+require_once "Auth/OpenID/AX.php";
+require_once "Auth/OpenID/Message.php";
+
+class BogusAXMessage extends Auth_OpenID_AX_Message {
+ var $mode = 'bogus';
+
+ function getExtensionArgs()
+ {
+ return $this->_newArgs();
+ }
+}
+
+class AXMessageTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->bax = new BogusAXMessage();
+ }
+
+ function test_checkMode()
+ {
+ $result = $this->bax->_checkMode(array());
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+
+ $result = $this->bax->_checkMode(array('mode' => 'fetch_request'));
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+
+ // does not raise an exception when the mode is right
+ $result = $this->bax->_checkMode(array('mode' => $this->bax->mode));
+ $this->assertTrue($result === true);
+ }
+
+ /*
+ * _newArgs generates something that has the correct mode
+ */
+ function test_checkMode_newArgs()
+ {
+ $result = $this->bax->_checkMode($this->bax->_newArgs());
+ $this->assertTrue($result === true);
+ }
+}
+
+class AttrInfoTest extends PHPUnit_TestCase {
+ function test_construct()
+ {
+ $type_uri = 'a uri';
+ $ainfo = new Auth_OpenID_AX_AttrInfo($type_uri);
+
+ $this->assertEquals($type_uri, $ainfo->type_uri);
+ $this->assertEquals(1, $ainfo->count);
+ $this->assertFalse($ainfo->required);
+ $this->assertTrue($ainfo->alias === null);
+ }
+}
+
+class ToTypeURIsTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->aliases = new Auth_OpenID_NamespaceMap();
+ }
+
+ function test_empty()
+ {
+ foreach (array(null, '') as $empty) {
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases, $empty);
+ $this->assertEquals(array(), $uris);
+ }
+ }
+
+ function test_undefined()
+ {
+ $result = Auth_OpenID_AX_toTypeURIs($this->aliases,
+ 'http://janrain.com/');
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ }
+
+ function test_one()
+ {
+ $uri = 'http://janrain.com/';
+ $alias = 'openid_hackers';
+ $this->aliases->addAlias($uri, $alias);
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases, $alias);
+ $this->assertEquals(array($uri), $uris);
+ }
+
+ function test_two()
+ {
+ $uri1 = 'http://janrain.com/';
+ $alias1 = 'openid_hackers';
+ $this->aliases->addAlias($uri1, $alias1);
+
+ $uri2 = 'http://jyte.com/';
+ $alias2 = 'openid_hack';
+ $this->aliases->addAlias($uri2, $alias2);
+
+ $uris = Auth_OpenID_AX_toTypeURIs($this->aliases,
+ implode(',', array($alias1, $alias2)));
+ $this->assertEquals(array($uri1, $uri2), $uris);
+ }
+}
+
+class ParseAXValuesTest extends PHPUnit_TestCase {
+ function failUnlessAXKeyError($ax_args)
+ {
+ $msg = new Auth_OpenID_AX_KeyValueMessage();
+ $result = $msg->parseExtensionArgs($ax_args);
+ $this->assertTrue(Auth_OpenID_AX::isError($result));
+ $this->assertTrue($result->message);
+ }
+
+ function failUnlessAXValues($ax_args, $expected_args)
+ {
+ $msg = new Auth_OpenID_AX_KeyValueMessage();
+ $msg->parseExtensionArgs($ax_args);
+ $this->assertEquals($expected_args, $msg->data);
+ }
+
+ function test_emptyIsValid()
+ {
+ $this->failUnlessAXValues(array(), array());
+ }
+
+ function test_missingValueForAliasExplodes()
+ {
+ $this->failUnlessAXKeyError(array('type.foo' => 'urn:foo'));
+ }
+
+ function test_countPresentButNotValue()
+ {
+ $this->failUnlessAXKeyError(array('type.foo' => 'urn:foo',
+ 'count.foo' => '1'));
+ }
+
+ function test_countPresentAndIsZero()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'count.foo' => '0',
+ ), array('urn:foo' => array()));
+ }
+
+ function test_singletonEmpty()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ ), array('urn:foo' => array()));
+ }
+
+ function test_doubleAlias()
+ {
+ $this->failUnlessAXKeyError(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ 'type.bar' => 'urn:foo',
+ 'value.bar' => '',
+ ));
+ }
+
+ function test_doubleSingleton()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => '',
+ 'type.bar' => 'urn:bar',
+ 'value.bar' => '',
+ ), array('urn:foo' => array(), 'urn:bar' => array()));
+ }
+
+ function test_singletonValue()
+ {
+ $this->failUnlessAXValues(
+ array('type.foo' => 'urn:foo',
+ 'value.foo' => 'Westfall',
+ ), array('urn:foo' => array('Westfall')));
+ }
+}
+
+class FetchRequestTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_FetchRequest();
+ $this->type_a = 'http://janrain.example.com/a';
+ $this->alias_a = 'a';
+ }
+
+ function test_mode()
+ {
+ $this->assertEquals($this->msg->mode, 'fetch_request');
+ }
+
+ function test_construct()
+ {
+ $this->assertEquals(array(), $this->msg->requested_attributes);
+ $this->assertEquals(null, $this->msg->update_url);
+
+ $msg = new Auth_OpenID_AX_FetchRequest('hailstorm');
+ $this->assertEquals(array(), $msg->requested_attributes);
+ $this->assertEquals('hailstorm', $msg->update_url);
+ }
+
+ function test_add()
+ {
+ $uri = 'mud://puddle';
+
+ // Not yet added:
+ $this->assertFalse(in_array($uri, $this->msg->iterTypes()));
+
+ $attr = new Auth_OpenID_AX_AttrInfo($uri);
+ $this->msg->add($attr);
+
+ // Present after adding
+ $this->assertTrue(in_array($uri, $this->msg->iterTypes()));
+ }
+
+ function test_addTwice()
+ {
+ $uri = 'lightning://storm';
+
+ $attr = new Auth_OpenID_AX_AttrInfo($uri);
+ $this->msg->add($attr);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->add($attr)));
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $expected_args = array(
+ 'mode' =>'fetch_request',
+ );
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs());
+ }
+
+ function test_getExtensionArgs_noAlias()
+ {
+ $attr = new Auth_OpenID_AX_AttrInfo('type://of.transportation');
+
+ $this->msg->add($attr);
+ $ax_args = $this->msg->getExtensionArgs();
+ $found = false;
+ $alias = null;
+
+ foreach ($ax_args as $k => $v) {
+ if (($v == $attr->type_uri) && (strpos($k, 'type.') === 0)) {
+ $alias = substr($k, 5);
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->fail("Didn't find the type definition");
+ return;
+ }
+
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $alias => $attr->type_uri,
+ 'if_available' => $alias));
+ }
+
+ function test_getExtensionArgs_alias_if_available()
+ {
+ $attr = new Auth_OpenID_AX_AttrInfo(
+ 'type://of.transportation', 1, false,
+ 'transport');
+
+ $this->msg->add($attr);
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $attr->alias => $attr->type_uri,
+ 'if_available' => $attr->alias));
+ }
+
+ function test_getExtensionArgs_alias_req()
+ {
+ $attr = new Auth_OpenID_AX_AttrInfo(
+ 'type://of.transportation',
+ 1, true, 'transport');
+
+ $this->msg->add($attr);
+ $this->failUnlessExtensionArgs(array(
+ 'type.' . $attr->alias => $attr->type_uri,
+ 'required' => $attr->alias));
+ }
+
+ /*
+ * Make sure that getExtensionArgs has the expected result
+ *
+ * This method will fill in the mode.
+ */
+ function failUnlessExtensionArgs($expected_args)
+ {
+ $expected_args['mode'] = $this->msg->mode;
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs());
+ }
+
+ function test_isIterable()
+ {
+ $this->assertEquals(array(), $this->msg->iterAttrs());
+ $this->assertEquals(array(), $this->msg->iterTypes());
+ }
+
+ function test_getRequiredAttrs_empty()
+ {
+ $this->assertEquals(array(), $this->msg->getRequiredAttrs());
+ }
+
+ function test_parseExtensionArgs_extraType()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a);
+
+ $this->assertTrue(Auth_OpenID_AX::isError(
+ $this->msg->parseExtensionArgs($extension_args)));
+ }
+
+ function test_parseExtensionArgs()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals(array($this->type_a), $this->msg->iterTypes());
+ $attr_info = Auth_OpenID::arrayGet($this->msg->requested_attributes,
+ $this->type_a);
+ $this->assertTrue($attr_info);
+ $this->assertFalse($attr_info->required);
+ $this->assertEquals($this->type_a, $attr_info->type_uri);
+ $this->assertEquals($this->alias_a, $attr_info->alias);
+ $this->assertEquals(array($attr_info),
+ $this->msg->iterAttrs());
+ }
+
+ function test_extensionArgs_idempotent()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args, $this->msg->getExtensionArgs());
+
+ $attr = $this->msg->requested_attributes[$this->type_a];
+ $this->assertFalse($attr->required);
+ }
+
+ function test_extensionArgs_idempotent_count_required()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '2',
+ 'required' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args, $this->msg->getExtensionArgs());
+
+ $attr = $this->msg->requested_attributes[$this->type_a];
+ $this->assertTrue($attr->required);
+ }
+
+ function test_extensionArgs_count1()
+ {
+ $extension_args = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '1',
+ 'if_available' => $this->alias_a);
+
+ $extension_args_norm = array(
+ 'mode' => 'fetch_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'if_available' => $this->alias_a);
+
+ $this->msg->parseExtensionArgs($extension_args);
+ $this->assertEquals($extension_args_norm, $this->msg->getExtensionArgs());
+ }
+}
+
+class FetchResponseTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_FetchResponse();
+ $this->value_a = 'monkeys';
+ $this->type_a = 'http://phone.home/';
+ $this->alias_a = 'robocop';
+ }
+
+ function test_construct()
+ {
+ $this->assertTrue($this->msg->update_url === null);
+ $this->assertEquals(array(), $this->msg->data);
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ );
+ $req = null;
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_empty_request()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ );
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_empty_request_some()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ );
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $req->add(new Auth_OpenID_AX_AttrInfo('http://not.found/'));
+ $this->assertEquals($expected_args, $this->msg->getExtensionArgs($req));
+ }
+
+ function test_getExtensionArgs_some_request()
+ {
+ $expected_args = array(
+ 'mode' => 'fetch_response',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'value.' . $this->alias_a => $this->value_a,
+ );
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $req->add(new Auth_OpenID_AX_AttrInfo($this->type_a, 1, false, $this->alias_a));
+ $this->msg->addValue($this->type_a, $this->value_a);
+
+ $result = $this->msg->getExtensionArgs($req);
+ $this->assertEquals($expected_args, $result);
+ }
+
+ function test_getExtensionArgs_some_not_request()
+ {
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->msg->addValue($this->type_a, $this->value_a);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->getExtensionArgs($req)));
+ }
+
+ function test_getSingle_success()
+ {
+ $req = new Auth_OpenID_AX_FetchRequest();
+ $this->msg->addValue($this->type_a, $this->value_a);
+ $this->assertEquals($this->value_a, $this->msg->getSingle($this->type_a));
+ }
+
+ function test_getSingle_none()
+ {
+ $this->assertEquals(null, $this->msg->getSingle($this->type_a));
+ }
+
+ function test_getSingle_extra()
+ {
+ $data = array('x', 'y');
+ $this->msg->setValues($this->type_a, $data);
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->getSingle($this->type_a)));
+ }
+
+ function test_get()
+ {
+ $this->assertTrue(Auth_OpenID_AX::isError($this->msg->get($this->type_a)));
+ }
+}
+
+class StoreRequestTest extends PHPUnit_TestCase {
+ function setUp()
+ {
+ $this->msg = new Auth_OpenID_AX_StoreRequest();
+ $this->type_a = 'http://three.count/';
+ $this->alias_a = 'juggling';
+ }
+
+ function test_construct()
+ {
+ $this->assertEquals(array(), $this->msg->data);
+ }
+
+ function test_getExtensionArgs_empty()
+ {
+ $args = $this->msg->getExtensionArgs();
+ $expected_args = array(
+ 'mode' => 'store_request',
+ );
+ $this->assertEquals($expected_args, $args);
+ }
+
+ function test_getExtensionArgs_nonempty()
+ {
+ $data = array('foo', 'bar');
+ $this->msg->setValues($this->type_a, $data);
+ $aliases = new Auth_OpenID_NamespaceMap();
+ $aliases->addAlias($this->type_a, $this->alias_a);
+ $args = $this->msg->getExtensionArgs($aliases);
+ $expected_args = array(
+ 'mode' => 'store_request',
+ 'type.' . $this->alias_a => $this->type_a,
+ 'count.' . $this->alias_a => '2',
+ sprintf('value.%s.1', $this->alias_a) => 'foo',
+ sprintf('value.%s.2', $this->alias_a) => 'bar',
+ );
+ $this->assertEquals($expected_args, $args);
+ }
+}
+
+class StoreResponseTest extends PHPUnit_TestCase {
+ function test_success()
+ {
+ $msg = new Auth_OpenID_AX_StoreResponse();
+ $this->assertTrue($msg->succeeded());
+ $this->assertFalse($msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_success'),
+ $msg->getExtensionArgs());
+ }
+
+ function test_fail_nomsg()
+ {
+ $msg = new Auth_OpenID_AX_StoreResponse(false);
+ $this->assertFalse($msg->succeeded());
+ $this->assertFalse($msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_failure'),
+ $msg->getExtensionArgs());
+ }
+
+ function test_fail_msg()
+ {
+ $reason = 'no reason, really';
+ $msg = new Auth_OpenID_AX_StoreResponse(false, $reason);
+ $this->assertFalse($msg->succeeded());
+ $this->assertEquals($reason, $msg->error_message);
+ $this->assertEquals(array('mode' => 'store_response_failure',
+ 'error' => $reason), $msg->getExtensionArgs());
+ }
+}
+
+class Tests_Auth_OpenID_AX extends PHPUnit_TestSuite {
+ function getName()
+ {
+ return "Tests_Auth_OpenID_AX";
+ }
+
+ function Tests_Auth_OpenID_AX()
+ {
+ $this->addTestSuite('StoreResponseTest');
+ $this->addTestSuite('StoreRequestTest');
+ $this->addTestSuite('FetchResponseTest');
+ $this->addTestSuite('FetchRequestTest');
+ $this->addTestSuite('ParseAXValuesTest');
+ $this->addTestSuite('ToTypeURIsTest');
+ $this->addTestSuite('AttrInfoTest');
+ $this->addTestSuite('AXMessageTest');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/TestDriver.php b/Tests/TestDriver.php
index 0b5d6b2..db2985a 100644
--- a/Tests/TestDriver.php
+++ b/Tests/TestDriver.php
@@ -117,6 +117,7 @@ $_tests = array(
'Association',
'AssociationResponse',
'AuthRequest',
+ 'AX',
'BigMath',
'Consumer',
'CryptUtil',