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 */ 45require_once 'Auth/OpenID/Message.php'; 46require_once 'Auth/OpenID/Extension.php'; 47 48// The data fields that are listed in the sreg spec 49global $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 */ 65function 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 77define('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> 81define('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. 86define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1); 87 88Auth_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 */ 97function 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 */ 109class 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 */ 169class 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 */ 417class 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