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