1<?php 2 3/** 4 * This module documents the main interface with the OpenID consumer 5 * library. The only part of the library which has to be used and 6 * isn't documented in full here is the store required to create an 7 * Auth_OpenID_Consumer instance. More on the abstract store type and 8 * concrete implementations of it that are provided in the 9 * documentation for the Auth_OpenID_Consumer constructor. 10 * 11 * OVERVIEW 12 * 13 * The OpenID identity verification process most commonly uses the 14 * following steps, as visible to the user of this library: 15 * 16 * 1. The user enters their OpenID into a field on the consumer's 17 * site, and hits a login button. 18 * 2. The consumer site discovers the user's OpenID server using the 19 * YADIS protocol. 20 * 3. The consumer site sends the browser a redirect to the identity 21 * server. This is the authentication request as described in 22 * the OpenID specification. 23 * 4. The identity server's site sends the browser a redirect back 24 * to the consumer site. This redirect contains the server's 25 * response to the authentication request. 26 * 27 * The most important part of the flow to note is the consumer's site 28 * must handle two separate HTTP requests in order to perform the full 29 * identity check. 30 * 31 * LIBRARY DESIGN 32 * 33 * This consumer library is designed with that flow in mind. The goal 34 * is to make it as easy as possible to perform the above steps 35 * securely. 36 * 37 * At a high level, there are two important parts in the consumer 38 * library. The first important part is this module, which contains 39 * the interface to actually use this library. The second is the 40 * Auth_OpenID_Interface class, which describes the interface to use 41 * if you need to create a custom method for storing the state this 42 * library needs to maintain between requests. 43 * 44 * In general, the second part is less important for users of the 45 * library to know about, as several implementations are provided 46 * which cover a wide variety of situations in which consumers may use 47 * the library. 48 * 49 * This module contains a class, Auth_OpenID_Consumer, with methods 50 * corresponding to the actions necessary in each of steps 2, 3, and 4 51 * described in the overview. Use of this library should be as easy 52 * as creating an Auth_OpenID_Consumer instance and calling the 53 * methods appropriate for the action the site wants to take. 54 * 55 * STORES AND DUMB MODE 56 * 57 * OpenID is a protocol that works best when the consumer site is able 58 * to store some state. This is the normal mode of operation for the 59 * protocol, and is sometimes referred to as smart mode. There is 60 * also a fallback mode, known as dumb mode, which is available when 61 * the consumer site is not able to store state. This mode should be 62 * avoided when possible, as it leaves the implementation more 63 * vulnerable to replay attacks. 64 * 65 * The mode the library works in for normal operation is determined by 66 * the store that it is given. The store is an abstraction that 67 * handles the data that the consumer needs to manage between http 68 * requests in order to operate efficiently and securely. 69 * 70 * Several store implementation are provided, and the interface is 71 * fully documented so that custom stores can be used as well. See 72 * the documentation for the Auth_OpenID_Consumer class for more 73 * information on the interface for stores. The implementations that 74 * are provided allow the consumer site to store the necessary data in 75 * several different ways, including several SQL databases and normal 76 * files on disk. 77 * 78 * There is an additional concrete store provided that puts the system 79 * in dumb mode. This is not recommended, as it removes the library's 80 * ability to stop replay attacks reliably. It still uses time-based 81 * checking to make replay attacks only possible within a small 82 * window, but they remain possible within that window. This store 83 * should only be used if the consumer site has no way to retain data 84 * between requests at all. 85 * 86 * IMMEDIATE MODE 87 * 88 * In the flow described above, the user may need to confirm to the 89 * lidentity server that it's ok to authorize his or her identity. 90 * The server may draw pages asking for information from the user 91 * before it redirects the browser back to the consumer's site. This 92 * is generally transparent to the consumer site, so it is typically 93 * ignored as an implementation detail. 94 * 95 * There can be times, however, where the consumer site wants to get a 96 * response immediately. When this is the case, the consumer can put 97 * the library in immediate mode. In immediate mode, there is an 98 * extra response possible from the server, which is essentially the 99 * server reporting that it doesn't have enough information to answer 100 * the question yet. 101 * 102 * USING THIS LIBRARY 103 * 104 * Integrating this library into an application is usually a 105 * relatively straightforward process. The process should basically 106 * follow this plan: 107 * 108 * Add an OpenID login field somewhere on your site. When an OpenID 109 * is entered in that field and the form is submitted, it should make 110 * a request to the your site which includes that OpenID URL. 111 * 112 * First, the application should instantiate the Auth_OpenID_Consumer 113 * class using the store of choice (Auth_OpenID_FileStore or one of 114 * the SQL-based stores). If the application has a custom 115 * session-management implementation, an object implementing the 116 * {@link Auth_Yadis_PHPSession} interface should be passed as the 117 * second parameter. Otherwise, the default uses $_SESSION. 118 * 119 * Next, the application should call the Auth_OpenID_Consumer object's 120 * 'begin' method. This method takes the OpenID URL. The 'begin' 121 * method returns an Auth_OpenID_AuthRequest object. 122 * 123 * Next, the application should call the 'redirectURL' method of the 124 * Auth_OpenID_AuthRequest object. The 'return_to' URL parameter is 125 * the URL that the OpenID server will send the user back to after 126 * attempting to verify his or her identity. The 'trust_root' is the 127 * URL (or URL pattern) that identifies your web site to the user when 128 * he or she is authorizing it. Send a redirect to the resulting URL 129 * to the user's browser. 130 * 131 * That's the first half of the authentication process. The second 132 * half of the process is done after the user's ID server sends the 133 * user's browser a redirect back to your site to complete their 134 * login. 135 * 136 * When that happens, the user will contact your site at the URL given 137 * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL 138 * call made above. The request will have several query parameters 139 * added to the URL by the identity server as the information 140 * necessary to finish the request. 141 * 142 * Lastly, instantiate an Auth_OpenID_Consumer instance as above and 143 * call its 'complete' method, passing in all the received query 144 * arguments. 145 * 146 * There are multiple possible return types possible from that 147 * method. These indicate the whether or not the login was successful, 148 * and include any additional information appropriate for their type. 149 * 150 * PHP versions 4 and 5 151 * 152 * LICENSE: See the COPYING file included in this distribution. 153 * 154 * @package OpenID 155 * @author JanRain, Inc. <openid@janrain.com> 156 * @copyright 2005-2008 Janrain, Inc. 157 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 158 */ 159 160/** 161 * Require utility classes and functions for the consumer. 162 */ 163require_once "Auth/OpenID.php"; 164require_once "Auth/OpenID/Message.php"; 165require_once "Auth/OpenID/HMAC.php"; 166require_once "Auth/OpenID/Association.php"; 167require_once "Auth/OpenID/CryptUtil.php"; 168require_once "Auth/OpenID/DiffieHellman.php"; 169require_once "Auth/OpenID/KVForm.php"; 170require_once "Auth/OpenID/Nonce.php"; 171require_once "Auth/OpenID/Discover.php"; 172require_once "Auth/OpenID/URINorm.php"; 173require_once "Auth/Yadis/Manager.php"; 174require_once "Auth/Yadis/XRI.php"; 175 176/** 177 * This is the status code returned when the complete method returns 178 * successfully. 179 */ 180define('Auth_OpenID_SUCCESS', 'success'); 181 182/** 183 * Status to indicate cancellation of OpenID authentication. 184 */ 185define('Auth_OpenID_CANCEL', 'cancel'); 186 187/** 188 * This is the status code completeAuth returns when the value it 189 * received indicated an invalid login. 190 */ 191define('Auth_OpenID_FAILURE', 'failure'); 192 193/** 194 * This is the status code completeAuth returns when the 195 * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the 196 * identity server sends back a URL to send the user to to complete his 197 * or her login. 198 */ 199define('Auth_OpenID_SETUP_NEEDED', 'setup needed'); 200 201/** 202 * This is the status code beginAuth returns when the page fetched 203 * from the entered OpenID URL doesn't contain the necessary link tags 204 * to function as an identity page. 205 */ 206define('Auth_OpenID_PARSE_ERROR', 'parse error'); 207 208/** 209 * An OpenID consumer implementation that performs discovery and does 210 * session management. See the Consumer.php file documentation for 211 * more information. 212 * 213 * @package OpenID 214 */ 215class Auth_OpenID_Consumer { 216 217 /** @var Auth_OpenID_GenericConsumer */ 218 public $consumer; 219 220 /** @var Auth_Yadis_PHPSession */ 221 public $session; 222 223 private $discoverMethod = 'Auth_OpenID_discover'; 224 225 private $session_key_prefix = "_openid_consumer_"; 226 227 private $_token_suffix = "last_token"; 228 229 /** @var string */ 230 private $_token_key; 231 232 /** 233 * Initialize a Consumer instance. 234 * 235 * You should create a new instance of the Consumer object with 236 * every HTTP request that handles OpenID transactions. 237 * 238 * @param Auth_OpenID_OpenIDStore $store This must be an object 239 * that implements the interface in {@link 240 * Auth_OpenID_OpenIDStore}. Several concrete implementations are 241 * provided, to cover most common use cases. For stores backed by 242 * MySQL, PostgreSQL, or SQLite, see the {@link 243 * Auth_OpenID_SQLStore} class and its sublcasses. For a 244 * filesystem-backed store, see the {@link Auth_OpenID_FileStore} 245 * module. As a last resort, if it isn't possible for the server 246 * to store state at all, an instance of {@link 247 * Auth_OpenID_DumbStore} can be used. 248 * 249 * @param mixed $session An object which implements the interface 250 * of the {@link Auth_Yadis_PHPSession} class. Particularly, this 251 * object is expected to have these methods: get($key), set($key), 252 * $value), and del($key). This defaults to a session object 253 * which wraps PHP's native session machinery. You should only 254 * need to pass something here if you have your own sessioning 255 * implementation. 256 * 257 * @param string $consumer_cls The name of the class to instantiate 258 * when creating the internal consumer object. This is used for 259 * testing. 260 */ 261 function __construct($store, $session = null, $consumer_cls = null) 262 { 263 if ($session === null) { 264 $session = new Auth_Yadis_PHPSession(); 265 } 266 267 $this->session = $session; 268 269 if ($consumer_cls !== null) { 270 $this->consumer = new $consumer_cls($store); 271 } else { 272 $this->consumer = new Auth_OpenID_GenericConsumer($store); 273 } 274 275 $this->_token_key = $this->session_key_prefix . $this->_token_suffix; 276 } 277 278 /** 279 * Used in testing to define the discovery mechanism. 280 * 281 * @access private 282 * @param Auth_Yadis_PHPSession $session 283 * @param string $openid_url 284 * @param string $session_key_prefix 285 * @return Auth_Yadis_Discovery 286 */ 287 function getDiscoveryObject($session, $openid_url, $session_key_prefix) 288 { 289 return new Auth_Yadis_Discovery($session, $openid_url, $session_key_prefix); 290 } 291 292 /** 293 * Start the OpenID authentication process. See steps 1-2 in the 294 * overview at the top of this file. 295 * 296 * @param string $user_url Identity URL given by the user. This 297 * method performs a textual transformation of the URL to try and 298 * make sure it is normalized. For example, a user_url of 299 * example.com will be normalized to http://example.com/ 300 * normalizing and resolving any redirects the server might issue. 301 * 302 * @param bool $anonymous True if the OpenID request is to be sent 303 * to the server without any identifier information. Use this 304 * when you want to transport data but don't want to do OpenID 305 * authentication with identifiers. 306 * 307 * @return Auth_OpenID_AuthRequest $auth_request An object 308 * containing the discovered information will be returned, with a 309 * method for building a redirect URL to the server, as described 310 * in step 3 of the overview. This object may also be used to add 311 * extension arguments to the request, using its 'addExtensionArg' 312 * method. 313 */ 314 function begin($user_url, $anonymous=false) 315 { 316 $openid_url = $user_url; 317 318 $disco = $this->getDiscoveryObject($this->session, 319 $openid_url, 320 $this->session_key_prefix); 321 322 // Set the 'stale' attribute of the manager. If discovery 323 // fails in a fatal way, the stale flag will cause the manager 324 // to be cleaned up next time discovery is attempted. 325 326 $m = $disco->getManager(); 327 $loader = new Auth_Yadis_ManagerLoader(); 328 329 if ($m) { 330 if ($m->stale) { 331 $disco->destroyManager(); 332 } else { 333 $m->stale = true; 334 $disco->session->set($disco->session_key, 335 serialize($loader->toSession($m))); 336 } 337 } 338 339 $endpoint = $disco->getNextService($this->discoverMethod, 340 $this->consumer->fetcher); 341 342 // Reset the 'stale' attribute of the manager. 343 $m = $disco->getManager(); 344 if ($m) { 345 $m->stale = false; 346 $disco->session->set($disco->session_key, 347 serialize($loader->toSession($m))); 348 } 349 350 if ($endpoint === null) { 351 return null; 352 } else { 353 return $this->beginWithoutDiscovery($endpoint, 354 $anonymous); 355 } 356 } 357 358 /** 359 * Start OpenID verification without doing OpenID server 360 * discovery. This method is used internally by Consumer.begin 361 * after discovery is performed, and exists to provide an 362 * interface for library users needing to perform their own 363 * discovery. 364 * 365 * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service 366 * endpoint descriptor. 367 * 368 * @param bool $anonymous Set to true if you want to perform OpenID 369 * without identifiers. 370 * 371 * @return Auth_OpenID_AuthRequest|Auth_OpenID_FailureResponse $auth_request An OpenID 372 * authentication request object. 373 */ 374 function beginWithoutDiscovery($endpoint, $anonymous=false) 375 { 376 $loader = new Auth_OpenID_ServiceEndpointLoader(); 377 $auth_req = $this->consumer->begin($endpoint); 378 $this->session->set($this->_token_key, 379 $loader->toSession($auth_req->endpoint)); 380 if (!$auth_req->setAnonymous($anonymous)) { 381 return new Auth_OpenID_FailureResponse(null, 382 "OpenID 1 requests MUST include the identifier " . 383 "in the request."); 384 } 385 return $auth_req; 386 } 387 388 /** 389 * Called to interpret the server's response to an OpenID 390 * request. It is called in step 4 of the flow described in the 391 * consumer overview. 392 * 393 * @param string $current_url The URL used to invoke the application. 394 * Extract the URL from your application's web 395 * request framework and specify it here to have it checked 396 * against the openid.current_url value in the response. If 397 * the current_url URL check fails, the status of the 398 * completion will be FAILURE. 399 * 400 * @param array $query An array of the query parameters (key => 401 * value pairs) for this HTTP request. Defaults to null. If 402 * null, the GET or POST data are automatically gotten from the 403 * PHP environment. It is only useful to override $query for 404 * testing. 405 * 406 * @return Auth_OpenID_ConsumerResponse $response A instance of an 407 * Auth_OpenID_ConsumerResponse subclass. The type of response is 408 * indicated by the status attribute, which will be one of 409 * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED. 410 */ 411 function complete($current_url, $query=null) 412 { 413 if ($current_url && !is_string($current_url)) { 414 // This is ugly, but we need to complain loudly when 415 // someone uses the API incorrectly. 416 trigger_error("current_url must be a string; see NEWS file " . 417 "for upgrading notes.", 418 E_USER_ERROR); 419 } 420 421 if ($query === null) { 422 $query = Auth_OpenID::getQuery(); 423 } 424 425 $loader = new Auth_OpenID_ServiceEndpointLoader(); 426 $endpoint_data = $this->session->get($this->_token_key); 427 $endpoint = 428 $loader->fromSession($endpoint_data); 429 430 $message = Auth_OpenID_Message::fromPostArgs($query); 431 $response = $this->consumer->complete($message, $endpoint, 432 $current_url); 433 $this->session->del($this->_token_key); 434 435 if (in_array($response->status, [Auth_OpenID_SUCCESS, Auth_OpenID_CANCEL])) { 436 if ($response->identity_url !== null) { 437 $disco = $this->getDiscoveryObject($this->session, 438 $response->identity_url, 439 $this->session_key_prefix); 440 $disco->cleanup(true); 441 } 442 } 443 444 return $response; 445 } 446} 447 448/** 449 * A class implementing HMAC/DH-SHA1 consumer sessions. 450 * 451 * @package OpenID 452 */ 453class Auth_OpenID_DiffieHellmanSHA1ConsumerSession { 454 public $session_type = 'DH-SHA1'; 455 public $hash_func = 'Auth_OpenID_SHA1'; 456 public $secret_size = 20; 457 public $allowed_assoc_types = ['HMAC-SHA1']; 458 459 /** @var Auth_OpenID_DiffieHellman */ 460 protected $dh; 461 462 function __construct($dh = null) 463 { 464 if ($dh === null) { 465 $dh = new Auth_OpenID_DiffieHellman(); 466 } 467 468 $this->dh = $dh; 469 } 470 471 function getRequest() 472 { 473 $math = Auth_OpenID_getMathLib(); 474 475 $cpub = $math->longToBase64($this->dh->public); 476 477 $args = ['dh_consumer_public' => $cpub]; 478 479 if (!$this->dh->usingDefaultValues()) { 480 $args = array_merge($args, [ 481 'dh_modulus' => 482 $math->longToBase64($this->dh->mod), 483 'dh_gen' => 484 $math->longToBase64($this->dh->gen) 485 ]); 486 } 487 488 return $args; 489 } 490 491 /** 492 * @param Auth_OpenID_Message $response 493 * @return null|string 494 */ 495 function extractSecret($response) 496 { 497 if (!$response->hasKey(Auth_OpenID_OPENID_NS, 498 'dh_server_public')) { 499 return null; 500 } 501 502 if (!$response->hasKey(Auth_OpenID_OPENID_NS, 503 'enc_mac_key')) { 504 return null; 505 } 506 507 $math = Auth_OpenID_getMathLib(); 508 509 $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS, 510 'dh_server_public')); 511 $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS, 512 'enc_mac_key')); 513 514 return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func); 515 } 516} 517 518/** 519 * A class implementing HMAC/DH-SHA256 consumer sessions. 520 * 521 * @package OpenID 522 */ 523class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends 524 Auth_OpenID_DiffieHellmanSHA1ConsumerSession { 525 public $session_type = 'DH-SHA256'; 526 public $hash_func = 'Auth_OpenID_SHA256'; 527 public $secret_size = 32; 528 public $allowed_assoc_types = ['HMAC-SHA256']; 529} 530 531/** 532 * A class implementing plaintext consumer sessions. 533 * 534 * @package OpenID 535 */ 536class Auth_OpenID_PlainTextConsumerSession { 537 public $session_type = 'no-encryption'; 538 public $allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']; 539 540 function getRequest() 541 { 542 return []; 543 } 544 545 /** 546 * @param Auth_OpenID_Message $response 547 * @return bool|null|string 548 */ 549 function extractSecret($response) 550 { 551 if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) { 552 return null; 553 } 554 555 return base64_decode($response->getArg(Auth_OpenID_OPENID_NS, 556 'mac_key')); 557 } 558} 559 560/** 561 * Returns available session types. 562 */ 563function Auth_OpenID_getAvailableSessionTypes() 564{ 565 $types = [ 566 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession', 567 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession', 568 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession' 569 ]; 570 571 return $types; 572} 573 574/** 575 * This class is the interface to the OpenID consumer logic. 576 * Instances of it maintain no per-request state, so they can be 577 * reused (or even used by multiple threads concurrently) as needed. 578 * 579 * @package OpenID 580 */ 581class Auth_OpenID_GenericConsumer { 582 /** 583 * @access private 584 */ 585 public $discoverMethod = 'Auth_OpenID_discover'; 586 587 /** 588 * This consumer's store object. 589 */ 590 public $store; 591 592 /** 593 * @access private 594 */ 595 public $_use_assocs; 596 597 /** 598 * @access private 599 */ 600 public $openid1_nonce_query_arg_name = 'janrain_nonce'; 601 602 /** 603 * Another query parameter that gets added to the return_to for 604 * OpenID 1; if the user's session state is lost, use this claimed 605 * identifier to do discovery when verifying the response. 606 */ 607 public $openid1_return_to_identifier_name = 'openid1_claimed_id'; 608 609 /** @var Auth_Yadis_ParanoidHTTPFetcher|Auth_Yadis_PlainHTTPFetcher */ 610 public $fetcher; 611 612 /** @var array */ 613 public $session_types; 614 615 /** @var Auth_OpenID_SessionNegotiator */ 616 public $negotiator; 617 618 /** 619 * This method initializes a new {@link Auth_OpenID_Consumer} 620 * instance to access the library. 621 * 622 * @param Auth_OpenID_OpenIDStore $store This must be an object 623 * that implements the interface in {@link Auth_OpenID_OpenIDStore}. 624 * Several concrete implementations are provided, to cover most common use 625 * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see 626 * the {@link Auth_OpenID_SQLStore} class and its sublcasses. For a 627 * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module. 628 * As a last resort, if it isn't possible for the server to store 629 * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used. 630 */ 631 function __construct($store) 632 { 633 $this->store = $store; 634 $this->negotiator = Auth_OpenID_getDefaultNegotiator(); 635 $this->_use_assocs = (is_null($this->store) ? false : true); 636 if (get_class($this->store) == "Auth_OpenID_DumbStore") { 637 $this->_use_assocs = false; 638 } 639 640 $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); 641 642 $this->session_types = Auth_OpenID_getAvailableSessionTypes(); 643 } 644 645 /** 646 * Called to begin OpenID authentication using the specified 647 * {@link Auth_OpenID_ServiceEndpoint}. 648 * 649 * @access private 650 * @param Auth_OpenID_ServiceEndpoint $service_endpoint 651 * @return Auth_OpenID_AuthRequest 652 */ 653 function begin($service_endpoint) 654 { 655 $assoc = $this->_getAssociation($service_endpoint); 656 $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc); 657 $r->return_to_args[$this->openid1_nonce_query_arg_name] = 658 Auth_OpenID_mkNonce(); 659 660 if ($r->message->isOpenID1()) { 661 $r->return_to_args[$this->openid1_return_to_identifier_name] = 662 $r->endpoint->claimed_id; 663 } 664 665 return $r; 666 } 667 668 /** 669 * Given an {@link Auth_OpenID_Message}, {@link 670 * Auth_OpenID_ServiceEndpoint} and optional return_to URL, 671 * complete OpenID authentication. 672 * 673 * @access private 674 * @param Auth_OpenID_Message $message 675 * @param Auth_OpenID_ServiceEndpoint $endpoint 676 * @param string $return_to 677 * @return Auth_OpenID_SuccessResponse 678 */ 679 function complete($message, $endpoint, $return_to) 680 { 681 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode', 682 '<no mode set>'); 683 684 $mode_methods = [ 685 'cancel' => '_complete_cancel', 686 'error' => '_complete_error', 687 'setup_needed' => '_complete_setup_needed', 688 'id_res' => '_complete_id_res', 689 ]; 690 691 $method = Auth_OpenID::arrayGet($mode_methods, $mode, 692 '_completeInvalid'); 693 694 return call_user_func_array([$this, $method], 695 [$message, $endpoint, $return_to]); 696 } 697 698 /** 699 * @access private 700 * @param Auth_OpenID_Message $message 701 * @param Auth_OpenID_ServiceEndpoint $endpoint 702 * @return Auth_OpenID_FailureResponse 703 */ 704 function _completeInvalid($message, $endpoint) 705 { 706 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode', 707 '<No mode set>'); 708 709 return new Auth_OpenID_FailureResponse($endpoint, 710 sprintf("Invalid openid.mode '%s'", $mode)); 711 } 712 713 /** 714 * @access private 715 * @param Auth_OpenID_Message $message 716 * @param Auth_OpenID_ServiceEndpoint $endpoint 717 * @return Auth_OpenID_CancelResponse 718 */ 719 function _complete_cancel($message, $endpoint) 720 { 721 return new Auth_OpenID_CancelResponse($endpoint); 722 } 723 724 /** 725 * @access private 726 * @param Auth_OpenID_Message $message 727 * @param Auth_OpenID_ServiceEndpoint $endpoint 728 * @return Auth_OpenID_FailureResponse 729 */ 730 function _complete_error($message, $endpoint) 731 { 732 $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error'); 733 $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact'); 734 $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference'); 735 736 return new Auth_OpenID_FailureResponse($endpoint, $error, 737 $contact, $reference); 738 } 739 740 /** 741 * @access private 742 * @param Auth_OpenID_Message $message 743 * @param Auth_OpenID_ServiceEndpoint $endpoint 744 * @return Auth_OpenID_FailureResponse|Auth_OpenID_SetupNeededResponse 745 */ 746 function _complete_setup_needed($message, $endpoint) 747 { 748 if (!$message->isOpenID2()) { 749 return $this->_completeInvalid($message, $endpoint); 750 } 751 752 $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS, 753 'user_setup_url'); 754 return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url); 755 } 756 757 /** 758 * @access private 759 * @param Auth_OpenID_Message $message 760 * @param Auth_OpenID_ServiceEndpoint $endpoint 761 * @param string $return_to 762 * @return Auth_OpenID_FailureResponse|Auth_OpenID_SetupNeededResponse|Auth_OpenID_SuccessResponse|null 763 */ 764 function _complete_id_res($message, $endpoint, $return_to) 765 { 766 $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS, 'user_setup_url'); 767 768 if ($this->_checkSetupNeeded($message)) { 769 return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url); 770 } else { 771 return $this->_doIdRes($message, $endpoint, $return_to); 772 } 773 } 774 775 /** 776 * @access private 777 * @param Auth_OpenID_Message $message 778 * @return bool 779 */ 780 function _checkSetupNeeded($message) 781 { 782 // In OpenID 1, we check to see if this is a cancel from 783 // immediate mode by the presence of the user_setup_url 784 // parameter. 785 if ($message->isOpenID1()) { 786 $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS, 787 'user_setup_url'); 788 if ($user_setup_url !== null) { 789 return true; 790 } 791 } 792 793 return false; 794 } 795 796 /** 797 * @access private 798 * @param Auth_OpenID_Message $message 799 * @param Auth_OpenID_ServiceEndpoint $endpoint 800 * @param string $return_to 801 * @return Auth_OpenID_FailureResponse|Auth_OpenID_SuccessResponse|mixed|null|string 802 */ 803 function _doIdRes($message, $endpoint, $return_to) 804 { 805 // Checks for presence of appropriate fields (and checks 806 // signed list fields) 807 $result = $this->_idResCheckForFields($message); 808 809 if (Auth_OpenID::isFailure($result)) { 810 return $result; 811 } 812 813 if (!$this->_checkReturnTo($message, $return_to)) { 814 return new Auth_OpenID_FailureResponse(null, 815 sprintf("return_to does not match return URL. Expected %s, got %s", 816 $return_to, 817 $message->getArg(Auth_OpenID_OPENID_NS, 'return_to'))); 818 } 819 820 // Verify discovery information: 821 $result = $this->_verifyDiscoveryResults($message, $endpoint); 822 823 if (Auth_OpenID::isFailure($result)) { 824 return $result; 825 } 826 827 $endpoint = $result; 828 829 $result = $this->_idResCheckSignature($message, $endpoint->server_url); 830 831 if (Auth_OpenID::isFailure($result)) { 832 return $result; 833 } 834 835 $result = $this->_idResCheckNonce($message, $endpoint); 836 837 if (Auth_OpenID::isFailure($result)) { 838 return $result; 839 } 840 841 $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed', 842 Auth_OpenID_NO_DEFAULT); 843 if (Auth_OpenID::isFailure($signed_list_str)) { 844 return $signed_list_str; 845 } 846 $signed_list = explode(',', $signed_list_str); 847 848 $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid."); 849 850 return new Auth_OpenID_SuccessResponse($endpoint, $message, 851 $signed_fields); 852 853 } 854 855 /** 856 * @access private 857 * @param Auth_OpenID_Message $message 858 * @param string $return_to 859 * @return bool 860 */ 861 function _checkReturnTo($message, $return_to) 862 { 863 // Check an OpenID message and its openid.return_to value 864 // against a return_to URL from an application. Return True 865 // on success, False on failure. 866 867 // Check the openid.return_to args against args in the 868 // original message. 869 $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs( 870 $message->toPostArgs()); 871 if (Auth_OpenID::isFailure($result)) { 872 return false; 873 } 874 875 // Check the return_to base URL against the one in the 876 // message. 877 $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS, 878 'return_to'); 879 if (Auth_OpenID::isFailure($return_to)) { 880 // XXX log me 881 return false; 882 } 883 884 $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to)); 885 $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to)); 886 887 // If port is absent from both, add it so it's equal in the 888 // check below. 889 if ((!array_key_exists('port', $return_to_parts)) && 890 (!array_key_exists('port', $msg_return_to_parts))) { 891 $return_to_parts['port'] = null; 892 $msg_return_to_parts['port'] = null; 893 } 894 895 // If path is absent from both, add it so it's equal in the 896 // check below. 897 if ((!array_key_exists('path', $return_to_parts)) && 898 (!array_key_exists('path', $msg_return_to_parts))) { 899 $return_to_parts['path'] = null; 900 $msg_return_to_parts['path'] = null; 901 } 902 903 // The URL scheme, authority, and path MUST be the same 904 // between the two URLs. 905 foreach (['scheme', 'host', 'port', 'path'] as $component) { 906 // If the url component is absent in either URL, fail. 907 // There should always be a scheme, host, port, and path. 908 if (!array_key_exists($component, $return_to_parts)) { 909 return false; 910 } 911 912 if (!array_key_exists($component, $msg_return_to_parts)) { 913 return false; 914 } 915 916 if (Auth_OpenID::arrayGet($return_to_parts, $component) !== 917 Auth_OpenID::arrayGet($msg_return_to_parts, $component)) { 918 return false; 919 } 920 } 921 922 return true; 923 } 924 925 /** 926 * @access private 927 * @param array $query 928 * @return Auth_OpenID_FailureResponse|bool 929 */ 930 function _verifyReturnToArgs($query) 931 { 932 // Verify that the arguments in the return_to URL are present in this 933 // response. 934 935 $message = Auth_OpenID_Message::fromPostArgs($query); 936 $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to'); 937 938 if (Auth_OpenID::isFailure($return_to)) { 939 return $return_to; 940 } 941 // XXX: this should be checked by _idResCheckForFields 942 if (!$return_to) { 943 return new Auth_OpenID_FailureResponse(null, 944 "Response has no return_to"); 945 } 946 947 $parsed_url = parse_url($return_to); 948 949 $q = []; 950 if (array_key_exists('query', $parsed_url)) { 951 $rt_query = $parsed_url['query']; 952 $q = Auth_OpenID::parse_str($rt_query); 953 } 954 955 foreach ($q as $rt_key => $rt_value) { 956 if (!array_key_exists($rt_key, $query)) { 957 return new Auth_OpenID_FailureResponse(null, 958 sprintf("return_to parameter %s absent from query", $rt_key)); 959 } else { 960 $value = $query[$rt_key]; 961 if ($rt_value != $value) { 962 return new Auth_OpenID_FailureResponse(null, 963 sprintf("parameter %s value %s does not match " . 964 "return_to value %s", $rt_key, 965 $value, $rt_value)); 966 } 967 } 968 } 969 970 // Make sure all non-OpenID arguments in the response are also 971 // in the signed return_to. 972 $bare_args = $message->getArgs(Auth_OpenID_BARE_NS); 973 foreach ($bare_args as $key => $value) { 974 if (Auth_OpenID::arrayGet($q, $key) != $value) { 975 return new Auth_OpenID_FailureResponse(null, 976 sprintf("Parameter %s = %s not in return_to URL", 977 $key, $value)); 978 } 979 } 980 981 return true; 982 } 983 984 /** 985 * @access private 986 * @param Auth_OpenID_Message $message 987 * @param string $server_url 988 * @return Auth_OpenID_FailureResponse|null 989 */ 990 function _idResCheckSignature($message, $server_url) 991 { 992 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 993 'assoc_handle'); 994 if (Auth_OpenID::isFailure($assoc_handle)) { 995 return $assoc_handle; 996 } 997 998 $assoc = $this->store->getAssociation($server_url, $assoc_handle); 999 1000 if ($assoc) { 1001 if ($assoc->getExpiresIn() <= 0) { 1002 // XXX: It might be a good idea sometimes to re-start 1003 // the authentication with a new association. Doing it 1004 // automatically opens the possibility for 1005 // denial-of-service by a server that just returns 1006 // expired associations (or really short-lived 1007 // associations) 1008 return new Auth_OpenID_FailureResponse(null, 1009 'Association with ' . $server_url . ' expired'); 1010 } 1011 1012 if (!$assoc->checkMessageSignature($message)) { 1013 // If we get a "bad signature" here, it means that the association 1014 // is unrecoverabley corrupted in some way. Any futher attempts 1015 // to login with this association is likely to fail. Drop it. 1016 $this->store->removeAssociation($server_url, $assoc_handle); 1017 return new Auth_OpenID_FailureResponse(null, 1018 "Bad signature"); 1019 } 1020 } else { 1021 // It's not an association we know about. Stateless mode 1022 // is our only possible path for recovery. XXX - async 1023 // framework will not want to block on this call to 1024 // _checkAuth. 1025 if (!$this->_checkAuth($message, $server_url)) { 1026 return new Auth_OpenID_FailureResponse(null, 1027 "Server denied check_authentication"); 1028 } 1029 } 1030 1031 return null; 1032 } 1033 1034 /** 1035 * @access private 1036 * @param Auth_OpenID_Message $message 1037 * @param Auth_OpenID_ServiceEndpoint|null $endpoint 1038 * @return Auth_OpenID_FailureResponse|Auth_OpenID_ServiceEndpoint 1039 */ 1040 function _verifyDiscoveryResults($message, $endpoint=null) 1041 { 1042 if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) { 1043 return $this->_verifyDiscoveryResultsOpenID2($message, $endpoint); 1044 } else { 1045 return $this->_verifyDiscoveryResultsOpenID1($message, $endpoint); 1046 } 1047 } 1048 1049 /** 1050 * @access private 1051 * @param Auth_OpenID_Message $message 1052 * @param Auth_OpenID_ServiceEndpoint $endpoint 1053 * @return Auth_OpenID_FailureResponse|Auth_OpenID_ServiceEndpoint 1054 */ 1055 function _verifyDiscoveryResultsOpenID1($message, $endpoint) 1056 { 1057 $claimed_id = $message->getArg(Auth_OpenID_BARE_NS, 1058 $this->openid1_return_to_identifier_name); 1059 1060 if (($endpoint === null) && ($claimed_id === null)) { 1061 return new Auth_OpenID_FailureResponse($endpoint, 1062 'When using OpenID 1, the claimed ID must be supplied, ' . 1063 'either by passing it through as a return_to parameter ' . 1064 'or by using a session, and supplied to the GenericConsumer ' . 1065 'as the argument to complete()'); 1066 } else if (($endpoint !== null) && ($claimed_id === null)) { 1067 $claimed_id = $endpoint->claimed_id; 1068 } 1069 1070 $to_match = new Auth_OpenID_ServiceEndpoint(); 1071 $to_match->type_uris = [Auth_OpenID_TYPE_1_1]; 1072 $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS, 1073 'identity'); 1074 1075 // Restore delegate information from the initiation phase 1076 $to_match->claimed_id = $claimed_id; 1077 1078 if ($to_match->local_id === null) { 1079 return new Auth_OpenID_FailureResponse($endpoint, 1080 "Missing required field openid.identity"); 1081 } 1082 1083 $to_match_1_0 = $to_match->copy(); 1084 $to_match_1_0->type_uris = [Auth_OpenID_TYPE_1_0]; 1085 1086 if ($endpoint !== null) { 1087 $result = $this->_verifyDiscoverySingle($endpoint, $to_match); 1088 1089 if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) { 1090 $result = $this->_verifyDiscoverySingle($endpoint, 1091 $to_match_1_0); 1092 } 1093 1094 if (Auth_OpenID::isFailure($result)) { 1095 // oidutil.log("Error attempting to use stored 1096 // discovery information: " + str(e)) 1097 // oidutil.log("Attempting discovery to 1098 // verify endpoint") 1099 } else { 1100 return $endpoint; 1101 } 1102 } 1103 1104 // Endpoint is either bad (failed verification) or None 1105 return $this->_discoverAndVerify($to_match->claimed_id, 1106 [$to_match, $to_match_1_0]); 1107 } 1108 1109 /** 1110 * @access private 1111 * @param Auth_OpenID_ServiceEndpoint $endpoint 1112 * @param Auth_OpenID_ServiceEndpoint $to_match 1113 * @return Auth_OpenID_FailureResponse|null 1114 */ 1115 function _verifyDiscoverySingle($endpoint, $to_match) 1116 { 1117 // Every type URI that's in the to_match endpoint has to be 1118 // present in the discovered endpoint. 1119 foreach ($to_match->type_uris as $type_uri) { 1120 if (!$endpoint->usesExtension($type_uri)) { 1121 return new Auth_OpenID_TypeURIMismatch($endpoint, 1122 "Required type ".$type_uri." not present"); 1123 } 1124 } 1125 1126 // Fragments do not influence discovery, so we can't compare a 1127 // claimed identifier with a fragment to discovered 1128 // information. 1129 list($defragged_claimed_id) = Auth_OpenID::urldefrag($to_match->claimed_id); 1130 1131 if ($defragged_claimed_id != $endpoint->claimed_id) { 1132 return new Auth_OpenID_FailureResponse($endpoint, 1133 sprintf('Claimed ID does not match (different subjects!), ' . 1134 'Expected %s, got %s', $defragged_claimed_id, 1135 $endpoint->claimed_id)); 1136 } 1137 1138 if ($to_match->getLocalID() != $endpoint->getLocalID()) { 1139 return new Auth_OpenID_FailureResponse($endpoint, 1140 sprintf('local_id mismatch. Expected %s, got %s', 1141 $to_match->getLocalID(), $endpoint->getLocalID())); 1142 } 1143 1144 // If the server URL is None, this must be an OpenID 1 1145 // response, because op_endpoint is a required parameter in 1146 // OpenID 2. In that case, we don't actually care what the 1147 // discovered server_url is, because signature checking or 1148 // check_auth should take care of that check for us. 1149 if ($to_match->server_url === null) { 1150 if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) { 1151 return new Auth_OpenID_FailureResponse($endpoint, 1152 "Preferred namespace mismatch (bug)"); 1153 } 1154 } else if ($to_match->server_url != $endpoint->server_url) { 1155 return new Auth_OpenID_FailureResponse($endpoint, 1156 sprintf('OP Endpoint mismatch. Expected %s, got %s', 1157 $to_match->server_url, $endpoint->server_url)); 1158 } 1159 1160 return null; 1161 } 1162 1163 /** 1164 * @access private 1165 * @param Auth_OpenID_Message $message 1166 * @param Auth_OpenID_ServiceEndpoint $endpoint 1167 * @return Auth_OpenID_FailureResponse|Auth_OpenID_ServiceEndpoint 1168 */ 1169 function _verifyDiscoveryResultsOpenID2($message, $endpoint) 1170 { 1171 $to_match = new Auth_OpenID_ServiceEndpoint(); 1172 $to_match->type_uris = [Auth_OpenID_TYPE_2_0]; 1173 $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS, 1174 'claimed_id'); 1175 1176 $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS, 1177 'identity'); 1178 1179 $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS, 1180 'op_endpoint'); 1181 1182 if ($to_match->server_url === null) { 1183 return new Auth_OpenID_FailureResponse($endpoint, 1184 "OP Endpoint URL missing"); 1185 } 1186 1187 // claimed_id and identifier must both be present or both be 1188 // absent 1189 if (($to_match->claimed_id === null) && 1190 ($to_match->local_id !== null)) { 1191 return new Auth_OpenID_FailureResponse($endpoint, 1192 'openid.identity is present without openid.claimed_id'); 1193 } 1194 1195 if (($to_match->claimed_id !== null) && 1196 ($to_match->local_id === null)) { 1197 return new Auth_OpenID_FailureResponse($endpoint, 1198 'openid.claimed_id is present without openid.identity'); 1199 } 1200 1201 if ($to_match->claimed_id === null) { 1202 // This is a response without identifiers, so there's 1203 // really no checking that we can do, so return an 1204 // endpoint that's for the specified `openid.op_endpoint' 1205 return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL( 1206 $to_match->server_url); 1207 } 1208 1209 if (!$endpoint) { 1210 // The claimed ID doesn't match, so we have to do 1211 // discovery again. This covers not using sessions, OP 1212 // identifier endpoints and responses that didn't match 1213 // the original request. 1214 // oidutil.log('No pre-discovered information supplied.') 1215 return $this->_discoverAndVerify($to_match->claimed_id, 1216 [$to_match]); 1217 } else { 1218 1219 // The claimed ID matches, so we use the endpoint that we 1220 // discovered in initiation. This should be the most 1221 // common case. 1222 $result = $this->_verifyDiscoverySingle($endpoint, $to_match); 1223 1224 if (Auth_OpenID::isFailure($result)) { 1225 $endpoint = $this->_discoverAndVerify($to_match->claimed_id, 1226 [$to_match]); 1227 if (Auth_OpenID::isFailure($endpoint)) { 1228 return $endpoint; 1229 } 1230 } 1231 } 1232 1233 // The endpoint we return should have the claimed ID from the 1234 // message we just verified, fragment and all. 1235 if ($endpoint->claimed_id != $to_match->claimed_id) { 1236 $endpoint->claimed_id = $to_match->claimed_id; 1237 } 1238 1239 return $endpoint; 1240 } 1241 1242 /** 1243 * @access private 1244 * @param string $claimed_id 1245 * @param Auth_OpenID_ServiceEndpoint[] $to_match_endpoints 1246 * @return Auth_OpenID_FailureResponse 1247 */ 1248 function _discoverAndVerify($claimed_id, $to_match_endpoints) 1249 { 1250 // oidutil.log('Performing discovery on %s' % (claimed_id,)) 1251 list(, $services) = call_user_func_array($this->discoverMethod, 1252 [ 1253 $claimed_id, 1254 $this->fetcher, 1255 ]); 1256 1257 if (!$services) { 1258 return new Auth_OpenID_FailureResponse(null, 1259 sprintf("No OpenID information found at %s", 1260 $claimed_id)); 1261 } 1262 1263 return $this->_verifyDiscoveryServices($claimed_id, $services, 1264 $to_match_endpoints); 1265 } 1266 1267 /** 1268 * @access private 1269 * @param string $claimed_id 1270 * @param Auth_OpenID_ServiceEndpoint[] $services 1271 * @param Auth_OpenID_ServiceEndpoint[] $to_match_endpoints 1272 * @return Auth_OpenID_FailureResponse|Auth_OpenID_ServiceEndpoint 1273 */ 1274 function _verifyDiscoveryServices($claimed_id, 1275 $services, $to_match_endpoints) 1276 { 1277 // Search the services resulting from discovery to find one 1278 // that matches the information from the assertion 1279 1280 $result = null; 1281 foreach ($services as $endpoint) { 1282 foreach ($to_match_endpoints as $to_match_endpoint) { 1283 $result = $this->_verifyDiscoverySingle($endpoint, $to_match_endpoint); 1284 1285 if (!Auth_OpenID::isFailure($result)) { 1286 // It matches, so discover verification has 1287 // succeeded. Return this endpoint. 1288 return $endpoint; 1289 } 1290 } 1291 } 1292 1293 $message = $result instanceof Auth_OpenID_FailureResponse ? $result->message : ''; 1294 1295 return new Auth_OpenID_FailureResponse(null, 1296 sprintf('No matching endpoint found after discovering %s: %s', $claimed_id, $message)); 1297 } 1298 1299 /** 1300 * Extract the nonce from an OpenID 1 response. Return the nonce 1301 * from the BARE_NS since we independently check the return_to 1302 * arguments are the same as those in the response message. 1303 * 1304 * See the openid1_nonce_query_arg_name class variable 1305 * 1306 * @param Auth_OpenID_Message $message 1307 * @return string The nonce as a string or null 1308 * 1309 * @access private 1310 */ 1311 function _idResGetNonceOpenID1($message) 1312 { 1313 return $message->getArg(Auth_OpenID_BARE_NS, $this->openid1_nonce_query_arg_name); 1314 } 1315 1316 /** 1317 * @access private 1318 * @param Auth_OpenID_Message $message 1319 * @param Auth_OpenID_ServiceEndpoint $endpoint 1320 * @return Auth_OpenID_FailureResponse|null 1321 */ 1322 function _idResCheckNonce($message, $endpoint) 1323 { 1324 if ($message->isOpenID1()) { 1325 // This indicates that the nonce was generated by the consumer 1326 $nonce = $this->_idResGetNonceOpenID1($message); 1327 $server_url = ''; 1328 } else { 1329 $nonce = $message->getArg(Auth_OpenID_OPENID2_NS, 1330 'response_nonce'); 1331 1332 $server_url = $endpoint->server_url; 1333 } 1334 1335 if ($nonce === null) { 1336 return new Auth_OpenID_FailureResponse($endpoint, 1337 "Nonce missing from response"); 1338 } 1339 1340 $parts = Auth_OpenID_splitNonce($nonce); 1341 1342 if ($parts === null) { 1343 return new Auth_OpenID_FailureResponse($endpoint, 1344 "Malformed nonce in response"); 1345 } 1346 1347 list($timestamp, $salt) = $parts; 1348 1349 if (!$this->store->useNonce($server_url, $timestamp, $salt)) { 1350 return new Auth_OpenID_FailureResponse($endpoint, 1351 "Nonce already used or out of range"); 1352 } 1353 1354 return null; 1355 } 1356 1357 /** 1358 * @access private 1359 * @param Auth_OpenID_Message $message 1360 * @return Auth_OpenID_FailureResponse|mixed|null|string 1361 */ 1362 function _idResCheckForFields($message) 1363 { 1364 $basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']; 1365 $basic_sig_fields = ['return_to', 'identity']; 1366 1367 $require_fields = [ 1368 Auth_OpenID_OPENID2_NS => array_merge($basic_fields, 1369 ['op_endpoint']), 1370 1371 Auth_OpenID_OPENID1_NS => array_merge($basic_fields, 1372 ['identity']) 1373 ]; 1374 1375 $require_sigs = [ 1376 Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields, 1377 [ 1378 'response_nonce', 1379 'claimed_id', 1380 'assoc_handle', 1381 'op_endpoint' 1382 ]), 1383 Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields, 1384 ['nonce']) 1385 ]; 1386 1387 foreach ($require_fields[$message->getOpenIDNamespace()] as $field) { 1388 if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) { 1389 return new Auth_OpenID_FailureResponse(null, 1390 "Missing required field '".$field."'"); 1391 } 1392 } 1393 1394 $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 1395 'signed', 1396 Auth_OpenID_NO_DEFAULT); 1397 if (Auth_OpenID::isFailure($signed_list_str)) { 1398 return $signed_list_str; 1399 } 1400 $signed_list = explode(',', $signed_list_str); 1401 1402 foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) { 1403 // Field is present and not in signed list 1404 if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) && 1405 (!in_array($field, $signed_list))) { 1406 return new Auth_OpenID_FailureResponse(null, 1407 "'".$field."' not signed"); 1408 } 1409 } 1410 1411 return null; 1412 } 1413 1414 /** 1415 * @access private 1416 * @param Auth_OpenID_Message $message 1417 * @param string $server_url 1418 * @return bool 1419 */ 1420 function _checkAuth($message, $server_url) 1421 { 1422 $request = $this->_createCheckAuthRequest($message); 1423 if ($request === null) { 1424 return false; 1425 } 1426 1427 $resp_message = $this->_makeKVPost($request, $server_url); 1428 if (($resp_message === null) || 1429 (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) { 1430 return false; 1431 } 1432 1433 return $this->_processCheckAuthResponse($resp_message, $server_url); 1434 } 1435 1436 /** 1437 * @access private 1438 * @param Auth_OpenID_Message $message 1439 * @return Auth_OpenID_Message|null 1440 */ 1441 function _createCheckAuthRequest($message) 1442 { 1443 $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed'); 1444 if ($signed) { 1445 foreach (explode(',', $signed) as $k) { 1446 $value = $message->getAliasedArg($k); 1447 if ($value === null) { 1448 return null; 1449 } 1450 } 1451 } 1452 $ca_message = $message->copy(); 1453 $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode', 1454 'check_authentication'); 1455 return $ca_message; 1456 } 1457 1458 /** 1459 * @access private 1460 * @param Auth_OpenID_Message $response 1461 * @param string $server_url 1462 * @return bool 1463 */ 1464 function _processCheckAuthResponse($response, $server_url) 1465 { 1466 $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid', 1467 'false'); 1468 1469 $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS, 1470 'invalidate_handle'); 1471 1472 if ($invalidate_handle !== null) { 1473 $this->store->removeAssociation($server_url, 1474 $invalidate_handle); 1475 } 1476 1477 if ($is_valid == 'true') { 1478 return true; 1479 } 1480 1481 return false; 1482 } 1483 1484 /** 1485 * Adapt a POST response to a Message. 1486 * 1487 * @param Auth_Yadis_HTTPResponse $response Result of a POST to an OpenID endpoint. 1488 * @access private 1489 * @return Auth_OpenID_Message|Auth_OpenID_ServerErrorContainer|null 1490 */ 1491 static function _httpResponseToMessage($response) 1492 { 1493 // Should this function be named Message.fromHTTPResponse instead? 1494 $response_message = Auth_OpenID_Message::fromKVForm($response->body); 1495 1496 if ($response->status == 400) { 1497 return Auth_OpenID_ServerErrorContainer::fromMessage( 1498 $response_message); 1499 } else if ($response->status != 200 and $response->status != 206) { 1500 return null; 1501 } 1502 1503 return $response_message; 1504 } 1505 1506 /** 1507 * @access private 1508 * @param Auth_OpenID_Message $message 1509 * @param string $server_url 1510 * @return Auth_OpenID_Message|Auth_OpenID_ServerErrorContainer|null 1511 */ 1512 function _makeKVPost($message, $server_url) 1513 { 1514 $body = $message->toURLEncoded(); 1515 $resp = $this->fetcher->post($server_url, $body); 1516 1517 if ($resp === null) { 1518 return null; 1519 } 1520 1521 return $this->_httpResponseToMessage($resp); 1522 } 1523 1524 /** 1525 * @access private 1526 * @param Auth_OpenID_ServiceEndpoint $endpoint 1527 * @return Auth_OpenID_Association|Auth_OpenID_Message|Auth_OpenID_ServerErrorContainer|null 1528 */ 1529 function _getAssociation($endpoint) 1530 { 1531 if (!$this->_use_assocs) { 1532 return null; 1533 } 1534 1535 $assoc = $this->store->getAssociation($endpoint->server_url); 1536 1537 if (($assoc === null) || 1538 ($assoc->getExpiresIn() <= 0)) { 1539 1540 $assoc = $this->_negotiateAssociation($endpoint); 1541 1542 if ($assoc !== null) { 1543 $this->store->storeAssociation($endpoint->server_url, $assoc); 1544 } 1545 } 1546 1547 return $assoc; 1548 } 1549 1550 /** 1551 * Handle ServerErrors resulting from association requests. 1552 * 1553 * @param $server_error 1554 * @return array|null $result If server replied with an C{unsupported-type} 1555 * error, return a tuple of supported C{association_type}, 1556 * C{session_type}. Otherwise logs the error and returns null. 1557 * @access private 1558 */ 1559 function _extractSupportedAssociationType($server_error) 1560 { 1561 // Any error message whose code is not 'unsupported-type' 1562 // should be considered a total failure. 1563 if (($server_error->error_code != 'unsupported-type') || 1564 ($server_error->message->isOpenID1())) { 1565 return null; 1566 } 1567 1568 // The server didn't like the association/session type that we 1569 // sent, and it sent us back a message that might tell us how 1570 // to handle it. 1571 1572 // Extract the session_type and assoc_type from the error 1573 // message 1574 $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS, 1575 'assoc_type'); 1576 1577 $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS, 1578 'session_type'); 1579 1580 if (($assoc_type === null) || ($session_type === null)) { 1581 return null; 1582 } else if (!$this->negotiator->isAllowed($assoc_type, 1583 $session_type)) { 1584 return null; 1585 } else { 1586 return [$assoc_type, $session_type]; 1587 } 1588 } 1589 1590 /** 1591 * @access private 1592 * @param Auth_OpenID_ServiceEndpoint $endpoint 1593 * @return Auth_OpenID_Association|null 1594 */ 1595 function _negotiateAssociation($endpoint) 1596 { 1597 // Get our preferred session/association type from the negotiatior. 1598 list($assoc_type, $session_type) = $this->negotiator->getAllowedType(); 1599 1600 $assoc = $this->_requestAssociation( 1601 $endpoint, $assoc_type, $session_type); 1602 1603 if (Auth_OpenID::isFailure($assoc)) { 1604 return null; 1605 } 1606 1607 if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) { 1608 $supportedTypes = $this->_extractSupportedAssociationType($assoc); 1609 1610 if ($supportedTypes !== null) { 1611 list($assoc_type, $session_type) = $supportedTypes; 1612 1613 // Attempt to create an association from the assoc_type 1614 // and session_type that the server told us it 1615 // supported. 1616 $assoc = $this->_requestAssociation( 1617 $endpoint, $assoc_type, $session_type); 1618 1619 if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) { 1620 // Do not keep trying, since it rejected the 1621 // association type that it told us to use. 1622 // oidutil.log('Server %s refused its suggested association 1623 // 'type: session_type=%s, assoc_type=%s' 1624 // % (endpoint.server_url, session_type, 1625 // assoc_type)) 1626 return null; 1627 } else { 1628 return $assoc; 1629 } 1630 } else { 1631 return null; 1632 } 1633 } else { 1634 return $assoc; 1635 } 1636 } 1637 1638 /** 1639 * @access private 1640 * @param Auth_OpenID_ServiceEndpoint $endpoint 1641 * @param string $assoc_type 1642 * @param string $session_type 1643 * @return Auth_OpenID_Association|Auth_OpenID_Message|Auth_OpenID_ServerErrorContainer|null 1644 */ 1645 function _requestAssociation($endpoint, $assoc_type, $session_type) 1646 { 1647 list($assoc_session, $args) = $this->_createAssociateRequest( 1648 $endpoint, $assoc_type, $session_type); 1649 1650 $response_message = $this->_makeKVPost($args, $endpoint->server_url); 1651 1652 if ($response_message === null) { 1653 // oidutil.log('openid.associate request failed: %s' % (why[0],)) 1654 return null; 1655 } else if (is_a($response_message, 1656 'Auth_OpenID_ServerErrorContainer')) { 1657 return $response_message; 1658 } 1659 1660 return $this->_extractAssociation($response_message, $assoc_session); 1661 } 1662 1663 /** 1664 * @access private 1665 * @param Auth_OpenID_Message $assoc_response 1666 * @param Auth_OpenID_PlainTextConsumerSession $assoc_session 1667 * @return Auth_OpenID_Association|Auth_OpenID_FailureResponse|null 1668 */ 1669 function _extractAssociation($assoc_response, $assoc_session) 1670 { 1671 // Extract the common fields from the response, raising an 1672 // exception if they are not found 1673 $assoc_type = $assoc_response->getArg( 1674 Auth_OpenID_OPENID_NS, 'assoc_type', 1675 Auth_OpenID_NO_DEFAULT); 1676 1677 if (Auth_OpenID::isFailure($assoc_type)) { 1678 return $assoc_type; 1679 } 1680 1681 $assoc_handle = $assoc_response->getArg( 1682 Auth_OpenID_OPENID_NS, 'assoc_handle', 1683 Auth_OpenID_NO_DEFAULT); 1684 1685 if (Auth_OpenID::isFailure($assoc_handle)) { 1686 return $assoc_handle; 1687 } 1688 1689 // expires_in is a base-10 string. The Python parsing will 1690 // accept literals that have whitespace around them and will 1691 // accept negative values. Neither of these are really in-spec, 1692 // but we think it's OK to accept them. 1693 $expires_in_str = $assoc_response->getArg( 1694 Auth_OpenID_OPENID_NS, 'expires_in', 1695 Auth_OpenID_NO_DEFAULT); 1696 1697 if (Auth_OpenID::isFailure($expires_in_str)) { 1698 return $expires_in_str; 1699 } 1700 1701 $expires_in = Auth_OpenID::intval($expires_in_str); 1702 if ($expires_in === false) { 1703 1704 $err = sprintf("Could not parse expires_in from association ". 1705 "response %s", print_r($assoc_response, true)); 1706 return new Auth_OpenID_FailureResponse(null, $err); 1707 } 1708 1709 // OpenID 1 has funny association session behaviour. 1710 if ($assoc_response->isOpenID1()) { 1711 $session_type = $this->_getOpenID1SessionType($assoc_response); 1712 } else { 1713 $session_type = $assoc_response->getArg( 1714 Auth_OpenID_OPENID2_NS, 'session_type', 1715 Auth_OpenID_NO_DEFAULT); 1716 1717 if (Auth_OpenID::isFailure($session_type)) { 1718 return $session_type; 1719 } 1720 } 1721 1722 // Session type mismatch 1723 if ($assoc_session->session_type != $session_type) { 1724 if ($assoc_response->isOpenID1() && 1725 ($session_type == 'no-encryption')) { 1726 // In OpenID 1, any association request can result in 1727 // a 'no-encryption' association response. Setting 1728 // assoc_session to a new no-encryption session should 1729 // make the rest of this function work properly for 1730 // that case. 1731 $assoc_session = new Auth_OpenID_PlainTextConsumerSession(); 1732 } else { 1733 // Any other mismatch, regardless of protocol version 1734 // results in the failure of the association session 1735 // altogether. 1736 return null; 1737 } 1738 } 1739 1740 // Make sure assoc_type is valid for session_type 1741 if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) { 1742 return null; 1743 } 1744 1745 // Delegate to the association session to extract the secret 1746 // from the response, however is appropriate for that session 1747 // type. 1748 $secret = $assoc_session->extractSecret($assoc_response); 1749 1750 if ($secret === null) { 1751 return null; 1752 } 1753 1754 return Auth_OpenID_Association::fromExpiresIn( 1755 $expires_in, $assoc_handle, $secret, $assoc_type); 1756 } 1757 1758 /** 1759 * @access private 1760 * @param Auth_OpenID_ServiceEndpoint $endpoint 1761 * @param string $assoc_type 1762 * @param string $session_type 1763 * @return array|null 1764 */ 1765 function _createAssociateRequest($endpoint, $assoc_type, $session_type) 1766 { 1767 if (array_key_exists($session_type, $this->session_types)) { 1768 $session_type_class = $this->session_types[$session_type]; 1769 1770 if (is_callable($session_type_class)) { 1771 /** @var Auth_OpenID_PlainTextConsumerSession $assoc_session */ 1772 $assoc_session = $session_type_class(); 1773 } else { 1774 $assoc_session = new $session_type_class(); 1775 } 1776 } else { 1777 return null; 1778 } 1779 1780 $args = [ 1781 'mode' => 'associate', 1782 'assoc_type' => $assoc_type 1783 ]; 1784 1785 if (!$endpoint->compatibilityMode()) { 1786 $args['ns'] = Auth_OpenID_OPENID2_NS; 1787 } 1788 1789 // Leave out the session type if we're in compatibility mode 1790 // *and* it's no-encryption. 1791 if ((!$endpoint->compatibilityMode()) || 1792 ($assoc_session->session_type != 'no-encryption')) { 1793 $args['session_type'] = $assoc_session->session_type; 1794 } 1795 1796 $args = array_merge($args, $assoc_session->getRequest()); 1797 $message = Auth_OpenID_Message::fromOpenIDArgs($args); 1798 return [$assoc_session, $message]; 1799 } 1800 1801 /** 1802 * Given an association response message, extract the OpenID 1.X 1803 * session type. 1804 * 1805 * This function mostly takes care of the 'no-encryption' default 1806 * behavior in OpenID 1. 1807 * 1808 * If the association type is plain-text, this function will 1809 * return 'no-encryption' 1810 * 1811 * @access private 1812 * @param Auth_OpenID_Message $assoc_response 1813 * @return string The association type for this message 1814 */ 1815 function _getOpenID1SessionType($assoc_response) 1816 { 1817 // If it's an OpenID 1 message, allow session_type to default 1818 // to None (which signifies "no-encryption") 1819 $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS, 1820 'session_type'); 1821 1822 // Handle the differences between no-encryption association 1823 // respones in OpenID 1 and 2: 1824 1825 // no-encryption is not really a valid session type for OpenID 1826 // 1, but we'll accept it anyway, while issuing a warning. 1827 if ($session_type == 'no-encryption') { 1828 // oidutil.log('WARNING: OpenID server sent "no-encryption"' 1829 // 'for OpenID 1.X') 1830 } else if (($session_type == '') || ($session_type === null)) { 1831 // Missing or empty session type is the way to flag a 1832 // 'no-encryption' response. Change the session type to 1833 // 'no-encryption' so that it can be handled in the same 1834 // way as OpenID 2 'no-encryption' respones. 1835 $session_type = 'no-encryption'; 1836 } 1837 1838 return $session_type; 1839 } 1840} 1841 1842/** 1843 * This class represents an authentication request from a consumer to 1844 * an OpenID server. 1845 * 1846 * @package OpenID 1847 */ 1848class Auth_OpenID_AuthRequest { 1849 1850 /** @var Auth_OpenID_Association */ 1851 public $assoc; 1852 1853 /** @var Auth_OpenID_ServiceEndpoint */ 1854 public $endpoint; 1855 1856 /** @var array */ 1857 public $return_to_args; 1858 1859 /** @var Auth_OpenID_Message */ 1860 public $message; 1861 1862 /** @var bool */ 1863 public $_anonymous; 1864 1865 /** 1866 * Initialize an authentication request with the specified token, 1867 * association, and endpoint. 1868 * 1869 * Users of this library should not create instances of this 1870 * class. Instances of this class are created by the library when 1871 * needed. 1872 * 1873 * @param Auth_OpenID_ServiceEndpoint $endpoint 1874 * @param Auth_OpenID_Association $assoc 1875 */ 1876 function __construct($endpoint, $assoc) 1877 { 1878 $this->assoc = $assoc; 1879 $this->endpoint = $endpoint; 1880 $this->return_to_args = []; 1881 $this->message = new Auth_OpenID_Message($endpoint->preferredNamespace()); 1882 $this->_anonymous = false; 1883 } 1884 1885 /** 1886 * Add an extension to this checkid request. 1887 * 1888 * @param Auth_OpenID_Extension $extension_request An object that implements the extension 1889 * request interface for adding arguments to an OpenID message. 1890 */ 1891 function addExtension($extension_request) 1892 { 1893 $extension_request->toMessage($this->message); 1894 } 1895 1896 /** 1897 * Add an extension argument to this OpenID authentication 1898 * request. 1899 * 1900 * Use caution when adding arguments, because they will be 1901 * URL-escaped and appended to the redirect URL, which can easily 1902 * get quite long. 1903 * 1904 * @param string $namespace The namespace for the extension. For 1905 * example, the simple registration extension uses the namespace 1906 * 'sreg'. 1907 * 1908 * @param string $key The key within the extension namespace. For 1909 * example, the nickname field in the simple registration 1910 * extension's key is 'nickname'. 1911 * 1912 * @param string $value The value to provide to the server for 1913 * this argument. 1914 * 1915 * @return Auth_OpenID_FailureResponse|bool|null|string 1916 */ 1917 function addExtensionArg($namespace, $key, $value) 1918 { 1919 return $this->message->setArg($namespace, $key, $value); 1920 } 1921 1922 /** 1923 * Set whether this request should be made anonymously. If a 1924 * request is anonymous, the identifier will not be sent in the 1925 * request. This is only useful if you are making another kind of 1926 * request with an extension in this request. 1927 * 1928 * Anonymous requests are not allowed when the request is made 1929 * with OpenID 1. 1930 * 1931 * @param bool $is_anonymous 1932 * @return bool 1933 */ 1934 function setAnonymous($is_anonymous) 1935 { 1936 if ($is_anonymous && $this->message->isOpenID1()) { 1937 return false; 1938 } else { 1939 $this->_anonymous = $is_anonymous; 1940 return true; 1941 } 1942 } 1943 1944 /** 1945 * Produce a {@link Auth_OpenID_Message} representing this 1946 * request. 1947 * 1948 * @param string $realm The URL (or URL pattern) that identifies 1949 * your web site to the user when she is authorizing it. 1950 * 1951 * @param string $return_to The URL that the OpenID provider will 1952 * send the user back to after attempting to verify her identity. 1953 * 1954 * Not specifying a return_to URL means that the user will not be 1955 * returned to the site issuing the request upon its completion. 1956 * 1957 * @param bool $immediate If true, the OpenID provider is to send 1958 * back a response immediately, useful for behind-the-scenes 1959 * authentication attempts. Otherwise the OpenID provider may 1960 * engage the user before providing a response. This is the 1961 * default case, as the user may need to provide credentials or 1962 * approve the request before a positive response can be sent. 1963 * 1964 * @return Auth_OpenID_Message|Auth_OpenID_FailureResponse 1965 */ 1966 function getMessage($realm, $return_to=null, $immediate=false) 1967 { 1968 if ($return_to) { 1969 $return_to = Auth_OpenID::appendArgs($return_to, 1970 $this->return_to_args); 1971 } else if ($immediate) { 1972 // raise ValueError( 1973 // '"return_to" is mandatory when 1974 //using "checkid_immediate"') 1975 return new Auth_OpenID_FailureResponse(null, 1976 "'return_to' is mandatory when using checkid_immediate"); 1977 } else if ($this->message->isOpenID1()) { 1978 // raise ValueError('"return_to" is 1979 // mandatory for OpenID 1 requests') 1980 return new Auth_OpenID_FailureResponse(null, 1981 "'return_to' is mandatory for OpenID 1 requests"); 1982 } else if ($this->return_to_args) { 1983 // raise ValueError('extra "return_to" arguments 1984 // were specified, but no return_to was specified') 1985 return new Auth_OpenID_FailureResponse(null, 1986 "extra 'return_to' arguments where specified, " . 1987 "but no return_to was specified"); 1988 } 1989 1990 if ($immediate) { 1991 $mode = 'checkid_immediate'; 1992 } else { 1993 $mode = 'checkid_setup'; 1994 } 1995 1996 $message = $this->message->copy(); 1997 if ($message->isOpenID1()) { 1998 $realm_key = 'trust_root'; 1999 } else { 2000 $realm_key = 'realm'; 2001 } 2002 2003 $message->updateArgs(Auth_OpenID_OPENID_NS, 2004 [ 2005 $realm_key => $realm, 2006 'mode' => $mode, 2007 'return_to' => $return_to 2008 ]); 2009 2010 if (!$this->_anonymous) { 2011 if ($this->endpoint->isOPIdentifier()) { 2012 // This will never happen when we're in compatibility 2013 // mode, as long as isOPIdentifier() returns False 2014 // whenever preferredNamespace() returns OPENID1_NS. 2015 $claimed_id = $request_identity = 2016 Auth_OpenID_IDENTIFIER_SELECT; 2017 } else { 2018 $request_identity = $this->endpoint->getLocalID(); 2019 $claimed_id = $this->endpoint->claimed_id; 2020 } 2021 2022 // This is true for both OpenID 1 and 2 2023 $message->setArg(Auth_OpenID_OPENID_NS, 'identity', 2024 $request_identity); 2025 2026 if ($message->isOpenID2()) { 2027 $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id', 2028 $claimed_id); 2029 } 2030 } 2031 2032 if ($this->assoc) { 2033 $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', 2034 $this->assoc->handle); 2035 } 2036 2037 return $message; 2038 } 2039 2040 function redirectURL($realm, $return_to = null, 2041 $immediate = false) 2042 { 2043 $message = $this->getMessage($realm, $return_to, $immediate); 2044 2045 if (Auth_OpenID::isFailure($message)) { 2046 return $message; 2047 } 2048 2049 return $message->toURL($this->endpoint->server_url); 2050 } 2051 2052 /** 2053 * Get html for a form to submit this request to the IDP. 2054 * 2055 * form_tag_attrs: An array of attributes to be added to the form 2056 * tag. 'accept-charset' and 'enctype' have defaults that can be 2057 * overridden. If a value is supplied for 'action' or 'method', it 2058 * will be replaced. 2059 * 2060 * @param string $realm 2061 * @param null|string $return_to 2062 * @param bool $immediate 2063 * @param null|array $form_tag_attrs 2064 * @return Auth_OpenID_FailureResponse|Auth_OpenID_Message|string 2065 */ 2066 function formMarkup($realm, $return_to=null, $immediate=false, 2067 $form_tag_attrs=null) 2068 { 2069 $message = $this->getMessage($realm, $return_to, $immediate); 2070 2071 if (Auth_OpenID::isFailure($message)) { 2072 return $message; 2073 } 2074 2075 return $message->toFormMarkup($this->endpoint->server_url, $form_tag_attrs); 2076 } 2077 2078 /** 2079 * Get a complete html document that will autosubmit the request 2080 * to the IDP. 2081 * 2082 * Wraps formMarkup. See the documentation for that function. 2083 * 2084 * @param string $realm 2085 * @param string $return_to 2086 * @param bool $immediate 2087 * @param array $form_tag_attrs 2088 * @return Auth_OpenID_FailureResponse|Auth_OpenID_Message|string 2089 */ 2090 function htmlMarkup($realm, $return_to=null, $immediate=false, 2091 $form_tag_attrs=null) 2092 { 2093 $form = $this->formMarkup($realm, $return_to, $immediate, 2094 $form_tag_attrs); 2095 2096 if (Auth_OpenID::isFailure($form)) { 2097 return $form; 2098 } 2099 return Auth_OpenID::autoSubmitHTML($form); 2100 } 2101 2102 function shouldSendRedirect() 2103 { 2104 return $this->endpoint->compatibilityMode(); 2105 } 2106} 2107 2108/** 2109 * The base class for responses from the Auth_OpenID_Consumer. 2110 * 2111 * @package OpenID 2112 */ 2113class Auth_OpenID_ConsumerResponse { 2114 public $status = null; 2115 2116 /** @var null|string */ 2117 public $identity_url = null; 2118 2119 /** @var Auth_OpenID_ServiceEndpoint */ 2120 public $endpoint; 2121 2122 /** 2123 * @param Auth_OpenID_ServiceEndpoint|null $endpoint 2124 */ 2125 function setEndpoint($endpoint) 2126 { 2127 $this->endpoint = $endpoint; 2128 if ($endpoint === null) { 2129 $this->identity_url = null; 2130 } else { 2131 $this->identity_url = $endpoint->claimed_id; 2132 } 2133 } 2134 2135 /** 2136 * Return the display identifier for this response. 2137 * 2138 * The display identifier is related to the Claimed Identifier, but the 2139 * two are not always identical. The display identifier is something the 2140 * user should recognize as what they entered, whereas the response's 2141 * claimed identifier (in the identity_url attribute) may have extra 2142 * information for better persistence. 2143 * 2144 * URLs will be stripped of their fragments for display. XRIs will 2145 * display the human-readable identifier (i-name) instead of the 2146 * persistent identifier (i-number). 2147 * 2148 * Use the display identifier in your user interface. Use 2149 * identity_url for querying your database or authorization server. 2150 * 2151 */ 2152 function getDisplayIdentifier() 2153 { 2154 if ($this->endpoint !== null) { 2155 return $this->endpoint->getDisplayIdentifier(); 2156 } 2157 return null; 2158 } 2159} 2160 2161/** 2162 * A response with a status of Auth_OpenID_SUCCESS. Indicates that 2163 * this request is a successful acknowledgement from the OpenID server 2164 * that the supplied URL is, indeed controlled by the requesting 2165 * agent. This has three relevant attributes: 2166 * 2167 * claimed_id - The identity URL that has been authenticated 2168 * 2169 * signed_args - The arguments in the server's response that were 2170 * signed and verified. 2171 * 2172 * status - Auth_OpenID_SUCCESS. 2173 * 2174 * @package OpenID 2175 */ 2176class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse { 2177 public $status = Auth_OpenID_SUCCESS; 2178 2179 /** @var array */ 2180 public $signed_args = []; 2181 /** @var Auth_OpenID_Message */ 2182 public $message; 2183 2184 /** 2185 * @access private 2186 * @param Auth_OpenID_ServiceEndpoint $endpoint 2187 * @param Auth_OpenID_Message $message 2188 * @param array|null $signed_args 2189 */ 2190 function __construct($endpoint, $message, $signed_args=null) 2191 { 2192 $this->endpoint = $endpoint; 2193 $this->identity_url = $endpoint->claimed_id; 2194 $this->message = $message; 2195 2196 if ($this->signed_args !== null) { 2197 $this->signed_args = $signed_args; 2198 } 2199 } 2200 2201 /** 2202 * Extract signed extension data from the server's response. 2203 * 2204 * @param $namespace_uri 2205 * @param $require_signed 2206 * @return array|Auth_OpenID_FailureResponse|null|string 2207 * @internal param string $prefix The extension namespace from which to 2208 * extract the extension data. 2209 */ 2210 function extensionResponse($namespace_uri, $require_signed) 2211 { 2212 if ($require_signed) { 2213 return $this->getSignedNS($namespace_uri); 2214 } else { 2215 return $this->message->getArgs($namespace_uri); 2216 } 2217 } 2218 2219 function isOpenID1() 2220 { 2221 return $this->message->isOpenID1(); 2222 } 2223 2224 function isSigned($ns_uri, $ns_key) 2225 { 2226 // Return whether a particular key is signed, regardless of 2227 // its namespace alias 2228 return in_array($this->message->getKey($ns_uri, $ns_key), 2229 $this->signed_args); 2230 } 2231 2232 function getSigned($ns_uri, $ns_key, $default = null) 2233 { 2234 // Return the specified signed field if available, otherwise 2235 // return default 2236 if ($this->isSigned($ns_uri, $ns_key)) { 2237 return $this->message->getArg($ns_uri, $ns_key, $default); 2238 } else { 2239 return $default; 2240 } 2241 } 2242 2243 function getSignedNS($ns_uri) 2244 { 2245 $msg_args = $this->message->getArgs($ns_uri); 2246 if (Auth_OpenID::isFailure($msg_args)) { 2247 return null; 2248 } 2249 2250 foreach ($msg_args as $key => $value) { 2251 if (!$this->isSigned($ns_uri, $key)) { 2252 unset($msg_args[$key]); 2253 } 2254 } 2255 2256 return $msg_args; 2257 } 2258 2259 /** 2260 * Get the openid.return_to argument from this response. 2261 * 2262 * This is useful for verifying that this request was initiated by 2263 * this consumer. 2264 * 2265 * @return string $return_to The return_to URL supplied to the 2266 * server on the initial request, or null if the response did not 2267 * contain an 'openid.return_to' argument. 2268 */ 2269 function getReturnTo() 2270 { 2271 return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to'); 2272 } 2273} 2274 2275/** 2276 * A response with a status of Auth_OpenID_FAILURE. Indicates that the 2277 * OpenID protocol has failed. This could be locally or remotely 2278 * triggered. This has three relevant attributes: 2279 * 2280 * claimed_id - The identity URL for which authentication was 2281 * attempted, if it can be determined. Otherwise, null. 2282 * 2283 * message - A message indicating why the request failed, if one is 2284 * supplied. Otherwise, null. 2285 * 2286 * status - Auth_OpenID_FAILURE. 2287 * 2288 * @package OpenID 2289 */ 2290class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse { 2291 public $status = Auth_OpenID_FAILURE; 2292 2293 /** @var string */ 2294 public $message; 2295 2296 public $contact; 2297 2298 public $reference; 2299 2300 function __construct($endpoint, $message = null, $contact = null, $reference = null) 2301 { 2302 $this->setEndpoint($endpoint); 2303 $this->message = $message; 2304 $this->contact = $contact; 2305 $this->reference = $reference; 2306 } 2307} 2308 2309/** 2310 * A specific, internal failure used to detect type URI mismatch. 2311 * 2312 * @package OpenID 2313 */ 2314class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse { 2315} 2316 2317/** 2318 * Exception that is raised when the server returns a 400 response 2319 * code to a direct request. 2320 * 2321 * @package OpenID 2322 */ 2323class Auth_OpenID_ServerErrorContainer { 2324 2325 /** @var Auth_OpenID_Message */ 2326 public $message; 2327 2328 /** @var string */ 2329 public $error_code; 2330 /** @var string */ 2331 private $error_text; 2332 2333 /** 2334 * Auth_OpenID_ServerErrorContainer constructor. 2335 * 2336 * @param string $error_text 2337 * @param string $error_code 2338 * @param Auth_OpenID_Message $message 2339 */ 2340 function __construct($error_text, $error_code, $message) 2341 { 2342 $this->error_text = $error_text; 2343 $this->error_code = $error_code; 2344 $this->message = $message; 2345 } 2346 2347 /** 2348 * @access private 2349 * @param Auth_OpenID_Message $message 2350 * @return Auth_OpenID_ServerErrorContainer 2351 */ 2352 static function fromMessage($message) 2353 { 2354 $error_text = $message->getArg( 2355 Auth_OpenID_OPENID_NS, 'error', '<no error message supplied>'); 2356 $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code'); 2357 return new Auth_OpenID_ServerErrorContainer($error_text, 2358 $error_code, 2359 $message); 2360 } 2361} 2362 2363/** 2364 * A response with a status of Auth_OpenID_CANCEL. Indicates that the 2365 * user cancelled the OpenID authentication request. This has two 2366 * relevant attributes: 2367 * 2368 * claimed_id - The identity URL for which authentication was 2369 * attempted, if it can be determined. Otherwise, null. 2370 * 2371 * status - Auth_OpenID_SUCCESS. 2372 * 2373 * @package OpenID 2374 */ 2375class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse { 2376 public $status = Auth_OpenID_CANCEL; 2377 2378 /** 2379 * Auth_OpenID_CancelResponse constructor. 2380 * 2381 * @param Auth_OpenID_ServiceEndpoint $endpoint 2382 */ 2383 function __construct($endpoint) 2384 { 2385 $this->setEndpoint($endpoint); 2386 } 2387} 2388 2389/** 2390 * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates 2391 * that the request was in immediate mode, and the server is unable to 2392 * authenticate the user without further interaction. 2393 * 2394 * claimed_id - The identity URL for which authentication was 2395 * attempted. 2396 * 2397 * setup_url - A URL that can be used to send the user to the server 2398 * to set up for authentication. The user should be redirected in to 2399 * the setup_url, either in the current window or in a new browser 2400 * window. Null in OpenID 2. 2401 * 2402 * status - Auth_OpenID_SETUP_NEEDED. 2403 * 2404 * @package OpenID 2405 */ 2406class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse { 2407 public $status = Auth_OpenID_SETUP_NEEDED; 2408 2409 /** @var string */ 2410 public $setup_url = ''; 2411 2412 /** 2413 * Auth_OpenID_SetupNeededResponse constructor. 2414 * 2415 * @param Auth_OpenID_ServiceEndpoint $endpoint 2416 * @param string $setup_url 2417 */ 2418 function __construct($endpoint, $setup_url = null) 2419 { 2420 $this->setEndpoint($endpoint); 2421 $this->setup_url = $setup_url; 2422 } 2423} 2424