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