1<?php 2 3/** 4 * OpenID server protocol and logic. 5 * 6 * Overview 7 * 8 * An OpenID server must perform three tasks: 9 * 10 * 1. Examine the incoming request to determine its nature and validity. 11 * 2. Make a decision about how to respond to this request. 12 * 3. Format the response according to the protocol. 13 * 14 * The first and last of these tasks may performed by the {@link 15 * Auth_OpenID_Server::decodeRequest()} and {@link 16 * Auth_OpenID_Server::encodeResponse} methods. Who gets to do the 17 * intermediate task -- deciding how to respond to the request -- will 18 * depend on what type of request it is. 19 * 20 * If it's a request to authenticate a user (a 'checkid_setup' or 21 * 'checkid_immediate' request), you need to decide if you will assert 22 * that this user may claim the identity in question. Exactly how you 23 * do that is a matter of application policy, but it generally 24 * involves making sure the user has an account with your system and 25 * is logged in, checking to see if that identity is hers to claim, 26 * and verifying with the user that she does consent to releasing that 27 * information to the party making the request. 28 * 29 * Examine the properties of the {@link Auth_OpenID_CheckIDRequest} 30 * object, and if and when you've come to a decision, form a response 31 * by calling {@link Auth_OpenID_CheckIDRequest::answer()}. 32 * 33 * Other types of requests relate to establishing associations between 34 * client and server and verifing the authenticity of previous 35 * communications. {@link Auth_OpenID_Server} contains all the logic 36 * and data necessary to respond to such requests; just pass it to 37 * {@link Auth_OpenID_Server::handleRequest()}. 38 * 39 * OpenID Extensions 40 * 41 * Do you want to provide other information for your users in addition 42 * to authentication? Version 1.2 of the OpenID protocol allows 43 * consumers to add extensions to their requests. For example, with 44 * sites using the Simple Registration 45 * Extension 46 * (http://openid.net/specs/openid-simple-registration-extension-1_0.html), 47 * a user can agree to have their nickname and e-mail address sent to 48 * a site when they sign up. 49 * 50 * Since extensions do not change the way OpenID authentication works, 51 * code to handle extension requests may be completely separate from 52 * the {@link Auth_OpenID_Request} class here. But you'll likely want 53 * data sent back by your extension to be signed. {@link 54 * Auth_OpenID_ServerResponse} provides methods with which you can add 55 * data to it which can be signed with the other data in the OpenID 56 * signature. 57 * 58 * For example: 59 * 60 * <pre> // when request is a checkid_* request 61 * $response = $request->answer(true); 62 * // this will a signed 'openid.sreg.timezone' parameter to the response 63 * response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre> 64 * 65 * Stores 66 * 67 * The OpenID server needs to maintain state between requests in order 68 * to function. Its mechanism for doing this is called a store. The 69 * store interface is defined in Interface.php. Additionally, several 70 * concrete store implementations are provided, so that most sites 71 * won't need to implement a custom store. For a store backed by flat 72 * files on disk, see {@link Auth_OpenID_FileStore}. For stores based 73 * on MySQL, SQLite, or PostgreSQL, see the {@link 74 * Auth_OpenID_SQLStore} subclasses. 75 * 76 * Upgrading 77 * 78 * The keys by which a server looks up associations in its store have 79 * changed in version 1.2 of this library. If your store has entries 80 * created from version 1.0 code, you should empty it. 81 * 82 * PHP versions 4 and 5 83 * 84 * LICENSE: See the COPYING file included in this distribution. 85 * 86 * @package OpenID 87 * @author JanRain, Inc. <openid@janrain.com> 88 * @copyright 2005-2008 Janrain, Inc. 89 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 90 */ 91 92/** 93 * Required imports 94 */ 95require_once "Auth/OpenID.php"; 96require_once "Auth/OpenID/Association.php"; 97require_once "Auth/OpenID/CryptUtil.php"; 98require_once "Auth/OpenID/BigMath.php"; 99require_once "Auth/OpenID/DiffieHellman.php"; 100require_once "Auth/OpenID/KVForm.php"; 101require_once "Auth/OpenID/TrustRoot.php"; 102require_once "Auth/OpenID/ServerRequest.php"; 103require_once "Auth/OpenID/Message.php"; 104require_once "Auth/OpenID/Nonce.php"; 105 106define('AUTH_OPENID_HTTP_OK', 200); 107define('AUTH_OPENID_HTTP_REDIRECT', 302); 108define('AUTH_OPENID_HTTP_ERROR', 400); 109 110/** 111 * @access private 112 */ 113global $_Auth_OpenID_Request_Modes; 114$_Auth_OpenID_Request_Modes = array('checkid_setup', 115 'checkid_immediate'); 116 117/** 118 * @access private 119 */ 120define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm'); 121 122/** 123 * @access private 124 */ 125define('Auth_OpenID_ENCODE_URL', 'URL/redirect'); 126 127/** 128 * @access private 129 */ 130define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form'); 131 132/** 133 * @access private 134 */ 135function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError') 136{ 137 return is_a($obj, $cls); 138} 139 140/** 141 * An error class which gets instantiated and returned whenever an 142 * OpenID protocol error occurs. Be prepared to use this in place of 143 * an ordinary server response. 144 * 145 * @package OpenID 146 */ 147class Auth_OpenID_ServerError { 148 /** 149 * @access private 150 */ 151 function Auth_OpenID_ServerError($message = null, $text = null, 152 $reference = null, $contact = null) 153 { 154 $this->message = $message; 155 $this->text = $text; 156 $this->contact = $contact; 157 $this->reference = $reference; 158 } 159 160 function getReturnTo() 161 { 162 if ($this->message && 163 $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) { 164 return $this->message->getArg(Auth_OpenID_OPENID_NS, 165 'return_to'); 166 } else { 167 return null; 168 } 169 } 170 171 /** 172 * Returns the return_to URL for the request which caused this 173 * error. 174 */ 175 function hasReturnTo() 176 { 177 return $this->getReturnTo() !== null; 178 } 179 180 /** 181 * Encodes this error's response as a URL suitable for 182 * redirection. If the response has no return_to, another 183 * Auth_OpenID_ServerError is returned. 184 */ 185 function encodeToURL() 186 { 187 if (!$this->message) { 188 return null; 189 } 190 191 $msg = $this->toMessage(); 192 return $msg->toURL($this->getReturnTo()); 193 } 194 195 /** 196 * Encodes the response to key-value form. This is a 197 * machine-readable format used to respond to messages which came 198 * directly from the consumer and not through the user-agent. See 199 * the OpenID specification. 200 */ 201 function encodeToKVForm() 202 { 203 return Auth_OpenID_KVForm::fromArray( 204 array('mode' => 'error', 205 'error' => $this->toString())); 206 } 207 208 function toFormMarkup($form_tag_attrs=null) 209 { 210 $msg = $this->toMessage(); 211 return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs); 212 } 213 214 function toHTML($form_tag_attrs=null) 215 { 216 return Auth_OpenID::autoSubmitHTML( 217 $this->toFormMarkup($form_tag_attrs)); 218 } 219 220 function toMessage() 221 { 222 // Generate a Message object for sending to the relying party, 223 // after encoding. 224 $namespace = $this->message->getOpenIDNamespace(); 225 $reply = new Auth_OpenID_Message($namespace); 226 $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error'); 227 $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString()); 228 229 if ($this->contact !== null) { 230 $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact); 231 } 232 233 if ($this->reference !== null) { 234 $reply->setArg(Auth_OpenID_OPENID_NS, 'reference', 235 $this->reference); 236 } 237 238 return $reply; 239 } 240 241 /** 242 * Returns one of Auth_OpenID_ENCODE_URL, 243 * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of 244 * encoding expected for this error's payload. 245 */ 246 function whichEncoding() 247 { 248 global $_Auth_OpenID_Request_Modes; 249 250 if ($this->hasReturnTo()) { 251 if ($this->message->isOpenID2() && 252 (strlen($this->encodeToURL()) > 253 Auth_OpenID_OPENID1_URL_LIMIT)) { 254 return Auth_OpenID_ENCODE_HTML_FORM; 255 } else { 256 return Auth_OpenID_ENCODE_URL; 257 } 258 } 259 260 if (!$this->message) { 261 return null; 262 } 263 264 $mode = $this->message->getArg(Auth_OpenID_OPENID_NS, 265 'mode'); 266 267 if ($mode) { 268 if (!in_array($mode, $_Auth_OpenID_Request_Modes)) { 269 return Auth_OpenID_ENCODE_KVFORM; 270 } 271 } 272 return null; 273 } 274 275 /** 276 * Returns this error message. 277 */ 278 function toString() 279 { 280 if ($this->text) { 281 return $this->text; 282 } else { 283 return get_class($this) . " error"; 284 } 285 } 286} 287 288/** 289 * Error returned by the server code when a return_to is absent from a 290 * request. 291 * 292 * @package OpenID 293 */ 294class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError { 295 function Auth_OpenID_NoReturnToError($message = null, 296 $text = "No return_to URL available") 297 { 298 parent::Auth_OpenID_ServerError($message, $text); 299 } 300 301 function toString() 302 { 303 return "No return_to available"; 304 } 305} 306 307/** 308 * An error indicating that the return_to URL is malformed. 309 * 310 * @package OpenID 311 */ 312class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError { 313 function Auth_OpenID_MalformedReturnURL($message, $return_to) 314 { 315 $this->return_to = $return_to; 316 parent::Auth_OpenID_ServerError($message, "malformed return_to URL"); 317 } 318} 319 320/** 321 * This error is returned when the trust_root value is malformed. 322 * 323 * @package OpenID 324 */ 325class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError { 326 function Auth_OpenID_MalformedTrustRoot($message = null, 327 $text = "Malformed trust root") 328 { 329 parent::Auth_OpenID_ServerError($message, $text); 330 } 331 332 function toString() 333 { 334 return "Malformed trust root"; 335 } 336} 337 338/** 339 * The base class for all server request classes. 340 * 341 * @package OpenID 342 */ 343class Auth_OpenID_Request { 344 var $mode = null; 345} 346 347/** 348 * A request to verify the validity of a previous response. 349 * 350 * @package OpenID 351 */ 352class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request { 353 var $mode = "check_authentication"; 354 var $invalidate_handle = null; 355 356 function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed, 357 $invalidate_handle = null) 358 { 359 $this->assoc_handle = $assoc_handle; 360 $this->signed = $signed; 361 if ($invalidate_handle !== null) { 362 $this->invalidate_handle = $invalidate_handle; 363 } 364 $this->namespace = Auth_OpenID_OPENID2_NS; 365 $this->message = null; 366 } 367 368 static function fromMessage($message, $server=null) 369 { 370 $required_keys = array('assoc_handle', 'sig', 'signed'); 371 372 foreach ($required_keys as $k) { 373 if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) { 374 return new Auth_OpenID_ServerError($message, 375 sprintf("%s request missing required parameter %s from \ 376 query", "check_authentication", $k)); 377 } 378 } 379 380 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle'); 381 $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig'); 382 383 $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed'); 384 $signed_list = explode(",", $signed_list); 385 386 $signed = $message; 387 if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) { 388 $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res'); 389 } 390 391 $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed); 392 $result->message = $message; 393 $result->sig = $sig; 394 $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS, 395 'invalidate_handle'); 396 return $result; 397 } 398 399 function answer($signatory) 400 { 401 $is_valid = $signatory->verify($this->assoc_handle, $this->signed); 402 403 // Now invalidate that assoc_handle so it this checkAuth 404 // message cannot be replayed. 405 $signatory->invalidate($this->assoc_handle, true); 406 $response = new Auth_OpenID_ServerResponse($this); 407 408 $response->fields->setArg(Auth_OpenID_OPENID_NS, 409 'is_valid', 410 ($is_valid ? "true" : "false")); 411 412 if ($this->invalidate_handle) { 413 $assoc = $signatory->getAssociation($this->invalidate_handle, 414 false); 415 if (!$assoc) { 416 $response->fields->setArg(Auth_OpenID_OPENID_NS, 417 'invalidate_handle', 418 $this->invalidate_handle); 419 } 420 } 421 return $response; 422 } 423} 424 425/** 426 * A class implementing plaintext server sessions. 427 * 428 * @package OpenID 429 */ 430class Auth_OpenID_PlainTextServerSession { 431 /** 432 * An object that knows how to handle association requests with no 433 * session type. 434 */ 435 var $session_type = 'no-encryption'; 436 var $needs_math = false; 437 var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256'); 438 439 static function fromMessage($unused_request) 440 { 441 return new Auth_OpenID_PlainTextServerSession(); 442 } 443 444 function answer($secret) 445 { 446 return array('mac_key' => base64_encode($secret)); 447 } 448} 449 450/** 451 * A class implementing DH-SHA1 server sessions. 452 * 453 * @package OpenID 454 */ 455class Auth_OpenID_DiffieHellmanSHA1ServerSession { 456 /** 457 * An object that knows how to handle association requests with 458 * the Diffie-Hellman session type. 459 */ 460 461 var $session_type = 'DH-SHA1'; 462 var $needs_math = true; 463 var $allowed_assoc_types = array('HMAC-SHA1'); 464 var $hash_func = 'Auth_OpenID_SHA1'; 465 466 function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey) 467 { 468 $this->dh = $dh; 469 $this->consumer_pubkey = $consumer_pubkey; 470 } 471 472 static function getDH($message) 473 { 474 $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus'); 475 $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen'); 476 477 if ((($dh_modulus === null) && ($dh_gen !== null)) || 478 (($dh_gen === null) && ($dh_modulus !== null))) { 479 480 if ($dh_modulus === null) { 481 $missing = 'modulus'; 482 } else { 483 $missing = 'generator'; 484 } 485 486 return new Auth_OpenID_ServerError($message, 487 'If non-default modulus or generator is '. 488 'supplied, both must be supplied. Missing '. 489 $missing); 490 } 491 492 $lib = Auth_OpenID_getMathLib(); 493 494 if ($dh_modulus || $dh_gen) { 495 $dh_modulus = $lib->base64ToLong($dh_modulus); 496 $dh_gen = $lib->base64ToLong($dh_gen); 497 if ($lib->cmp($dh_modulus, 0) == 0 || 498 $lib->cmp($dh_gen, 0) == 0) { 499 return new Auth_OpenID_ServerError( 500 $message, "Failed to parse dh_mod or dh_gen"); 501 } 502 $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen); 503 } else { 504 $dh = new Auth_OpenID_DiffieHellman(); 505 } 506 507 $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS, 508 'dh_consumer_public'); 509 if ($consumer_pubkey === null) { 510 return new Auth_OpenID_ServerError($message, 511 'Public key for DH-SHA1 session '. 512 'not found in query'); 513 } 514 515 $consumer_pubkey = 516 $lib->base64ToLong($consumer_pubkey); 517 518 if ($consumer_pubkey === false) { 519 return new Auth_OpenID_ServerError($message, 520 "dh_consumer_public is not base64"); 521 } 522 523 return array($dh, $consumer_pubkey); 524 } 525 526 static function fromMessage($message) 527 { 528 $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message); 529 530 if (is_a($result, 'Auth_OpenID_ServerError')) { 531 return $result; 532 } else { 533 list($dh, $consumer_pubkey) = $result; 534 return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, 535 $consumer_pubkey); 536 } 537 } 538 539 function answer($secret) 540 { 541 $lib = Auth_OpenID_getMathLib(); 542 $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret, 543 $this->hash_func); 544 return array( 545 'dh_server_public' => 546 $lib->longToBase64($this->dh->public), 547 'enc_mac_key' => base64_encode($mac_key)); 548 } 549} 550 551/** 552 * A class implementing DH-SHA256 server sessions. 553 * 554 * @package OpenID 555 */ 556class Auth_OpenID_DiffieHellmanSHA256ServerSession 557 extends Auth_OpenID_DiffieHellmanSHA1ServerSession { 558 559 var $session_type = 'DH-SHA256'; 560 var $hash_func = 'Auth_OpenID_SHA256'; 561 var $allowed_assoc_types = array('HMAC-SHA256'); 562 563 static function fromMessage($message) 564 { 565 $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message); 566 567 if (is_a($result, 'Auth_OpenID_ServerError')) { 568 return $result; 569 } else { 570 list($dh, $consumer_pubkey) = $result; 571 return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh, 572 $consumer_pubkey); 573 } 574 } 575} 576 577/** 578 * A request to associate with the server. 579 * 580 * @package OpenID 581 */ 582class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request { 583 var $mode = "associate"; 584 585 static function getSessionClasses() 586 { 587 return array( 588 'no-encryption' => 'Auth_OpenID_PlainTextServerSession', 589 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession', 590 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession'); 591 } 592 593 function Auth_OpenID_AssociateRequest($session, $assoc_type) 594 { 595 $this->session = $session; 596 $this->namespace = Auth_OpenID_OPENID2_NS; 597 $this->assoc_type = $assoc_type; 598 } 599 600 static function fromMessage($message, $server=null) 601 { 602 if ($message->isOpenID1()) { 603 $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 604 'session_type'); 605 606 if ($session_type == 'no-encryption') { 607 // oidutil.log('Received OpenID 1 request with a no-encryption ' 608 // 'assocaition session type. Continuing anyway.') 609 } else if (!$session_type) { 610 $session_type = 'no-encryption'; 611 } 612 } else { 613 $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 614 'session_type'); 615 if ($session_type === null) { 616 return new Auth_OpenID_ServerError($message, 617 "session_type missing from request"); 618 } 619 } 620 621 $session_class = Auth_OpenID::arrayGet( 622 Auth_OpenID_AssociateRequest::getSessionClasses(), 623 $session_type); 624 625 if ($session_class === null) { 626 return new Auth_OpenID_ServerError($message, 627 "Unknown session type " . 628 $session_type); 629 } 630 631 $session = call_user_func(array($session_class, 'fromMessage'), 632 $message); 633 if (is_a($session, 'Auth_OpenID_ServerError')) { 634 return $session; 635 } 636 637 $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS, 638 'assoc_type', 'HMAC-SHA1'); 639 640 if (!in_array($assoc_type, $session->allowed_assoc_types)) { 641 $fmt = "Session type %s does not support association type %s"; 642 return new Auth_OpenID_ServerError($message, 643 sprintf($fmt, $session_type, $assoc_type)); 644 } 645 646 $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type); 647 $obj->message = $message; 648 $obj->namespace = $message->getOpenIDNamespace(); 649 return $obj; 650 } 651 652 function answer($assoc) 653 { 654 $response = new Auth_OpenID_ServerResponse($this); 655 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 656 array( 657 'expires_in' => sprintf('%d', $assoc->getExpiresIn()), 658 'assoc_type' => $this->assoc_type, 659 'assoc_handle' => $assoc->handle)); 660 661 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 662 $this->session->answer($assoc->secret)); 663 664 if (! ($this->session->session_type == 'no-encryption' 665 && $this->message->isOpenID1())) { 666 $response->fields->setArg(Auth_OpenID_OPENID_NS, 667 'session_type', 668 $this->session->session_type); 669 } 670 671 return $response; 672 } 673 674 function answerUnsupported($text_message, 675 $preferred_association_type=null, 676 $preferred_session_type=null) 677 { 678 if ($this->message->isOpenID1()) { 679 return new Auth_OpenID_ServerError($this->message); 680 } 681 682 $response = new Auth_OpenID_ServerResponse($this); 683 $response->fields->setArg(Auth_OpenID_OPENID_NS, 684 'error_code', 'unsupported-type'); 685 $response->fields->setArg(Auth_OpenID_OPENID_NS, 686 'error', $text_message); 687 688 if ($preferred_association_type) { 689 $response->fields->setArg(Auth_OpenID_OPENID_NS, 690 'assoc_type', 691 $preferred_association_type); 692 } 693 694 if ($preferred_session_type) { 695 $response->fields->setArg(Auth_OpenID_OPENID_NS, 696 'session_type', 697 $preferred_session_type); 698 } 699 $response->code = AUTH_OPENID_HTTP_ERROR; 700 return $response; 701 } 702} 703 704/** 705 * A request to confirm the identity of a user. 706 * 707 * @package OpenID 708 */ 709class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request { 710 /** 711 * Return-to verification callback. Default is 712 * Auth_OpenID_verifyReturnTo from TrustRoot.php. 713 */ 714 var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo'; 715 716 /** 717 * The mode of this request. 718 */ 719 var $mode = "checkid_setup"; // or "checkid_immediate" 720 721 /** 722 * Whether this request is for immediate mode. 723 */ 724 var $immediate = false; 725 726 /** 727 * The trust_root value for this request. 728 */ 729 var $trust_root = null; 730 731 /** 732 * The OpenID namespace for this request. 733 * deprecated since version 2.0.2 734 */ 735 var $namespace; 736 737 static function make($message, $identity, $return_to, $trust_root = null, 738 $immediate = false, $assoc_handle = null, $server = null) 739 { 740 if ($server === null) { 741 return new Auth_OpenID_ServerError($message, 742 "server must not be null"); 743 } 744 745 if ($return_to && 746 !Auth_OpenID_TrustRoot::_parse($return_to)) { 747 return new Auth_OpenID_MalformedReturnURL($message, $return_to); 748 } 749 750 $r = new Auth_OpenID_CheckIDRequest($identity, $return_to, 751 $trust_root, $immediate, 752 $assoc_handle, $server); 753 754 $r->namespace = $message->getOpenIDNamespace(); 755 $r->message = $message; 756 757 if (!$r->trustRootValid()) { 758 return new Auth_OpenID_UntrustedReturnURL($message, 759 $return_to, 760 $trust_root); 761 } else { 762 return $r; 763 } 764 } 765 766 function Auth_OpenID_CheckIDRequest($identity, $return_to, 767 $trust_root = null, $immediate = false, 768 $assoc_handle = null, $server = null, 769 $claimed_id = null) 770 { 771 $this->namespace = Auth_OpenID_OPENID2_NS; 772 $this->assoc_handle = $assoc_handle; 773 $this->identity = $identity; 774 if ($claimed_id === null) { 775 $this->claimed_id = $identity; 776 } else { 777 $this->claimed_id = $claimed_id; 778 } 779 $this->return_to = $return_to; 780 $this->trust_root = $trust_root; 781 $this->server = $server; 782 783 if ($immediate) { 784 $this->immediate = true; 785 $this->mode = "checkid_immediate"; 786 } else { 787 $this->immediate = false; 788 $this->mode = "checkid_setup"; 789 } 790 } 791 792 function equals($other) 793 { 794 return ( 795 (is_a($other, 'Auth_OpenID_CheckIDRequest')) && 796 ($this->namespace == $other->namespace) && 797 ($this->assoc_handle == $other->assoc_handle) && 798 ($this->identity == $other->identity) && 799 ($this->claimed_id == $other->claimed_id) && 800 ($this->return_to == $other->return_to) && 801 ($this->trust_root == $other->trust_root)); 802 } 803 804 /* 805 * Does the relying party publish the return_to URL for this 806 * response under the realm? It is up to the provider to set a 807 * policy for what kinds of realms should be allowed. This 808 * return_to URL verification reduces vulnerability to data-theft 809 * attacks based on open proxies, corss-site-scripting, or open 810 * redirectors. 811 * 812 * This check should only be performed after making sure that the 813 * return_to URL matches the realm. 814 * 815 * @return true if the realm publishes a document with the 816 * return_to URL listed, false if not or if discovery fails 817 */ 818 function returnToVerified() 819 { 820 $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); 821 return call_user_func_array($this->verifyReturnTo, 822 array($this->trust_root, $this->return_to, $fetcher)); 823 } 824 825 static function fromMessage($message, $server) 826 { 827 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 828 $immediate = null; 829 830 if ($mode == "checkid_immediate") { 831 $immediate = true; 832 $mode = "checkid_immediate"; 833 } else { 834 $immediate = false; 835 $mode = "checkid_setup"; 836 } 837 838 $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 839 'return_to'); 840 841 if (($message->isOpenID1()) && 842 (!$return_to)) { 843 $fmt = "Missing required field 'return_to' from checkid request"; 844 return new Auth_OpenID_ServerError($message, $fmt); 845 } 846 847 $identity = $message->getArg(Auth_OpenID_OPENID_NS, 848 'identity'); 849 $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id'); 850 if ($message->isOpenID1()) { 851 if ($identity === null) { 852 $s = "OpenID 1 message did not contain openid.identity"; 853 return new Auth_OpenID_ServerError($message, $s); 854 } 855 } else { 856 if ($identity && !$claimed_id) { 857 $s = "OpenID 2.0 message contained openid.identity but not " . 858 "claimed_id"; 859 return new Auth_OpenID_ServerError($message, $s); 860 } else if ($claimed_id && !$identity) { 861 $s = "OpenID 2.0 message contained openid.claimed_id " . 862 "but not identity"; 863 return new Auth_OpenID_ServerError($message, $s); 864 } 865 } 866 867 // There's a case for making self.trust_root be a TrustRoot 868 // here. But if TrustRoot isn't currently part of the 869 // "public" API, I'm not sure it's worth doing. 870 if ($message->isOpenID1()) { 871 $trust_root_param = 'trust_root'; 872 } else { 873 $trust_root_param = 'realm'; 874 } 875 $trust_root = $message->getArg(Auth_OpenID_OPENID_NS, 876 $trust_root_param); 877 if (! $trust_root) { 878 $trust_root = $return_to; 879 } 880 881 if (! $message->isOpenID1() && 882 ($return_to === null) && 883 ($trust_root === null)) { 884 return new Auth_OpenID_ServerError($message, 885 "openid.realm required when openid.return_to absent"); 886 } 887 888 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 889 'assoc_handle'); 890 891 $obj = Auth_OpenID_CheckIDRequest::make($message, 892 $identity, 893 $return_to, 894 $trust_root, 895 $immediate, 896 $assoc_handle, 897 $server); 898 899 if (is_a($obj, 'Auth_OpenID_ServerError')) { 900 return $obj; 901 } 902 903 $obj->claimed_id = $claimed_id; 904 905 return $obj; 906 } 907 908 function idSelect() 909 { 910 // Is the identifier to be selected by the IDP? 911 // So IDPs don't have to import the constant 912 return $this->identity == Auth_OpenID_IDENTIFIER_SELECT; 913 } 914 915 function trustRootValid() 916 { 917 if (!$this->trust_root) { 918 return true; 919 } 920 921 $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root); 922 if ($tr === false) { 923 return new Auth_OpenID_MalformedTrustRoot($this->message, 924 $this->trust_root); 925 } 926 927 if ($this->return_to !== null) { 928 return Auth_OpenID_TrustRoot::match($this->trust_root, 929 $this->return_to); 930 } else { 931 return true; 932 } 933 } 934 935 /** 936 * Respond to this request. Return either an 937 * {@link Auth_OpenID_ServerResponse} or 938 * {@link Auth_OpenID_ServerError}. 939 * 940 * @param bool $allow Allow this user to claim this identity, and 941 * allow the consumer to have this information? 942 * 943 * @param string $server_url DEPRECATED. Passing $op_endpoint to 944 * the {@link Auth_OpenID_Server} constructor makes this optional. 945 * 946 * When an OpenID 1.x immediate mode request does not succeed, it 947 * gets back a URL where the request may be carried out in a 948 * not-so-immediate fashion. Pass my URL in here (the fully 949 * qualified address of this server's endpoint, i.e. 950 * http://example.com/server), and I will use it as a base for the 951 * URL for a new request. 952 * 953 * Optional for requests where {@link $immediate} is false or 954 * $allow is true. 955 * 956 * @param string $identity The OP-local identifier to answer with. 957 * Only for use when the relying party requested identifier 958 * selection. 959 * 960 * @param string $claimed_id The claimed identifier to answer 961 * with, for use with identifier selection in the case where the 962 * claimed identifier and the OP-local identifier differ, 963 * i.e. when the claimed_id uses delegation. 964 * 965 * If $identity is provided but this is not, $claimed_id will 966 * default to the value of $identity. When answering requests 967 * that did not ask for identifier selection, the response 968 * $claimed_id will default to that of the request. 969 * 970 * This parameter is new in OpenID 2.0. 971 * 972 * @return mixed 973 */ 974 function answer($allow, $server_url = null, $identity = null, 975 $claimed_id = null) 976 { 977 if (!$this->return_to) { 978 return new Auth_OpenID_NoReturnToError(); 979 } 980 981 if (!$server_url) { 982 if ((!$this->message->isOpenID1()) && 983 (!$this->server->op_endpoint)) { 984 return new Auth_OpenID_ServerError(null, 985 "server should be constructed with op_endpoint to " . 986 "respond to OpenID 2.0 messages."); 987 } 988 989 $server_url = $this->server->op_endpoint; 990 } 991 992 if ($allow) { 993 $mode = 'id_res'; 994 } else if ($this->message->isOpenID1()) { 995 if ($this->immediate) { 996 $mode = 'id_res'; 997 } else { 998 $mode = 'cancel'; 999 } 1000 } else { 1001 if ($this->immediate) { 1002 $mode = 'setup_needed'; 1003 } else { 1004 $mode = 'cancel'; 1005 } 1006 } 1007 1008 if (!$this->trustRootValid()) { 1009 return new Auth_OpenID_UntrustedReturnURL(null, 1010 $this->return_to, 1011 $this->trust_root); 1012 } 1013 1014 $response = new Auth_OpenID_ServerResponse($this); 1015 1016 if ($claimed_id && 1017 ($this->message->isOpenID1())) { 1018 return new Auth_OpenID_ServerError(null, 1019 "claimed_id is new in OpenID 2.0 and not " . 1020 "available for ".$this->namespace); 1021 } 1022 1023 if ($identity && !$claimed_id) { 1024 $claimed_id = $identity; 1025 } 1026 1027 if ($allow) { 1028 1029 if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) { 1030 if (!$identity) { 1031 return new Auth_OpenID_ServerError(null, 1032 "This request uses IdP-driven identifier selection. " . 1033 "You must supply an identifier in the response."); 1034 } 1035 1036 $response_identity = $identity; 1037 $response_claimed_id = $claimed_id; 1038 1039 } else if ($this->identity) { 1040 if ($identity && 1041 ($this->identity != $identity)) { 1042 $fmt = "Request was for %s, cannot reply with identity %s"; 1043 return new Auth_OpenID_ServerError(null, 1044 sprintf($fmt, $this->identity, $identity)); 1045 } 1046 1047 $response_identity = $this->identity; 1048 $response_claimed_id = $this->claimed_id; 1049 } else { 1050 if ($identity) { 1051 return new Auth_OpenID_ServerError(null, 1052 "This request specified no identity and " . 1053 "you supplied ".$identity); 1054 } 1055 1056 $response_identity = null; 1057 } 1058 1059 if (($this->message->isOpenID1()) && 1060 ($response_identity === null)) { 1061 return new Auth_OpenID_ServerError(null, 1062 "Request was an OpenID 1 request, so response must " . 1063 "include an identifier."); 1064 } 1065 1066 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 1067 array('mode' => $mode, 1068 'return_to' => $this->return_to, 1069 'response_nonce' => Auth_OpenID_mkNonce())); 1070 1071 if (!$this->message->isOpenID1()) { 1072 $response->fields->setArg(Auth_OpenID_OPENID_NS, 1073 'op_endpoint', $server_url); 1074 } 1075 1076 if ($response_identity !== null) { 1077 $response->fields->setArg( 1078 Auth_OpenID_OPENID_NS, 1079 'identity', 1080 $response_identity); 1081 if ($this->message->isOpenID2()) { 1082 $response->fields->setArg( 1083 Auth_OpenID_OPENID_NS, 1084 'claimed_id', 1085 $response_claimed_id); 1086 } 1087 } 1088 1089 } else { 1090 $response->fields->setArg(Auth_OpenID_OPENID_NS, 1091 'mode', $mode); 1092 1093 if ($this->immediate) { 1094 if (($this->message->isOpenID1()) && 1095 (!$server_url)) { 1096 return new Auth_OpenID_ServerError(null, 1097 'setup_url is required for $allow=false \ 1098 in OpenID 1.x immediate mode.'); 1099 } 1100 1101 $setup_request = new Auth_OpenID_CheckIDRequest( 1102 $this->identity, 1103 $this->return_to, 1104 $this->trust_root, 1105 false, 1106 $this->assoc_handle, 1107 $this->server, 1108 $this->claimed_id); 1109 $setup_request->message = $this->message; 1110 1111 $setup_url = $setup_request->encodeToURL($server_url); 1112 1113 if ($setup_url === null) { 1114 return new Auth_OpenID_NoReturnToError(); 1115 } 1116 1117 $response->fields->setArg(Auth_OpenID_OPENID_NS, 1118 'user_setup_url', 1119 $setup_url); 1120 } 1121 } 1122 1123 return $response; 1124 } 1125 1126 function encodeToURL($server_url) 1127 { 1128 if (!$this->return_to) { 1129 return new Auth_OpenID_NoReturnToError(); 1130 } 1131 1132 // Imported from the alternate reality where these classes are 1133 // used in both the client and server code, so Requests are 1134 // Encodable too. That's right, code imported from alternate 1135 // realities all for the love of you, id_res/user_setup_url. 1136 1137 $q = array('mode' => $this->mode, 1138 'identity' => $this->identity, 1139 'claimed_id' => $this->claimed_id, 1140 'return_to' => $this->return_to); 1141 1142 if ($this->trust_root) { 1143 if ($this->message->isOpenID1()) { 1144 $q['trust_root'] = $this->trust_root; 1145 } else { 1146 $q['realm'] = $this->trust_root; 1147 } 1148 } 1149 1150 if ($this->assoc_handle) { 1151 $q['assoc_handle'] = $this->assoc_handle; 1152 } 1153 1154 $response = new Auth_OpenID_Message( 1155 $this->message->getOpenIDNamespace()); 1156 $response->updateArgs(Auth_OpenID_OPENID_NS, $q); 1157 return $response->toURL($server_url); 1158 } 1159 1160 function getCancelURL() 1161 { 1162 if (!$this->return_to) { 1163 return new Auth_OpenID_NoReturnToError(); 1164 } 1165 1166 if ($this->immediate) { 1167 return new Auth_OpenID_ServerError(null, 1168 "Cancel is not an appropriate \ 1169 response to immediate mode \ 1170 requests."); 1171 } 1172 1173 $response = new Auth_OpenID_Message( 1174 $this->message->getOpenIDNamespace()); 1175 $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel'); 1176 return $response->toURL($this->return_to); 1177 } 1178} 1179 1180/** 1181 * This class encapsulates the response to an OpenID server request. 1182 * 1183 * @package OpenID 1184 */ 1185class Auth_OpenID_ServerResponse { 1186 1187 function Auth_OpenID_ServerResponse($request) 1188 { 1189 $this->request = $request; 1190 $this->fields = new Auth_OpenID_Message($this->request->namespace); 1191 } 1192 1193 function whichEncoding() 1194 { 1195 global $_Auth_OpenID_Request_Modes; 1196 1197 if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) { 1198 if ($this->fields->isOpenID2() && 1199 (strlen($this->encodeToURL()) > 1200 Auth_OpenID_OPENID1_URL_LIMIT)) { 1201 return Auth_OpenID_ENCODE_HTML_FORM; 1202 } else { 1203 return Auth_OpenID_ENCODE_URL; 1204 } 1205 } else { 1206 return Auth_OpenID_ENCODE_KVFORM; 1207 } 1208 } 1209 1210 /* 1211 * Returns the form markup for this response. 1212 * 1213 * @return str 1214 */ 1215 function toFormMarkup($form_tag_attrs=null) 1216 { 1217 return $this->fields->toFormMarkup($this->request->return_to, 1218 $form_tag_attrs); 1219 } 1220 1221 /* 1222 * Returns an HTML document containing the form markup for this 1223 * response that autosubmits with javascript. 1224 */ 1225 function toHTML() 1226 { 1227 return Auth_OpenID::autoSubmitHTML($this->toFormMarkup()); 1228 } 1229 1230 /* 1231 * Returns True if this response's encoding is ENCODE_HTML_FORM. 1232 * Convenience method for server authors. 1233 * 1234 * @return bool 1235 */ 1236 function renderAsForm() 1237 { 1238 return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM; 1239 } 1240 1241 1242 function encodeToURL() 1243 { 1244 return $this->fields->toURL($this->request->return_to); 1245 } 1246 1247 function addExtension($extension_response) 1248 { 1249 $extension_response->toMessage($this->fields); 1250 } 1251 1252 function needsSigning() 1253 { 1254 return $this->fields->getArg(Auth_OpenID_OPENID_NS, 1255 'mode') == 'id_res'; 1256 } 1257 1258 function encodeToKVForm() 1259 { 1260 return $this->fields->toKVForm(); 1261 } 1262} 1263 1264/** 1265 * A web-capable response object which you can use to generate a 1266 * user-agent response. 1267 * 1268 * @package OpenID 1269 */ 1270class Auth_OpenID_WebResponse { 1271 var $code = AUTH_OPENID_HTTP_OK; 1272 var $body = ""; 1273 1274 function Auth_OpenID_WebResponse($code = null, $headers = null, 1275 $body = null) 1276 { 1277 if ($code) { 1278 $this->code = $code; 1279 } 1280 1281 if ($headers !== null) { 1282 $this->headers = $headers; 1283 } else { 1284 $this->headers = array(); 1285 } 1286 1287 if ($body !== null) { 1288 $this->body = $body; 1289 } 1290 } 1291} 1292 1293/** 1294 * Responsible for the signature of query data and the verification of 1295 * OpenID signature values. 1296 * 1297 * @package OpenID 1298 */ 1299class Auth_OpenID_Signatory { 1300 1301 // = 14 * 24 * 60 * 60; # 14 days, in seconds 1302 var $SECRET_LIFETIME = 1209600; 1303 1304 // keys have a bogus server URL in them because the filestore 1305 // really does expect that key to be a URL. This seems a little 1306 // silly for the server store, since I expect there to be only one 1307 // server URL. 1308 var $normal_key = 'http://localhost/|normal'; 1309 var $dumb_key = 'http://localhost/|dumb'; 1310 1311 /** 1312 * Create a new signatory using a given store. 1313 */ 1314 function Auth_OpenID_Signatory($store) 1315 { 1316 // assert store is not None 1317 $this->store = $store; 1318 } 1319 1320 /** 1321 * Verify, using a given association handle, a signature with 1322 * signed key-value pairs from an HTTP request. 1323 */ 1324 function verify($assoc_handle, $message) 1325 { 1326 $assoc = $this->getAssociation($assoc_handle, true); 1327 if (!$assoc) { 1328 // oidutil.log("failed to get assoc with handle %r to verify sig %r" 1329 // % (assoc_handle, sig)) 1330 return false; 1331 } 1332 1333 return $assoc->checkMessageSignature($message); 1334 } 1335 1336 /** 1337 * Given a response, sign the fields in the response's 'signed' 1338 * list, and insert the signature into the response. 1339 */ 1340 function sign($response) 1341 { 1342 $signed_response = $response; 1343 $assoc_handle = $response->request->assoc_handle; 1344 1345 if ($assoc_handle) { 1346 // normal mode 1347 $assoc = $this->getAssociation($assoc_handle, false, false); 1348 if (!$assoc || ($assoc->getExpiresIn() <= 0)) { 1349 // fall back to dumb mode 1350 $signed_response->fields->setArg(Auth_OpenID_OPENID_NS, 1351 'invalidate_handle', $assoc_handle); 1352 $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1'); 1353 1354 if ($assoc && ($assoc->getExpiresIn() <= 0)) { 1355 $this->invalidate($assoc_handle, false); 1356 } 1357 1358 $assoc = $this->createAssociation(true, $assoc_type); 1359 } 1360 } else { 1361 // dumb mode. 1362 $assoc = $this->createAssociation(true); 1363 } 1364 1365 $signed_response->fields = $assoc->signMessage( 1366 $signed_response->fields); 1367 return $signed_response; 1368 } 1369 1370 /** 1371 * Make a new association. 1372 */ 1373 function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1') 1374 { 1375 $secret = Auth_OpenID_CryptUtil::getBytes( 1376 Auth_OpenID_getSecretSize($assoc_type)); 1377 1378 $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4)); 1379 $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq); 1380 1381 $assoc = Auth_OpenID_Association::fromExpiresIn( 1382 $this->SECRET_LIFETIME, $handle, $secret, $assoc_type); 1383 1384 if ($dumb) { 1385 $key = $this->dumb_key; 1386 } else { 1387 $key = $this->normal_key; 1388 } 1389 1390 $this->store->storeAssociation($key, $assoc); 1391 return $assoc; 1392 } 1393 1394 /** 1395 * Given an association handle, get the association from the 1396 * store, or return a ServerError or null if something goes wrong. 1397 */ 1398 function getAssociation($assoc_handle, $dumb, $check_expiration=true) 1399 { 1400 if ($assoc_handle === null) { 1401 return new Auth_OpenID_ServerError(null, 1402 "assoc_handle must not be null"); 1403 } 1404 1405 if ($dumb) { 1406 $key = $this->dumb_key; 1407 } else { 1408 $key = $this->normal_key; 1409 } 1410 1411 $assoc = $this->store->getAssociation($key, $assoc_handle); 1412 1413 if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) { 1414 if ($check_expiration) { 1415 $this->store->removeAssociation($key, $assoc_handle); 1416 $assoc = null; 1417 } 1418 } 1419 1420 return $assoc; 1421 } 1422 1423 /** 1424 * Invalidate a given association handle. 1425 */ 1426 function invalidate($assoc_handle, $dumb) 1427 { 1428 if ($dumb) { 1429 $key = $this->dumb_key; 1430 } else { 1431 $key = $this->normal_key; 1432 } 1433 $this->store->removeAssociation($key, $assoc_handle); 1434 } 1435} 1436 1437/** 1438 * Encode an {@link Auth_OpenID_ServerResponse} to an 1439 * {@link Auth_OpenID_WebResponse}. 1440 * 1441 * @package OpenID 1442 */ 1443class Auth_OpenID_Encoder { 1444 1445 var $responseFactory = 'Auth_OpenID_WebResponse'; 1446 1447 /** 1448 * Encode an {@link Auth_OpenID_ServerResponse} and return an 1449 * {@link Auth_OpenID_WebResponse}. 1450 */ 1451 function encode($response) 1452 { 1453 $cls = $this->responseFactory; 1454 1455 $encode_as = $response->whichEncoding(); 1456 if ($encode_as == Auth_OpenID_ENCODE_KVFORM) { 1457 $wr = new $cls(null, null, $response->encodeToKVForm()); 1458 if (is_a($response, 'Auth_OpenID_ServerError')) { 1459 $wr->code = AUTH_OPENID_HTTP_ERROR; 1460 } 1461 } else if ($encode_as == Auth_OpenID_ENCODE_URL) { 1462 $location = $response->encodeToURL(); 1463 $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT, 1464 array('location' => $location)); 1465 } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) { 1466 $wr = new $cls(AUTH_OPENID_HTTP_OK, array(), 1467 $response->toHTML()); 1468 } else { 1469 return new Auth_OpenID_EncodingError($response); 1470 } 1471 /* Allow the response to carry a custom error code (ex: for Association errors) */ 1472 if(isset($response->code)) { 1473 $wr->code = $response->code; 1474 } 1475 return $wr; 1476 } 1477} 1478 1479/** 1480 * An encoder which also takes care of signing fields when required. 1481 * 1482 * @package OpenID 1483 */ 1484class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder { 1485 1486 function Auth_OpenID_SigningEncoder($signatory) 1487 { 1488 $this->signatory = $signatory; 1489 } 1490 1491 /** 1492 * Sign an {@link Auth_OpenID_ServerResponse} and return an 1493 * {@link Auth_OpenID_WebResponse}. 1494 */ 1495 function encode($response) 1496 { 1497 // the isinstance is a bit of a kludge... it means there isn't 1498 // really an adapter to make the interfaces quite match. 1499 if (!is_a($response, 'Auth_OpenID_ServerError') && 1500 $response->needsSigning()) { 1501 1502 if (!$this->signatory) { 1503 return new Auth_OpenID_ServerError(null, 1504 "Must have a store to sign request"); 1505 } 1506 1507 if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) { 1508 return new Auth_OpenID_AlreadySigned($response); 1509 } 1510 $response = $this->signatory->sign($response); 1511 } 1512 1513 return parent::encode($response); 1514 } 1515} 1516 1517/** 1518 * Decode an incoming query into an Auth_OpenID_Request. 1519 * 1520 * @package OpenID 1521 */ 1522class Auth_OpenID_Decoder { 1523 1524 function Auth_OpenID_Decoder($server) 1525 { 1526 $this->server = $server; 1527 1528 $this->handlers = array( 1529 'checkid_setup' => 'Auth_OpenID_CheckIDRequest', 1530 'checkid_immediate' => 'Auth_OpenID_CheckIDRequest', 1531 'check_authentication' => 'Auth_OpenID_CheckAuthRequest', 1532 'associate' => 'Auth_OpenID_AssociateRequest' 1533 ); 1534 } 1535 1536 /** 1537 * Given an HTTP query in an array (key-value pairs), decode it 1538 * into an Auth_OpenID_Request object. 1539 */ 1540 function decode($query) 1541 { 1542 if (!$query) { 1543 return null; 1544 } 1545 1546 $message = Auth_OpenID_Message::fromPostArgs($query); 1547 1548 if ($message === null) { 1549 /* 1550 * It's useful to have a Message attached to a 1551 * ProtocolError, so we override the bad ns value to build 1552 * a Message out of it. Kinda kludgy, since it's made of 1553 * lies, but the parts that aren't lies are more useful 1554 * than a 'None'. 1555 */ 1556 $old_ns = $query['openid.ns']; 1557 1558 $query['openid.ns'] = Auth_OpenID_OPENID2_NS; 1559 $message = Auth_OpenID_Message::fromPostArgs($query); 1560 return new Auth_OpenID_ServerError( 1561 $message, 1562 sprintf("Invalid OpenID namespace URI: %s", $old_ns)); 1563 } 1564 1565 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 1566 if (!$mode) { 1567 return new Auth_OpenID_ServerError($message, 1568 "No mode value in message"); 1569 } 1570 1571 if (Auth_OpenID::isFailure($mode)) { 1572 return new Auth_OpenID_ServerError($message, 1573 $mode->message); 1574 } 1575 1576 $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode, 1577 $this->defaultDecoder($message)); 1578 1579 if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) { 1580 return call_user_func_array(array($handlerCls, 'fromMessage'), 1581 array($message, $this->server)); 1582 } else { 1583 return $handlerCls; 1584 } 1585 } 1586 1587 function defaultDecoder($message) 1588 { 1589 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 1590 1591 if (Auth_OpenID::isFailure($mode)) { 1592 return new Auth_OpenID_ServerError($message, 1593 $mode->message); 1594 } 1595 1596 return new Auth_OpenID_ServerError($message, 1597 sprintf("Unrecognized OpenID mode %s", $mode)); 1598 } 1599} 1600 1601/** 1602 * An error that indicates an encoding problem occurred. 1603 * 1604 * @package OpenID 1605 */ 1606class Auth_OpenID_EncodingError { 1607 function Auth_OpenID_EncodingError($response) 1608 { 1609 $this->response = $response; 1610 } 1611} 1612 1613/** 1614 * An error that indicates that a response was already signed. 1615 * 1616 * @package OpenID 1617 */ 1618class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError { 1619 // This response is already signed. 1620} 1621 1622/** 1623 * An error that indicates that the given return_to is not under the 1624 * given trust_root. 1625 * 1626 * @package OpenID 1627 */ 1628class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError { 1629 function Auth_OpenID_UntrustedReturnURL($message, $return_to, 1630 $trust_root) 1631 { 1632 parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL"); 1633 $this->return_to = $return_to; 1634 $this->trust_root = $trust_root; 1635 } 1636 1637 function toString() 1638 { 1639 return sprintf("return_to %s not under trust_root %s", 1640 $this->return_to, $this->trust_root); 1641 } 1642} 1643 1644/** 1645 * I handle requests for an OpenID server. 1646 * 1647 * Some types of requests (those which are not checkid requests) may 1648 * be handed to my {@link handleRequest} method, and I will take care 1649 * of it and return a response. 1650 * 1651 * For your convenience, I also provide an interface to {@link 1652 * Auth_OpenID_Decoder::decode()} and {@link 1653 * Auth_OpenID_SigningEncoder::encode()} through my methods {@link 1654 * decodeRequest} and {@link encodeResponse}. 1655 * 1656 * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}. 1657 * 1658 * Example: 1659 * 1660 * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path), 1661 * "http://example.com/op"); 1662 * $request = $oserver->decodeRequest(); 1663 * if (in_array($request->mode, array('checkid_immediate', 1664 * 'checkid_setup'))) { 1665 * if ($app->isAuthorized($request->identity, $request->trust_root)) { 1666 * $response = $request->answer(true); 1667 * } else if ($request->immediate) { 1668 * $response = $request->answer(false); 1669 * } else { 1670 * $app->showDecidePage($request); 1671 * return; 1672 * } 1673 * } else { 1674 * $response = $oserver->handleRequest($request); 1675 * } 1676 * 1677 * $webresponse = $oserver->encode($response);</pre> 1678 * 1679 * @package OpenID 1680 */ 1681class Auth_OpenID_Server { 1682 function Auth_OpenID_Server($store, $op_endpoint=null) 1683 { 1684 $this->store = $store; 1685 $this->signatory = new Auth_OpenID_Signatory($this->store); 1686 $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory); 1687 $this->decoder = new Auth_OpenID_Decoder($this); 1688 $this->op_endpoint = $op_endpoint; 1689 $this->negotiator = Auth_OpenID_getDefaultNegotiator(); 1690 } 1691 1692 /** 1693 * Handle a request. Given an {@link Auth_OpenID_Request} object, 1694 * call the appropriate {@link Auth_OpenID_Server} method to 1695 * process the request and generate a response. 1696 * 1697 * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request} 1698 * returned by {@link Auth_OpenID_Server::decodeRequest()}. 1699 * 1700 * @return Auth_OpenID_ServerResponse $response A response object 1701 * capable of generating a user-agent reply. 1702 */ 1703 function handleRequest($request) 1704 { 1705 if (method_exists($this, "openid_" . $request->mode)) { 1706 $handler = array($this, "openid_" . $request->mode); 1707 return call_user_func($handler, &$request); 1708 } 1709 return null; 1710 } 1711 1712 /** 1713 * The callback for 'check_authentication' messages. 1714 */ 1715 function openid_check_authentication($request) 1716 { 1717 return $request->answer($this->signatory); 1718 } 1719 1720 /** 1721 * The callback for 'associate' messages. 1722 */ 1723 function openid_associate($request) 1724 { 1725 $assoc_type = $request->assoc_type; 1726 $session_type = $request->session->session_type; 1727 if ($this->negotiator->isAllowed($assoc_type, $session_type)) { 1728 $assoc = $this->signatory->createAssociation(false, 1729 $assoc_type); 1730 return $request->answer($assoc); 1731 } else { 1732 $message = sprintf('Association type %s is not supported with '. 1733 'session type %s', $assoc_type, $session_type); 1734 list($preferred_assoc_type, $preferred_session_type) = 1735 $this->negotiator->getAllowedType(); 1736 return $request->answerUnsupported($message, 1737 $preferred_assoc_type, 1738 $preferred_session_type); 1739 } 1740 } 1741 1742 /** 1743 * Encodes as response in the appropriate format suitable for 1744 * sending to the user agent. 1745 */ 1746 function encodeResponse($response) 1747 { 1748 return $this->encoder->encode($response); 1749 } 1750 1751 /** 1752 * Decodes a query args array into the appropriate 1753 * {@link Auth_OpenID_Request} object. 1754 */ 1755 function decodeRequest($query=null) 1756 { 1757 if ($query === null) { 1758 $query = Auth_OpenID::getQuery(); 1759 } 1760 1761 return $this->decoder->decode($query); 1762 } 1763} 1764 1765 1766