Package openid :: Module sreg
[frames] | no frames]

Source Code for Module openid.sreg

  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 #pylint:disable-msg=W0104 
 45  except NameError: 
 46      # For Python 2.2 
 47      basestring = (str, unicode) #pylint:disable-msg=W0622 
 48   
 49  __all__ = [ 
 50      'SRegRequest', 
 51      'SRegResponse', 
 52      'data_fields', 
 53      'ns_uri', 
 54      'supportsSReg', 
 55      ] 
 56   
 57  # The data fields that are listed in the sreg spec 
 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   
70 -def checkFieldName(field_name):
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 # URI used in the wild for Yadis documents advertising simple 82 # registration support 83 ns_uri_1_0 = 'http://openid.net/sreg/1.0' 84 85 # URI in the draft specification for simple registration 1.1 86 # <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html> 87 ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1' 88 89 # This attribute will always hold the preferred URI to use when adding 90 # sreg support to an XRDS file or in an OpenID namespace declaration. 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
99 -def supportsSReg(endpoint):
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
112 -class SRegNamespaceError(ValueError):
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
126 -def getSRegNS(message):
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 # See if there exists an alias for one of the two defined simple 146 # registration types. 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 # There is no alias for either of the types, so try to add 153 # one. We default to using the modern value (1.1) 154 sreg_ns_uri = ns_uri_1_1 155 try: 156 message.namespaces.addAlias(ns_uri_1_1, 'sreg') 157 except KeyError, why: 158 # An alias for the string 'sreg' already exists, but it's 159 # defined for something other than simple registration 160 raise SRegNamespaceError(why[0]) 161 162 # we know that sreg_ns_uri defined, because it's defined in the 163 # else clause of the loop as well, so disable the warning 164 return sreg_ns_uri #pylint:disable-msg=W0631
165
166 -class SRegRequest(Extension):
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 # Assign getSRegNS to a static method so that it can be 202 # overridden for testing. 203 _getSRegNS = staticmethod(getSRegNS) 204
205 - def fromOpenIDRequest(cls, request):
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 # Since we're going to mess with namespace URI mapping, don't 219 # mutate the object that was passed in. 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
230 - def parseExtensionArgs(self, args, strict=False):
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
271 - def allRequestedFields(self):
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
279 - def wereFieldsRequested(self):
280 """Have any simple registration fields been requested? 281 282 @rtype: bool 283 """ 284 return bool(self.allRequestedFields())
285
286 - def __contains__(self, field_name):
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
351 - def getExtensionArgs(self):
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
374 -class SRegResponse(Extension):
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
396 - def __init__(self, data=None, sreg_ns_uri=ns_uri):
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
405 - def extractResponse(cls, request, data):
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 # Assign getSRegArgs to a static method so that it can be 434 # overridden for testing 435 _getSRegNS = staticmethod(getSRegNS) 436
437 - def fromSuccessResponse(cls, success_response, signed_only=True):
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
469 - def getExtensionArgs(self):
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 # Read-only dictionary interface
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
484 - def items(self):
485 """All of the data values in this simple registration response 486 """ 487 return self.data.items()
488
489 - def iteritems(self):
490 return self.data.iteritems()
491
492 - def keys(self):
493 return self.data.keys()
494
495 - def iterkeys(self):
496 return self.data.iterkeys()
497
498 - def has_key(self, key):
499 return key in self
500
501 - def __contains__(self, field_name):
502 checkFieldName(field_name) 503 return field_name in self.data
504
505 - def __iter__(self):
506 return iter(self.data)
507
508 - def __getitem__(self, field_name):
509 checkFieldName(field_name) 510 return self.data[field_name]
511
512 - def __nonzero__(self):
513 return bool(self.data)
514