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