TYPO3 API  SVNRelease
Server.php
Go to the documentation of this file.
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 ?>