--- a/lib/openid-php/Auth/OpenID/SReg.php +++ b/lib/openid-php/Auth/OpenID/SReg.php @@ -1,1 +1,522 @@ - +addExtension($sreg_req); + * + * 2. The OpenID provider extracts the simple registration request + * from the OpenID request using {@link + * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's + * approval and data, creates an {@link Auth_OpenID_SRegResponse} + * object and adds it to the id_res response: + * + * $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest( + * $checkid_request); + * // [ get the user's approval and data, informing the user that + * // the fields in sreg_response were requested ] + * $sreg_resp = Auth_OpenID_SRegResponse::extractResponse( + * $sreg_req, $user_data); + * $sreg_resp->toMessage($openid_response->fields); + * + * 3. The relying party uses {@link + * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data + * from the OpenID response: + * + * $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse( + * $success_response); + * + * @package OpenID + */ + +/** + * Import message and extension internals. + */ +require_once 'Auth/OpenID/Message.php'; +require_once 'Auth/OpenID/Extension.php'; + +// The data fields that are listed in the sreg spec +global $Auth_OpenID_sreg_data_fields; +$Auth_OpenID_sreg_data_fields = array( + 'fullname' => 'Full Name', + 'nickname' => 'Nickname', + 'dob' => 'Date of Birth', + 'email' => 'E-mail Address', + 'gender' => 'Gender', + 'postcode' => 'Postal Code', + 'country' => 'Country', + 'language' => 'Language', + 'timezone' => 'Time Zone'); + +/** + * Check to see that the given value is a valid simple registration + * data field name. Return true if so, false if not. + */ +function Auth_OpenID_checkFieldName($field_name) +{ + global $Auth_OpenID_sreg_data_fields; + + if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) { + return false; + } + return true; +} + +// URI used in the wild for Yadis documents advertising simple +// registration support +define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0'); + +// URI in the draft specification for simple registration 1.1 +// +define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1'); + +// This attribute will always hold the preferred URI to use when +// adding sreg support to an XRDS file or in an OpenID namespace +// declaration. +define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1); + +Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg'); + +/** + * Does the given endpoint advertise support for simple + * registration? + * + * $endpoint: The endpoint object as returned by OpenID discovery. + * returns whether an sreg type was advertised by the endpoint + */ +function Auth_OpenID_supportsSReg($endpoint) +{ + return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) || + $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0)); +} + +/** + * A base class for classes dealing with Simple Registration protocol + * messages. + * + * @package OpenID + */ +class Auth_OpenID_SRegBase extends Auth_OpenID_Extension { + /** + * Extract the simple registration namespace URI from the given + * OpenID message. Handles OpenID 1 and 2, as well as both sreg + * namespace URIs found in the wild, as well as missing namespace + * definitions (for OpenID 1) + * + * $message: The OpenID message from which to parse simple + * registration fields. This may be a request or response message. + * + * Returns the sreg namespace URI for the supplied message. The + * message may be modified to define a simple registration + * namespace. + * + * @access private + */ + static function _getSRegNS($message) + { + $alias = null; + $found_ns_uri = null; + + // See if there exists an alias for one of the two defined + // simple registration types. + foreach (array(Auth_OpenID_SREG_NS_URI_1_1, + Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) { + $alias = $message->namespaces->getAlias($sreg_ns_uri); + if ($alias !== null) { + $found_ns_uri = $sreg_ns_uri; + break; + } + } + + if ($alias === null) { + // There is no alias for either of the types, so try to + // add one. We default to using the modern value (1.1) + $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1; + if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1, + 'sreg') === null) { + // An alias for the string 'sreg' already exists, but + // it's defined for something other than simple + // registration + return null; + } + } + + return $found_ns_uri; + } +} + +/** + * An object to hold the state of a simple registration request. + * + * required: A list of the required fields in this simple registration + * request + * + * optional: A list of the optional fields in this simple registration + * request + * + * @package OpenID + */ +class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase { + + var $ns_alias = 'sreg'; + + /** + * Initialize an empty simple registration request. + */ + static function build($required=null, $optional=null, + $policy_url=null, + $sreg_ns_uri=Auth_OpenID_SREG_NS_URI, + $cls='Auth_OpenID_SRegRequest') + { + $obj = new $cls(); + + $obj->required = array(); + $obj->optional = array(); + $obj->policy_url = $policy_url; + $obj->ns_uri = $sreg_ns_uri; + + if ($required) { + if (!$obj->requestFields($required, true, true)) { + return null; + } + } + + if ($optional) { + if (!$obj->requestFields($optional, false, true)) { + return null; + } + } + + return $obj; + } + + /** + * Create a simple registration request that contains the fields + * that were requested in the OpenID request with the given + * arguments + * + * $request: The OpenID authentication request from which to + * extract an sreg request. + * + * $cls: name of class to use when creating sreg request object. + * Used for testing. + * + * Returns the newly created simple registration request + */ + static function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest') + { + + $obj = call_user_func_array(array($cls, 'build'), + array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls)); + + // Since we're going to mess with namespace URI mapping, don't + // mutate the object that was passed in. + $m = $request->message; + + $obj->ns_uri = $obj->_getSRegNS($m); + $args = $m->getArgs($obj->ns_uri); + + if ($args === null || Auth_OpenID::isFailure($args)) { + return null; + } + + $obj->parseExtensionArgs($args); + + return $obj; + } + + /** + * Parse the unqualified simple registration request parameters + * and add them to this object. + * + * This method is essentially the inverse of + * getExtensionArgs. This method restores the serialized simple + * registration request fields. + * + * If you are extracting arguments from a standard OpenID + * checkid_* request, you probably want to use fromOpenIDRequest, + * which will extract the sreg namespace and arguments from the + * OpenID request. This method is intended for cases where the + * OpenID server needs more control over how the arguments are + * parsed than that method provides. + * + * $args == $message->getArgs($ns_uri); + * $request->parseExtensionArgs($args); + * + * $args: The unqualified simple registration arguments + * + * strict: Whether requests with fields that are not defined in + * the simple registration specification should be tolerated (and + * ignored) + */ + function parseExtensionArgs($args, $strict=false) + { + foreach (array('required', 'optional') as $list_name) { + $required = ($list_name == 'required'); + $items = Auth_OpenID::arrayGet($args, $list_name); + if ($items) { + foreach (explode(',', $items) as $field_name) { + if (!$this->requestField($field_name, $required, $strict)) { + if ($strict) { + return false; + } + } + } + } + } + + $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url'); + + return true; + } + + /** + * A list of all of the simple registration fields that were + * requested, whether they were required or optional. + */ + function allRequestedFields() + { + return array_merge($this->required, $this->optional); + } + + /** + * Have any simple registration fields been requested? + */ + function wereFieldsRequested() + { + return count($this->allRequestedFields()); + } + + /** + * Was this field in the request? + */ + function contains($field_name) + { + return (in_array($field_name, $this->required) || + in_array($field_name, $this->optional)); + } + + /** + * Request the specified field from the OpenID user + * + * $field_name: the unqualified simple registration field name + * + * required: whether the given field should be presented to the + * user as being a required to successfully complete the request + * + * strict: whether to raise an exception when a field is added to + * a request more than once + */ + function requestField($field_name, + $required=false, $strict=false) + { + if (!Auth_OpenID_checkFieldName($field_name)) { + return false; + } + + if ($strict) { + if ($this->contains($field_name)) { + return false; + } + } else { + if (in_array($field_name, $this->required)) { + return true; + } + + if (in_array($field_name, $this->optional)) { + if ($required) { + unset($this->optional[array_search($field_name, + $this->optional)]); + } else { + return true; + } + } + } + + if ($required) { + $this->required[] = $field_name; + } else { + $this->optional[] = $field_name; + } + + return true; + } + + /** + * Add the given list of fields to the request + * + * field_names: The simple registration data fields to request + * + * required: Whether these values should be presented to the user + * as required + * + * strict: whether to raise an exception when a field is added to + * a request more than once + */ + function requestFields($field_names, $required=false, $strict=false) + { + if (!is_array($field_names)) { + return false; + } + + foreach ($field_names as $field_name) { + if (!$this->requestField($field_name, $required, $strict=$strict)) { + return false; + } + } + + return true; + } + + /** + * Get a dictionary of unqualified simple registration arguments + * representing this request. + * + * This method is essentially the inverse of + * C{L{parseExtensionArgs}}. This method serializes the simple + * registration request fields. + */ + function getExtensionArgs() + { + $args = array(); + + if ($this->required) { + $args['required'] = implode(',', $this->required); + } + + if ($this->optional) { + $args['optional'] = implode(',', $this->optional); + } + + if ($this->policy_url) { + $args['policy_url'] = $this->policy_url; + } + + return $args; + } +} + +/** + * Represents the data returned in a simple registration response + * inside of an OpenID C{id_res} response. This object will be created + * by the OpenID server, added to the C{id_res} response object, and + * then extracted from the C{id_res} message by the Consumer. + * + * @package OpenID + */ +class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase { + + var $ns_alias = 'sreg'; + + function Auth_OpenID_SRegResponse($data=null, + $sreg_ns_uri=Auth_OpenID_SREG_NS_URI) + { + if ($data === null) { + $this->data = array(); + } else { + $this->data = $data; + } + + $this->ns_uri = $sreg_ns_uri; + } + + /** + * Take a C{L{SRegRequest}} and a dictionary of simple + * registration values and create a C{L{SRegResponse}} object + * containing that data. + * + * request: The simple registration request object + * + * data: The simple registration data for this response, as a + * dictionary from unqualified simple registration field name to + * string (unicode) value. For instance, the nickname should be + * stored under the key 'nickname'. + */ + static function extractResponse($request, $data) + { + $obj = new Auth_OpenID_SRegResponse(); + $obj->ns_uri = $request->ns_uri; + + foreach ($request->allRequestedFields() as $field) { + $value = Auth_OpenID::arrayGet($data, $field); + if ($value !== null) { + $obj->data[$field] = $value; + } + } + + return $obj; + } + + /** + * Create a C{L{SRegResponse}} object from a successful OpenID + * library response + * (C{L{openid.consumer.consumer.SuccessResponse}}) response + * message + * + * success_response: A SuccessResponse from consumer.complete() + * + * signed_only: Whether to process only data that was + * signed in the id_res message from the server. + * + * Returns a simple registration response containing the data that + * was supplied with the C{id_res} response. + */ + static function fromSuccessResponse($success_response, $signed_only=true) + { + global $Auth_OpenID_sreg_data_fields; + + $obj = new Auth_OpenID_SRegResponse(); + $obj->ns_uri = $obj->_getSRegNS($success_response->message); + + if ($signed_only) { + $args = $success_response->getSignedNS($obj->ns_uri); + } else { + $args = $success_response->message->getArgs($obj->ns_uri); + } + + if ($args === null || Auth_OpenID::isFailure($args)) { + return null; + } + + foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) { + if (in_array($field_name, array_keys($args))) { + $obj->data[$field_name] = $args[$field_name]; + } + } + + return $obj; + } + + function getExtensionArgs() + { + return $this->data; + } + + // Read-only dictionary interface + function get($field_name, $default=null) + { + if (!Auth_OpenID_checkFieldName($field_name)) { + return null; + } + + return Auth_OpenID::arrayGet($this->data, $field_name, $default); + } + + function contents() + { + return $this->data; + } +} + + +