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

Source Code for Module openid.extensions.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      'ns_uri_1_0', 
 55      'ns_uri_1_1', 
 56      'supportsSReg', 
 57      ] 
 58   
 59  # The data fields that are listed in the sreg spec 
 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   
72 -def checkFieldName(field_name):
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 # URI used in the wild for Yadis documents advertising simple 84 # registration support 85 ns_uri_1_0 = 'http://openid.net/sreg/1.0' 86 87 # URI in the draft specification for simple registration 1.1 88 # <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html> 89 ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1' 90 91 # This attribute will always hold the preferred URI to use when adding 92 # sreg support to an XRDS file or in an OpenID namespace declaration. 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
101 -def supportsSReg(endpoint):
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
114 -class SRegNamespaceError(ValueError):
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
128 -def getSRegNS(message):
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 # See if there exists an alias for one of the two defined simple 148 # registration types. 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 # There is no alias for either of the types, so try to add 155 # one. We default to using the modern value (1.1) 156 sreg_ns_uri = ns_uri_1_1 157 try: 158 message.namespaces.addAlias(ns_uri_1_1, 'sreg') 159 except KeyError, why: 160 # An alias for the string 'sreg' already exists, but it's 161 # defined for something other than simple registration 162 raise SRegNamespaceError(why[0]) 163 164 # we know that sreg_ns_uri defined, because it's defined in the 165 # else clause of the loop as well, so disable the warning 166 return sreg_ns_uri #pylint:disable-msg=W0631
167
168 -class SRegRequest(Extension):
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 # Assign getSRegNS to a static method so that it can be 204 # overridden for testing. 205 _getSRegNS = staticmethod(getSRegNS) 206
207 - def fromOpenIDRequest(cls, request):
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 # Since we're going to mess with namespace URI mapping, don't 221 # mutate the object that was passed in. 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
232 - def parseExtensionArgs(self, args, strict=False):
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
273 - def allRequestedFields(self):
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
281 - def wereFieldsRequested(self):
282 """Have any simple registration fields been requested? 283 284 @rtype: bool 285 """ 286 return bool(self.allRequestedFields())
287
288 - def __contains__(self, field_name):
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
353 - def getExtensionArgs(self):
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
376 -class SRegResponse(Extension):
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
398 - def __init__(self, data=None, sreg_ns_uri=ns_uri):
399 Extension.__init__(self) 400 if data is None: 401 self.data = {} 402 else: 403 self.data = data 404 405 self.ns_uri = sreg_ns_uri
406
407 - def extractResponse(cls, request, data):
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 # Assign getSRegArgs to a static method so that it can be 436 # overridden for testing 437 _getSRegNS = staticmethod(getSRegNS) 438
439 - def fromSuccessResponse(cls, success_response, signed_only=True):
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
474 - def getExtensionArgs(self):
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 # Read-only dictionary interface
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
489 - def items(self):
490 """All of the data values in this simple registration response 491 """ 492 return self.data.items()
493
494 - def iteritems(self):
495 return self.data.iteritems()
496
497 - def keys(self):
498 return self.data.keys()
499
500 - def iterkeys(self):
501 return self.data.iterkeys()
502
503 - def has_key(self, key):
504 return key in self
505
506 - def __contains__(self, field_name):
507 checkFieldName(field_name) 508 return field_name in self.data
509
510 - def __iter__(self):
511 return iter(self.data)
512
513 - def __getitem__(self, field_name):
514 checkFieldName(field_name) 515 return self.data[field_name]
516
517 - def __nonzero__(self):
518 return bool(self.data)
519