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