00001 <?php
00002
00163 require_once "Auth/OpenID.php";
00164 require_once "Auth/OpenID/Message.php";
00165 require_once "Auth/OpenID/HMAC.php";
00166 require_once "Auth/OpenID/Association.php";
00167 require_once "Auth/OpenID/CryptUtil.php";
00168 require_once "Auth/OpenID/DiffieHellman.php";
00169 require_once "Auth/OpenID/KVForm.php";
00170 require_once "Auth/OpenID/Nonce.php";
00171 require_once "Auth/OpenID/Discover.php";
00172 require_once "Auth/OpenID/URINorm.php";
00173 require_once "Auth/Yadis/Manager.php";
00174 require_once "Auth/Yadis/XRI.php";
00175
00180 define('Auth_OpenID_SUCCESS', 'success');
00181
00185 define('Auth_OpenID_CANCEL', 'cancel');
00186
00191 define('Auth_OpenID_FAILURE', 'failure');
00192
00199 define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
00200
00206 define('Auth_OpenID_PARSE_ERROR', 'parse error');
00207
00215 class Auth_OpenID_Consumer {
00216
00220 var $discoverMethod = 'Auth_OpenID_discover';
00221
00225 var $session_key_prefix = "_openid_consumer_";
00226
00230 var $_token_suffix = "last_token";
00231
00261 function Auth_OpenID_Consumer(&$store, $session = null,
00262 $consumer_cls = null)
00263 {
00264 if ($session === null) {
00265 $session = new Auth_Yadis_PHPSession();
00266 }
00267
00268 $this->session =& $session;
00269
00270 if ($consumer_cls !== null) {
00271 $this->consumer =& new $consumer_cls($store);
00272 } else {
00273 $this->consumer =& new Auth_OpenID_GenericConsumer($store);
00274 }
00275
00276 $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
00277 }
00278
00284 function getDiscoveryObject(&$session, $openid_url,
00285 $session_key_prefix)
00286 {
00287 return new Auth_Yadis_Discovery($session, $openid_url,
00288 $session_key_prefix);
00289 }
00290
00313 function begin($user_url, $anonymous=false)
00314 {
00315 $openid_url = $user_url;
00316
00317 $disco = $this->getDiscoveryObject($this->session,
00318 $openid_url,
00319 $this->session_key_prefix);
00320
00321
00322
00323
00324
00325 $m = $disco->getManager();
00326 $loader = new Auth_Yadis_ManagerLoader();
00327
00328 if ($m) {
00329 if ($m->stale) {
00330 $disco->destroyManager();
00331 } else {
00332 $m->stale = true;
00333 $disco->session->set($disco->session_key,
00334 serialize($loader->toSession($m)));
00335 }
00336 }
00337
00338 $endpoint = $disco->getNextService($this->discoverMethod,
00339 $this->consumer->fetcher);
00340
00341
00342 $m =& $disco->getManager();
00343 if ($m) {
00344 $m->stale = false;
00345 $disco->session->set($disco->session_key,
00346 serialize($loader->toSession($m)));
00347 }
00348
00349 if ($endpoint === null) {
00350 return null;
00351 } else {
00352 return $this->beginWithoutDiscovery($endpoint,
00353 $anonymous);
00354 }
00355 }
00356
00373 function &beginWithoutDiscovery($endpoint, $anonymous=false)
00374 {
00375 $loader = new Auth_OpenID_ServiceEndpointLoader();
00376 $auth_req = $this->consumer->begin($endpoint);
00377 $this->session->set($this->_token_key,
00378 $loader->toSession($auth_req->endpoint));
00379 if (!$auth_req->setAnonymous($anonymous)) {
00380 return new Auth_OpenID_FailureResponse(null,
00381 "OpenID 1 requests MUST include the identifier " .
00382 "in the request.");
00383 }
00384 return $auth_req;
00385 }
00386
00410 function complete($current_url, $query=null)
00411 {
00412 if ($current_url && !is_string($current_url)) {
00413
00414
00415 trigger_error("current_url must be a string; see NEWS file " .
00416 "for upgrading notes.",
00417 E_USER_ERROR);
00418 }
00419
00420 if ($query === null) {
00421 $query = Auth_OpenID::getQuery();
00422 }
00423
00424 $loader = new Auth_OpenID_ServiceEndpointLoader();
00425 $endpoint_data = $this->session->get($this->_token_key);
00426 $endpoint =
00427 $loader->fromSession($endpoint_data);
00428
00429 $message = Auth_OpenID_Message::fromPostArgs($query);
00430 $response = $this->consumer->complete($message, $endpoint,
00431 $current_url);
00432 $this->session->del($this->_token_key);
00433
00434 if (in_array($response->status, array(Auth_OpenID_SUCCESS,
00435 Auth_OpenID_CANCEL))) {
00436 if ($response->identity_url !== null) {
00437 $disco = $this->getDiscoveryObject($this->session,
00438 $response->identity_url,
00439 $this->session_key_prefix);
00440 $disco->cleanup(true);
00441 }
00442 }
00443
00444 return $response;
00445 }
00446 }
00447
00453 class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
00454 var $session_type = 'DH-SHA1';
00455 var $hash_func = 'Auth_OpenID_SHA1';
00456 var $secret_size = 20;
00457 var $allowed_assoc_types = array('HMAC-SHA1');
00458
00459 function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
00460 {
00461 if ($dh === null) {
00462 $dh = new Auth_OpenID_DiffieHellman();
00463 }
00464
00465 $this->dh = $dh;
00466 }
00467
00468 function getRequest()
00469 {
00470 $math =& Auth_OpenID_getMathLib();
00471
00472 $cpub = $math->longToBase64($this->dh->public);
00473
00474 $args = array('dh_consumer_public' => $cpub);
00475
00476 if (!$this->dh->usingDefaultValues()) {
00477 $args = array_merge($args, array(
00478 'dh_modulus' =>
00479 $math->longToBase64($this->dh->mod),
00480 'dh_gen' =>
00481 $math->longToBase64($this->dh->gen)));
00482 }
00483
00484 return $args;
00485 }
00486
00487 function extractSecret($response)
00488 {
00489 if (!$response->hasKey(Auth_OpenID_OPENID_NS,
00490 'dh_server_public')) {
00491 return null;
00492 }
00493
00494 if (!$response->hasKey(Auth_OpenID_OPENID_NS,
00495 'enc_mac_key')) {
00496 return null;
00497 }
00498
00499 $math =& Auth_OpenID_getMathLib();
00500
00501 $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
00502 'dh_server_public'));
00503 $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
00504 'enc_mac_key'));
00505
00506 return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
00507 }
00508 }
00509
00515 class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
00516 Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
00517 var $session_type = 'DH-SHA256';
00518 var $hash_func = 'Auth_OpenID_SHA256';
00519 var $secret_size = 32;
00520 var $allowed_assoc_types = array('HMAC-SHA256');
00521 }
00522
00528 class Auth_OpenID_PlainTextConsumerSession {
00529 var $session_type = 'no-encryption';
00530 var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
00531
00532 function getRequest()
00533 {
00534 return array();
00535 }
00536
00537 function extractSecret($response)
00538 {
00539 if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) {
00540 return null;
00541 }
00542
00543 return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
00544 'mac_key'));
00545 }
00546 }
00547
00551 function Auth_OpenID_getAvailableSessionTypes()
00552 {
00553 $types = array(
00554 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
00555 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
00556 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
00557
00558 return $types;
00559 }
00560
00568 class Auth_OpenID_GenericConsumer {
00572 var $discoverMethod = 'Auth_OpenID_discover';
00573
00577 var $store;
00578
00582 var $_use_assocs;
00583
00587 var $openid1_nonce_query_arg_name = 'janrain_nonce';
00588
00594 var $openid1_return_to_identifier_name = 'openid1_claimed_id';
00595
00614 function Auth_OpenID_GenericConsumer(&$store)
00615 {
00616 $this->store =& $store;
00617 $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
00618 $this->_use_assocs = ($this->store ? true : false);
00619
00620 $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
00621
00622 $this->session_types = Auth_OpenID_getAvailableSessionTypes();
00623 }
00624
00631 function begin($service_endpoint)
00632 {
00633 $assoc = $this->_getAssociation($service_endpoint);
00634 $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
00635 $r->return_to_args[$this->openid1_nonce_query_arg_name] =
00636 Auth_OpenID_mkNonce();
00637
00638 if ($r->message->isOpenID1()) {
00639 $r->return_to_args[$this->openid1_return_to_identifier_name] =
00640 $r->endpoint->claimed_id;
00641 }
00642
00643 return $r;
00644 }
00645
00653 function complete($message, $endpoint, $return_to)
00654 {
00655 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
00656 '<no mode set>');
00657
00658 $mode_methods = array(
00659 'cancel' => '_complete_cancel',
00660 'error' => '_complete_error',
00661 'setup_needed' => '_complete_setup_needed',
00662 'id_res' => '_complete_id_res',
00663 );
00664
00665 $method = Auth_OpenID::arrayGet($mode_methods, $mode,
00666 '_completeInvalid');
00667
00668 return call_user_func_array(array(&$this, $method),
00669 array($message, $endpoint, $return_to));
00670 }
00671
00675 function _completeInvalid($message, &$endpoint, $unused)
00676 {
00677 $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
00678 '<No mode set>');
00679
00680 return new Auth_OpenID_FailureResponse($endpoint,
00681 sprintf("Invalid openid.mode '%s'", $mode));
00682 }
00683
00687 function _complete_cancel($message, &$endpoint, $unused)
00688 {
00689 return new Auth_OpenID_CancelResponse($endpoint);
00690 }
00691
00695 function _complete_error($message, &$endpoint, $unused)
00696 {
00697 $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
00698 $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
00699 $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
00700
00701 return new Auth_OpenID_FailureResponse($endpoint, $error,
00702 $contact, $reference);
00703 }
00704
00708 function _complete_setup_needed($message, &$endpoint, $unused)
00709 {
00710 if (!$message->isOpenID2()) {
00711 return $this->_completeInvalid($message, $endpoint);
00712 }
00713
00714 $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
00715 'user_setup_url');
00716 return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
00717 }
00718
00722 function _complete_id_res($message, &$endpoint, $return_to)
00723 {
00724 $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
00725 'user_setup_url');
00726
00727 if ($this->_checkSetupNeeded($message)) {
00728 return new Auth_OpenID_SetupNeededResponse(
00729 $endpoint, $user_setup_url);
00730 } else {
00731 return $this->_doIdRes($message, $endpoint, $return_to);
00732 }
00733 }
00734
00738 function _checkSetupNeeded($message)
00739 {
00740
00741
00742
00743 if ($message->isOpenID1()) {
00744 $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
00745 'user_setup_url');
00746 if ($user_setup_url !== null) {
00747 return true;
00748 }
00749 }
00750
00751 return false;
00752 }
00753
00757 function _doIdRes($message, $endpoint, $return_to)
00758 {
00759
00760
00761 $result = $this->_idResCheckForFields($message);
00762
00763 if (Auth_OpenID::isFailure($result)) {
00764 return $result;
00765 }
00766
00767 if (!$this->_checkReturnTo($message, $return_to)) {
00768 return new Auth_OpenID_FailureResponse(null,
00769 sprintf("return_to does not match return URL. Expected %s, got %s",
00770 $return_to,
00771 $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
00772 }
00773
00774
00775 $result = $this->_verifyDiscoveryResults($message, $endpoint);
00776
00777 if (Auth_OpenID::isFailure($result)) {
00778 return $result;
00779 }
00780
00781 $endpoint = $result;
00782
00783 $result = $this->_idResCheckSignature($message,
00784 $endpoint->server_url);
00785
00786 if (Auth_OpenID::isFailure($result)) {
00787 return $result;
00788 }
00789
00790 $result = $this->_idResCheckNonce($message, $endpoint);
00791
00792 if (Auth_OpenID::isFailure($result)) {
00793 return $result;
00794 }
00795
00796 $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
00797 Auth_OpenID_NO_DEFAULT);
00798 if (Auth_OpenID::isFailure($signed_list_str)) {
00799 return $signed_list_str;
00800 }
00801 $signed_list = explode(',', $signed_list_str);
00802
00803 $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
00804
00805 return new Auth_OpenID_SuccessResponse($endpoint, $message,
00806 $signed_fields);
00807
00808 }
00809
00813 function _checkReturnTo($message, $return_to)
00814 {
00815
00816
00817
00818
00819
00820
00821 $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
00822 $message->toPostArgs());
00823 if (Auth_OpenID::isFailure($result)) {
00824 return false;
00825 }
00826
00827
00828
00829 $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
00830 'return_to');
00831 if (Auth_OpenID::isFailure($return_to)) {
00832
00833 return false;
00834 }
00835
00836 $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
00837 $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
00838
00839
00840
00841 if ((!array_key_exists('port', $return_to_parts)) &&
00842 (!array_key_exists('port', $msg_return_to_parts))) {
00843 $return_to_parts['port'] = null;
00844 $msg_return_to_parts['port'] = null;
00845 }
00846
00847
00848
00849 if ((!array_key_exists('path', $return_to_parts)) &&
00850 (!array_key_exists('path', $msg_return_to_parts))) {
00851 $return_to_parts['path'] = null;
00852 $msg_return_to_parts['path'] = null;
00853 }
00854
00855
00856
00857 foreach (array('scheme', 'host', 'port', 'path') as $component) {
00858
00859
00860 if (!array_key_exists($component, $return_to_parts)) {
00861 return false;
00862 }
00863
00864 if (!array_key_exists($component, $msg_return_to_parts)) {
00865 return false;
00866 }
00867
00868 if (Auth_OpenID::arrayGet($return_to_parts, $component) !==
00869 Auth_OpenID::arrayGet($msg_return_to_parts, $component)) {
00870 return false;
00871 }
00872 }
00873
00874 return true;
00875 }
00876
00880 function _verifyReturnToArgs($query)
00881 {
00882
00883
00884
00885 $message = Auth_OpenID_Message::fromPostArgs($query);
00886 $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
00887
00888 if (Auth_OpenID::isFailure($return_to)) {
00889 return $return_to;
00890 }
00891
00892 if (!$return_to) {
00893 return new Auth_OpenID_FailureResponse(null,
00894 "Response has no return_to");
00895 }
00896
00897 $parsed_url = parse_url($return_to);
00898
00899 $q = array();
00900 if (array_key_exists('query', $parsed_url)) {
00901 $rt_query = $parsed_url['query'];
00902 $q = Auth_OpenID::parse_str($rt_query);
00903 }
00904
00905 foreach ($q as $rt_key => $rt_value) {
00906 if (!array_key_exists($rt_key, $query)) {
00907 return new Auth_OpenID_FailureResponse(null,
00908 sprintf("return_to parameter %s absent from query", $rt_key));
00909 } else {
00910 $value = $query[$rt_key];
00911 if ($rt_value != $value) {
00912 return new Auth_OpenID_FailureResponse(null,
00913 sprintf("parameter %s value %s does not match " .
00914 "return_to value %s", $rt_key,
00915 $value, $rt_value));
00916 }
00917 }
00918 }
00919
00920
00921
00922 $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
00923 foreach ($bare_args as $key => $value) {
00924 if (Auth_OpenID::arrayGet($q, $key) != $value) {
00925 return new Auth_OpenID_FailureResponse(null,
00926 sprintf("Parameter %s = %s not in return_to URL",
00927 $key, $value));
00928 }
00929 }
00930
00931 return true;
00932 }
00933
00937 function _idResCheckSignature($message, $server_url)
00938 {
00939 $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
00940 'assoc_handle');
00941 if (Auth_OpenID::isFailure($assoc_handle)) {
00942 return $assoc_handle;
00943 }
00944
00945 $assoc = $this->store->getAssociation($server_url, $assoc_handle);
00946
00947 if ($assoc) {
00948 if ($assoc->getExpiresIn() <= 0) {
00949
00950
00951
00952
00953
00954
00955 return new Auth_OpenID_FailureResponse(null,
00956 'Association with ' . $server_url . ' expired');
00957 }
00958
00959 if (!$assoc->checkMessageSignature($message)) {
00960 return new Auth_OpenID_FailureResponse(null,
00961 "Bad signature");
00962 }
00963 } else {
00964
00965
00966
00967
00968 if (!$this->_checkAuth($message, $server_url)) {
00969 return new Auth_OpenID_FailureResponse(null,
00970 "Server denied check_authentication");
00971 }
00972 }
00973
00974 return null;
00975 }
00976
00980 function _verifyDiscoveryResults($message, $endpoint=null)
00981 {
00982 if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) {
00983 return $this->_verifyDiscoveryResultsOpenID2($message,
00984 $endpoint);
00985 } else {
00986 return $this->_verifyDiscoveryResultsOpenID1($message,
00987 $endpoint);
00988 }
00989 }
00990
00994 function _verifyDiscoveryResultsOpenID1($message, $endpoint)
00995 {
00996 $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
00997 $this->openid1_return_to_identifier_name);
00998
00999 if (($endpoint === null) && ($claimed_id === null)) {
01000 return new Auth_OpenID_FailureResponse($endpoint,
01001 'When using OpenID 1, the claimed ID must be supplied, ' .
01002 'either by passing it through as a return_to parameter ' .
01003 'or by using a session, and supplied to the GenericConsumer ' .
01004 'as the argument to complete()');
01005 } else if (($endpoint !== null) && ($claimed_id === null)) {
01006 $claimed_id = $endpoint->claimed_id;
01007 }
01008
01009 $to_match = new Auth_OpenID_ServiceEndpoint();
01010 $to_match->type_uris = array(Auth_OpenID_TYPE_1_1);
01011 $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS,
01012 'identity');
01013
01014
01015 $to_match->claimed_id = $claimed_id;
01016
01017 if ($to_match->local_id === null) {
01018 return new Auth_OpenID_FailureResponse($endpoint,
01019 "Missing required field openid.identity");
01020 }
01021
01022 $to_match_1_0 = $to_match->copy();
01023 $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
01024
01025 if ($endpoint !== null) {
01026 $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
01027
01028 if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
01029 $result = $this->_verifyDiscoverySingle($endpoint,
01030 $to_match_1_0);
01031 }
01032
01033 if (Auth_OpenID::isFailure($result)) {
01034
01035
01036
01037
01038 } else {
01039 return $endpoint;
01040 }
01041 }
01042
01043
01044 return $this->_discoverAndVerify($to_match->claimed_id,
01045 array($to_match, $to_match_1_0));
01046 }
01047
01051 function _verifyDiscoverySingle($endpoint, $to_match)
01052 {
01053
01054
01055 foreach ($to_match->type_uris as $type_uri) {
01056 if (!$endpoint->usesExtension($type_uri)) {
01057 return new Auth_OpenID_TypeURIMismatch($endpoint,
01058 "Required type ".$type_uri." not present");
01059 }
01060 }
01061
01062
01063
01064
01065 list($defragged_claimed_id, $_) =
01066 Auth_OpenID::urldefrag($to_match->claimed_id);
01067
01068 if ($defragged_claimed_id != $endpoint->claimed_id) {
01069 return new Auth_OpenID_FailureResponse($endpoint,
01070 sprintf('Claimed ID does not match (different subjects!), ' .
01071 'Expected %s, got %s', $defragged_claimed_id,
01072 $endpoint->claimed_id));
01073 }
01074
01075 if ($to_match->getLocalID() != $endpoint->getLocalID()) {
01076 return new Auth_OpenID_FailureResponse($endpoint,
01077 sprintf('local_id mismatch. Expected %s, got %s',
01078 $to_match->getLocalID(), $endpoint->getLocalID()));
01079 }
01080
01081
01082
01083
01084
01085
01086 if ($to_match->server_url === null) {
01087 if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) {
01088 return new Auth_OpenID_FailureResponse($endpoint,
01089 "Preferred namespace mismatch (bug)");
01090 }
01091 } else if ($to_match->server_url != $endpoint->server_url) {
01092 return new Auth_OpenID_FailureResponse($endpoint,
01093 sprintf('OP Endpoint mismatch. Expected %s, got %s',
01094 $to_match->server_url, $endpoint->server_url));
01095 }
01096
01097 return null;
01098 }
01099
01103 function _verifyDiscoveryResultsOpenID2($message, $endpoint)
01104 {
01105 $to_match = new Auth_OpenID_ServiceEndpoint();
01106 $to_match->type_uris = array(Auth_OpenID_TYPE_2_0);
01107 $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS,
01108 'claimed_id');
01109
01110 $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS,
01111 'identity');
01112
01113 $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS,
01114 'op_endpoint');
01115
01116 if ($to_match->server_url === null) {
01117 return new Auth_OpenID_FailureResponse($endpoint,
01118 "OP Endpoint URL missing");
01119 }
01120
01121
01122
01123 if (($to_match->claimed_id === null) &&
01124 ($to_match->local_id !== null)) {
01125 return new Auth_OpenID_FailureResponse($endpoint,
01126 'openid.identity is present without openid.claimed_id');
01127 }
01128
01129 if (($to_match->claimed_id !== null) &&
01130 ($to_match->local_id === null)) {
01131 return new Auth_OpenID_FailureResponse($endpoint,
01132 'openid.claimed_id is present without openid.identity');
01133 }
01134
01135 if ($to_match->claimed_id === null) {
01136
01137
01138
01139 return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
01140 $to_match->server_url);
01141 }
01142
01143 if (!$endpoint) {
01144
01145
01146
01147
01148
01149 return $this->_discoverAndVerify($to_match->claimed_id,
01150 array($to_match));
01151 } else {
01152
01153
01154
01155
01156 $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
01157
01158 if (Auth_OpenID::isFailure($result)) {
01159 $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
01160 array($to_match));
01161 if (Auth_OpenID::isFailure($endpoint)) {
01162 return $endpoint;
01163 }
01164 }
01165 }
01166
01167
01168
01169 if ($endpoint->claimed_id != $to_match->claimed_id) {
01170 $endpoint->claimed_id = $to_match->claimed_id;
01171 }
01172
01173 return $endpoint;
01174 }
01175
01179 function _discoverAndVerify($claimed_id, $to_match_endpoints)
01180 {
01181
01182 list($unused, $services) = call_user_func($this->discoverMethod,
01183 $claimed_id,
01184 $this->fetcher);
01185
01186 if (!$services) {
01187 return new Auth_OpenID_FailureResponse(null,
01188 sprintf("No OpenID information found at %s",
01189 $claimed_id));
01190 }
01191
01192 return $this->_verifyDiscoveryServices($claimed_id, $services,
01193 $to_match_endpoints);
01194 }
01195
01199 function _verifyDiscoveryServices($claimed_id,
01200 &$services, &$to_match_endpoints)
01201 {
01202
01203
01204
01205 foreach ($services as $endpoint) {
01206 foreach ($to_match_endpoints as $to_match_endpoint) {
01207 $result = $this->_verifyDiscoverySingle($endpoint,
01208 $to_match_endpoint);
01209
01210 if (!Auth_OpenID::isFailure($result)) {
01211
01212
01213 return $endpoint;
01214 }
01215 }
01216 }
01217
01218 return new Auth_OpenID_FailureResponse(null,
01219 sprintf('No matching endpoint found after discovering %s',
01220 $claimed_id));
01221 }
01222
01234 function _idResGetNonceOpenID1($message, $endpoint)
01235 {
01236 return $message->getArg(Auth_OpenID_BARE_NS,
01237 $this->openid1_nonce_query_arg_name);
01238 }
01239
01243 function _idResCheckNonce($message, $endpoint)
01244 {
01245 if ($message->isOpenID1()) {
01246
01247 $nonce = $this->_idResGetNonceOpenID1($message, $endpoint);
01248 $server_url = '';
01249 } else {
01250 $nonce = $message->getArg(Auth_OpenID_OPENID2_NS,
01251 'response_nonce');
01252
01253 $server_url = $endpoint->server_url;
01254 }
01255
01256 if ($nonce === null) {
01257 return new Auth_OpenID_FailureResponse($endpoint,
01258 "Nonce missing from response");
01259 }
01260
01261 $parts = Auth_OpenID_splitNonce($nonce);
01262
01263 if ($parts === null) {
01264 return new Auth_OpenID_FailureResponse($endpoint,
01265 "Malformed nonce in response");
01266 }
01267
01268 list($timestamp, $salt) = $parts;
01269
01270 if (!$this->store->useNonce($server_url, $timestamp, $salt)) {
01271 return new Auth_OpenID_FailureResponse($endpoint,
01272 "Nonce already used or out of range");
01273 }
01274
01275 return null;
01276 }
01277
01281 function _idResCheckForFields($message)
01282 {
01283 $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
01284 $basic_sig_fields = array('return_to', 'identity');
01285
01286 $require_fields = array(
01287 Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
01288 array('op_endpoint')),
01289
01290 Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
01291 array('identity'))
01292 );
01293
01294 $require_sigs = array(
01295 Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
01296 array('response_nonce',
01297 'claimed_id',
01298 'assoc_handle')),
01299 Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
01300 array('nonce'))
01301 );
01302
01303 foreach ($require_fields[$message->getOpenIDNamespace()] as $field) {
01304 if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
01305 return new Auth_OpenID_FailureResponse(null,
01306 "Missing required field '".$field."'");
01307 }
01308 }
01309
01310 $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
01311 'signed',
01312 Auth_OpenID_NO_DEFAULT);
01313 if (Auth_OpenID::isFailure($signed_list_str)) {
01314 return $signed_list_str;
01315 }
01316 $signed_list = explode(',', $signed_list_str);
01317
01318 foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
01319
01320 if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
01321 (!in_array($field, $signed_list))) {
01322 return new Auth_OpenID_FailureResponse(null,
01323 "'".$field."' not signed");
01324 }
01325 }
01326
01327 return null;
01328 }
01329
01333 function _checkAuth($message, $server_url)
01334 {
01335 $request = $this->_createCheckAuthRequest($message);
01336 if ($request === null) {
01337 return false;
01338 }
01339
01340 $resp_message = $this->_makeKVPost($request, $server_url);
01341 if (($resp_message === null) ||
01342 (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) {
01343 return false;
01344 }
01345
01346 return $this->_processCheckAuthResponse($resp_message, $server_url);
01347 }
01348
01352 function _createCheckAuthRequest($message)
01353 {
01354 $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
01355 if ($signed) {
01356 foreach (explode(',', $signed) as $k) {
01357 $value = $message->getAliasedArg($k);
01358 if ($value === null) {
01359 return null;
01360 }
01361 }
01362 }
01363 $ca_message = $message->copy();
01364 $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode',
01365 'check_authentication');
01366 return $ca_message;
01367 }
01368
01372 function _processCheckAuthResponse($response, $server_url)
01373 {
01374 $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid',
01375 'false');
01376
01377 $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS,
01378 'invalidate_handle');
01379
01380 if ($invalidate_handle !== null) {
01381 $this->store->removeAssociation($server_url,
01382 $invalidate_handle);
01383 }
01384
01385 if ($is_valid == 'true') {
01386 return true;
01387 }
01388
01389 return false;
01390 }
01391
01399 function _httpResponseToMessage($response, $server_url)
01400 {
01401
01402 $response_message = Auth_OpenID_Message::fromKVForm($response->body);
01403
01404 if ($response->status == 400) {
01405 return Auth_OpenID_ServerErrorContainer::fromMessage(
01406 $response_message);
01407 } else if ($response->status != 200 and $response->status != 206) {
01408 return null;
01409 }
01410
01411 return $response_message;
01412 }
01413
01417 function _makeKVPost($message, $server_url)
01418 {
01419 $body = $message->toURLEncoded();
01420 $resp = $this->fetcher->post($server_url, $body);
01421
01422 if ($resp === null) {
01423 return null;
01424 }
01425
01426 return $this->_httpResponseToMessage($resp, $server_url);
01427 }
01428
01432 function _getAssociation($endpoint)
01433 {
01434 if (!$this->_use_assocs) {
01435 return null;
01436 }
01437
01438 $assoc = $this->store->getAssociation($endpoint->server_url);
01439
01440 if (($assoc === null) ||
01441 ($assoc->getExpiresIn() <= 0)) {
01442
01443 $assoc = $this->_negotiateAssociation($endpoint);
01444
01445 if ($assoc !== null) {
01446 $this->store->storeAssociation($endpoint->server_url,
01447 $assoc);
01448 }
01449 }
01450
01451 return $assoc;
01452 }
01453
01463 function _extractSupportedAssociationType(&$server_error, &$endpoint,
01464 $assoc_type)
01465 {
01466
01467
01468 if (($server_error->error_code != 'unsupported-type') ||
01469 ($server_error->message->isOpenID1())) {
01470 return null;
01471 }
01472
01473
01474
01475
01476
01477
01478
01479 $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
01480 'assoc_type');
01481
01482 $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
01483 'session_type');
01484
01485 if (($assoc_type === null) || ($session_type === null)) {
01486 return null;
01487 } else if (!$this->negotiator->isAllowed($assoc_type,
01488 $session_type)) {
01489 return null;
01490 } else {
01491 return array($assoc_type, $session_type);
01492 }
01493 }
01494
01498 function _negotiateAssociation($endpoint)
01499 {
01500
01501 list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
01502
01503 $assoc = $this->_requestAssociation(
01504 $endpoint, $assoc_type, $session_type);
01505
01506 if (Auth_OpenID::isFailure($assoc)) {
01507 return null;
01508 }
01509
01510 if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
01511 $why = $assoc;
01512
01513 $supportedTypes = $this->_extractSupportedAssociationType(
01514 $why, $endpoint, $assoc_type);
01515
01516 if ($supportedTypes !== null) {
01517 list($assoc_type, $session_type) = $supportedTypes;
01518
01519
01520
01521
01522 $assoc = $this->_requestAssociation(
01523 $endpoint, $assoc_type, $session_type);
01524
01525 if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
01526
01527
01528
01529
01530
01531
01532 return null;
01533 } else {
01534 return $assoc;
01535 }
01536 } else {
01537 return null;
01538 }
01539 } else {
01540 return $assoc;
01541 }
01542 }
01543
01547 function _requestAssociation($endpoint, $assoc_type, $session_type)
01548 {
01549 list($assoc_session, $args) = $this->_createAssociateRequest(
01550 $endpoint, $assoc_type, $session_type);
01551
01552 $response_message = $this->_makeKVPost($args, $endpoint->server_url);
01553
01554 if ($response_message === null) {
01555
01556 return null;
01557 } else if (is_a($response_message,
01558 'Auth_OpenID_ServerErrorContainer')) {
01559 return $response_message;
01560 }
01561
01562 return $this->_extractAssociation($response_message, $assoc_session);
01563 }
01564
01568 function _extractAssociation(&$assoc_response, &$assoc_session)
01569 {
01570
01571
01572 $assoc_type = $assoc_response->getArg(
01573 Auth_OpenID_OPENID_NS, 'assoc_type',
01574 Auth_OpenID_NO_DEFAULT);
01575
01576 if (Auth_OpenID::isFailure($assoc_type)) {
01577 return $assoc_type;
01578 }
01579
01580 $assoc_handle = $assoc_response->getArg(
01581 Auth_OpenID_OPENID_NS, 'assoc_handle',
01582 Auth_OpenID_NO_DEFAULT);
01583
01584 if (Auth_OpenID::isFailure($assoc_handle)) {
01585 return $assoc_handle;
01586 }
01587
01588
01589
01590
01591
01592 $expires_in_str = $assoc_response->getArg(
01593 Auth_OpenID_OPENID_NS, 'expires_in',
01594 Auth_OpenID_NO_DEFAULT);
01595
01596 if (Auth_OpenID::isFailure($expires_in_str)) {
01597 return $expires_in_str;
01598 }
01599
01600 $expires_in = Auth_OpenID::intval($expires_in_str);
01601 if ($expires_in === false) {
01602
01603 $err = sprintf("Could not parse expires_in from association ".
01604 "response %s", print_r($assoc_response, true));
01605 return new Auth_OpenID_FailureResponse(null, $err);
01606 }
01607
01608
01609 if ($assoc_response->isOpenID1()) {
01610 $session_type = $this->_getOpenID1SessionType($assoc_response);
01611 } else {
01612 $session_type = $assoc_response->getArg(
01613 Auth_OpenID_OPENID2_NS, 'session_type',
01614 Auth_OpenID_NO_DEFAULT);
01615
01616 if (Auth_OpenID::isFailure($session_type)) {
01617 return $session_type;
01618 }
01619 }
01620
01621
01622 if ($assoc_session->session_type != $session_type) {
01623 if ($assoc_response->isOpenID1() &&
01624 ($session_type == 'no-encryption')) {
01625
01626
01627
01628
01629
01630 $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
01631 } else {
01632
01633
01634
01635 return null;
01636 }
01637 }
01638
01639
01640 if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
01641 return null;
01642 }
01643
01644
01645
01646
01647 $secret = $assoc_session->extractSecret($assoc_response);
01648
01649 if ($secret === null) {
01650 return null;
01651 }
01652
01653 return Auth_OpenID_Association::fromExpiresIn(
01654 $expires_in, $assoc_handle, $secret, $assoc_type);
01655 }
01656
01660 function _createAssociateRequest($endpoint, $assoc_type, $session_type)
01661 {
01662 if (array_key_exists($session_type, $this->session_types)) {
01663 $session_type_class = $this->session_types[$session_type];
01664
01665 if (is_callable($session_type_class)) {
01666 $assoc_session = $session_type_class();
01667 } else {
01668 $assoc_session = new $session_type_class();
01669 }
01670 } else {
01671 return null;
01672 }
01673
01674 $args = array(
01675 'mode' => 'associate',
01676 'assoc_type' => $assoc_type);
01677
01678 if (!$endpoint->compatibilityMode()) {
01679 $args['ns'] = Auth_OpenID_OPENID2_NS;
01680 }
01681
01682
01683
01684 if ((!$endpoint->compatibilityMode()) ||
01685 ($assoc_session->session_type != 'no-encryption')) {
01686 $args['session_type'] = $assoc_session->session_type;
01687 }
01688
01689 $args = array_merge($args, $assoc_session->getRequest());
01690 $message = Auth_OpenID_Message::fromOpenIDArgs($args);
01691 return array($assoc_session, $message);
01692 }
01693
01707 function _getOpenID1SessionType($assoc_response)
01708 {
01709
01710
01711 $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
01712 'session_type');
01713
01714
01715
01716
01717
01718
01719 if ($session_type == 'no-encryption') {
01720
01721
01722 } else if (($session_type == '') || ($session_type === null)) {
01723
01724
01725
01726
01727 $session_type = 'no-encryption';
01728 }
01729
01730 return $session_type;
01731 }
01732 }
01733
01740 class Auth_OpenID_AuthRequest {
01741
01750 function Auth_OpenID_AuthRequest(&$endpoint, $assoc)
01751 {
01752 $this->assoc = $assoc;
01753 $this->endpoint =& $endpoint;
01754 $this->return_to_args = array();
01755 $this->message = new Auth_OpenID_Message(
01756 $endpoint->preferredNamespace());
01757 $this->_anonymous = false;
01758 }
01759
01766 function addExtension(&$extension_request)
01767 {
01768 $extension_request->toMessage($this->message);
01769 }
01770
01790 function addExtensionArg($namespace, $key, $value)
01791 {
01792 return $this->message->setArg($namespace, $key, $value);
01793 }
01794
01804 function setAnonymous($is_anonymous)
01805 {
01806 if ($is_anonymous && $this->message->isOpenID1()) {
01807 return false;
01808 } else {
01809 $this->_anonymous = $is_anonymous;
01810 return true;
01811 }
01812 }
01813
01834 function getMessage($realm, $return_to=null, $immediate=false)
01835 {
01836 if ($return_to) {
01837 $return_to = Auth_OpenID::appendArgs($return_to,
01838 $this->return_to_args);
01839 } else if ($immediate) {
01840
01841
01842
01843 return new Auth_OpenID_FailureResponse(null,
01844 "'return_to' is mandatory when using checkid_immediate");
01845 } else if ($this->message->isOpenID1()) {
01846
01847
01848 return new Auth_OpenID_FailureResponse(null,
01849 "'return_to' is mandatory for OpenID 1 requests");
01850 } else if ($this->return_to_args) {
01851
01852
01853 return new Auth_OpenID_FailureResponse(null,
01854 "extra 'return_to' arguments where specified, " .
01855 "but no return_to was specified");
01856 }
01857
01858 if ($immediate) {
01859 $mode = 'checkid_immediate';
01860 } else {
01861 $mode = 'checkid_setup';
01862 }
01863
01864 $message = $this->message->copy();
01865 if ($message->isOpenID1()) {
01866 $realm_key = 'trust_root';
01867 } else {
01868 $realm_key = 'realm';
01869 }
01870
01871 $message->updateArgs(Auth_OpenID_OPENID_NS,
01872 array(
01873 $realm_key => $realm,
01874 'mode' => $mode,
01875 'return_to' => $return_to));
01876
01877 if (!$this->_anonymous) {
01878 if ($this->endpoint->isOPIdentifier()) {
01879
01880
01881
01882 $claimed_id = $request_identity =
01883 Auth_OpenID_IDENTIFIER_SELECT;
01884 } else {
01885 $request_identity = $this->endpoint->getLocalID();
01886 $claimed_id = $this->endpoint->claimed_id;
01887 }
01888
01889
01890 $message->setArg(Auth_OpenID_OPENID_NS, 'identity',
01891 $request_identity);
01892
01893 if ($message->isOpenID2()) {
01894 $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id',
01895 $claimed_id);
01896 }
01897 }
01898
01899 if ($this->assoc) {
01900 $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
01901 $this->assoc->handle);
01902 }
01903
01904 return $message;
01905 }
01906
01907 function redirectURL($realm, $return_to = null,
01908 $immediate = false)
01909 {
01910 $message = $this->getMessage($realm, $return_to, $immediate);
01911
01912 if (Auth_OpenID::isFailure($message)) {
01913 return $message;
01914 }
01915
01916 return $message->toURL($this->endpoint->server_url);
01917 }
01918
01927 function formMarkup($realm, $return_to=null, $immediate=false,
01928 $form_tag_attrs=null)
01929 {
01930 $message = $this->getMessage($realm, $return_to, $immediate);
01931
01932 if (Auth_OpenID::isFailure($message)) {
01933 return $message;
01934 }
01935
01936 return $message->toFormMarkup($this->endpoint->server_url,
01937 $form_tag_attrs);
01938 }
01939
01946 function htmlMarkup($realm, $return_to=null, $immediate=false,
01947 $form_tag_attrs=null)
01948 {
01949 $form = $this->formMarkup($realm, $return_to, $immediate,
01950 $form_tag_attrs);
01951
01952 if (Auth_OpenID::isFailure($form)) {
01953 return $form;
01954 }
01955 return Auth_OpenID::autoSubmitHTML($form);
01956 }
01957
01958 function shouldSendRedirect()
01959 {
01960 return $this->endpoint->compatibilityMode();
01961 }
01962 }
01963
01969 class Auth_OpenID_ConsumerResponse {
01970 var $status = null;
01971
01972 function setEndpoint($endpoint)
01973 {
01974 $this->endpoint = $endpoint;
01975 if ($endpoint === null) {
01976 $this->identity_url = null;
01977 } else {
01978 $this->identity_url = $endpoint->claimed_id;
01979 }
01980 }
01981
01999 function getDisplayIdentifier()
02000 {
02001 if ($this->endpoint !== null) {
02002 return $this->endpoint->getDisplayIdentifier();
02003 }
02004 return null;
02005 }
02006 }
02007
02023 class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse {
02024 var $status = Auth_OpenID_SUCCESS;
02025
02029 function Auth_OpenID_SuccessResponse($endpoint, $message, $signed_args=null)
02030 {
02031 $this->endpoint = $endpoint;
02032 $this->identity_url = $endpoint->claimed_id;
02033 $this->signed_args = $signed_args;
02034 $this->message = $message;
02035
02036 if ($this->signed_args === null) {
02037 $this->signed_args = array();
02038 }
02039 }
02040
02047 function extensionResponse($namespace_uri, $require_signed)
02048 {
02049 if ($require_signed) {
02050 return $this->getSignedNS($namespace_uri);
02051 } else {
02052 return $this->message->getArgs($namespace_uri);
02053 }
02054 }
02055
02056 function isOpenID1()
02057 {
02058 return $this->message->isOpenID1();
02059 }
02060
02061 function isSigned($ns_uri, $ns_key)
02062 {
02063
02064
02065 return in_array($this->message->getKey($ns_uri, $ns_key),
02066 $this->signed_args);
02067 }
02068
02069 function getSigned($ns_uri, $ns_key, $default = null)
02070 {
02071
02072
02073 if ($this->isSigned($ns_uri, $ns_key)) {
02074 return $this->message->getArg($ns_uri, $ns_key, $default);
02075 } else {
02076 return $default;
02077 }
02078 }
02079
02080 function getSignedNS($ns_uri)
02081 {
02082 $args = array();
02083
02084 $msg_args = $this->message->getArgs($ns_uri);
02085 if (Auth_OpenID::isFailure($msg_args)) {
02086 return null;
02087 }
02088
02089 foreach ($msg_args as $key => $value) {
02090 if (!$this->isSigned($ns_uri, $key)) {
02091 return null;
02092 }
02093 }
02094
02095 return $msg_args;
02096 }
02097
02108 function getReturnTo()
02109 {
02110 return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to');
02111 }
02112 }
02113
02129 class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse {
02130 var $status = Auth_OpenID_FAILURE;
02131
02132 function Auth_OpenID_FailureResponse($endpoint, $message = null,
02133 $contact = null, $reference = null)
02134 {
02135 $this->setEndpoint($endpoint);
02136 $this->message = $message;
02137 $this->contact = $contact;
02138 $this->reference = $reference;
02139 }
02140 }
02141
02147 class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse {
02148 }
02149
02156 class Auth_OpenID_ServerErrorContainer {
02157 function Auth_OpenID_ServerErrorContainer($error_text,
02158 $error_code,
02159 $message)
02160 {
02161 $this->error_text = $error_text;
02162 $this->error_code = $error_code;
02163 $this->message = $message;
02164 }
02165
02169 function fromMessage($message)
02170 {
02171 $error_text = $message->getArg(
02172 Auth_OpenID_OPENID_NS, 'error', '<no error message supplied>');
02173 $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code');
02174 return new Auth_OpenID_ServerErrorContainer($error_text,
02175 $error_code,
02176 $message);
02177 }
02178 }
02179
02192 class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse {
02193 var $status = Auth_OpenID_CANCEL;
02194
02195 function Auth_OpenID_CancelResponse($endpoint)
02196 {
02197 $this->setEndpoint($endpoint);
02198 }
02199 }
02200
02218 class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse {
02219 var $status = Auth_OpenID_SETUP_NEEDED;
02220
02221 function Auth_OpenID_SetupNeededResponse($endpoint,
02222 $setup_url = null)
02223 {
02224 $this->setEndpoint($endpoint);
02225 $this->setup_url = $setup_url;
02226 }
02227 }
02228
02229 ?>