final move of files
[web.mtrack] / Auth / OpenID / SReg.php
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     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     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     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     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     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 ?>