1
2 """OpenID support for Relying Parties (aka Consumers).
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 isn't
6 documented in full here is the store required to create an
7 C{L{Consumer}} instance. More on the abstract store type and
8 concrete implementations of it that are provided in the documentation
9 for the C{L{__init__<Consumer.__init__>}} method of the
10 C{L{Consumer}} class.
11
12
13 OVERVIEW
14 ========
15
16 The OpenID identity verification process most commonly uses the
17 following steps, as visible to the user of this library:
18
19 1. The user enters their OpenID into a field on the consumer's
20 site, and hits a login button.
21
22 2. The consumer site discovers the user's OpenID provider using
23 the Yadis protocol.
24
25 3. The consumer site sends the browser a redirect to the
26 OpenID provider. This is the authentication request as
27 described in the OpenID specification.
28
29 4. The OpenID provider's site sends the browser a redirect
30 back to the consumer site. This redirect contains the
31 provider's response to the authentication request.
32
33 The most important part of the flow to note is the consumer's site
34 must handle two separate HTTP requests in order to perform the
35 full identity check.
36
37
38 LIBRARY DESIGN
39 ==============
40
41 This consumer library is designed with that flow in mind. The
42 goal is to make it as easy as possible to perform the above steps
43 securely.
44
45 At a high level, there are two important parts in the consumer
46 library. The first important part is this module, which contains
47 the interface to actually use this library. The second is the
48 C{L{openid.store.interface}} module, which describes the
49 interface to use if you need to create a custom method for storing
50 the state this library needs to maintain between requests.
51
52 In general, the second part is less important for users of the
53 library to know about, as several implementations are provided
54 which cover a wide variety of situations in which consumers may
55 use the library.
56
57 This module contains a class, C{L{Consumer}}, with methods
58 corresponding to the actions necessary in each of steps 2, 3, and
59 4 described in the overview. Use of this library should be as easy
60 as creating an C{L{Consumer}} instance and calling the methods
61 appropriate for the action the site wants to take.
62
63
64 SESSIONS, STORES, AND STATELESS MODE
65 ====================================
66
67 The C{L{Consumer}} object keeps track of two types of state:
68
69 1. State of the user's current authentication attempt. Things like
70 the identity URL, the list of endpoints discovered for that
71 URL, and in case where some endpoints are unreachable, the list
72 of endpoints already tried. This state needs to be held from
73 Consumer.begin() to Consumer.complete(), but it is only applicable
74 to a single session with a single user agent, and at the end of
75 the authentication process (i.e. when an OP replies with either
76 C{id_res} or C{cancel}) it may be discarded.
77
78 2. State of relationships with servers, i.e. shared secrets
79 (associations) with servers and nonces seen on signed messages.
80 This information should persist from one session to the next and
81 should not be bound to a particular user-agent.
82
83
84 These two types of storage are reflected in the first two arguments of
85 Consumer's constructor, C{session} and C{store}. C{session} is a
86 dict-like object and we hope your web framework provides you with one
87 of these bound to the user agent. C{store} is an instance of
88 L{openid.store.interface.OpenIDStore}.
89
90 Since the store does hold secrets shared between your application and the
91 OpenID provider, you should be careful about how you use it in a shared
92 hosting environment. If the filesystem or database permissions of your
93 web host allow strangers to read from them, do not store your data there!
94 If you have no safe place to store your data, construct your consumer
95 with C{None} for the store, and it will operate only in stateless mode.
96 Stateless mode may be slower, put more load on the OpenID provider, and
97 trusts the provider to keep you safe from replay attacks.
98
99
100 Several store implementation are provided, and the interface is
101 fully documented so that custom stores can be used as well. See
102 the documentation for the C{L{Consumer}} class for more
103 information on the interface for stores. The implementations that
104 are provided allow the consumer site to store the necessary data
105 in several different ways, including several SQL databases and
106 normal files on disk.
107
108
109 IMMEDIATE MODE
110 ==============
111
112 In the flow described above, the user may need to confirm to the
113 OpenID provider that it's ok to disclose his or her identity.
114 The provider may draw pages asking for information from the user
115 before it redirects the browser back to the consumer's site. This
116 is generally transparent to the consumer site, so it is typically
117 ignored as an implementation detail.
118
119 There can be times, however, where the consumer site wants to get
120 a response immediately. When this is the case, the consumer can
121 put the library in immediate mode. In immediate mode, there is an
122 extra response possible from the server, which is essentially the
123 server reporting that it doesn't have enough information to answer
124 the question yet.
125
126
127 USING THIS LIBRARY
128 ==================
129
130 Integrating this library into an application is usually a
131 relatively straightforward process. The process should basically
132 follow this plan:
133
134 Add an OpenID login field somewhere on your site. When an OpenID
135 is entered in that field and the form is submitted, it should make
136 a request to your site which includes that OpenID URL.
137
138 First, the application should L{instantiate a Consumer<Consumer.__init__>}
139 with a session for per-user state and store for shared state.
140 using the store of choice.
141
142 Next, the application should call the 'C{L{begin<Consumer.begin>}}' method on the
143 C{L{Consumer}} instance. This method takes the OpenID URL. The
144 C{L{begin<Consumer.begin>}} method returns an C{L{AuthRequest}}
145 object.
146
147 Next, the application should call the
148 C{L{redirectURL<AuthRequest.redirectURL>}} method on the
149 C{L{AuthRequest}} object. The parameter C{return_to} is the URL
150 that the OpenID server will send the user back to after attempting
151 to verify his or her identity. The C{realm} parameter is the
152 URL (or URL pattern) that identifies your web site to the user
153 when he or she is authorizing it. Send a redirect to the
154 resulting URL to the user's browser.
155
156 That's the first half of the authentication process. The second
157 half of the process is done after the user's OpenID Provider sends the
158 user's browser a redirect back to your site to complete their
159 login.
160
161 When that happens, the user will contact your site at the URL
162 given as the C{return_to} URL to the
163 C{L{redirectURL<AuthRequest.redirectURL>}} call made
164 above. The request will have several query parameters added to
165 the URL by the OpenID provider as the information necessary to
166 finish the request.
167
168 Get a C{L{Consumer}} instance with the same session and store as
169 before and call its C{L{complete<Consumer.complete>}} method,
170 passing in all the received query arguments.
171
172 There are multiple possible return types possible from that
173 method. These indicate whether or not the login was successful,
174 and include any additional information appropriate for their type.
175
176 @var SUCCESS: constant used as the status for
177 L{SuccessResponse<openid.consumer.consumer.SuccessResponse>} objects.
178
179 @var FAILURE: constant used as the status for
180 L{FailureResponse<openid.consumer.consumer.FailureResponse>} objects.
181
182 @var CANCEL: constant used as the status for
183 L{CancelResponse<openid.consumer.consumer.CancelResponse>} objects.
184
185 @var SETUP_NEEDED: constant used as the status for
186 L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>}
187 objects.
188 """
189
190 import cgi
191 import copy
192 from urlparse import urlparse, urldefrag
193
194 from openid import fetchers
195
196 from openid.consumer.discover import discover, OpenIDServiceEndpoint, \
197 DiscoveryFailure, OPENID_1_0_TYPE, OPENID_1_1_TYPE, OPENID_2_0_TYPE
198 from openid.message import Message, OPENID_NS, OPENID2_NS, OPENID1_NS, \
199 IDENTIFIER_SELECT, no_default, BARE_NS
200 from openid import cryptutil
201 from openid import oidutil
202 from openid.association import Association, default_negotiator, \
203 SessionNegotiator
204 from openid.dh import DiffieHellman
205 from openid.store.nonce import mkNonce, split as splitNonce
206 from openid.yadis.manager import Discovery
207 from openid import urinorm
208
209
210 __all__ = ['AuthRequest', 'Consumer', 'SuccessResponse',
211 'SetupNeededResponse', 'CancelResponse', 'FailureResponse',
212 'SUCCESS', 'FAILURE', 'CANCEL', 'SETUP_NEEDED',
213 ]
214
215
216 -def makeKVPost(request_message, server_url):
217 """Make a Direct Request to an OpenID Provider and return the
218 result as a Message object.
219
220 @raises openid.fetchers.HTTPFetchingError: if an error is
221 encountered in making the HTTP post.
222
223 @rtype: L{openid.message.Message}
224 """
225
226 resp = fetchers.fetch(server_url, body=request_message.toURLEncoded())
227
228
229 return _httpResponseToMessage(resp, server_url)
230
231
233 """Adapt a POST response to a Message.
234
235 @type response: L{openid.fetchers.HTTPResponse}
236 @param response: Result of a POST to an OpenID endpoint.
237
238 @rtype: L{openid.message.Message}
239
240 @raises openid.fetchers.HTTPFetchingError: if the server returned a
241 status of other than 200 or 400.
242
243 @raises ServerError: if the server returned an OpenID error.
244 """
245
246 response_message = Message.fromKVForm(response.body)
247 if response.status == 400:
248 raise ServerError.fromMessage(response_message)
249
250 elif response.status not in (200, 206):
251 fmt = 'bad status code from server %s: %s'
252 error_message = fmt % (server_url, response.status)
253 raise fetchers.HTTPFetchingError(error_message)
254
255 return response_message
256
257
258
260 """An OpenID consumer implementation that performs discovery and
261 does session management.
262
263 @ivar consumer: an instance of an object implementing the OpenID
264 protocol, but doing no discovery or session management.
265
266 @type consumer: GenericConsumer
267
268 @ivar session: A dictionary-like object representing the user's
269 session data. This is used for keeping state of the OpenID
270 transaction when the user is redirected to the server.
271
272 @cvar session_key_prefix: A string that is prepended to session
273 keys to ensure that they are unique. This variable may be
274 changed to suit your application.
275 """
276 session_key_prefix = "_openid_consumer_"
277
278 _token = 'last_token'
279
280 _discover = staticmethod(discover)
281
282 - def __init__(self, session, store, consumer_class=None):
283 """Initialize a Consumer instance.
284
285 You should create a new instance of the Consumer object with
286 every HTTP request that handles OpenID transactions.
287
288 @param session: See L{the session instance variable<openid.consumer.consumer.Consumer.session>}
289
290 @param store: an object that implements the interface in
291 C{L{openid.store.interface.OpenIDStore}}. Several
292 implementations are provided, to cover common database
293 environments.
294
295 @type store: C{L{openid.store.interface.OpenIDStore}}
296
297 @see: L{openid.store.interface}
298 @see: L{openid.store}
299 """
300 self.session = session
301 if consumer_class is None:
302 consumer_class = GenericConsumer
303 self.consumer = consumer_class(store)
304 self._token_key = self.session_key_prefix + self._token
305
306 - def begin(self, user_url, anonymous=False):
307 """Start the OpenID authentication process. See steps 1-2 in
308 the overview at the top of this file.
309
310 @param user_url: Identity URL given by the user. This method
311 performs a textual transformation of the URL to try and
312 make sure it is normalized. For example, a user_url of
313 example.com will be normalized to http://example.com/
314 normalizing and resolving any redirects the server might
315 issue.
316
317 @type user_url: unicode
318
319 @param anonymous: Whether to make an anonymous request of the OpenID
320 provider. Such a request does not ask for an authorization
321 assertion for an OpenID identifier, but may be used with
322 extensions to pass other data. e.g. "I don't care who you are,
323 but I'd like to know your time zone."
324
325 @type anonymous: bool
326
327 @returns: An object containing the discovered information will
328 be returned, with a method for building a redirect URL to
329 the server, as described in step 3 of the overview. This
330 object may also be used to add extension arguments to the
331 request, using its
332 L{addExtensionArg<openid.consumer.consumer.AuthRequest.addExtensionArg>}
333 method.
334
335 @returntype: L{AuthRequest<openid.consumer.consumer.AuthRequest>}
336
337 @raises openid.consumer.discover.DiscoveryFailure: when I fail to
338 find an OpenID server for this URL. If the C{yadis} package
339 is available, L{openid.consumer.discover.DiscoveryFailure} is
340 an alias for C{yadis.discover.DiscoveryFailure}.
341 """
342 disco = Discovery(self.session, user_url, self.session_key_prefix)
343 try:
344 service = disco.getNextService(self._discover)
345 except fetchers.HTTPFetchingError, why:
346 raise DiscoveryFailure(
347 'Error fetching XRDS document: %s' % (why[0],), None)
348
349 if service is None:
350 raise DiscoveryFailure(
351 'No usable OpenID services found for %s' % (user_url,), None)
352 else:
353 return self.beginWithoutDiscovery(service, anonymous)
354
356 """Start OpenID verification without doing OpenID server
357 discovery. This method is used internally by Consumer.begin
358 after discovery is performed, and exists to provide an
359 interface for library users needing to perform their own
360 discovery.
361
362 @param service: an OpenID service endpoint descriptor. This
363 object and factories for it are found in the
364 L{openid.consumer.discover} module.
365
366 @type service:
367 L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>}
368
369 @returns: an OpenID authentication request object.
370
371 @rtype: L{AuthRequest<openid.consumer.consumer.AuthRequest>}
372
373 @See: Openid.consumer.consumer.Consumer.begin
374 @see: openid.consumer.discover
375 """
376 auth_req = self.consumer.begin(service)
377 self.session[self._token_key] = auth_req.endpoint
378
379 try:
380 auth_req.setAnonymous(anonymous)
381 except ValueError, why:
382 raise ProtocolError(str(why))
383
384 return auth_req
385
386 - def complete(self, query, current_url):
387 """Called to interpret the server's response to an OpenID
388 request. It is called in step 4 of the flow described in the
389 consumer overview.
390
391 @param query: A dictionary of the query parameters for this
392 HTTP request.
393
394 @param current_url: The URL used to invoke the application.
395 Extract the URL from your application's web
396 request framework and specify it here to have it checked
397 against the openid.return_to value in the response. If
398 the return_to URL check fails, the status of the
399 completion will be FAILURE.
400
401 @returns: a subclass of Response. The type of response is
402 indicated by the status attribute, which will be one of
403 SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
404
405 @see: L{SuccessResponse<openid.consumer.consumer.SuccessResponse>}
406 @see: L{CancelResponse<openid.consumer.consumer.CancelResponse>}
407 @see: L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>}
408 @see: L{FailureResponse<openid.consumer.consumer.FailureResponse>}
409 """
410
411 endpoint = self.session.get(self._token_key)
412
413 message = Message.fromPostArgs(query)
414 response = self.consumer.complete(message, endpoint, current_url)
415
416 try:
417 del self.session[self._token_key]
418 except KeyError:
419 pass
420
421 if (response.status in ['success', 'cancel'] and
422 response.identity_url is not None):
423
424 disco = Discovery(self.session,
425 response.identity_url,
426 self.session_key_prefix)
427
428
429 disco.cleanup(force=True)
430
431 return response
432
434 """Set the order in which association types/sessions should be
435 attempted. For instance, to only allow HMAC-SHA256
436 associations created with a DH-SHA256 association session:
437
438 >>> consumer.setAssociationPreference([('HMAC-SHA256', 'DH-SHA256')])
439
440 Any association type/association type pair that is not in this
441 list will not be attempted at all.
442
443 @param association_preferences: The list of allowed
444 (association type, association session type) pairs that
445 should be allowed for this consumer to use, in order from
446 most preferred to least preferred.
447 @type association_preferences: [(str, str)]
448
449 @returns: None
450
451 @see: C{L{openid.association.SessionNegotiator}}
452 """
453 self.consumer.negotiator = SessionNegotiator(association_preferences)
454
487
493
495 session_type = 'no-encryption'
496 allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']
497
498 - def getRequest(self):
500
502 mac_key64 = response.getArg(OPENID_NS, 'mac_key', no_default)
503 return oidutil.fromBase64(mac_key64)
504
506 """Internally-used exception that indicates that an immediate-mode
507 request cancelled."""
508 - def __init__(self, user_setup_url=None):
509 Exception.__init__(self, user_setup_url)
510 self.user_setup_url = user_setup_url
511
513 """Exception that indicates that a message violated the
514 protocol. It is raised and caught internally to this file."""
515
517 """A protocol error arising from type URIs mismatching
518 """
519
520 - def __init__(self, expected, endpoint):
524
526 s = '<%s.%s: Required type %s not found in %s for endpoint %s>' % (
527 self.__class__.__module__, self.__class__.__name__,
528 self.expected, self.endpoint.type_uris, self.endpoint)
529 return s
530
531
532
534 """Exception that is raised when the server returns a 400 response
535 code to a direct request."""
536
537 - def __init__(self, error_text, error_code, message):
538 Exception.__init__(self, error_text)
539 self.error_text = error_text
540 self.error_code = error_code
541 self.message = message
542
544 """Generate a ServerError instance, extracting the error text
545 and the error code from the message."""
546 error_text = message.getArg(
547 OPENID_NS, 'error', '<no error message supplied>')
548 error_code = message.getArg(OPENID_NS, 'error_code')
549 return cls(error_text, error_code, message)
550
551 fromMessage = classmethod(fromMessage)
552
554 """This is the implementation of the common logic for OpenID
555 consumers. It is unaware of the application in which it is
556 running.
557
558 @ivar negotiator: An object that controls the kind of associations
559 that the consumer makes. It defaults to
560 C{L{openid.association.default_negotiator}}. Assign a
561 different negotiator to it if you have specific requirements
562 for how associations are made.
563 @type negotiator: C{L{openid.association.SessionNegotiator}}
564 """
565
566
567
568
569
570
571
572 openid1_nonce_query_arg_name = 'janrain_nonce'
573
574
575
576
577 openid1_return_to_identifier_name = 'openid1_claimed_id'
578
579 session_types = {
580 'DH-SHA1':DiffieHellmanSHA1ConsumerSession,
581 'DH-SHA256':DiffieHellmanSHA256ConsumerSession,
582 'no-encryption':PlainTextConsumerSession,
583 }
584
585 _discover = staticmethod(discover)
586
590
591 - def begin(self, service_endpoint):
608
609 - def complete(self, message, endpoint, return_to):
610 """Process the OpenID message, using the specified endpoint
611 and return_to URL as context. This method will handle any
612 OpenID message that is sent to the return_to URL.
613 """
614 mode = message.getArg(OPENID_NS, 'mode', '<No mode set>')
615
616 modeMethod = getattr(self, '_complete_' + mode,
617 self._completeInvalid)
618
619 return modeMethod(message, endpoint, return_to)
620
623
631
638
649
654
656 """Check an OpenID message and its openid.return_to value
657 against a return_to URL from an application. Return True on
658 success, False on failure.
659 """
660
661
662 try:
663 self._verifyReturnToArgs(message.toPostArgs())
664 except ProtocolError, why:
665 oidutil.log("Verifying return_to arguments: %s" % (why[0],))
666 return False
667
668
669 msg_return_to = message.getArg(OPENID_NS, 'return_to')
670
671
672
673 app_parts = urlparse(urinorm.urinorm(return_to))
674 msg_parts = urlparse(urinorm.urinorm(msg_return_to))
675
676
677
678 for part in range(0, 3):
679 if app_parts[part] != msg_parts[part]:
680 return False
681
682 return True
683
684 _makeKVPost = staticmethod(makeKVPost)
685
687 """Check an id_res message to see if it is a
688 checkid_immediate cancel response.
689
690 @raises SetupNeededError: if it is a checkid_immediate cancellation
691 """
692
693
694
695 if message.isOpenID1():
696 user_setup_url = message.getArg(OPENID1_NS, 'user_setup_url')
697 if user_setup_url is not None:
698 raise SetupNeededError(user_setup_url)
699
700 - def _doIdRes(self, message, endpoint, return_to):
701 """Handle id_res responses that are not cancellations of
702 immediate mode requests.
703
704 @param message: the response paramaters.
705 @param endpoint: the discovered endpoint object. May be None.
706
707 @raises ProtocolError: If the message contents are not
708 well-formed according to the OpenID specification. This
709 includes missing fields or not signing fields that should
710 be signed.
711
712 @raises DiscoveryFailure: If the subject of the id_res message
713 does not match the supplied endpoint, and discovery on the
714 identifier in the message fails (this should only happen
715 when using OpenID 2)
716
717 @returntype: L{Response}
718 """
719
720
721 self._idResCheckForFields(message)
722
723 if not self._checkReturnTo(message, return_to):
724 raise ProtocolError(
725 "return_to does not match return URL. Expected %r, got %r"
726 % (return_to, message.getArg(OPENID_NS, 'return_to')))
727
728
729
730 endpoint = self._verifyDiscoveryResults(message, endpoint)
731 oidutil.log("Received id_res response from %s using association %s" %
732 (endpoint.server_url,
733 message.getArg(OPENID_NS, 'assoc_handle')))
734
735 self._idResCheckSignature(message, endpoint.server_url)
736
737
738 self._idResCheckNonce(message, endpoint)
739
740 signed_list_str = message.getArg(OPENID_NS, 'signed', no_default)
741 signed_list = signed_list_str.split(',')
742 signed_fields = ["openid." + s for s in signed_list]
743 return SuccessResponse(endpoint, message, signed_fields)
744
746 """Extract the nonce from an OpenID 1 response. Return the
747 nonce from the BARE_NS since we independently check the
748 return_to arguments are the same as those in the response
749 message.
750
751 See the openid1_nonce_query_arg_name class variable
752
753 @returns: The nonce as a string or None
754 """
755 return message.getArg(BARE_NS, self.openid1_nonce_query_arg_name)
756
777
805
807
808
809
810
811
812
813 basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
814 basic_sig_fields = ['return_to', 'identity']
815
816 require_fields = {
817 OPENID2_NS: basic_fields + ['op_endpoint'],
818 OPENID1_NS: basic_fields + ['identity'],
819 }
820
821 require_sigs = {
822 OPENID2_NS: basic_sig_fields + ['response_nonce',
823 'claimed_id',
824 'assoc_handle',
825 'op_endpoint',],
826 OPENID1_NS: basic_sig_fields,
827 }
828
829 for field in require_fields[message.getOpenIDNamespace()]:
830 if not message.hasKey(OPENID_NS, field):
831 raise ProtocolError('Missing required field %r' % (field,))
832
833 signed_list_str = message.getArg(OPENID_NS, 'signed', no_default)
834 signed_list = signed_list_str.split(',')
835
836 for field in require_sigs[message.getOpenIDNamespace()]:
837
838 if message.hasKey(OPENID_NS, field) and field not in signed_list:
839 raise ProtocolError('"%s" not signed' % (field,))
840
841
843 """Verify that the arguments in the return_to URL are present in this
844 response.
845 """
846 message = Message.fromPostArgs(query)
847 return_to = message.getArg(OPENID_NS, 'return_to')
848
849 if return_to is None:
850 raise ProtocolError('Response has no return_to')
851
852 parsed_url = urlparse(return_to)
853 rt_query = parsed_url[4]
854 parsed_args = cgi.parse_qsl(rt_query)
855
856 for rt_key, rt_value in parsed_args:
857 try:
858 value = query[rt_key]
859 if rt_value != value:
860 format = ("parameter %s value %r does not match "
861 "return_to's value %r")
862 raise ProtocolError(format % (rt_key, value, rt_value))
863 except KeyError:
864 format = "return_to parameter %s absent from query %r"
865 raise ProtocolError(format % (rt_key, query))
866
867
868
869 bare_args = message.getArgs(BARE_NS)
870 for pair in bare_args.iteritems():
871 if pair not in parsed_args:
872 raise ProtocolError("Parameter %s not in return_to URL" % (pair[0],))
873
874 _verifyReturnToArgs = staticmethod(_verifyReturnToArgs)
875
877 """
878 Extract the information from an OpenID assertion message and
879 verify it against the original
880
881 @param endpoint: The endpoint that resulted from doing discovery
882 @param resp_msg: The id_res message object
883
884 @returns: the verified endpoint
885 """
886 if resp_msg.getOpenIDNamespace() == OPENID2_NS:
887 return self._verifyDiscoveryResultsOpenID2(resp_msg, endpoint)
888 else:
889 return self._verifyDiscoveryResultsOpenID1(resp_msg, endpoint)
890
891
947
949 claimed_id = resp_msg.getArg(BARE_NS, self.openid1_return_to_identifier_name)
950
951 if endpoint is None and claimed_id is None:
952 raise RuntimeError(
953 'When using OpenID 1, the claimed ID must be supplied, '
954 'either by passing it through as a return_to parameter '
955 'or by using a session, and supplied to the GenericConsumer '
956 'as the argument to complete()')
957 elif endpoint is not None and claimed_id is None:
958 claimed_id = endpoint.claimed_id
959
960 to_match = OpenIDServiceEndpoint()
961 to_match.type_uris = [OPENID_1_1_TYPE]
962 to_match.local_id = resp_msg.getArg(OPENID1_NS, 'identity')
963
964 to_match.claimed_id = claimed_id
965
966 if to_match.local_id is None:
967 raise ProtocolError('Missing required field openid.identity')
968
969 to_match_1_0 = copy.copy(to_match)
970 to_match_1_0.type_uris = [OPENID_1_0_TYPE]
971
972 if endpoint is not None:
973 try:
974 try:
975 self._verifyDiscoverySingle(endpoint, to_match)
976 except TypeURIMismatch:
977 self._verifyDiscoverySingle(endpoint, to_match_1_0)
978 except ProtocolError, e:
979 oidutil.log("Error attempting to use stored discovery information: " +
980 str(e))
981 oidutil.log("Attempting discovery to verify endpoint")
982 else:
983 return endpoint
984
985
986 return self._discoverAndVerify(claimed_id, [to_match, to_match_1_0])
987
989 """Verify that the given endpoint matches the information
990 extracted from the OpenID assertion, and raise an exception if
991 there is a mismatch.
992
993 @type endpoint: openid.consumer.discover.OpenIDServiceEndpoint
994 @type to_match: openid.consumer.discover.OpenIDServiceEndpoint
995
996 @rtype: NoneType
997
998 @raises ProtocolError: when the endpoint does not match the
999 discovered information.
1000 """
1001
1002
1003 for type_uri in to_match.type_uris:
1004 if not endpoint.usesExtension(type_uri):
1005 raise TypeURIMismatch(type_uri, endpoint)
1006
1007
1008
1009 defragged_claimed_id, _ = urldefrag(to_match.claimed_id)
1010 if defragged_claimed_id != endpoint.claimed_id:
1011 raise ProtocolError(
1012 'Claimed ID does not match (different subjects!), '
1013 'Expected %s, got %s' %
1014 (defragged_claimed_id, endpoint.claimed_id))
1015
1016 if to_match.getLocalID() != endpoint.getLocalID():
1017 raise ProtocolError('local_id mismatch. Expected %s, got %s' %
1018 (to_match.getLocalID(), endpoint.getLocalID()))
1019
1020
1021
1022
1023
1024
1025 if to_match.server_url is None:
1026 assert to_match.preferredNamespace() == OPENID1_NS, (
1027 """The code calling this must ensure that OpenID 2
1028 responses have a non-none `openid.op_endpoint' and
1029 that it is set as the `server_url' attribute of the
1030 `to_match' endpoint.""")
1031
1032 elif to_match.server_url != endpoint.server_url:
1033 raise ProtocolError('OP Endpoint mismatch. Expected %s, got %s' %
1034 (to_match.server_url, endpoint.server_url))
1035
1037 """Given an endpoint object created from the information in an
1038 OpenID response, perform discovery and verify the discovery
1039 results, returning the matching endpoint that is the result of
1040 doing that discovery.
1041
1042 @type to_match: openid.consumer.discover.OpenIDServiceEndpoint
1043 @param to_match: The endpoint whose information we're confirming
1044
1045 @rtype: openid.consumer.discover.OpenIDServiceEndpoint
1046 @returns: The result of performing discovery on the claimed
1047 identifier in `to_match'
1048
1049 @raises DiscoveryFailure: when discovery fails.
1050 """
1051 oidutil.log('Performing discovery on %s' % (claimed_id,))
1052 _, services = self._discover(claimed_id)
1053 if not services:
1054 raise DiscoveryFailure('No OpenID information found at %s' %
1055 (claimed_id,), None)
1056 return self._verifyDiscoveredServices(claimed_id, services,
1057 to_match_endpoints)
1058
1059
1061 """See @L{_discoverAndVerify}"""
1062
1063
1064
1065 failure_messages = []
1066 for endpoint in services:
1067 for to_match_endpoint in to_match_endpoints:
1068 try:
1069 self._verifyDiscoverySingle(
1070 endpoint, to_match_endpoint)
1071 except ProtocolError, why:
1072 failure_messages.append(str(why))
1073 else:
1074
1075
1076 return endpoint
1077 else:
1078 oidutil.log('Discovery verification failure for %s' %
1079 (claimed_id,))
1080 for failure_message in failure_messages:
1081 oidutil.log(' * Endpoint mismatch: ' + failure_message)
1082
1083 raise DiscoveryFailure(
1084 'No matching endpoint found after discovering %s'
1085 % (claimed_id,), None)
1086
1088 """Make a check_authentication request to verify this message.
1089
1090 @returns: True if the request is valid.
1091 @rtype: bool
1092 """
1093 oidutil.log('Using OpenID check_authentication')
1094 request = self._createCheckAuthRequest(message)
1095 if request is None:
1096 return False
1097 try:
1098 response = self._makeKVPost(request, server_url)
1099 except (fetchers.HTTPFetchingError, ServerError), e:
1100 oidutil.log('check_authentication failed: %s' % (e[0],))
1101 return False
1102 else:
1103 return self._processCheckAuthResponse(response, server_url)
1104
1106 """Generate a check_authentication request message given an
1107 id_res message.
1108 """
1109 signed = message.getArg(OPENID_NS, 'signed')
1110 if signed:
1111 for k in signed.split(','):
1112 oidutil.log(k)
1113 val = message.getAliasedArg(k)
1114
1115
1116 if val is None:
1117 oidutil.log('Missing signed field %r' % (k,))
1118 return None
1119
1120 check_auth_message = message.copy()
1121 check_auth_message.setArg(OPENID_NS, 'mode', 'check_authentication')
1122 return check_auth_message
1123
1125 """Process the response message from a check_authentication
1126 request, invalidating associations if requested.
1127 """
1128 is_valid = response.getArg(OPENID_NS, 'is_valid', 'false')
1129
1130 invalidate_handle = response.getArg(OPENID_NS, 'invalidate_handle')
1131 if invalidate_handle is not None:
1132 oidutil.log(
1133 'Received "invalidate_handle" from server %s' % (server_url,))
1134 if self.store is None:
1135 oidutil.log('Unexpectedly got invalidate_handle without '
1136 'a store!')
1137 else:
1138 self.store.removeAssociation(server_url, invalidate_handle)
1139
1140 if is_valid == 'true':
1141 return True
1142 else:
1143 oidutil.log('Server responds that checkAuth call is not valid')
1144 return False
1145
1147 """Get an association for the endpoint's server_url.
1148
1149 First try seeing if we have a good association in the
1150 store. If we do not, then attempt to negotiate an association
1151 with the server.
1152
1153 If we negotiate a good association, it will get stored.
1154
1155 @returns: A valid association for the endpoint's server_url or None
1156 @rtype: openid.association.Association or NoneType
1157 """
1158 assoc = self.store.getAssociation(endpoint.server_url)
1159
1160 if assoc is None or assoc.expiresIn <= 0:
1161 assoc = self._negotiateAssociation(endpoint)
1162 if assoc is not None:
1163 self.store.storeAssociation(endpoint.server_url, assoc)
1164
1165 return assoc
1166
1168 """Make association requests to the server, attempting to
1169 create a new association.
1170
1171 @returns: a new association object
1172
1173 @rtype: L{openid.association.Association}
1174 """
1175
1176 assoc_type, session_type = self.negotiator.getAllowedType()
1177
1178 try:
1179 assoc = self._requestAssociation(
1180 endpoint, assoc_type, session_type)
1181 except ServerError, why:
1182 supportedTypes = self._extractSupportedAssociationType(why,
1183 endpoint,
1184 assoc_type)
1185 if supportedTypes is not None:
1186 assoc_type, session_type = supportedTypes
1187
1188
1189
1190 try:
1191 assoc = self._requestAssociation(
1192 endpoint, assoc_type, session_type)
1193 except ServerError, why:
1194
1195
1196 oidutil.log('Server %s refused its suggested association '
1197 'type: session_type=%s, assoc_type=%s'
1198 % (endpoint.server_url, session_type,
1199 assoc_type))
1200 return None
1201 else:
1202 return assoc
1203 else:
1204 return assoc
1205
1208 """Handle ServerErrors resulting from association requests.
1209
1210 @returns: If server replied with an C{unsupported-type} error,
1211 return a tuple of supported C{association_type}, C{session_type}.
1212 Otherwise logs the error and returns None.
1213 @rtype: tuple or None
1214 """
1215
1216
1217 if server_error.error_code != 'unsupported-type' or \
1218 server_error.message.isOpenID1():
1219 oidutil.log(
1220 'Server error when requesting an association from %r: %s'
1221 % (endpoint.server_url, server_error.error_text))
1222 return None
1223
1224
1225
1226
1227 oidutil.log(
1228 'Unsupported association type %s: %s' % (assoc_type,
1229 server_error.error_text,))
1230
1231
1232
1233 assoc_type = server_error.message.getArg(OPENID_NS, 'assoc_type')
1234 session_type = server_error.message.getArg(OPENID_NS, 'session_type')
1235
1236 if assoc_type is None or session_type is None:
1237 oidutil.log('Server responded with unsupported association '
1238 'session but did not supply a fallback.')
1239 return None
1240 elif not self.negotiator.isAllowed(assoc_type, session_type):
1241 fmt = ('Server sent unsupported session/association type: '
1242 'session_type=%s, assoc_type=%s')
1243 oidutil.log(fmt % (session_type, assoc_type))
1244 return None
1245 else:
1246 return assoc_type, session_type
1247
1248
1250 """Make and process one association request to this endpoint's
1251 OP endpoint URL.
1252
1253 @returns: An association object or None if the association
1254 processing failed.
1255
1256 @raises ServerError: when the remote OpenID server returns an error.
1257 """
1258 assoc_session, args = self._createAssociateRequest(
1259 endpoint, assoc_type, session_type)
1260
1261 try:
1262 response = self._makeKVPost(args, endpoint.server_url)
1263 except fetchers.HTTPFetchingError, why:
1264 oidutil.log('openid.associate request failed: %s' % (why[0],))
1265 return None
1266
1267 try:
1268 assoc = self._extractAssociation(response, assoc_session)
1269 except KeyError, why:
1270 oidutil.log('Missing required parameter in response from %s: %s'
1271 % (endpoint.server_url, why[0]))
1272 return None
1273 except ProtocolError, why:
1274 oidutil.log('Protocol error parsing response from %s: %s' % (
1275 endpoint.server_url, why[0]))
1276 return None
1277 else:
1278 return assoc
1279
1281 """Create an association request for the given assoc_type and
1282 session_type.
1283
1284 @param endpoint: The endpoint whose server_url will be
1285 queried. The important bit about the endpoint is whether
1286 it's in compatiblity mode (OpenID 1.1)
1287
1288 @param assoc_type: The association type that the request
1289 should ask for.
1290 @type assoc_type: str
1291
1292 @param session_type: The session type that should be used in
1293 the association request. The session_type is used to
1294 create an association session object, and that session
1295 object is asked for any additional fields that it needs to
1296 add to the request.
1297 @type session_type: str
1298
1299 @returns: a pair of the association session object and the
1300 request message that will be sent to the server.
1301 @rtype: (association session type (depends on session_type),
1302 openid.message.Message)
1303 """
1304 session_type_class = self.session_types[session_type]
1305 assoc_session = session_type_class()
1306
1307 args = {
1308 'mode': 'associate',
1309 'assoc_type': assoc_type,
1310 }
1311
1312 if not endpoint.compatibilityMode():
1313 args['ns'] = OPENID2_NS
1314
1315
1316
1317 if (not endpoint.compatibilityMode() or
1318 assoc_session.session_type != 'no-encryption'):
1319 args['session_type'] = assoc_session.session_type
1320
1321 args.update(assoc_session.getRequest())
1322 message = Message.fromOpenIDArgs(args)
1323 return assoc_session, message
1324
1326 """Given an association response message, extract the OpenID
1327 1.X session type.
1328
1329 This function mostly takes care of the 'no-encryption' default
1330 behavior in OpenID 1.
1331
1332 If the association type is plain-text, this function will
1333 return 'no-encryption'
1334
1335 @returns: The association type for this message
1336 @rtype: str
1337
1338 @raises KeyError: when the session_type field is absent.
1339 """
1340
1341
1342 session_type = assoc_response.getArg(OPENID1_NS, 'session_type')
1343
1344
1345
1346
1347
1348
1349
1350 if session_type == 'no-encryption':
1351 oidutil.log('WARNING: OpenID server sent "no-encryption"'
1352 'for OpenID 1.X')
1353
1354
1355
1356
1357
1358 elif session_type == '' or session_type is None:
1359 session_type = 'no-encryption'
1360
1361 return session_type
1362
1364 """Attempt to extract an association from the response, given
1365 the association response message and the established
1366 association session.
1367
1368 @param assoc_response: The association response message from
1369 the server
1370 @type assoc_response: openid.message.Message
1371
1372 @param assoc_session: The association session object that was
1373 used when making the request
1374 @type assoc_session: depends on the session type of the request
1375
1376 @raises ProtocolError: when data is malformed
1377 @raises KeyError: when a field is missing
1378
1379 @rtype: openid.association.Association
1380 """
1381
1382
1383 assoc_type = assoc_response.getArg(
1384 OPENID_NS, 'assoc_type', no_default)
1385 assoc_handle = assoc_response.getArg(
1386 OPENID_NS, 'assoc_handle', no_default)
1387
1388
1389
1390
1391
1392 expires_in_str = assoc_response.getArg(
1393 OPENID_NS, 'expires_in', no_default)
1394 try:
1395 expires_in = int(expires_in_str)
1396 except ValueError, why:
1397 raise ProtocolError('Invalid expires_in field: %s' % (why[0],))
1398
1399
1400 if assoc_response.isOpenID1():
1401 session_type = self._getOpenID1SessionType(assoc_response)
1402 else:
1403 session_type = assoc_response.getArg(
1404 OPENID2_NS, 'session_type', no_default)
1405
1406
1407 if assoc_session.session_type != session_type:
1408 if (assoc_response.isOpenID1() and
1409 session_type == 'no-encryption'):
1410
1411
1412
1413
1414
1415 assoc_session = PlainTextConsumerSession()
1416 else:
1417
1418
1419
1420 fmt = 'Session type mismatch. Expected %r, got %r'
1421 message = fmt % (assoc_session.session_type, session_type)
1422 raise ProtocolError(message)
1423
1424
1425 if assoc_type not in assoc_session.allowed_assoc_types:
1426 fmt = 'Unsupported assoc_type for session %s returned: %s'
1427 raise ProtocolError(fmt % (assoc_session.session_type, assoc_type))
1428
1429
1430
1431
1432 try:
1433 secret = assoc_session.extractSecret(assoc_response)
1434 except ValueError, why:
1435 fmt = 'Malformed response for %s session: %s'
1436 raise ProtocolError(fmt % (assoc_session.session_type, why[0]))
1437
1438 return Association.fromExpiresIn(
1439 expires_in, assoc_handle, secret, assoc_type)
1440
1442 """An object that holds the state necessary for generating an
1443 OpenID authentication request. This object holds the association
1444 with the server and the discovered information with which the
1445 request will be made.
1446
1447 It is separate from the consumer because you may wish to add
1448 things to the request before sending it on its way to the
1449 server. It also has serialization options that let you encode the
1450 authentication request as a URL or as a form POST.
1451 """
1452
1454 """
1455 Creates a new AuthRequest object. This just stores each
1456 argument in an appropriately named field.
1457
1458 Users of this library should not create instances of this
1459 class. Instances of this class are created by the library
1460 when needed.
1461 """
1462 self.assoc = assoc
1463 self.endpoint = endpoint
1464 self.return_to_args = {}
1465 self.message = Message(endpoint.preferredNamespace())
1466 self._anonymous = False
1467
1469 """Set whether this request should be made anonymously. If a
1470 request is anonymous, the identifier will not be sent in the
1471 request. This is only useful if you are making another kind of
1472 request with an extension in this request.
1473
1474 Anonymous requests are not allowed when the request is made
1475 with OpenID 1.
1476
1477 @raises ValueError: when attempting to set an OpenID1 request
1478 as anonymous
1479 """
1480 if is_anonymous and self.message.isOpenID1():
1481 raise ValueError('OpenID 1 requests MUST include the '
1482 'identifier in the request')
1483 else:
1484 self._anonymous = is_anonymous
1485
1487 """Add an extension to this checkid request.
1488
1489 @param extension_request: An object that implements the
1490 extension interface for adding arguments to an OpenID
1491 message.
1492 """
1493 extension_request.toMessage(self.message)
1494
1496 """Add an extension argument to this OpenID authentication
1497 request.
1498
1499 Use caution when adding arguments, because they will be
1500 URL-escaped and appended to the redirect URL, which can easily
1501 get quite long.
1502
1503 @param namespace: The namespace for the extension. For
1504 example, the simple registration extension uses the
1505 namespace C{sreg}.
1506
1507 @type namespace: str
1508
1509 @param key: The key within the extension namespace. For
1510 example, the nickname field in the simple registration
1511 extension's key is C{nickname}.
1512
1513 @type key: str
1514
1515 @param value: The value to provide to the server for this
1516 argument.
1517
1518 @type value: str
1519 """
1520 self.message.setArg(namespace, key, value)
1521
1522 - def getMessage(self, realm, return_to=None, immediate=False):
1523 """Produce a L{openid.message.Message} representing this request.
1524
1525 @param realm: The URL (or URL pattern) that identifies your
1526 web site to the user when she is authorizing it.
1527
1528 @type realm: str
1529
1530 @param return_to: The URL that the OpenID provider will send the
1531 user back to after attempting to verify her identity.
1532
1533 Not specifying a return_to URL means that the user will not
1534 be returned to the site issuing the request upon its
1535 completion.
1536
1537 @type return_to: str
1538
1539 @param immediate: If True, the OpenID provider is to send back
1540 a response immediately, useful for behind-the-scenes
1541 authentication attempts. Otherwise the OpenID provider
1542 may engage the user before providing a response. This is
1543 the default case, as the user may need to provide
1544 credentials or approve the request before a positive
1545 response can be sent.
1546
1547 @type immediate: bool
1548
1549 @returntype: L{openid.message.Message}
1550 """
1551 if return_to:
1552 return_to = oidutil.appendArgs(return_to, self.return_to_args)
1553 elif immediate:
1554 raise ValueError(
1555 '"return_to" is mandatory when using "checkid_immediate"')
1556 elif self.message.isOpenID1():
1557 raise ValueError('"return_to" is mandatory for OpenID 1 requests')
1558 elif self.return_to_args:
1559 raise ValueError('extra "return_to" arguments were specified, '
1560 'but no return_to was specified')
1561
1562 if immediate:
1563 mode = 'checkid_immediate'
1564 else:
1565 mode = 'checkid_setup'
1566
1567 message = self.message.copy()
1568 if message.isOpenID1():
1569 realm_key = 'trust_root'
1570 else:
1571 realm_key = 'realm'
1572
1573 message.updateArgs(OPENID_NS,
1574 {
1575 realm_key:realm,
1576 'mode':mode,
1577 'return_to':return_to,
1578 })
1579
1580 if not self._anonymous:
1581 if self.endpoint.isOPIdentifier():
1582
1583
1584
1585 claimed_id = request_identity = IDENTIFIER_SELECT
1586 else:
1587 request_identity = self.endpoint.getLocalID()
1588 claimed_id = self.endpoint.claimed_id
1589
1590
1591 message.setArg(OPENID_NS, 'identity', request_identity)
1592
1593 if message.isOpenID2():
1594 message.setArg(OPENID2_NS, 'claimed_id', claimed_id)
1595
1596 if self.assoc:
1597 message.setArg(OPENID_NS, 'assoc_handle', self.assoc.handle)
1598 assoc_log_msg = 'with assocication %s' % (self.assoc.handle,)
1599 else:
1600 assoc_log_msg = 'using stateless mode.'
1601
1602 oidutil.log("Generated %s request to %s %s" %
1603 (mode, self.endpoint.server_url, assoc_log_msg))
1604
1605 return message
1606
1607 - def redirectURL(self, realm, return_to=None, immediate=False):
1608 """Returns a URL with an encoded OpenID request.
1609
1610 The resulting URL is the OpenID provider's endpoint URL with
1611 parameters appended as query arguments. You should redirect
1612 the user agent to this URL.
1613
1614 OpenID 2.0 endpoints also accept POST requests, see
1615 C{L{shouldSendRedirect}} and C{L{formMarkup}}.
1616
1617 @param realm: The URL (or URL pattern) that identifies your
1618 web site to the user when she is authorizing it.
1619
1620 @type realm: str
1621
1622 @param return_to: The URL that the OpenID provider will send the
1623 user back to after attempting to verify her identity.
1624
1625 Not specifying a return_to URL means that the user will not
1626 be returned to the site issuing the request upon its
1627 completion.
1628
1629 @type return_to: str
1630
1631 @param immediate: If True, the OpenID provider is to send back
1632 a response immediately, useful for behind-the-scenes
1633 authentication attempts. Otherwise the OpenID provider
1634 may engage the user before providing a response. This is
1635 the default case, as the user may need to provide
1636 credentials or approve the request before a positive
1637 response can be sent.
1638
1639 @type immediate: bool
1640
1641 @returns: The URL to redirect the user agent to.
1642
1643 @returntype: str
1644 """
1645 message = self.getMessage(realm, return_to, immediate)
1646 return message.toURL(self.endpoint.server_url)
1647
1661
1662 - def htmlMarkup(self, realm, return_to=None, immediate=False,
1663 form_tag_attrs=None):
1664 """Get an autosubmitting HTML page that submits this request to the
1665 IDP. This is just a wrapper for formMarkup.
1666
1667 @see: formMarkup
1668
1669 @returns: str
1670 """
1671 return oidutil.autoSubmitHTML(self.formMarkup(realm,
1672 return_to,
1673 immediate,
1674 form_tag_attrs))
1675
1677 """Should this OpenID authentication request be sent as a HTTP
1678 redirect or as a POST (form submission)?
1679
1680 @rtype: bool
1681 """
1682 return self.endpoint.compatibilityMode()
1683
1684 FAILURE = 'failure'
1685 SUCCESS = 'success'
1686 CANCEL = 'cancel'
1687 SETUP_NEEDED = 'setup_needed'
1688
1690 status = None
1691
1698
1700 """Return the display identifier for this response.
1701
1702 The display identifier is related to the Claimed Identifier, but the
1703 two are not always identical. The display identifier is something the
1704 user should recognize as what they entered, whereas the response's
1705 claimed identifier (in the L{identity_url} attribute) may have extra
1706 information for better persistence.
1707
1708 URLs will be stripped of their fragments for display. XRIs will
1709 display the human-readable identifier (i-name) instead of the
1710 persistent identifier (i-number).
1711
1712 Use the display identifier in your user interface. Use
1713 L{identity_url} for querying your database or authorization server.
1714 """
1715 if self.endpoint is not None:
1716 return self.endpoint.getDisplayIdentifier()
1717 return None
1718
1720 """A response with a status of SUCCESS. Indicates that this request is a
1721 successful acknowledgement from the OpenID server that the
1722 supplied URL is, indeed controlled by the requesting agent.
1723
1724 @ivar identity_url: The identity URL that has been authenticated; the Claimed Identifier.
1725 See also L{getDisplayIdentifier}.
1726
1727 @ivar endpoint: The endpoint that authenticated the identifier. You
1728 may access other discovered information related to this endpoint,
1729 such as the CanonicalID of an XRI, through this object.
1730 @type endpoint: L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>}
1731
1732 @ivar signed_fields: The arguments in the server's response that
1733 were signed and verified.
1734
1735 @cvar status: SUCCESS
1736 """
1737
1738 status = SUCCESS
1739
1740 - def __init__(self, endpoint, message, signed_fields=None):
1751
1753 """Was this authentication response an OpenID 1 authentication
1754 response?
1755 """
1756 return self.message.isOpenID1()
1757
1759 """Return whether a particular key is signed, regardless of
1760 its namespace alias
1761 """
1762 return self.message.getKey(ns_uri, ns_key) in self.signed_fields
1763
1764 - def getSigned(self, ns_uri, ns_key, default=None):
1765 """Return the specified signed field if available,
1766 otherwise return default
1767 """
1768 if self.isSigned(ns_uri, ns_key):
1769 return self.message.getArg(ns_uri, ns_key, default)
1770 else:
1771 return default
1772
1774 """Get signed arguments from the response message. Return a
1775 dict of all arguments in the specified namespace. If any of
1776 the arguments are not signed, return None.
1777 """
1778 msg_args = self.message.getArgs(ns_uri)
1779
1780 for key in msg_args.iterkeys():
1781 if not self.isSigned(ns_uri, key):
1782 oidutil.log("SuccessResponse.getSignedNS: (%s, %s) not signed."
1783 % (ns_uri, key))
1784 return None
1785
1786 return msg_args
1787
1789 """Return response arguments in the specified namespace.
1790
1791 @param namespace_uri: The namespace URI of the arguments to be
1792 returned.
1793
1794 @param require_signed: True if the arguments should be among
1795 those signed in the response, False if you don't care.
1796
1797 If require_signed is True and the arguments are not signed,
1798 return None.
1799 """
1800 if require_signed:
1801 return self.getSignedNS(namespace_uri)
1802 else:
1803 return self.message.getArgs(namespace_uri)
1804
1806 """Get the openid.return_to argument from this response.
1807
1808 This is useful for verifying that this request was initiated
1809 by this consumer.
1810
1811 @returns: The return_to URL supplied to the server on the
1812 initial request, or C{None} if the response did not contain
1813 an C{openid.return_to} argument.
1814
1815 @returntype: str
1816 """
1817 return self.getSigned(OPENID_NS, 'return_to')
1818
1826
1828 return not (self == other)
1829
1831 return '<%s.%s id=%r signed=%r>' % (
1832 self.__class__.__module__,
1833 self.__class__.__name__,
1834 self.identity_url, self.signed_fields)
1835
1836
1838 """A response with a status of FAILURE. Indicates that the OpenID
1839 protocol has failed. This could be locally or remotely triggered.
1840
1841 @ivar identity_url: The identity URL for which authenitcation was
1842 attempted, if it can be determined. Otherwise, None.
1843
1844 @ivar message: A message indicating why the request failed, if one
1845 is supplied. otherwise, None.
1846
1847 @cvar status: FAILURE
1848 """
1849
1850 status = FAILURE
1851
1852 - def __init__(self, endpoint, message=None, contact=None,
1853 reference=None):
1854 self.setEndpoint(endpoint)
1855 self.message = message
1856 self.contact = contact
1857 self.reference = reference
1858
1860 return "<%s.%s id=%r message=%r>" % (
1861 self.__class__.__module__, self.__class__.__name__,
1862 self.identity_url, self.message)
1863
1864
1866 """A response with a status of CANCEL. Indicates that the user
1867 cancelled the OpenID authentication request.
1868
1869 @ivar identity_url: The identity URL for which authenitcation was
1870 attempted, if it can be determined. Otherwise, None.
1871
1872 @cvar status: CANCEL
1873 """
1874
1875 status = CANCEL
1876
1879
1881 """A response with a status of SETUP_NEEDED. Indicates that the
1882 request was in immediate mode, and the server is unable to
1883 authenticate the user without further interaction.
1884
1885 @ivar identity_url: The identity URL for which authenitcation was
1886 attempted.
1887
1888 @ivar setup_url: A URL that can be used to send the user to the
1889 server to set up for authentication. The user should be
1890 redirected in to the setup_url, either in the current window
1891 or in a new browser window. C{None} in OpenID 2.0.
1892
1893 @cvar status: SETUP_NEEDED
1894 """
1895
1896 status = SETUP_NEEDED
1897
1898 - def __init__(self, endpoint, setup_url=None):
1899 self.setEndpoint(endpoint)
1900 self.setup_url = setup_url
1901