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