|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /** 00004 * OpenID server protocol and logic. 00005 * 00006 * Overview 00007 * 00008 * An OpenID server must perform three tasks: 00009 * 00010 * 1. Examine the incoming request to determine its nature and validity. 00011 * 2. Make a decision about how to respond to this request. 00012 * 3. Format the response according to the protocol. 00013 * 00014 * The first and last of these tasks may performed by the {@link 00015 * Auth_OpenID_Server::decodeRequest()} and {@link 00016 * Auth_OpenID_Server::encodeResponse} methods. Who gets to do the 00017 * intermediate task -- deciding how to respond to the request -- will 00018 * depend on what type of request it is. 00019 * 00020 * If it's a request to authenticate a user (a 'checkid_setup' or 00021 * 'checkid_immediate' request), you need to decide if you will assert 00022 * that this user may claim the identity in question. Exactly how you 00023 * do that is a matter of application policy, but it generally 00024 * involves making sure the user has an account with your system and 00025 * is logged in, checking to see if that identity is hers to claim, 00026 * and verifying with the user that she does consent to releasing that 00027 * information to the party making the request. 00028 * 00029 * Examine the properties of the {@link Auth_OpenID_CheckIDRequest} 00030 * object, and if and when you've come to a decision, form a response 00031 * by calling {@link Auth_OpenID_CheckIDRequest::answer()}. 00032 * 00033 * Other types of requests relate to establishing associations between 00034 * client and server and verifing the authenticity of previous 00035 * communications. {@link Auth_OpenID_Server} contains all the logic 00036 * and data necessary to respond to such requests; just pass it to 00037 * {@link Auth_OpenID_Server::handleRequest()}. 00038 * 00039 * OpenID Extensions 00040 * 00041 * Do you want to provide other information for your users in addition 00042 * to authentication? Version 1.2 of the OpenID protocol allows 00043 * consumers to add extensions to their requests. For example, with 00044 * sites using the Simple Registration 00045 * Extension 00046 * (http://www.openidenabled.com/openid/simple-registration-extension/), 00047 * a user can agree to have their nickname and e-mail address sent to 00048 * a site when they sign up. 00049 * 00050 * Since extensions do not change the way OpenID authentication works, 00051 * code to handle extension requests may be completely separate from 00052 * the {@link Auth_OpenID_Request} class here. But you'll likely want 00053 * data sent back by your extension to be signed. {@link 00054 * Auth_OpenID_ServerResponse} provides methods with which you can add 00055 * data to it which can be signed with the other data in the OpenID 00056 * signature. 00057 * 00058 * For example: 00059 * 00060 * <pre> // when request is a checkid_* request 00061 * $response = $request->answer(true); 00062 * // this will a signed 'openid.sreg.timezone' parameter to the response 00063 * response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre> 00064 * 00065 * Stores 00066 * 00067 * The OpenID server needs to maintain state between requests in order 00068 * to function. Its mechanism for doing this is called a store. The 00069 * store interface is defined in Interface.php. Additionally, several 00070 * concrete store implementations are provided, so that most sites 00071 * won't need to implement a custom store. For a store backed by flat 00072 * files on disk, see {@link Auth_OpenID_FileStore}. For stores based 00073 * on MySQL, SQLite, or PostgreSQL, see the {@link 00074 * Auth_OpenID_SQLStore} subclasses. 00075 * 00076 * Upgrading 00077 * 00078 * The keys by which a server looks up associations in its store have 00079 * changed in version 1.2 of this library. If your store has entries 00080 * created from version 1.0 code, you should empty it. 00081 * 00082 * PHP versions 4 and 5 00083 * 00084 * LICENSE: See the COPYING file included in this distribution. 00085 * 00086 * @package OpenID 00087 * @author JanRain, Inc. <openid@janrain.com> 00088 * @copyright 2005-2008 Janrain, Inc. 00089 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 00090 */ 00091 00092 /** 00093 * Required imports 00094 */ 00095 require_once "Auth/OpenID.php"; 00096 require_once "Auth/OpenID/Association.php"; 00097 require_once "Auth/OpenID/CryptUtil.php"; 00098 require_once "Auth/OpenID/BigMath.php"; 00099 require_once "Auth/OpenID/DiffieHellman.php"; 00100 require_once "Auth/OpenID/KVForm.php"; 00101 require_once "Auth/OpenID/TrustRoot.php"; 00102 require_once "Auth/OpenID/ServerRequest.php"; 00103 require_once "Auth/OpenID/Message.php"; 00104 require_once "Auth/OpenID/Nonce.php"; 00105 00106 define('AUTH_OPENID_HTTP_OK', 200); 00107 define('AUTH_OPENID_HTTP_REDIRECT', 302); 00108 define('AUTH_OPENID_HTTP_ERROR', 400); 00109 00110 /** 00111 * @access private 00112 */ 00113 global $_Auth_OpenID_Request_Modes; 00114 $_Auth_OpenID_Request_Modes = array('checkid_setup', 00115 'checkid_immediate'); 00116 00117 /** 00118 * @access private 00119 */ 00120 define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm'); 00121 00122 /** 00123 * @access private 00124 */ 00125 define('Auth_OpenID_ENCODE_URL', 'URL/redirect'); 00126 00127 /** 00128 * @access private 00129 */ 00130 define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form'); 00131 00132 /** 00133 * @access private 00134 */ 00135 function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError') 00136 { 00137 return is_a($obj, $cls); 00138 } 00139 00140 /** 00141 * An error class which gets instantiated and returned whenever an 00142 * OpenID protocol error occurs. Be prepared to use this in place of 00143 * an ordinary server response. 00144 * 00145 * @package OpenID 00146 */ 00147 class Auth_OpenID_ServerError { 00148 /** 00149 * @access private 00150 */ 00151 function Auth_OpenID_ServerError($message = null, $text = null, 00152 $reference = null, $contact = null) 00153 { 00154 $this->message = $message; 00155 $this->text = $text; 00156 $this->contact = $contact; 00157 $this->reference = $reference; 00158 } 00159 00160 function getReturnTo() 00161 { 00162 if ($this->message && 00163 $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) { 00164 return $this->message->getArg(Auth_OpenID_OPENID_NS, 00165 'return_to'); 00166 } else { 00167 return null; 00168 } 00169 } 00170 00171 /** 00172 * Returns the return_to URL for the request which caused this 00173 * error. 00174 */ 00175 function hasReturnTo() 00176 { 00177 return $this->getReturnTo() !== null; 00178 } 00179 00180 /** 00181 * Encodes this error's response as a URL suitable for 00182 * redirection. If the response has no return_to, another 00183 * Auth_OpenID_ServerError is returned. 00184 */ 00185 function encodeToURL() 00186 { 00187 if (!$this->message) { 00188 return null; 00189 } 00190 00191 $msg = $this->toMessage(); 00192 return $msg->toURL($this->getReturnTo()); 00193 } 00194 00195 /** 00196 * Encodes the response to key-value form. This is a 00197 * machine-readable format used to respond to messages which came 00198 * directly from the consumer and not through the user-agent. See 00199 * the OpenID specification. 00200 */ 00201 function encodeToKVForm() 00202 { 00203 return Auth_OpenID_KVForm::fromArray( 00204 array('mode' => 'error', 00205 'error' => $this->toString())); 00206 } 00207 00208 function toFormMarkup($form_tag_attrs=null) 00209 { 00210 $msg = $this->toMessage(); 00211 return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs); 00212 } 00213 00214 function toHTML($form_tag_attrs=null) 00215 { 00216 return Auth_OpenID::autoSubmitHTML( 00217 $this->toFormMarkup($form_tag_attrs)); 00218 } 00219 00220 function toMessage() 00221 { 00222 // Generate a Message object for sending to the relying party, 00223 // after encoding. 00224 $namespace = $this->message->getOpenIDNamespace(); 00225 $reply = new Auth_OpenID_Message($namespace); 00226 $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error'); 00227 $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString()); 00228 00229 if ($this->contact !== null) { 00230 $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact); 00231 } 00232 00233 if ($this->reference !== null) { 00234 $reply->setArg(Auth_OpenID_OPENID_NS, 'reference', 00235 $this->reference); 00236 } 00237 00238 return $reply; 00239 } 00240 00241 /** 00242 * Returns one of Auth_OpenID_ENCODE_URL, 00243 * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of 00244 * encoding expected for this error's payload. 00245 */ 00246 function whichEncoding() 00247 { 00248 global $_Auth_OpenID_Request_Modes; 00249 00250 if ($this->hasReturnTo()) { 00251 if ($this->message->isOpenID2() && 00252 (strlen($this->encodeToURL()) > 00253 Auth_OpenID_OPENID1_URL_LIMIT)) { 00254 return Auth_OpenID_ENCODE_HTML_FORM; 00255 } else { 00256 return Auth_OpenID_ENCODE_URL; 00257 } 00258 } 00259 00260 if (!$this->message) { 00261 return null; 00262 } 00263 00264 $mode = $this->message->getArg(Auth_OpenID_OPENID_NS, 00265 'mode'); 00266 00267 if ($mode) { 00268 if (!in_array($mode, $_Auth_OpenID_Request_Modes)) { 00269 return Auth_OpenID_ENCODE_KVFORM; 00270 } 00271 } 00272 return null; 00273 } 00274 00275 /** 00276 * Returns this error message. 00277 */ 00278 function toString() 00279 { 00280 if ($this->text) { 00281 return $this->text; 00282 } else { 00283 return get_class($this) . " error"; 00284 } 00285 } 00286 } 00287 00288 /** 00289 * Error returned by the server code when a return_to is absent from a 00290 * request. 00291 * 00292 * @package OpenID 00293 */ 00294 class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError { 00295 function Auth_OpenID_NoReturnToError($message = null, 00296 $text = "No return_to URL available") 00297 { 00298 parent::Auth_OpenID_ServerError($message, $text); 00299 } 00300 00301 function toString() 00302 { 00303 return "No return_to available"; 00304 } 00305 } 00306 00307 /** 00308 * An error indicating that the return_to URL is malformed. 00309 * 00310 * @package OpenID 00311 */ 00312 class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError { 00313 function Auth_OpenID_MalformedReturnURL($message, $return_to) 00314 { 00315 $this->return_to = $return_to; 00316 parent::Auth_OpenID_ServerError($message, "malformed return_to URL"); 00317 } 00318 } 00319 00320 /** 00321 * This error is returned when the trust_root value is malformed. 00322 * 00323 * @package OpenID 00324 */ 00325 class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError { 00326 function Auth_OpenID_MalformedTrustRoot($message = null, 00327 $text = "Malformed trust root") 00328 { 00329 parent::Auth_OpenID_ServerError($message, $text); 00330 } 00331 00332 function toString() 00333 { 00334 return "Malformed trust root"; 00335 } 00336 } 00337 00338 /** 00339 * The base class for all server request classes. 00340 * 00341 * @package OpenID 00342 */ 00343 class Auth_OpenID_Request { 00344 var $mode = null; 00345 } 00346 00347 /** 00348 * A request to verify the validity of a previous response. 00349 * 00350 * @package OpenID 00351 */ 00352 class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request { 00353 var $mode = "check_authentication"; 00354 var $invalidate_handle = null; 00355 00356 function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed, 00357 $invalidate_handle = null) 00358 { 00359 $this->assoc_handle = $assoc_handle; 00360 $this->signed = $signed; 00361 if ($invalidate_handle !== null) { 00362 $this->invalidate_handle = $invalidate_handle; 00363 } 00364 $this->namespace = Auth_OpenID_OPENID2_NS; 00365 $this->message = null; 00366 } 00367 00368 function fromMessage($message, $server=null) 00369 { 00370 $required_keys = array('assoc_handle', 'sig', 'signed'); 00371 00372 foreach ($required_keys as $k) { 00373 if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) { 00374 return new Auth_OpenID_ServerError($message, 00375 sprintf("%s request missing required parameter %s from \ 00376 query", "check_authentication", $k)); 00377 } 00378 } 00379 00380 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle'); 00381 $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig'); 00382 00383 $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed'); 00384 $signed_list = explode(",", $signed_list); 00385 00386 $signed = $message; 00387 if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) { 00388 $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res'); 00389 } 00390 00391 $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed); 00392 $result->message = $message; 00393 $result->sig = $sig; 00394 $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS, 00395 'invalidate_handle'); 00396 return $result; 00397 } 00398 00399 function answer(&$signatory) 00400 { 00401 $is_valid = $signatory->verify($this->assoc_handle, $this->signed); 00402 00403 // Now invalidate that assoc_handle so it this checkAuth 00404 // message cannot be replayed. 00405 $signatory->invalidate($this->assoc_handle, true); 00406 $response = new Auth_OpenID_ServerResponse($this); 00407 00408 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00409 'is_valid', 00410 ($is_valid ? "true" : "false")); 00411 00412 if ($this->invalidate_handle) { 00413 $assoc = $signatory->getAssociation($this->invalidate_handle, 00414 false); 00415 if (!$assoc) { 00416 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00417 'invalidate_handle', 00418 $this->invalidate_handle); 00419 } 00420 } 00421 return $response; 00422 } 00423 } 00424 00425 /** 00426 * A class implementing plaintext server sessions. 00427 * 00428 * @package OpenID 00429 */ 00430 class Auth_OpenID_PlainTextServerSession { 00431 /** 00432 * An object that knows how to handle association requests with no 00433 * session type. 00434 */ 00435 var $session_type = 'no-encryption'; 00436 var $needs_math = false; 00437 var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256'); 00438 00439 function fromMessage($unused_request) 00440 { 00441 return new Auth_OpenID_PlainTextServerSession(); 00442 } 00443 00444 function answer($secret) 00445 { 00446 return array('mac_key' => base64_encode($secret)); 00447 } 00448 } 00449 00450 /** 00451 * A class implementing DH-SHA1 server sessions. 00452 * 00453 * @package OpenID 00454 */ 00455 class Auth_OpenID_DiffieHellmanSHA1ServerSession { 00456 /** 00457 * An object that knows how to handle association requests with 00458 * the Diffie-Hellman session type. 00459 */ 00460 00461 var $session_type = 'DH-SHA1'; 00462 var $needs_math = true; 00463 var $allowed_assoc_types = array('HMAC-SHA1'); 00464 var $hash_func = 'Auth_OpenID_SHA1'; 00465 00466 function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey) 00467 { 00468 $this->dh = $dh; 00469 $this->consumer_pubkey = $consumer_pubkey; 00470 } 00471 00472 function getDH($message) 00473 { 00474 $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus'); 00475 $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen'); 00476 00477 if ((($dh_modulus === null) && ($dh_gen !== null)) || 00478 (($dh_gen === null) && ($dh_modulus !== null))) { 00479 00480 if ($dh_modulus === null) { 00481 $missing = 'modulus'; 00482 } else { 00483 $missing = 'generator'; 00484 } 00485 00486 return new Auth_OpenID_ServerError($message, 00487 'If non-default modulus or generator is '. 00488 'supplied, both must be supplied. Missing '. 00489 $missing); 00490 } 00491 00492 $lib =& Auth_OpenID_getMathLib(); 00493 00494 if ($dh_modulus || $dh_gen) { 00495 $dh_modulus = $lib->base64ToLong($dh_modulus); 00496 $dh_gen = $lib->base64ToLong($dh_gen); 00497 if ($lib->cmp($dh_modulus, 0) == 0 || 00498 $lib->cmp($dh_gen, 0) == 0) { 00499 return new Auth_OpenID_ServerError( 00500 $message, "Failed to parse dh_mod or dh_gen"); 00501 } 00502 $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen); 00503 } else { 00504 $dh = new Auth_OpenID_DiffieHellman(); 00505 } 00506 00507 $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS, 00508 'dh_consumer_public'); 00509 if ($consumer_pubkey === null) { 00510 return new Auth_OpenID_ServerError($message, 00511 'Public key for DH-SHA1 session '. 00512 'not found in query'); 00513 } 00514 00515 $consumer_pubkey = 00516 $lib->base64ToLong($consumer_pubkey); 00517 00518 if ($consumer_pubkey === false) { 00519 return new Auth_OpenID_ServerError($message, 00520 "dh_consumer_public is not base64"); 00521 } 00522 00523 return array($dh, $consumer_pubkey); 00524 } 00525 00526 function fromMessage($message) 00527 { 00528 $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message); 00529 00530 if (is_a($result, 'Auth_OpenID_ServerError')) { 00531 return $result; 00532 } else { 00533 list($dh, $consumer_pubkey) = $result; 00534 return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, 00535 $consumer_pubkey); 00536 } 00537 } 00538 00539 function answer($secret) 00540 { 00541 $lib =& Auth_OpenID_getMathLib(); 00542 $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret, 00543 $this->hash_func); 00544 return array( 00545 'dh_server_public' => 00546 $lib->longToBase64($this->dh->public), 00547 'enc_mac_key' => base64_encode($mac_key)); 00548 } 00549 } 00550 00551 /** 00552 * A class implementing DH-SHA256 server sessions. 00553 * 00554 * @package OpenID 00555 */ 00556 class Auth_OpenID_DiffieHellmanSHA256ServerSession 00557 extends Auth_OpenID_DiffieHellmanSHA1ServerSession { 00558 00559 var $session_type = 'DH-SHA256'; 00560 var $hash_func = 'Auth_OpenID_SHA256'; 00561 var $allowed_assoc_types = array('HMAC-SHA256'); 00562 00563 function fromMessage($message) 00564 { 00565 $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message); 00566 00567 if (is_a($result, 'Auth_OpenID_ServerError')) { 00568 return $result; 00569 } else { 00570 list($dh, $consumer_pubkey) = $result; 00571 return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh, 00572 $consumer_pubkey); 00573 } 00574 } 00575 } 00576 00577 /** 00578 * A request to associate with the server. 00579 * 00580 * @package OpenID 00581 */ 00582 class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request { 00583 var $mode = "associate"; 00584 00585 function getSessionClasses() 00586 { 00587 return array( 00588 'no-encryption' => 'Auth_OpenID_PlainTextServerSession', 00589 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession', 00590 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession'); 00591 } 00592 00593 function Auth_OpenID_AssociateRequest(&$session, $assoc_type) 00594 { 00595 $this->session =& $session; 00596 $this->namespace = Auth_OpenID_OPENID2_NS; 00597 $this->assoc_type = $assoc_type; 00598 } 00599 00600 function fromMessage($message, $server=null) 00601 { 00602 if ($message->isOpenID1()) { 00603 $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 00604 'session_type'); 00605 00606 if ($session_type == 'no-encryption') { 00607 // oidutil.log('Received OpenID 1 request with a no-encryption ' 00608 // 'assocaition session type. Continuing anyway.') 00609 } else if (!$session_type) { 00610 $session_type = 'no-encryption'; 00611 } 00612 } else { 00613 $session_type = $message->getArg(Auth_OpenID_OPENID_NS, 00614 'session_type'); 00615 if ($session_type === null) { 00616 return new Auth_OpenID_ServerError($message, 00617 "session_type missing from request"); 00618 } 00619 } 00620 00621 $session_class = Auth_OpenID::arrayGet( 00622 Auth_OpenID_AssociateRequest::getSessionClasses(), 00623 $session_type); 00624 00625 if ($session_class === null) { 00626 return new Auth_OpenID_ServerError($message, 00627 "Unknown session type " . 00628 $session_type); 00629 } 00630 00631 $session = call_user_func(array($session_class, 'fromMessage'), 00632 $message); 00633 if (is_a($session, 'Auth_OpenID_ServerError')) { 00634 return $session; 00635 } 00636 00637 $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS, 00638 'assoc_type', 'HMAC-SHA1'); 00639 00640 if (!in_array($assoc_type, $session->allowed_assoc_types)) { 00641 $fmt = "Session type %s does not support association type %s"; 00642 return new Auth_OpenID_ServerError($message, 00643 sprintf($fmt, $session_type, $assoc_type)); 00644 } 00645 00646 $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type); 00647 $obj->message = $message; 00648 $obj->namespace = $message->getOpenIDNamespace(); 00649 return $obj; 00650 } 00651 00652 function answer($assoc) 00653 { 00654 $response = new Auth_OpenID_ServerResponse($this); 00655 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 00656 array( 00657 'expires_in' => sprintf('%d', $assoc->getExpiresIn()), 00658 'assoc_type' => $this->assoc_type, 00659 'assoc_handle' => $assoc->handle)); 00660 00661 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 00662 $this->session->answer($assoc->secret)); 00663 00664 if (! ($this->session->session_type == 'no-encryption' 00665 && $this->message->isOpenID1())) { 00666 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00667 'session_type', 00668 $this->session->session_type); 00669 } 00670 00671 return $response; 00672 } 00673 00674 function answerUnsupported($text_message, 00675 $preferred_association_type=null, 00676 $preferred_session_type=null) 00677 { 00678 if ($this->message->isOpenID1()) { 00679 return new Auth_OpenID_ServerError($this->message); 00680 } 00681 00682 $response = new Auth_OpenID_ServerResponse($this); 00683 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00684 'error_code', 'unsupported-type'); 00685 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00686 'error', $text_message); 00687 00688 if ($preferred_association_type) { 00689 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00690 'assoc_type', 00691 $preferred_association_type); 00692 } 00693 00694 if ($preferred_session_type) { 00695 $response->fields->setArg(Auth_OpenID_OPENID_NS, 00696 'session_type', 00697 $preferred_session_type); 00698 } 00699 00700 return $response; 00701 } 00702 } 00703 00704 /** 00705 * A request to confirm the identity of a user. 00706 * 00707 * @package OpenID 00708 */ 00709 class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request { 00710 /** 00711 * Return-to verification callback. Default is 00712 * Auth_OpenID_verifyReturnTo from TrustRoot.php. 00713 */ 00714 var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo'; 00715 00716 /** 00717 * The mode of this request. 00718 */ 00719 var $mode = "checkid_setup"; // or "checkid_immediate" 00720 00721 /** 00722 * Whether this request is for immediate mode. 00723 */ 00724 var $immediate = false; 00725 00726 /** 00727 * The trust_root value for this request. 00728 */ 00729 var $trust_root = null; 00730 00731 /** 00732 * The OpenID namespace for this request. 00733 * deprecated since version 2.0.2 00734 */ 00735 var $namespace; 00736 00737 function make(&$message, $identity, $return_to, $trust_root = null, 00738 $immediate = false, $assoc_handle = null, $server = null) 00739 { 00740 if ($server === null) { 00741 return new Auth_OpenID_ServerError($message, 00742 "server must not be null"); 00743 } 00744 00745 if ($return_to && 00746 !Auth_OpenID_TrustRoot::_parse($return_to)) { 00747 return new Auth_OpenID_MalformedReturnURL($message, $return_to); 00748 } 00749 00750 $r = new Auth_OpenID_CheckIDRequest($identity, $return_to, 00751 $trust_root, $immediate, 00752 $assoc_handle, $server); 00753 00754 $r->namespace = $message->getOpenIDNamespace(); 00755 $r->message =& $message; 00756 00757 if (!$r->trustRootValid()) { 00758 return new Auth_OpenID_UntrustedReturnURL($message, 00759 $return_to, 00760 $trust_root); 00761 } else { 00762 return $r; 00763 } 00764 } 00765 00766 function Auth_OpenID_CheckIDRequest($identity, $return_to, 00767 $trust_root = null, $immediate = false, 00768 $assoc_handle = null, $server = null, 00769 $claimed_id = null) 00770 { 00771 $this->namespace = Auth_OpenID_OPENID2_NS; 00772 $this->assoc_handle = $assoc_handle; 00773 $this->identity = $identity; 00774 if ($claimed_id === null) { 00775 $this->claimed_id = $identity; 00776 } else { 00777 $this->claimed_id = $claimed_id; 00778 } 00779 $this->return_to = $return_to; 00780 $this->trust_root = $trust_root; 00781 $this->server =& $server; 00782 00783 if ($immediate) { 00784 $this->immediate = true; 00785 $this->mode = "checkid_immediate"; 00786 } else { 00787 $this->immediate = false; 00788 $this->mode = "checkid_setup"; 00789 } 00790 } 00791 00792 function equals($other) 00793 { 00794 return ( 00795 (is_a($other, 'Auth_OpenID_CheckIDRequest')) && 00796 ($this->namespace == $other->namespace) && 00797 ($this->assoc_handle == $other->assoc_handle) && 00798 ($this->identity == $other->identity) && 00799 ($this->claimed_id == $other->claimed_id) && 00800 ($this->return_to == $other->return_to) && 00801 ($this->trust_root == $other->trust_root)); 00802 } 00803 00804 /* 00805 * Does the relying party publish the return_to URL for this 00806 * response under the realm? It is up to the provider to set a 00807 * policy for what kinds of realms should be allowed. This 00808 * return_to URL verification reduces vulnerability to data-theft 00809 * attacks based on open proxies, corss-site-scripting, or open 00810 * redirectors. 00811 * 00812 * This check should only be performed after making sure that the 00813 * return_to URL matches the realm. 00814 * 00815 * @return true if the realm publishes a document with the 00816 * return_to URL listed, false if not or if discovery fails 00817 */ 00818 function returnToVerified() 00819 { 00820 return call_user_func_array($this->verifyReturnTo, 00821 array($this->trust_root, $this->return_to)); 00822 } 00823 00824 function fromMessage(&$message, $server) 00825 { 00826 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 00827 $immediate = null; 00828 00829 if ($mode == "checkid_immediate") { 00830 $immediate = true; 00831 $mode = "checkid_immediate"; 00832 } else { 00833 $immediate = false; 00834 $mode = "checkid_setup"; 00835 } 00836 00837 $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 00838 'return_to'); 00839 00840 if (($message->isOpenID1()) && 00841 (!$return_to)) { 00842 $fmt = "Missing required field 'return_to' from checkid request"; 00843 return new Auth_OpenID_ServerError($message, $fmt); 00844 } 00845 00846 $identity = $message->getArg(Auth_OpenID_OPENID_NS, 00847 'identity'); 00848 $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id'); 00849 if ($message->isOpenID1()) { 00850 if ($identity === null) { 00851 $s = "OpenID 1 message did not contain openid.identity"; 00852 return new Auth_OpenID_ServerError($message, $s); 00853 } 00854 } else { 00855 if ($identity && !$claimed_id) { 00856 $s = "OpenID 2.0 message contained openid.identity but not " . 00857 "claimed_id"; 00858 return new Auth_OpenID_ServerError($message, $s); 00859 } else if ($claimed_id && !$identity) { 00860 $s = "OpenID 2.0 message contained openid.claimed_id " . 00861 "but not identity"; 00862 return new Auth_OpenID_ServerError($message, $s); 00863 } 00864 } 00865 00866 // There's a case for making self.trust_root be a TrustRoot 00867 // here. But if TrustRoot isn't currently part of the 00868 // "public" API, I'm not sure it's worth doing. 00869 if ($message->isOpenID1()) { 00870 $trust_root_param = 'trust_root'; 00871 } else { 00872 $trust_root_param = 'realm'; 00873 } 00874 $trust_root = $message->getArg(Auth_OpenID_OPENID_NS, 00875 $trust_root_param); 00876 if (! $trust_root) { 00877 $trust_root = $return_to; 00878 } 00879 00880 if (! $message->isOpenID1() && 00881 ($return_to === null) && 00882 ($trust_root === null)) { 00883 return new Auth_OpenID_ServerError($message, 00884 "openid.realm required when openid.return_to absent"); 00885 } 00886 00887 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 00888 'assoc_handle'); 00889 00890 $obj = Auth_OpenID_CheckIDRequest::make($message, 00891 $identity, 00892 $return_to, 00893 $trust_root, 00894 $immediate, 00895 $assoc_handle, 00896 $server); 00897 00898 if (is_a($obj, 'Auth_OpenID_ServerError')) { 00899 return $obj; 00900 } 00901 00902 $obj->claimed_id = $claimed_id; 00903 00904 return $obj; 00905 } 00906 00907 function idSelect() 00908 { 00909 // Is the identifier to be selected by the IDP? 00910 // So IDPs don't have to import the constant 00911 return $this->identity == Auth_OpenID_IDENTIFIER_SELECT; 00912 } 00913 00914 function trustRootValid() 00915 { 00916 if (!$this->trust_root) { 00917 return true; 00918 } 00919 00920 $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root); 00921 if ($tr === false) { 00922 return new Auth_OpenID_MalformedTrustRoot($this->message, 00923 $this->trust_root); 00924 } 00925 00926 if ($this->return_to !== null) { 00927 return Auth_OpenID_TrustRoot::match($this->trust_root, 00928 $this->return_to); 00929 } else { 00930 return true; 00931 } 00932 } 00933 00934 /** 00935 * Respond to this request. Return either an 00936 * {@link Auth_OpenID_ServerResponse} or 00937 * {@link Auth_OpenID_ServerError}. 00938 * 00939 * @param bool $allow Allow this user to claim this identity, and 00940 * allow the consumer to have this information? 00941 * 00942 * @param string $server_url DEPRECATED. Passing $op_endpoint to 00943 * the {@link Auth_OpenID_Server} constructor makes this optional. 00944 * 00945 * When an OpenID 1.x immediate mode request does not succeed, it 00946 * gets back a URL where the request may be carried out in a 00947 * not-so-immediate fashion. Pass my URL in here (the fully 00948 * qualified address of this server's endpoint, i.e. 00949 * http://example.com/server), and I will use it as a base for the 00950 * URL for a new request. 00951 * 00952 * Optional for requests where {@link $immediate} is false or 00953 * $allow is true. 00954 * 00955 * @param string $identity The OP-local identifier to answer with. 00956 * Only for use when the relying party requested identifier 00957 * selection. 00958 * 00959 * @param string $claimed_id The claimed identifier to answer 00960 * with, for use with identifier selection in the case where the 00961 * claimed identifier and the OP-local identifier differ, 00962 * i.e. when the claimed_id uses delegation. 00963 * 00964 * If $identity is provided but this is not, $claimed_id will 00965 * default to the value of $identity. When answering requests 00966 * that did not ask for identifier selection, the response 00967 * $claimed_id will default to that of the request. 00968 * 00969 * This parameter is new in OpenID 2.0. 00970 * 00971 * @return mixed 00972 */ 00973 function answer($allow, $server_url = null, $identity = null, 00974 $claimed_id = null) 00975 { 00976 if (!$this->return_to) { 00977 return new Auth_OpenID_NoReturnToError(); 00978 } 00979 00980 if (!$server_url) { 00981 if ((!$this->message->isOpenID1()) && 00982 (!$this->server->op_endpoint)) { 00983 return new Auth_OpenID_ServerError(null, 00984 "server should be constructed with op_endpoint to " . 00985 "respond to OpenID 2.0 messages."); 00986 } 00987 00988 $server_url = $this->server->op_endpoint; 00989 } 00990 00991 if ($allow) { 00992 $mode = 'id_res'; 00993 } else if ($this->message->isOpenID1()) { 00994 if ($this->immediate) { 00995 $mode = 'id_res'; 00996 } else { 00997 $mode = 'cancel'; 00998 } 00999 } else { 01000 if ($this->immediate) { 01001 $mode = 'setup_needed'; 01002 } else { 01003 $mode = 'cancel'; 01004 } 01005 } 01006 01007 if (!$this->trustRootValid()) { 01008 return new Auth_OpenID_UntrustedReturnURL(null, 01009 $this->return_to, 01010 $this->trust_root); 01011 } 01012 01013 $response = new Auth_OpenID_ServerResponse($this); 01014 01015 if ($claimed_id && 01016 ($this->message->isOpenID1())) { 01017 return new Auth_OpenID_ServerError(null, 01018 "claimed_id is new in OpenID 2.0 and not " . 01019 "available for ".$this->namespace); 01020 } 01021 01022 if ($identity && !$claimed_id) { 01023 $claimed_id = $identity; 01024 } 01025 01026 if ($allow) { 01027 01028 if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) { 01029 if (!$identity) { 01030 return new Auth_OpenID_ServerError(null, 01031 "This request uses IdP-driven identifier selection. " . 01032 "You must supply an identifier in the response."); 01033 } 01034 01035 $response_identity = $identity; 01036 $response_claimed_id = $claimed_id; 01037 01038 } else if ($this->identity) { 01039 if ($identity && 01040 ($this->identity != $identity)) { 01041 $fmt = "Request was for %s, cannot reply with identity %s"; 01042 return new Auth_OpenID_ServerError(null, 01043 sprintf($fmt, $this->identity, $identity)); 01044 } 01045 01046 $response_identity = $this->identity; 01047 $response_claimed_id = $this->claimed_id; 01048 } else { 01049 if ($identity) { 01050 return new Auth_OpenID_ServerError(null, 01051 "This request specified no identity and " . 01052 "you supplied ".$identity); 01053 } 01054 01055 $response_identity = null; 01056 } 01057 01058 if (($this->message->isOpenID1()) && 01059 ($response_identity === null)) { 01060 return new Auth_OpenID_ServerError(null, 01061 "Request was an OpenID 1 request, so response must " . 01062 "include an identifier."); 01063 } 01064 01065 $response->fields->updateArgs(Auth_OpenID_OPENID_NS, 01066 array('mode' => $mode, 01067 'return_to' => $this->return_to, 01068 'response_nonce' => Auth_OpenID_mkNonce())); 01069 01070 if (!$this->message->isOpenID1()) { 01071 $response->fields->setArg(Auth_OpenID_OPENID_NS, 01072 'op_endpoint', $server_url); 01073 } 01074 01075 if ($response_identity !== null) { 01076 $response->fields->setArg( 01077 Auth_OpenID_OPENID_NS, 01078 'identity', 01079 $response_identity); 01080 if ($this->message->isOpenID2()) { 01081 $response->fields->setArg( 01082 Auth_OpenID_OPENID_NS, 01083 'claimed_id', 01084 $response_claimed_id); 01085 } 01086 } 01087 01088 } else { 01089 $response->fields->setArg(Auth_OpenID_OPENID_NS, 01090 'mode', $mode); 01091 01092 if ($this->immediate) { 01093 if (($this->message->isOpenID1()) && 01094 (!$server_url)) { 01095 return new Auth_OpenID_ServerError(null, 01096 'setup_url is required for $allow=false \ 01097 in OpenID 1.x immediate mode.'); 01098 } 01099 01100 $setup_request = new Auth_OpenID_CheckIDRequest( 01101 $this->identity, 01102 $this->return_to, 01103 $this->trust_root, 01104 false, 01105 $this->assoc_handle, 01106 $this->server, 01107 $this->claimed_id); 01108 $setup_request->message = $this->message; 01109 01110 $setup_url = $setup_request->encodeToURL($server_url); 01111 01112 if ($setup_url === null) { 01113 return new Auth_OpenID_NoReturnToError(); 01114 } 01115 01116 $response->fields->setArg(Auth_OpenID_OPENID_NS, 01117 'user_setup_url', 01118 $setup_url); 01119 } 01120 } 01121 01122 return $response; 01123 } 01124 01125 function encodeToURL($server_url) 01126 { 01127 if (!$this->return_to) { 01128 return new Auth_OpenID_NoReturnToError(); 01129 } 01130 01131 // Imported from the alternate reality where these classes are 01132 // used in both the client and server code, so Requests are 01133 // Encodable too. That's right, code imported from alternate 01134 // realities all for the love of you, id_res/user_setup_url. 01135 01136 $q = array('mode' => $this->mode, 01137 'identity' => $this->identity, 01138 'claimed_id' => $this->claimed_id, 01139 'return_to' => $this->return_to); 01140 01141 if ($this->trust_root) { 01142 if ($this->message->isOpenID1()) { 01143 $q['trust_root'] = $this->trust_root; 01144 } else { 01145 $q['realm'] = $this->trust_root; 01146 } 01147 } 01148 01149 if ($this->assoc_handle) { 01150 $q['assoc_handle'] = $this->assoc_handle; 01151 } 01152 01153 $response = new Auth_OpenID_Message( 01154 $this->message->getOpenIDNamespace()); 01155 $response->updateArgs(Auth_OpenID_OPENID_NS, $q); 01156 return $response->toURL($server_url); 01157 } 01158 01159 function getCancelURL() 01160 { 01161 if (!$this->return_to) { 01162 return new Auth_OpenID_NoReturnToError(); 01163 } 01164 01165 if ($this->immediate) { 01166 return new Auth_OpenID_ServerError(null, 01167 "Cancel is not an appropriate \ 01168 response to immediate mode \ 01169 requests."); 01170 } 01171 01172 $response = new Auth_OpenID_Message( 01173 $this->message->getOpenIDNamespace()); 01174 $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel'); 01175 return $response->toURL($this->return_to); 01176 } 01177 } 01178 01179 /** 01180 * This class encapsulates the response to an OpenID server request. 01181 * 01182 * @package OpenID 01183 */ 01184 class Auth_OpenID_ServerResponse { 01185 01186 function Auth_OpenID_ServerResponse(&$request) 01187 { 01188 $this->request =& $request; 01189 $this->fields = new Auth_OpenID_Message($this->request->namespace); 01190 } 01191 01192 function whichEncoding() 01193 { 01194 global $_Auth_OpenID_Request_Modes; 01195 01196 if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) { 01197 if ($this->fields->isOpenID2() && 01198 (strlen($this->encodeToURL()) > 01199 Auth_OpenID_OPENID1_URL_LIMIT)) { 01200 return Auth_OpenID_ENCODE_HTML_FORM; 01201 } else { 01202 return Auth_OpenID_ENCODE_URL; 01203 } 01204 } else { 01205 return Auth_OpenID_ENCODE_KVFORM; 01206 } 01207 } 01208 01209 /* 01210 * Returns the form markup for this response. 01211 * 01212 * @return str 01213 */ 01214 function toFormMarkup($form_tag_attrs=null) 01215 { 01216 return $this->fields->toFormMarkup($this->request->return_to, 01217 $form_tag_attrs); 01218 } 01219 01220 /* 01221 * Returns an HTML document containing the form markup for this 01222 * response that autosubmits with javascript. 01223 */ 01224 function toHTML() 01225 { 01226 return Auth_OpenID::autoSubmitHTML($this->toFormMarkup()); 01227 } 01228 01229 /* 01230 * Returns True if this response's encoding is ENCODE_HTML_FORM. 01231 * Convenience method for server authors. 01232 * 01233 * @return bool 01234 */ 01235 function renderAsForm() 01236 { 01237 return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM; 01238 } 01239 01240 01241 function encodeToURL() 01242 { 01243 return $this->fields->toURL($this->request->return_to); 01244 } 01245 01246 function addExtension($extension_response) 01247 { 01248 $extension_response->toMessage($this->fields); 01249 } 01250 01251 function needsSigning() 01252 { 01253 return $this->fields->getArg(Auth_OpenID_OPENID_NS, 01254 'mode') == 'id_res'; 01255 } 01256 01257 function encodeToKVForm() 01258 { 01259 return $this->fields->toKVForm(); 01260 } 01261 } 01262 01263 /** 01264 * A web-capable response object which you can use to generate a 01265 * user-agent response. 01266 * 01267 * @package OpenID 01268 */ 01269 class Auth_OpenID_WebResponse { 01270 var $code = AUTH_OPENID_HTTP_OK; 01271 var $body = ""; 01272 01273 function Auth_OpenID_WebResponse($code = null, $headers = null, 01274 $body = null) 01275 { 01276 if ($code) { 01277 $this->code = $code; 01278 } 01279 01280 if ($headers !== null) { 01281 $this->headers = $headers; 01282 } else { 01283 $this->headers = array(); 01284 } 01285 01286 if ($body !== null) { 01287 $this->body = $body; 01288 } 01289 } 01290 } 01291 01292 /** 01293 * Responsible for the signature of query data and the verification of 01294 * OpenID signature values. 01295 * 01296 * @package OpenID 01297 */ 01298 class Auth_OpenID_Signatory { 01299 01300 // = 14 * 24 * 60 * 60; # 14 days, in seconds 01301 var $SECRET_LIFETIME = 1209600; 01302 01303 // keys have a bogus server URL in them because the filestore 01304 // really does expect that key to be a URL. This seems a little 01305 // silly for the server store, since I expect there to be only one 01306 // server URL. 01307 var $normal_key = 'http://localhost/|normal'; 01308 var $dumb_key = 'http://localhost/|dumb'; 01309 01310 /** 01311 * Create a new signatory using a given store. 01312 */ 01313 function Auth_OpenID_Signatory(&$store) 01314 { 01315 // assert store is not None 01316 $this->store =& $store; 01317 } 01318 01319 /** 01320 * Verify, using a given association handle, a signature with 01321 * signed key-value pairs from an HTTP request. 01322 */ 01323 function verify($assoc_handle, $message) 01324 { 01325 $assoc = $this->getAssociation($assoc_handle, true); 01326 if (!$assoc) { 01327 // oidutil.log("failed to get assoc with handle %r to verify sig %r" 01328 // % (assoc_handle, sig)) 01329 return false; 01330 } 01331 01332 return $assoc->checkMessageSignature($message); 01333 } 01334 01335 /** 01336 * Given a response, sign the fields in the response's 'signed' 01337 * list, and insert the signature into the response. 01338 */ 01339 function sign($response) 01340 { 01341 $signed_response = $response; 01342 $assoc_handle = $response->request->assoc_handle; 01343 01344 if ($assoc_handle) { 01345 // normal mode 01346 $assoc = $this->getAssociation($assoc_handle, false, false); 01347 if (!$assoc || ($assoc->getExpiresIn() <= 0)) { 01348 // fall back to dumb mode 01349 $signed_response->fields->setArg(Auth_OpenID_OPENID_NS, 01350 'invalidate_handle', $assoc_handle); 01351 $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1'); 01352 01353 if ($assoc && ($assoc->getExpiresIn() <= 0)) { 01354 $this->invalidate($assoc_handle, false); 01355 } 01356 01357 $assoc = $this->createAssociation(true, $assoc_type); 01358 } 01359 } else { 01360 // dumb mode. 01361 $assoc = $this->createAssociation(true); 01362 } 01363 01364 $signed_response->fields = $assoc->signMessage( 01365 $signed_response->fields); 01366 return $signed_response; 01367 } 01368 01369 /** 01370 * Make a new association. 01371 */ 01372 function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1') 01373 { 01374 $secret = Auth_OpenID_CryptUtil::getBytes( 01375 Auth_OpenID_getSecretSize($assoc_type)); 01376 01377 $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4)); 01378 $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq); 01379 01380 $assoc = Auth_OpenID_Association::fromExpiresIn( 01381 $this->SECRET_LIFETIME, $handle, $secret, $assoc_type); 01382 01383 if ($dumb) { 01384 $key = $this->dumb_key; 01385 } else { 01386 $key = $this->normal_key; 01387 } 01388 01389 $this->store->storeAssociation($key, $assoc); 01390 return $assoc; 01391 } 01392 01393 /** 01394 * Given an association handle, get the association from the 01395 * store, or return a ServerError or null if something goes wrong. 01396 */ 01397 function getAssociation($assoc_handle, $dumb, $check_expiration=true) 01398 { 01399 if ($assoc_handle === null) { 01400 return new Auth_OpenID_ServerError(null, 01401 "assoc_handle must not be null"); 01402 } 01403 01404 if ($dumb) { 01405 $key = $this->dumb_key; 01406 } else { 01407 $key = $this->normal_key; 01408 } 01409 01410 $assoc = $this->store->getAssociation($key, $assoc_handle); 01411 01412 if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) { 01413 if ($check_expiration) { 01414 $this->store->removeAssociation($key, $assoc_handle); 01415 $assoc = null; 01416 } 01417 } 01418 01419 return $assoc; 01420 } 01421 01422 /** 01423 * Invalidate a given association handle. 01424 */ 01425 function invalidate($assoc_handle, $dumb) 01426 { 01427 if ($dumb) { 01428 $key = $this->dumb_key; 01429 } else { 01430 $key = $this->normal_key; 01431 } 01432 $this->store->removeAssociation($key, $assoc_handle); 01433 } 01434 } 01435 01436 /** 01437 * Encode an {@link Auth_OpenID_ServerResponse} to an 01438 * {@link Auth_OpenID_WebResponse}. 01439 * 01440 * @package OpenID 01441 */ 01442 class Auth_OpenID_Encoder { 01443 01444 var $responseFactory = 'Auth_OpenID_WebResponse'; 01445 01446 /** 01447 * Encode an {@link Auth_OpenID_ServerResponse} and return an 01448 * {@link Auth_OpenID_WebResponse}. 01449 */ 01450 function encode(&$response) 01451 { 01452 $cls = $this->responseFactory; 01453 01454 $encode_as = $response->whichEncoding(); 01455 if ($encode_as == Auth_OpenID_ENCODE_KVFORM) { 01456 $wr = new $cls(null, null, $response->encodeToKVForm()); 01457 if (is_a($response, 'Auth_OpenID_ServerError')) { 01458 $wr->code = AUTH_OPENID_HTTP_ERROR; 01459 } 01460 } else if ($encode_as == Auth_OpenID_ENCODE_URL) { 01461 $location = $response->encodeToURL(); 01462 $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT, 01463 array('location' => $location)); 01464 } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) { 01465 $wr = new $cls(AUTH_OPENID_HTTP_OK, array(), 01466 $response->toFormMarkup()); 01467 } else { 01468 return new Auth_OpenID_EncodingError($response); 01469 } 01470 return $wr; 01471 } 01472 } 01473 01474 /** 01475 * An encoder which also takes care of signing fields when required. 01476 * 01477 * @package OpenID 01478 */ 01479 class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder { 01480 01481 function Auth_OpenID_SigningEncoder(&$signatory) 01482 { 01483 $this->signatory =& $signatory; 01484 } 01485 01486 /** 01487 * Sign an {@link Auth_OpenID_ServerResponse} and return an 01488 * {@link Auth_OpenID_WebResponse}. 01489 */ 01490 function encode(&$response) 01491 { 01492 // the isinstance is a bit of a kludge... it means there isn't 01493 // really an adapter to make the interfaces quite match. 01494 if (!is_a($response, 'Auth_OpenID_ServerError') && 01495 $response->needsSigning()) { 01496 01497 if (!$this->signatory) { 01498 return new Auth_OpenID_ServerError(null, 01499 "Must have a store to sign request"); 01500 } 01501 01502 if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) { 01503 return new Auth_OpenID_AlreadySigned($response); 01504 } 01505 $response = $this->signatory->sign($response); 01506 } 01507 01508 return parent::encode($response); 01509 } 01510 } 01511 01512 /** 01513 * Decode an incoming query into an Auth_OpenID_Request. 01514 * 01515 * @package OpenID 01516 */ 01517 class Auth_OpenID_Decoder { 01518 01519 function Auth_OpenID_Decoder(&$server) 01520 { 01521 $this->server =& $server; 01522 01523 $this->handlers = array( 01524 'checkid_setup' => 'Auth_OpenID_CheckIDRequest', 01525 'checkid_immediate' => 'Auth_OpenID_CheckIDRequest', 01526 'check_authentication' => 'Auth_OpenID_CheckAuthRequest', 01527 'associate' => 'Auth_OpenID_AssociateRequest' 01528 ); 01529 } 01530 01531 /** 01532 * Given an HTTP query in an array (key-value pairs), decode it 01533 * into an Auth_OpenID_Request object. 01534 */ 01535 function decode($query) 01536 { 01537 if (!$query) { 01538 return null; 01539 } 01540 01541 $message = Auth_OpenID_Message::fromPostArgs($query); 01542 01543 if ($message === null) { 01544 /* 01545 * It's useful to have a Message attached to a 01546 * ProtocolError, so we override the bad ns value to build 01547 * a Message out of it. Kinda kludgy, since it's made of 01548 * lies, but the parts that aren't lies are more useful 01549 * than a 'None'. 01550 */ 01551 $old_ns = $query['openid.ns']; 01552 01553 $query['openid.ns'] = Auth_OpenID_OPENID2_NS; 01554 $message = Auth_OpenID_Message::fromPostArgs($query); 01555 return new Auth_OpenID_ServerError( 01556 $message, 01557 sprintf("Invalid OpenID namespace URI: %s", $old_ns)); 01558 } 01559 01560 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 01561 if (!$mode) { 01562 return new Auth_OpenID_ServerError($message, 01563 "No mode value in message"); 01564 } 01565 01566 if (Auth_OpenID::isFailure($mode)) { 01567 return new Auth_OpenID_ServerError($message, 01568 $mode->message); 01569 } 01570 01571 $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode, 01572 $this->defaultDecoder($message)); 01573 01574 if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) { 01575 return call_user_func_array(array($handlerCls, 'fromMessage'), 01576 array($message, $this->server)); 01577 } else { 01578 return $handlerCls; 01579 } 01580 } 01581 01582 function defaultDecoder($message) 01583 { 01584 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode'); 01585 01586 if (Auth_OpenID::isFailure($mode)) { 01587 return new Auth_OpenID_ServerError($message, 01588 $mode->message); 01589 } 01590 01591 return new Auth_OpenID_ServerError($message, 01592 sprintf("Unrecognized OpenID mode %s", $mode)); 01593 } 01594 } 01595 01596 /** 01597 * An error that indicates an encoding problem occurred. 01598 * 01599 * @package OpenID 01600 */ 01601 class Auth_OpenID_EncodingError { 01602 function Auth_OpenID_EncodingError(&$response) 01603 { 01604 $this->response =& $response; 01605 } 01606 } 01607 01608 /** 01609 * An error that indicates that a response was already signed. 01610 * 01611 * @package OpenID 01612 */ 01613 class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError { 01614 // This response is already signed. 01615 } 01616 01617 /** 01618 * An error that indicates that the given return_to is not under the 01619 * given trust_root. 01620 * 01621 * @package OpenID 01622 */ 01623 class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError { 01624 function Auth_OpenID_UntrustedReturnURL($message, $return_to, 01625 $trust_root) 01626 { 01627 parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL"); 01628 $this->return_to = $return_to; 01629 $this->trust_root = $trust_root; 01630 } 01631 01632 function toString() 01633 { 01634 return sprintf("return_to %s not under trust_root %s", 01635 $this->return_to, $this->trust_root); 01636 } 01637 } 01638 01639 /** 01640 * I handle requests for an OpenID server. 01641 * 01642 * Some types of requests (those which are not checkid requests) may 01643 * be handed to my {@link handleRequest} method, and I will take care 01644 * of it and return a response. 01645 * 01646 * For your convenience, I also provide an interface to {@link 01647 * Auth_OpenID_Decoder::decode()} and {@link 01648 * Auth_OpenID_SigningEncoder::encode()} through my methods {@link 01649 * decodeRequest} and {@link encodeResponse}. 01650 * 01651 * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}. 01652 * 01653 * Example: 01654 * 01655 * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path), 01656 * "http://example.com/op"); 01657 * $request = $oserver->decodeRequest(); 01658 * if (in_array($request->mode, array('checkid_immediate', 01659 * 'checkid_setup'))) { 01660 * if ($app->isAuthorized($request->identity, $request->trust_root)) { 01661 * $response = $request->answer(true); 01662 * } else if ($request->immediate) { 01663 * $response = $request->answer(false); 01664 * } else { 01665 * $app->showDecidePage($request); 01666 * return; 01667 * } 01668 * } else { 01669 * $response = $oserver->handleRequest($request); 01670 * } 01671 * 01672 * $webresponse = $oserver->encode($response);</pre> 01673 * 01674 * @package OpenID 01675 */ 01676 class Auth_OpenID_Server { 01677 function Auth_OpenID_Server(&$store, $op_endpoint=null) 01678 { 01679 $this->store =& $store; 01680 $this->signatory = new Auth_OpenID_Signatory($this->store); 01681 $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory); 01682 $this->decoder = new Auth_OpenID_Decoder($this); 01683 $this->op_endpoint = $op_endpoint; 01684 $this->negotiator =& Auth_OpenID_getDefaultNegotiator(); 01685 } 01686 01687 /** 01688 * Handle a request. Given an {@link Auth_OpenID_Request} object, 01689 * call the appropriate {@link Auth_OpenID_Server} method to 01690 * process the request and generate a response. 01691 * 01692 * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request} 01693 * returned by {@link Auth_OpenID_Server::decodeRequest()}. 01694 * 01695 * @return Auth_OpenID_ServerResponse $response A response object 01696 * capable of generating a user-agent reply. 01697 */ 01698 function handleRequest($request) 01699 { 01700 if (method_exists($this, "openid_" . $request->mode)) { 01701 $handler = array($this, "openid_" . $request->mode); 01702 return call_user_func($handler, $request); 01703 } 01704 return null; 01705 } 01706 01707 /** 01708 * The callback for 'check_authentication' messages. 01709 */ 01710 function openid_check_authentication(&$request) 01711 { 01712 return $request->answer($this->signatory); 01713 } 01714 01715 /** 01716 * The callback for 'associate' messages. 01717 */ 01718 function openid_associate(&$request) 01719 { 01720 $assoc_type = $request->assoc_type; 01721 $session_type = $request->session->session_type; 01722 if ($this->negotiator->isAllowed($assoc_type, $session_type)) { 01723 $assoc = $this->signatory->createAssociation(false, 01724 $assoc_type); 01725 return $request->answer($assoc); 01726 } else { 01727 $message = sprintf('Association type %s is not supported with '. 01728 'session type %s', $assoc_type, $session_type); 01729 list($preferred_assoc_type, $preferred_session_type) = 01730 $this->negotiator->getAllowedType(); 01731 return $request->answerUnsupported($message, 01732 $preferred_assoc_type, 01733 $preferred_session_type); 01734 } 01735 } 01736 01737 /** 01738 * Encodes as response in the appropriate format suitable for 01739 * sending to the user agent. 01740 */ 01741 function encodeResponse(&$response) 01742 { 01743 return $this->encoder->encode($response); 01744 } 01745 01746 /** 01747 * Decodes a query args array into the appropriate 01748 * {@link Auth_OpenID_Request} object. 01749 */ 01750 function decodeRequest($query=null) 01751 { 01752 if ($query === null) { 01753 $query = Auth_OpenID::getQuery(); 01754 } 01755 01756 return $this->decoder->decode($query); 01757 } 01758 } 01759 01760 ?>
1.8.0