1
2 """
3 This module documents the main interface with the OpenID consumer
4 library. The only part of the library which has to be used and isn't
5 documented in full here is the store required to create an
6 C{L{Consumer}} instance. More on the abstract store type and
7 concrete implementations of it that are provided in the documentation
8 for the C{L{__init__<Consumer.__init__>}} method of the
9 C{L{Consumer}} class.
10
11
12 OVERVIEW
13 ========
14
15 The OpenID identity verification process most commonly uses the
16 following steps, as visible to the user of this library:
17
18 1. The user enters their OpenID into a field on the consumer's
19 site, and hits a login button.
20
21 2. The consumer site discovers the user's OpenID server using
22 the YADIS protocol.
23
24 3. The consumer site sends the browser a redirect to the
25 identity server. This is the authentication request as
26 described in the OpenID specification.
27
28 4. The identity server's site sends the browser a redirect
29 back to the consumer site. This redirect contains the
30 server's response to the authentication request.
31
32 The most important part of the flow to note is the consumer's site
33 must handle two separate HTTP requests in order to perform the
34 full identity check.
35
36
37 LIBRARY DESIGN
38 ==============
39
40 This consumer library is designed with that flow in mind. The
41 goal is to make it as easy as possible to perform the above steps
42 securely.
43
44 At a high level, there are two important parts in the consumer
45 library. The first important part is this module, which contains
46 the interface to actually use this library. The second is the
47 C{L{openid.store.interface}} module, which describes the
48 interface to use if you need to create a custom method for storing
49 the state this library needs to maintain between requests.
50
51 In general, the second part is less important for users of the
52 library to know about, as several implementations are provided
53 which cover a wide variety of situations in which consumers may
54 use the library.
55
56 This module contains a class, C{L{Consumer}}, with methods
57 corresponding to the actions necessary in each of steps 2, 3, and
58 4 described in the overview. Use of this library should be as easy
59 as creating an C{L{Consumer}} instance and calling the methods
60 appropriate for the action the site wants to take.
61
62
63 STORES AND DUMB MODE
64 ====================
65
66 OpenID is a protocol that works best when the consumer site is
67 able to store some state. This is the normal mode of operation
68 for the protocol, and is sometimes referred to as smart mode.
69 There is also a fallback mode, known as dumb mode, which is
70 available when the consumer site is not able to store state. This
71 mode should be avoided when possible, as it leaves the
72 implementation more vulnerable to replay attacks.
73
74 The mode the library works in for normal operation is determined
75 by the store that it is given. The store is an abstraction that
76 handles the data that the consumer needs to manage between http
77 requests in order to operate efficiently and securely.
78
79 Several store implementation are provided, and the interface is
80 fully documented so that custom stores can be used as well. See
81 the documentation for the C{L{Consumer}} class for more
82 information on the interface for stores. The implementations that
83 are provided allow the consumer site to store the necessary data
84 in several different ways, including several SQL databases and
85 normal files on disk.
86
87 There is an additional concrete store provided that puts the
88 system in dumb mode. This is not recommended, as it removes the
89 library's ability to stop replay attacks reliably. It still uses
90 time-based checking to make replay attacks only possible within a
91 small window, but they remain possible within that window. This
92 store should only be used if the consumer site has no way to
93 retain data between requests at all.
94
95
96 IMMEDIATE MODE
97 ==============
98
99 In the flow described above, the user may need to confirm to the
100 identity server that it's ok to authorize his or her identity.
101 The server may draw pages asking for information from the user
102 before it redirects the browser back to the consumer's site. This
103 is generally transparent to the consumer site, so it is typically
104 ignored as an implementation detail.
105
106 There can be times, however, where the consumer site wants to get
107 a response immediately. When this is the case, the consumer can
108 put the library in immediate mode. In immediate mode, there is an
109 extra response possible from the server, which is essentially the
110 server reporting that it doesn't have enough information to answer
111 the question yet. In addition to saying that, the identity server
112 provides a URL to which the user can be sent to provide the needed
113 information and let the server finish handling the original
114 request.
115
116
117 USING THIS LIBRARY
118 ==================
119
120 Integrating this library into an application is usually a
121 relatively straightforward process. The process should basically
122 follow this plan:
123
124 Add an OpenID login field somewhere on your site. When an OpenID
125 is entered in that field and the form is submitted, it should make
126 a request to the your site which includes that OpenID URL.
127
128 First, the application should instantiate the C{L{Consumer}} class
129 using the store of choice. If the application has any sort of
130 session framework that provides per-client state management, a
131 dict-like object to access the session should be passed as the
132 optional second parameter. The library just expects the session
133 object to support a C{dict}-like interface, if it is provided.
134
135 Next, the application should call the 'begin' method on the
136 C{L{Consumer}} instance. This method takes the OpenID URL. The
137 C{L{begin<Consumer.begin>}} method returns an C{L{AuthRequest}}
138 object.
139
140 Next, the application should call the
141 C{L{redirectURL<AuthRequest.redirectURL>}} method on the
142 C{L{AuthRequest}} object. The parameter C{return_to} is the URL
143 that the OpenID server will send the user back to after attempting
144 to verify his or her identity. The C{trust_root} parameter is the
145 URL (or URL pattern) that identifies your web site to the user
146 when he or she is authorizing it. Send a redirect to the
147 resulting URL to the user's browser.
148
149 That's the first half of the authentication process. The second
150 half of the process is done after the user's ID server sends the
151 user's browser a redirect back to your site to complete their
152 login.
153
154 When that happens, the user will contact your site at the URL
155 given as the C{return_to} URL to the
156 C{L{redirectURL<AuthRequest.redirectURL>}} call made
157 above. The request will have several query parameters added to
158 the URL by the identity server as the information necessary to
159 finish the request.
160
161 Get an C{L{Consumer}} instance, and call its
162 C{L{complete<Consumer.complete>}} method, passing in all the
163 received query arguments.
164
165 There are multiple possible return types possible from that
166 method. These indicate the whether or not the login was
167 successful, and include any additional information appropriate for
168 their type.
169
170 @var SUCCESS: constant used as the status for
171 L{SuccessResponse<openid.consumer.consumer.SuccessResponse>} objects.
172
173 @var FAILURE: constant used as the status for
174 L{FailureResponse<openid.consumer.consumer.FailureResponse>} objects.
175
176 @var CANCEL: constant used as the status for
177 L{CancelResponse<openid.consumer.consumer.CancelResponse>} objects.
178
179 @var SETUP_NEEDED: constant used as the status for
180 L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>}
181 objects.
182 """
183
184 import string
185 import time
186 import urllib
187 import cgi
188 from urlparse import urlparse
189
190 from urljr import fetchers
191
192 from openid.consumer.discover import discover as openIDDiscover
193 from openid.consumer.discover import discoverXRI
194 from openid.consumer.discover import yadis_available, DiscoveryFailure
195 from openid import cryptutil
196 from openid import kvform
197 from openid import oidutil
198 from openid.association import Association
199 from openid.dh import DiffieHellman
200
201 __all__ = ['AuthRequest', 'Consumer', 'SuccessResponse',
202 'SetupNeededResponse', 'CancelResponse', 'FailureResponse',
203 'SUCCESS', 'FAILURE', 'CANCEL', 'SETUP_NEEDED',
204 ]
205
206 if yadis_available:
207 from yadis.manager import Discovery
208 from yadis import xri
209
211 """An OpenID consumer implementation that performs discovery and
212 does session management.
213
214 @ivar consumer: an instance of an object implementing the OpenID
215 protocol, but doing no discovery or session management.
216
217 @type consumer: GenericConsumer
218
219 @ivar session: A dictionary-like object representing the user's
220 session data. This is used for keeping state of the OpenID
221 transaction when the user is redirected to the server.
222
223 @cvar session_key_prefix: A string that is prepended to session
224 keys to ensure that they are unique. This variable may be
225 changed to suit your application.
226 """
227 session_key_prefix = "_openid_consumer_"
228
229 _token = 'last_token'
230
232 """Initialize a Consumer instance.
233
234 You should create a new instance of the Consumer object with
235 every HTTP request that handles OpenID transactions.
236
237 @param session: See L{the session instance variable<openid.consumer.consumer.Consumer.session>}
238
239 @param store: an object that implements the interface in
240 C{L{openid.store.interface.OpenIDStore}}. Several
241 implementations are provided, to cover common database
242 environments.
243
244 @type store: C{L{openid.store.interface.OpenIDStore}}
245
246 @see: L{openid.store.interface}
247 @see: L{openid.store}
248 """
249 self.session = session
250 self.consumer = GenericConsumer(store)
251 self._token_key = self.session_key_prefix + self._token
252
253 - def begin(self, user_url):
254 """Start the OpenID authentication process. See steps 1-2 in
255 the overview at the top of this file.
256
257 @param user_url: Identity URL given by the user. This method
258 performs a textual transformation of the URL to try and
259 make sure it is normalized. For example, a user_url of
260 example.com will be normalized to http://example.com/
261 normalizing and resolving any redirects the server might
262 issue.
263
264 @type user_url: str
265
266 @returns: An object containing the discovered information will
267 be returned, with a method for building a redirect URL to
268 the server, as described in step 3 of the overview. This
269 object may also be used to add extension arguments to the
270 request, using its
271 L{addExtensionArg<openid.consumer.consumer.AuthRequest.addExtensionArg>}
272 method.
273
274 @returntype: L{AuthRequest<openid.consumer.consumer.AuthRequest>}
275
276 @raises openid.consumer.discover.DiscoveryFailure: when I fail to
277 find an OpenID server for this URL. If the C{yadis} package
278 is available, L{openid.consumer.discover.DiscoveryFailure} is
279 an alias for C{yadis.discover.DiscoveryFailure}.
280 """
281 if yadis_available and xri.identifierScheme(user_url) == "XRI":
282 discoverMethod = discoverXRI
283 openid_url = user_url
284 else:
285 discoverMethod = openIDDiscover
286 openid_url = oidutil.normalizeUrl(user_url)
287
288 if yadis_available:
289 try:
290 disco = Discovery(self.session,
291 openid_url,
292 self.session_key_prefix)
293 service = disco.getNextService(discoverMethod)
294 except fetchers.HTTPFetchingError, e:
295 raise DiscoveryFailure('Error fetching XRDS document', e)
296 else:
297
298 _, services = openIDDiscover(user_url)
299 if not services:
300 service = None
301 else:
302 service = services[0]
303
304 if service is None:
305 raise DiscoveryFailure(
306 'No usable OpenID services found for %s' % (openid_url,), None)
307 else:
308 return self.beginWithoutDiscovery(service)
309
311 """Start OpenID verification without doing OpenID server
312 discovery. This method is used internally by Consumer.begin
313 after discovery is performed, and exists to provide an
314 interface for library users needing to perform their own
315 discovery.
316
317 @param service: an OpenID service endpoint descriptor. This
318 object and factories for it are found in the
319 L{openid.consumer.discover} module.
320
321 @type service:
322 L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>}
323
324 @returns: an OpenID authentication request object.
325
326 @rtype: L{AuthRequest<openid.consumer.consumer.AuthRequest>}
327
328 @See: Openid.consumer.consumer.Consumer.begin
329 @see: openid.consumer.discover
330 """
331 auth_req = self.consumer.begin(service)
332 self.session[self._token_key] = auth_req.endpoint
333 return auth_req
334
336 """Called to interpret the server's response to an OpenID
337 request. It is called in step 4 of the flow described in the
338 consumer overview.
339
340 @param query: A dictionary of the query parameters for this
341 HTTP request.
342
343 @returns: a subclass of Response. The type of response is
344 indicated by the status attribute, which will be one of
345 SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
346
347 @see: L{SuccessResponse<openid.consumer.consumer.SuccessResponse>}
348 @see: L{CancelResponse<openid.consumer.consumer.CancelResponse>}
349 @see: L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>}
350 @see: L{FailureResponse<openid.consumer.consumer.FailureResponse>}
351 """
352
353 endpoint = self.session.get(self._token_key)
354 if endpoint is None:
355 response = FailureResponse(None, 'No session state found')
356 else:
357 response = self.consumer.complete(query, endpoint)
358 del self.session[self._token_key]
359
360 if (response.status in ['success', 'cancel'] and
361 yadis_available and
362 response.identity_url is not None):
363
364 disco = Discovery(self.session,
365 response.identity_url,
366 self.session_key_prefix)
367
368
369 disco.cleanup()
370
371 return response
372
374 session_type = 'DH-SHA1'
375
381
383 cpub = cryptutil.longToBase64(self.dh.public)
384
385 args = {'openid.dh_consumer_public': cpub}
386
387 if not self.dh.usingDefaultValues():
388 args.update({
389 'openid.dh_modulus': cryptutil.longToBase64(self.dh.modulus),
390 'openid.dh_gen': cryptutil.longToBase64(self.dh.generator),
391 })
392
393 return args
394
396 spub = cryptutil.base64ToLong(response['dh_server_public'])
397 enc_mac_key = oidutil.fromBase64(response['enc_mac_key'])
398 return self.dh.xorSecret(spub, enc_mac_key)
399
401 session_type = None
402
403 - def getRequest(self):
404 return {}
405
407 return oidutil.fromBase64(response['mac_key'])
408
410 """This is the implementation of the common logic for OpenID
411 consumers. It is unaware of the application in which it is
412 running.
413 """
414
415 NONCE_LEN = 8
416 NONCE_CHRS = string.ascii_letters + string.digits
417
420
421 - def begin(self, service_endpoint):
422 nonce = self._createNonce()
423 assoc = self._getAssociation(service_endpoint.server_url)
424 request = AuthRequest(service_endpoint, assoc)
425 request.return_to_args['nonce'] = nonce
426 return request
427
429 mode = query.get('openid.mode', '<no mode specified>')
430
431 if isinstance(mode, list):
432 raise TypeError("query dict must have one value for each key, "
433 "not lists of values. Query is %r" % (query,))
434
435 if mode == 'cancel':
436 return CancelResponse(endpoint)
437 elif mode == 'error':
438 error = query.get('openid.error')
439 return FailureResponse(endpoint, error)
440 elif mode == 'id_res':
441 if endpoint.identity_url is None:
442 return FailureResponse(endpoint, 'No session state found')
443 try:
444 response = self._doIdRes(query, endpoint)
445 except fetchers.HTTPFetchingError, why:
446 message = 'HTTP request failed: %s' % (str(why),)
447 return FailureResponse(endpoint, message)
448 else:
449 if response.status == 'success':
450 return self._checkNonce(response, query.get('nonce'))
451 else:
452 return response
453 else:
454 return FailureResponse(endpoint,
455 'Invalid openid.mode: %r' % (mode,))
456
458 parsed_url = urlparse(response.getReturnTo())
459 query = parsed_url[4]
460 for k, v in cgi.parse_qsl(query):
461 if k == 'nonce':
462 if v != nonce:
463 return FailureResponse(response, 'Nonce mismatch')
464 else:
465 break
466 else:
467 return FailureResponse(response, 'Nonce missing from return_to: %r'
468 % (response.getReturnTo()))
469
470
471
472 if not self.store.useNonce(nonce):
473 return FailureResponse(response,
474 'Nonce missing from store')
475
476
477
478 return response
479
481 nonce = cryptutil.randomString(self.NONCE_LEN, self.NONCE_CHRS)
482 self.store.storeNonce(nonce)
483 return nonce
484
485 - def _makeKVPost(self, args, server_url):
486 mode = args['openid.mode']
487 body = urllib.urlencode(args)
488
489 resp = fetchers.fetch(server_url, body=body)
490 if resp is None:
491 fmt = 'openid.mode=%s: failed to fetch URL: %s'
492 oidutil.log(fmt % (mode, server_url))
493 return None
494
495 response = kvform.kvToDict(resp.body)
496 if resp.status == 400:
497 server_error = response.get('error', '<no message from server>')
498 fmt = 'openid.mode=%s: error returned from server %s: %s'
499 oidutil.log(fmt % (mode, server_url, server_error))
500 return None
501 elif resp.status != 200:
502 fmt = 'openid.mode=%s: bad status code from server %s: %s'
503 oidutil.log(fmt % (mode, server_url, resp.status))
504 return None
505
506 return response
507
509 """Handle id_res responses.
510
511 @param query: the response paramaters.
512 @param consumer_id: The normalized Claimed Identifier.
513 @param server_id: The Delegate Identifier.
514 @param server_url: OpenID server endpoint URL.
515
516 @returntype: L{Response}
517 """
518 user_setup_url = query.get('openid.user_setup_url')
519 if user_setup_url is not None:
520 return SetupNeededResponse(endpoint, user_setup_url)
521
522 return_to = query.get('openid.return_to')
523 server_id2 = query.get('openid.identity')
524 assoc_handle = query.get('openid.assoc_handle')
525
526 if return_to is None or server_id2 is None or assoc_handle is None:
527 return FailureResponse(endpoint, 'Missing required field')
528
529 if endpoint.getServerID() != server_id2:
530 return FailureResponse(endpoint, 'Server ID (delegate) mismatch')
531
532 signed = query.get('openid.signed')
533
534 assoc = self.store.getAssociation(endpoint.server_url, assoc_handle)
535
536 if assoc is None:
537
538
539 if self._checkAuth(query, endpoint.server_url):
540 return SuccessResponse.fromQuery(endpoint, query, signed)
541 else:
542 return FailureResponse(endpoint,
543 'Server denied check_authentication')
544
545 if assoc.expiresIn <= 0:
546
547
548
549
550
551 msg = 'Association with %s expired' % (endpoint.server_url,)
552 return FailureResponse(endpoint, msg)
553
554
555 sig = query.get('openid.sig')
556 if sig is None or signed is None:
557 return FailureResponse(endpoint, 'Missing argument signature')
558
559 signed_list = signed.split(',')
560
561
562 if endpoint.identity_url is not None and 'identity' not in signed_list:
563 msg = '"openid.identity" not signed'
564 return FailureResponse(endpoint, msg)
565
566 v_sig = assoc.signDict(signed_list, query)
567
568 if v_sig != sig:
569 return FailureResponse(endpoint, 'Bad signature')
570
571 return SuccessResponse.fromQuery(endpoint, query, signed)
572
574 request = self._createCheckAuthRequest(query)
575 if request is None:
576 return False
577 response = self._makeKVPost(request, server_url)
578 if response is None:
579 return False
580 return self._processCheckAuthResponse(response, server_url)
581
583 signed = query.get('openid.signed')
584 if signed is None:
585 oidutil.log('No signature present; checkAuth aborted')
586 return None
587
588
589
590 whitelist = ['assoc_handle', 'sig', 'signed', 'invalidate_handle']
591 signed = signed.split(',') + whitelist
592
593 check_args = dict([(k, v) for k, v in query.iteritems()
594 if k.startswith('openid.') and k[7:] in signed])
595
596 check_args['openid.mode'] = 'check_authentication'
597 return check_args
598
600 is_valid = response.get('is_valid', 'false')
601
602 invalidate_handle = response.get('invalidate_handle')
603 if invalidate_handle is not None:
604 self.store.removeAssociation(server_url, invalidate_handle)
605
606 if is_valid == 'true':
607 return True
608 else:
609 oidutil.log('Server responds that checkAuth call is not valid')
610 return False
611
613 if self.store.isDumb():
614 return None
615
616 assoc = self.store.getAssociation(server_url)
617
618 if assoc is None or assoc.expiresIn <= 0:
619 assoc_session, args = self._createAssociateRequest(server_url)
620 try:
621 response = self._makeKVPost(args, server_url)
622 except fetchers.HTTPFetchingError, why:
623 oidutil.log('openid.associate request failed: %s' %
624 (str(why),))
625 assoc = None
626 else:
627 assoc = self._parseAssociation(
628 response, assoc_session, server_url)
629
630 return assoc
631
633 proto = urlparse(server_url)[0]
634 if proto == 'https':
635 session_type = PlainTextConsumerSession
636 else:
637 session_type = DiffieHellmanConsumerSession
638
639 assoc_session = session_type()
640
641 args = {
642 'openid.mode': 'associate',
643 'openid.assoc_type':'HMAC-SHA1',
644 }
645
646 if assoc_session.session_type is not None:
647 args['openid.session_type'] = assoc_session.session_type
648
649 args.update(assoc_session.getRequest())
650 return assoc_session, args
651
653 try:
654 assoc_type = results['assoc_type']
655 assoc_handle = results['assoc_handle']
656 expires_in_str = results['expires_in']
657 except KeyError, e:
658 fmt = 'Getting association: missing key in response from %s: %s'
659 oidutil.log(fmt % (server_url, e[0]))
660 return None
661
662 if assoc_type != 'HMAC-SHA1':
663 fmt = 'Unsupported assoc_type returned from server %s: %s'
664 oidutil.log(fmt % (server_url, assoc_type))
665 return None
666
667 try:
668 expires_in = int(expires_in_str)
669 except ValueError, e:
670 fmt = 'Getting Association: invalid expires_in field: %s'
671 oidutil.log(fmt % (e[0],))
672 return None
673
674 session_type = results.get('session_type')
675 if session_type != assoc_session.session_type:
676 if session_type is None:
677 oidutil.log('Falling back to plain text association '
678 'session from %s' % assoc_session.session_type)
679 assoc_session = PlainTextConsumerSession()
680 else:
681 oidutil.log('Session type mismatch. Expected %r, got %r' %
682 (assoc_session.session_type, session_type))
683 return None
684
685 try:
686 secret = assoc_session.extractSecret(results)
687 except ValueError, why:
688 oidutil.log('Malformed response for %s session: %s' % (
689 assoc_session.session_type, why[0]))
690 return None
691 except KeyError, why:
692 fmt = 'Getting association: missing key in response from %s: %s'
693 oidutil.log(fmt % (server_url, why[0]))
694 return None
695
696 assoc = Association.fromExpiresIn(
697 expires_in, assoc_handle, secret, assoc_type)
698 self.store.storeAssociation(server_url, assoc)
699
700 return assoc
701
704 """
705 Creates a new AuthRequest object. This just stores each
706 argument in an appropriately named field.
707
708 Users of this library should not create instances of this
709 class. Instances of this class are created by the library
710 when needed.
711 """
712 self.assoc = assoc
713 self.endpoint = endpoint
714 self.extra_args = {}
715 self.return_to_args = {}
716
718 """Add an extension argument to this OpenID authentication
719 request.
720
721 Use caution when adding arguments, because they will be
722 URL-escaped and appended to the redirect URL, which can easily
723 get quite long.
724
725 @param namespace: The namespace for the extension. For
726 example, the simple registration extension uses the
727 namespace C{sreg}.
728
729 @type namespace: str
730
731 @param key: The key within the extension namespace. For
732 example, the nickname field in the simple registration
733 extension's key is C{nickname}.
734
735 @type key: str
736
737 @param value: The value to provide to the server for this
738 argument.
739
740 @type value: str
741 """
742 arg_name = '.'.join(['openid', namespace, key])
743 self.extra_args[arg_name] = value
744
745 - def redirectURL(self, trust_root, return_to, immediate=False):
746 if immediate:
747 mode = 'checkid_immediate'
748 else:
749 mode = 'checkid_setup'
750
751 return_to = oidutil.appendArgs(return_to, self.return_to_args)
752
753 redir_args = {
754 'openid.mode': mode,
755 'openid.identity': self.endpoint.getServerID(),
756 'openid.return_to': return_to,
757 'openid.trust_root': trust_root,
758 }
759
760 if self.assoc:
761 redir_args['openid.assoc_handle'] = self.assoc.handle
762
763 redir_args.update(self.extra_args)
764 return oidutil.appendArgs(self.endpoint.server_url, redir_args)
765
766 FAILURE = 'failure'
767 SUCCESS = 'success'
768 CANCEL = 'cancel'
769 SETUP_NEEDED = 'setup_needed'
770
773
775 """A response with a status of SUCCESS. Indicates that this request is a
776 successful acknowledgement from the OpenID server that the
777 supplied URL is, indeed controlled by the requesting agent.
778
779 @ivar identity_url: The identity URL that has been authenticated
780
781 @ivar endpoint: The endpoint that authenticated the identifier. You
782 may access other discovered information related to this endpoint,
783 such as the CanonicalID of an XRI, through this object.
784 @type endpoint: L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>}
785
786 @ivar signed_args: The arguments in the server's response that
787 were signed and verified.
788
789 @cvar status: SUCCESS
790 """
791
792 status = SUCCESS
793
794 - def __init__(self, endpoint, signed_args):
795 self.endpoint = endpoint
796 self.identity_url = endpoint.identity_url
797 self.signed_args = signed_args
798
799 - def fromQuery(cls, endpoint, query, signed):
800 signed_args = {}
801 for field_name in signed.split(','):
802 field_name = 'openid.' + field_name
803 signed_args[field_name] = query.get(field_name, '')
804 return cls(endpoint, signed_args)
805
806 fromQuery = classmethod(fromQuery)
807
809 """extract signed extension data from the server's response.
810
811 @param prefix: The extension namespace from which to extract
812 the extension data.
813 """
814 response = {}
815 prefix = 'openid.%s.' % (prefix,)
816 prefix_len = len(prefix)
817 for k, v in self.signed_args.iteritems():
818 if k.startswith(prefix):
819 response_key = k[prefix_len:]
820 response[response_key] = v
821
822 return response
823
825 """Get the openid.return_to argument from this response.
826
827 This is useful for verifying that this request was initiated
828 by this consumer.
829
830 @returns: The return_to URL supplied to the server on the
831 initial request, or C{None} if the response did not contain
832 an C{openid.return_to} argument.
833
834 @returntype: str
835 """
836 return self.signed_args.get('openid.return_to', None)
837
838
839
841 """A response with a status of FAILURE. Indicates that the OpenID
842 protocol has failed. This could be locally or remotely triggered.
843
844 @ivar identity_url: The identity URL for which authenitcation was
845 attempted, if it can be determined. Otherwise, None.
846
847 @ivar message: A message indicating why the request failed, if one
848 is supplied. otherwise, None.
849
850 @cvar status: FAILURE
851 """
852
853 status = FAILURE
854
855 - def __init__(self, endpoint, message=None):
856 self.endpoint = endpoint
857 if endpoint is not None:
858 self.identity_url = endpoint.identity_url
859 else:
860 self.identity_url = None
861 self.message = message
862
863
865 return "<%s.%s id=%r message=%r>" % (
866 self.__class__.__module__, self.__class__.__name__,
867 self.identity_url, self.message)
868
869
871 """A response with a status of CANCEL. Indicates that the user
872 cancelled the OpenID authentication request.
873
874 @ivar identity_url: The identity URL for which authenitcation was
875 attempted, if it can be determined. Otherwise, None.
876
877 @cvar status: CANCEL
878 """
879
880 status = CANCEL
881
883 self.endpoint = endpoint
884 self.identity_url = endpoint.identity_url
885
887 """A response with a status of SETUP_NEEDED. Indicates that the
888 request was in immediate mode, and the server is unable to
889 authenticate the user without further interaction.
890
891 @ivar identity_url: The identity URL for which authenitcation was
892 attempted.
893
894 @ivar setup_url: A URL that can be used to send the user to the
895 server to set up for authentication. The user should be
896 redirected in to the setup_url, either in the current window
897 or in a new browser window.
898
899 @cvar status: SETUP_NEEDED
900 """
901
902 status = SETUP_NEEDED
903
904 - def __init__(self, endpoint, setup_url=None):
905 self.endpoint = endpoint
906 self.identity_url = endpoint.identity_url
907 self.setup_url = setup_url
908