1: <?php
2:
3: /**
4: * Simple registration request and response parsing and object
5: * representation.
6: *
7: * This module contains objects representing simple registration
8: * requests and responses that can be used with both OpenID relying
9: * parties and OpenID providers.
10: *
11: * 1. The relying party creates a request object and adds it to the
12: * {@link Auth_OpenID_AuthRequest} object before making the
13: * checkid request to the OpenID provider:
14: *
15: * $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
16: * $auth_request->addExtension($sreg_req);
17: *
18: * 2. The OpenID provider extracts the simple registration request
19: * from the OpenID request using {@link
20: * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
21: * approval and data, creates an {@link Auth_OpenID_SRegResponse}
22: * object and adds it to the id_res response:
23: *
24: * $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
25: * $checkid_request);
26: * // [ get the user's approval and data, informing the user that
27: * // the fields in sreg_response were requested ]
28: * $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
29: * $sreg_req, $user_data);
30: * $sreg_resp->toMessage($openid_response->fields);
31: *
32: * 3. The relying party uses {@link
33: * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
34: * from the OpenID response:
35: *
36: * $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
37: * $success_response);
38: *
39: * @package OpenID
40: */
41:
42: /**
43: * Import message and extension internals.
44: */
45: require_once 'Auth/OpenID/Message.php';
46: require_once 'Auth/OpenID/Extension.php';
47:
48: // The data fields that are listed in the sreg spec
49: global $Auth_OpenID_sreg_data_fields;
50: $Auth_OpenID_sreg_data_fields = array(
51: 'fullname' => 'Full Name',
52: 'nickname' => 'Nickname',
53: 'dob' => 'Date of Birth',
54: 'email' => 'E-mail Address',
55: 'gender' => 'Gender',
56: 'postcode' => 'Postal Code',
57: 'country' => 'Country',
58: 'language' => 'Language',
59: 'timezone' => 'Time Zone');
60:
61: /**
62: * Check to see that the given value is a valid simple registration
63: * data field name. Return true if so, false if not.
64: */
65: function Auth_OpenID_checkFieldName($field_name)
66: {
67: global $Auth_OpenID_sreg_data_fields;
68:
69: if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
70: return false;
71: }
72: return true;
73: }
74:
75: // URI used in the wild for Yadis documents advertising simple
76: // registration support
77: define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');
78:
79: // URI in the draft specification for simple registration 1.1
80: // <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
81: define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');
82:
83: // This attribute will always hold the preferred URI to use when
84: // adding sreg support to an XRDS file or in an OpenID namespace
85: // declaration.
86: define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);
87:
88: Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');
89:
90: /**
91: * Does the given endpoint advertise support for simple
92: * registration?
93: *
94: * $endpoint: The endpoint object as returned by OpenID discovery.
95: * returns whether an sreg type was advertised by the endpoint
96: */
97: function Auth_OpenID_supportsSReg($endpoint)
98: {
99: return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
100: $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
101: }
102:
103: /**
104: * A base class for classes dealing with Simple Registration protocol
105: * messages.
106: *
107: * @package OpenID
108: */
109: class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
110: /**
111: * Extract the simple registration namespace URI from the given
112: * OpenID message. Handles OpenID 1 and 2, as well as both sreg
113: * namespace URIs found in the wild, as well as missing namespace
114: * definitions (for OpenID 1)
115: *
116: * $message: The OpenID message from which to parse simple
117: * registration fields. This may be a request or response message.
118: *
119: * Returns the sreg namespace URI for the supplied message. The
120: * message may be modified to define a simple registration
121: * namespace.
122: *
123: * @access private
124: */
125: static function _getSRegNS($message)
126: {
127: $alias = null;
128: $found_ns_uri = null;
129:
130: // See if there exists an alias for one of the two defined
131: // simple registration types.
132: foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
133: Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
134: $alias = $message->namespaces->getAlias($sreg_ns_uri);
135: if ($alias !== null) {
136: $found_ns_uri = $sreg_ns_uri;
137: break;
138: }
139: }
140:
141: if ($alias === null) {
142: // There is no alias for either of the types, so try to
143: // add one. We default to using the modern value (1.1)
144: $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
145: if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
146: 'sreg') === null) {
147: // An alias for the string 'sreg' already exists, but
148: // it's defined for something other than simple
149: // registration
150: return null;
151: }
152: }
153:
154: return $found_ns_uri;
155: }
156: }
157:
158: /**
159: * An object to hold the state of a simple registration request.
160: *
161: * required: A list of the required fields in this simple registration
162: * request
163: *
164: * optional: A list of the optional fields in this simple registration
165: * request
166: *
167: * @package OpenID
168: */
169: class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
170:
171: var $ns_alias = 'sreg';
172:
173: /**
174: * Initialize an empty simple registration request.
175: */
176: static function build($required=null, $optional=null,
177: $policy_url=null,
178: $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
179: $cls='Auth_OpenID_SRegRequest')
180: {
181: $obj = new $cls();
182:
183: $obj->required = array();
184: $obj->optional = array();
185: $obj->policy_url = $policy_url;
186: $obj->ns_uri = $sreg_ns_uri;
187:
188: if ($required) {
189: if (!$obj->requestFields($required, true, true)) {
190: return null;
191: }
192: }
193:
194: if ($optional) {
195: if (!$obj->requestFields($optional, false, true)) {
196: return null;
197: }
198: }
199:
200: return $obj;
201: }
202:
203: /**
204: * Create a simple registration request that contains the fields
205: * that were requested in the OpenID request with the given
206: * arguments
207: *
208: * $request: The OpenID authentication request from which to
209: * extract an sreg request.
210: *
211: * $cls: name of class to use when creating sreg request object.
212: * Used for testing.
213: *
214: * Returns the newly created simple registration request
215: */
216: static function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
217: {
218:
219: $obj = call_user_func_array(array($cls, 'build'),
220: array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
221:
222: // Since we're going to mess with namespace URI mapping, don't
223: // mutate the object that was passed in.
224: $m = $request->message;
225:
226: $obj->ns_uri = $obj->_getSRegNS($m);
227: $args = $m->getArgs($obj->ns_uri);
228:
229: if ($args === null || Auth_OpenID::isFailure($args)) {
230: return null;
231: }
232:
233: $obj->parseExtensionArgs($args);
234:
235: return $obj;
236: }
237:
238: /**
239: * Parse the unqualified simple registration request parameters
240: * and add them to this object.
241: *
242: * This method is essentially the inverse of
243: * getExtensionArgs. This method restores the serialized simple
244: * registration request fields.
245: *
246: * If you are extracting arguments from a standard OpenID
247: * checkid_* request, you probably want to use fromOpenIDRequest,
248: * which will extract the sreg namespace and arguments from the
249: * OpenID request. This method is intended for cases where the
250: * OpenID server needs more control over how the arguments are
251: * parsed than that method provides.
252: *
253: * $args == $message->getArgs($ns_uri);
254: * $request->parseExtensionArgs($args);
255: *
256: * $args: The unqualified simple registration arguments
257: *
258: * strict: Whether requests with fields that are not defined in
259: * the simple registration specification should be tolerated (and
260: * ignored)
261: */
262: function parseExtensionArgs($args, $strict=false)
263: {
264: foreach (array('required', 'optional') as $list_name) {
265: $required = ($list_name == 'required');
266: $items = Auth_OpenID::arrayGet($args, $list_name);
267: if ($items) {
268: foreach (explode(',', $items) as $field_name) {
269: if (!$this->requestField($field_name, $required, $strict)) {
270: if ($strict) {
271: return false;
272: }
273: }
274: }
275: }
276: }
277:
278: $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');
279:
280: return true;
281: }
282:
283: /**
284: * A list of all of the simple registration fields that were
285: * requested, whether they were required or optional.
286: */
287: function allRequestedFields()
288: {
289: return array_merge($this->required, $this->optional);
290: }
291:
292: /**
293: * Have any simple registration fields been requested?
294: */
295: function wereFieldsRequested()
296: {
297: return count($this->allRequestedFields());
298: }
299:
300: /**
301: * Was this field in the request?
302: */
303: function contains($field_name)
304: {
305: return (in_array($field_name, $this->required) ||
306: in_array($field_name, $this->optional));
307: }
308:
309: /**
310: * Request the specified field from the OpenID user
311: *
312: * $field_name: the unqualified simple registration field name
313: *
314: * required: whether the given field should be presented to the
315: * user as being a required to successfully complete the request
316: *
317: * strict: whether to raise an exception when a field is added to
318: * a request more than once
319: */
320: function requestField($field_name,
321: $required=false, $strict=false)
322: {
323: if (!Auth_OpenID_checkFieldName($field_name)) {
324: return false;
325: }
326:
327: if ($strict) {
328: if ($this->contains($field_name)) {
329: return false;
330: }
331: } else {
332: if (in_array($field_name, $this->required)) {
333: return true;
334: }
335:
336: if (in_array($field_name, $this->optional)) {
337: if ($required) {
338: unset($this->optional[array_search($field_name,
339: $this->optional)]);
340: } else {
341: return true;
342: }
343: }
344: }
345:
346: if ($required) {
347: $this->required[] = $field_name;
348: } else {
349: $this->optional[] = $field_name;
350: }
351:
352: return true;
353: }
354:
355: /**
356: * Add the given list of fields to the request
357: *
358: * field_names: The simple registration data fields to request
359: *
360: * required: Whether these values should be presented to the user
361: * as required
362: *
363: * strict: whether to raise an exception when a field is added to
364: * a request more than once
365: */
366: function requestFields($field_names, $required=false, $strict=false)
367: {
368: if (!is_array($field_names)) {
369: return false;
370: }
371:
372: foreach ($field_names as $field_name) {
373: if (!$this->requestField($field_name, $required, $strict=$strict)) {
374: return false;
375: }
376: }
377:
378: return true;
379: }
380:
381: /**
382: * Get a dictionary of unqualified simple registration arguments
383: * representing this request.
384: *
385: * This method is essentially the inverse of
386: * C{L{parseExtensionArgs}}. This method serializes the simple
387: * registration request fields.
388: */
389: function getExtensionArgs()
390: {
391: $args = array();
392:
393: if ($this->required) {
394: $args['required'] = implode(',', $this->required);
395: }
396:
397: if ($this->optional) {
398: $args['optional'] = implode(',', $this->optional);
399: }
400:
401: if ($this->policy_url) {
402: $args['policy_url'] = $this->policy_url;
403: }
404:
405: return $args;
406: }
407: }
408:
409: /**
410: * Represents the data returned in a simple registration response
411: * inside of an OpenID C{id_res} response. This object will be created
412: * by the OpenID server, added to the C{id_res} response object, and
413: * then extracted from the C{id_res} message by the Consumer.
414: *
415: * @package OpenID
416: */
417: class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
418:
419: var $ns_alias = 'sreg';
420:
421: function Auth_OpenID_SRegResponse($data=null,
422: $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
423: {
424: if ($data === null) {
425: $this->data = array();
426: } else {
427: $this->data = $data;
428: }
429:
430: $this->ns_uri = $sreg_ns_uri;
431: }
432:
433: /**
434: * Take a C{L{SRegRequest}} and a dictionary of simple
435: * registration values and create a C{L{SRegResponse}} object
436: * containing that data.
437: *
438: * request: The simple registration request object
439: *
440: * data: The simple registration data for this response, as a
441: * dictionary from unqualified simple registration field name to
442: * string (unicode) value. For instance, the nickname should be
443: * stored under the key 'nickname'.
444: */
445: static function extractResponse($request, $data)
446: {
447: $obj = new Auth_OpenID_SRegResponse();
448: $obj->ns_uri = $request->ns_uri;
449:
450: foreach ($request->allRequestedFields() as $field) {
451: $value = Auth_OpenID::arrayGet($data, $field);
452: if ($value !== null) {
453: $obj->data[$field] = $value;
454: }
455: }
456:
457: return $obj;
458: }
459:
460: /**
461: * Create a C{L{SRegResponse}} object from a successful OpenID
462: * library response
463: * (C{L{openid.consumer.consumer.SuccessResponse}}) response
464: * message
465: *
466: * success_response: A SuccessResponse from consumer.complete()
467: *
468: * signed_only: Whether to process only data that was
469: * signed in the id_res message from the server.
470: *
471: * Returns a simple registration response containing the data that
472: * was supplied with the C{id_res} response.
473: */
474: static function fromSuccessResponse($success_response, $signed_only=true)
475: {
476: global $Auth_OpenID_sreg_data_fields;
477:
478: $obj = new Auth_OpenID_SRegResponse();
479: $obj->ns_uri = $obj->_getSRegNS($success_response->message);
480:
481: if ($signed_only) {
482: $args = $success_response->getSignedNS($obj->ns_uri);
483: } else {
484: $args = $success_response->message->getArgs($obj->ns_uri);
485: }
486:
487: if ($args === null || Auth_OpenID::isFailure($args)) {
488: return null;
489: }
490:
491: foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
492: if (in_array($field_name, array_keys($args))) {
493: $obj->data[$field_name] = $args[$field_name];
494: }
495: }
496:
497: return $obj;
498: }
499:
500: function getExtensionArgs()
501: {
502: return $this->data;
503: }
504:
505: // Read-only dictionary interface
506: function get($field_name, $default=null)
507: {
508: if (!Auth_OpenID_checkFieldName($field_name)) {
509: return null;
510: }
511:
512: return Auth_OpenID::arrayGet($this->data, $field_name, $default);
513: }
514:
515: function contents()
516: {
517: return $this->data;
518: }
519: }
520:
521:
522: