diff options
-rw-r--r-- | Auth/OpenID.php | 20 | ||||
-rw-r--r-- | Auth/OpenID/AX.php | 849 | ||||
-rw-r--r-- | Tests/Auth/OpenID/AX.php | 567 | ||||
-rw-r--r-- | Tests/TestDriver.php | 1 |
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', |