1 """Simple registration request and response parsing and object representation
2
3 This module contains objects representing simple registration requests
4 and responses that can be used with both OpenID relying parties and
5 OpenID providers.
6
7 1. The relying party creates a request object and adds it to the
8 C{L{AuthRequest<openid.consumer.consumer.AuthRequest>}} object
9 before making the C{checkid_} request to the OpenID provider::
10
11 auth_request.addExtension(SRegRequest(required=['email']))
12
13 2. The OpenID provider extracts the simple registration request from
14 the OpenID request using C{L{SRegRequest.fromOpenIDRequest}},
15 gets the user's approval and data, creates a C{L{SRegResponse}}
16 object and adds it to the C{id_res} response::
17
18 sreg_req = SRegRequest.fromOpenIDRequest(checkid_request.message)
19 # [ get the user's approval and data, informing the user that
20 # the fields in sreg_response were requested ]
21 sreg_resp = SRegResponse.extractResponse(sreg_req, user_data)
22 sreg_resp.toMessage(openid_response.fields)
23
24 3. The relying party uses C{L{SRegResponse.fromSuccessResponse}} to
25 extract the data from the OpenID response::
26
27 sreg_resp = SRegResponse.fromSuccessResponse(success_response)
28
29 @since: 2.0
30
31 @var sreg_data_fields: The names of the data fields that are listed in
32 the sreg spec, and a description of them in English
33
34 @var sreg_uri: The preferred URI to use for the simple registration
35 namespace and XRD Type value
36 """
37
38 from openid.message import registerNamespaceAlias, \
39 NamespaceAliasRegistrationError
40 from openid.extension import Extension
41 from openid import oidutil
42
43 try:
44 basestring
45 except NameError:
46
47 basestring = (str, unicode)
48
49 __all__ = [
50 'SRegRequest',
51 'SRegResponse',
52 'sendSRegFields',
53 'data_fields',
54 'ns_uri',
55 'supportsSReg',
56 ]
57
58
59 data_fields = {
60 'fullname':'Full Name',
61 'nickname':'Nickname',
62 'dob':'Date of Birth',
63 'email':'E-mail Address',
64 'gender':'Gender',
65 'postcode':'Postal Code',
66 'country':'Country',
67 'language':'Language',
68 'timezone':'Time Zone',
69 }
70
72 """Check to see that the given value is a valid simple
73 registration data field name.
74
75 @raise ValueError: if the field name is not a valid simple
76 registration data field name
77 """
78 if field_name not in data_fields:
79 raise ValueError('%r is not a defined simple registration field' %
80 (field_name,))
81
82
83
84 ns_uri_1_0 = 'http://openid.net/sreg/1.0'
85
86
87
88 ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1'
89
90
91
92 ns_uri = ns_uri_1_1
93
94 try:
95 registerNamespaceAlias(ns_uri_1_1, 'sreg')
96 except NamespaceAliasRegistrationError, e:
97 oidutil.log('registerNamespaceAlias(%r, %r) failed: %s' % (ns_uri_1_1,
98 'sreg', str(e),))
99
101 """Does the given endpoint advertise support for simple
102 registration?
103
104 @param endpoint: The endpoint object as returned by OpenID discovery
105 @type endpoint: openid.consumer.discover.OpenIDEndpoint
106
107 @returns: Whether an sreg type was advertised by the endpoint
108 @rtype: bool
109 """
110 return (endpoint.usesExtension(ns_uri_1_1) or
111 endpoint.usesExtension(ns_uri_1_0))
112
114 """The simple registration namespace was not found and could not
115 be created using the expected name (there's another extension
116 using the name 'sreg')
117
118 This is not I{illegal}, for OpenID 2, although it probably
119 indicates a problem, since it's not expected that other extensions
120 will re-use the alias that is in use for OpenID 1.
121
122 If this is an OpenID 1 request, then there is no recourse. This
123 should not happen unless some code has modified the namespaces for
124 the message that is being processed.
125 """
126
128 """Extract the simple registration namespace URI from the given
129 OpenID message. Handles OpenID 1 and 2, as well as both sreg
130 namespace URIs found in the wild, as well as missing namespace
131 definitions (for OpenID 1)
132
133 @param message: The OpenID message from which to parse simple
134 registration fields. This may be a request or response message.
135 @type message: C{L{openid.message.Message}}
136
137 @returns: the sreg namespace URI for the supplied message. The
138 message may be modified to define a simple registration
139 namespace.
140 @rtype: C{str}
141
142 @raise ValueError: when using OpenID 1 if the message defines
143 the 'sreg' alias to be something other than a simple
144 registration type.
145 """
146
147
148 for sreg_ns_uri in [ns_uri_1_1, ns_uri_1_0]:
149 alias = message.namespaces.getAlias(sreg_ns_uri)
150 if alias is not None:
151 break
152 else:
153
154
155 sreg_ns_uri = ns_uri_1_1
156 try:
157 message.namespaces.addAlias(ns_uri_1_1, 'sreg')
158 except KeyError, why:
159
160
161 raise SRegNamespaceError(why[0])
162
163
164
165 return sreg_ns_uri
166
168 """An object to hold the state of a simple registration request.
169
170 @ivar required: A list of the required fields in this simple
171 registration request
172 @type required: [str]
173
174 @ivar optional: A list of the optional fields in this simple
175 registration request
176 @type optional: [str]
177
178 @ivar policy_url: The policy URL that was provided with the request
179 @type policy_url: str or NoneType
180
181 @group Consumer: requestField, requestFields, getExtensionArgs, addToOpenIDRequest
182 @group Server: fromOpenIDRequest, parseExtensionArgs
183 """
184
185 ns_alias = 'sreg'
186
187 - def __init__(self, required=None, optional=None, policy_url=None,
188 sreg_ns_uri=ns_uri):
189 """Initialize an empty simple registration request"""
190 Extension.__init__(self)
191 self.required = []
192 self.optional = []
193 self.policy_url = policy_url
194 self.ns_uri = sreg_ns_uri
195
196 if required:
197 self.requestFields(required, required=True, strict=True)
198
199 if optional:
200 self.requestFields(optional, required=False, strict=True)
201
202
203
204 _getSRegNS = staticmethod(getSRegNS)
205
207 """Create a simple registration request that contains the
208 fields that were requested in the OpenID request with the
209 given arguments
210
211 @param request: The OpenID request
212 @type request: openid.server.CheckIDRequest
213
214 @returns: The newly created simple registration request
215 @rtype: C{L{SRegRequest}}
216 """
217 self = cls()
218
219
220
221 message = request.message.copy()
222
223 self.ns_uri = self._getSRegNS(message)
224 args = message.getArgs(self.ns_uri)
225 self.parseExtensionArgs(args)
226
227 return self
228
229 fromOpenIDRequest = classmethod(fromOpenIDRequest)
230
232 """Parse the unqualified simple registration request
233 parameters and add them to this object.
234
235 This method is essentially the inverse of
236 C{L{getExtensionArgs}}. This method restores the serialized simple
237 registration request fields.
238
239 If you are extracting arguments from a standard OpenID
240 checkid_* request, you probably want to use C{L{fromOpenIDRequest}},
241 which will extract the sreg namespace and arguments from the
242 OpenID request. This method is intended for cases where the
243 OpenID server needs more control over how the arguments are
244 parsed than that method provides.
245
246 >>> args = message.getArgs(ns_uri)
247 >>> request.parseExtensionArgs(args)
248
249 @param args: The unqualified simple registration arguments
250 @type args: {str:str}
251
252 @param strict: Whether requests with fields that are not
253 defined in the simple registration specification should be
254 tolerated (and ignored)
255 @type strict: bool
256
257 @returns: None; updates this object
258 """
259 for list_name in ['required', 'optional']:
260 required = (list_name == 'required')
261 items = args.get(list_name)
262 if items:
263 for field_name in items.split(','):
264 try:
265 self.requestField(field_name, required, strict)
266 except ValueError:
267 if strict:
268 raise
269
270 self.policy_url = args.get('policy_url')
271
273 """A list of all of the simple registration fields that were
274 requested, whether they were required or optional.
275
276 @rtype: [str]
277 """
278 return self.required + self.optional
279
281 """Have any simple registration fields been requested?
282
283 @rtype: bool
284 """
285 return bool(self.allRequestedFields())
286
288 """Was this field in the request?"""
289 return (field_name in self.required or
290 field_name in self.optional)
291
292 - def requestField(self, field_name, required=False, strict=False):
293 """Request the specified field from the OpenID user
294
295 @param field_name: the unqualified simple registration field name
296 @type field_name: str
297
298 @param required: whether the given field should be presented
299 to the user as being a required to successfully complete
300 the request
301
302 @param strict: whether to raise an exception when a field is
303 added to a request more than once
304
305 @raise ValueError: when the field requested is not a simple
306 registration field or strict is set and the field was
307 requested more than once
308 """
309 checkFieldName(field_name)
310
311 if strict:
312 if field_name in self.required or field_name in self.optional:
313 raise ValueError('That field has already been requested')
314 else:
315 if field_name in self.required:
316 return
317
318 if field_name in self.optional:
319 if required:
320 self.optional.remove(field_name)
321 else:
322 return
323
324 if required:
325 self.required.append(field_name)
326 else:
327 self.optional.append(field_name)
328
329 - def requestFields(self, field_names, required=False, strict=False):
330 """Add the given list of fields to the request
331
332 @param field_names: The simple registration data fields to request
333 @type field_names: [str]
334
335 @param required: Whether these values should be presented to
336 the user as required
337
338 @param strict: whether to raise an exception when a field is
339 added to a request more than once
340
341 @raise ValueError: when a field requested is not a simple
342 registration field or strict is set and a field was
343 requested more than once
344 """
345 if isinstance(field_names, basestring):
346 raise TypeError('Fields should be passed as a list of '
347 'strings (not %r)' % (type(field_names),))
348
349 for field_name in field_names:
350 self.requestField(field_name, required, strict=strict)
351
353 """Get a dictionary of unqualified simple registration
354 arguments representing this request.
355
356 This method is essentially the inverse of
357 C{L{parseExtensionArgs}}. This method serializes the simple
358 registration request fields.
359
360 @rtype: {str:str}
361 """
362 args = {}
363
364 if self.required:
365 args['required'] = ','.join(self.required)
366
367 if self.optional:
368 args['optional'] = ','.join(self.optional)
369
370 if self.policy_url:
371 args['policy_url'] = self.policy_url
372
373 return args
374
376 """Represents the data returned in a simple registration response
377 inside of an OpenID C{id_res} response. This object will be
378 created by the OpenID server, added to the C{id_res} response
379 object, and then extracted from the C{id_res} message by the
380 Consumer.
381
382 @ivar data: The simple registration data, keyed by the unqualified
383 simple registration name of the field (i.e. nickname is keyed
384 by C{'nickname'})
385
386 @ivar ns_uri: The URI under which the simple registration data was
387 stored in the response message.
388
389 @group Server: extractResponse
390 @group Consumer: fromSuccessResponse
391 @group Read-only dictionary interface: keys, iterkeys, items, iteritems,
392 __iter__, get, __getitem__, keys, has_key
393 """
394
395 ns_alias = 'sreg'
396
398 Extension.__init__(self)
399 if data is None:
400 self.data = {}
401 else:
402 self.data = data
403
404 self.ns_uri = sreg_ns_uri
405
407 """Take a C{L{SRegRequest}} and a dictionary of simple
408 registration values and create a C{L{SRegResponse}}
409 object containing that data.
410
411 @param request: The simple registration request object
412 @type request: SRegRequest
413
414 @param data: The simple registration data for this
415 response, as a dictionary from unqualified simple
416 registration field name to string (unicode) value. For
417 instance, the nickname should be stored under the key
418 'nickname'.
419 @type data: {str:str}
420
421 @returns: a simple registration response object
422 @rtype: SRegResponse
423 """
424 self = cls()
425 self.ns_uri = request.ns_uri
426 for field in request.allRequestedFields():
427 value = data.get(field)
428 if value is not None:
429 self.data[field] = value
430 return self
431
432 extractResponse = classmethod(extractResponse)
433
434
435
436 _getSRegNS = staticmethod(getSRegNS)
437
439 """Create a C{L{SRegResponse}} object from a successful OpenID
440 library response
441 (C{L{openid.consumer.consumer.SuccessResponse}}) response
442 message
443
444 @param success_response: A SuccessResponse from consumer.complete()
445 @type success_response: C{L{openid.consumer.consumer.SuccessResponse}}
446
447 @param signed_only: Whether to process only data that was
448 signed in the id_res message from the server.
449 @type signed_only: bool
450
451 @rtype: SRegResponse
452 @returns: A simple registration response containing the data
453 that was supplied with the C{id_res} response.
454 """
455 self = cls()
456 self.ns_uri = self._getSRegNS(success_response.message)
457 if signed_only:
458 args = success_response.getSignedNS(self.ns_uri)
459 else:
460 args = success_response.message.getArgs(self.ns_uri)
461
462 for field_name in data_fields:
463 if field_name in args:
464 self.data[field_name] = args[field_name]
465
466 return self
467
468 fromSuccessResponse = classmethod(fromSuccessResponse)
469
471 """Get the fields to put in the simple registration namespace
472 when adding them to an id_res message.
473
474 @see: openid.extension
475 """
476 return self.data
477
478
479 - def get(self, field_name, default=None):
480 """Like dict.get, except that it checks that the field name is
481 defined by the simple registration specification"""
482 checkFieldName(field_name)
483 return self.data.get(field_name, default)
484
486 """All of the data values in this simple registration response
487 """
488 return self.data.items()
489
492
494 return self.data.keys()
495
498
500 return key in self
501
503 checkFieldName(field_name)
504 return field_name in self.data
505
507 return iter(self.data)
508
510 checkFieldName(field_name)
511 return self.data[field_name]
512
514 return bool(self.data)
515