0
# OpenID Extensions
1
2
Implementation of standard OpenID extensions including Simple Registration (SREG) for basic profile data and Attribute Exchange (AX) for flexible attribute sharing between consumers and servers.
3
4
## Capabilities
5
6
### Simple Registration Extension (SREG)
7
8
Standard extension for requesting and sharing basic profile information between OpenID consumers and servers.
9
10
```python { .api }
11
class SRegRequest:
12
"""Simple Registration extension request."""
13
14
def __init__(self, required=None, optional=None, policy_url=None, sreg_ns_uri=ns_uri):
15
"""
16
Initialize SREG request.
17
18
Parameters:
19
- required: list, required field names
20
- optional: list, optional field names
21
- policy_url: str, privacy policy URL
22
- sreg_ns_uri: str, SREG namespace URI
23
"""
24
25
@classmethod
26
def fromOpenIDRequest(cls, request):
27
"""
28
Create SREG request from OpenID authentication request.
29
30
Parameters:
31
- request: AuthRequest object
32
33
Returns:
34
SRegRequest object or None if no SREG data
35
"""
36
37
def parseExtensionArgs(self, args, strict=False):
38
"""
39
Parse extension arguments from OpenID message.
40
41
Parameters:
42
- args: dict, extension arguments
43
- strict: bool, whether to enforce strict validation
44
"""
45
46
def requestField(self, field_name, required=False, strict=False):
47
"""
48
Request a single profile field.
49
50
Parameters:
51
- field_name: str, field name (e.g., 'nickname', 'email')
52
- required: bool, whether field is required
53
- strict: bool, whether to enforce strict validation
54
"""
55
56
def requestFields(self, field_names, required=False, strict=False):
57
"""
58
Request multiple profile fields.
59
60
Parameters:
61
- field_names: list, field names to request
62
- required: bool, whether fields are required
63
- strict: bool, whether to enforce strict validation
64
"""
65
66
def getExtensionArgs(self):
67
"""
68
Get extension arguments for OpenID message.
69
70
Returns:
71
dict, extension arguments
72
"""
73
74
def allRequestedFields(self):
75
"""
76
Get all requested fields (required and optional).
77
78
Returns:
79
list, all requested field names
80
"""
81
82
def wereFieldsRequested(self):
83
"""
84
Check if any fields were requested.
85
86
Returns:
87
bool, True if fields were requested
88
"""
89
90
class SRegResponse:
91
"""Simple Registration extension response."""
92
93
def __init__(self, data=None, sreg_ns_uri=ns_uri):
94
"""
95
Initialize SREG response.
96
97
Parameters:
98
- data: dict, profile data
99
- sreg_ns_uri: str, SREG namespace URI
100
"""
101
102
@classmethod
103
def extractResponse(cls, request, data):
104
"""
105
Extract SREG response data matching request.
106
107
Parameters:
108
- request: SRegRequest object
109
- data: dict, available profile data
110
111
Returns:
112
SRegResponse object with matching fields
113
"""
114
115
@classmethod
116
def fromSuccessResponse(cls, success_response, signed_only=True):
117
"""
118
Create SREG response from successful OpenID response.
119
120
Parameters:
121
- success_response: SuccessResponse object
122
- signed_only: bool, whether to only include signed fields
123
124
Returns:
125
SRegResponse object or None if no SREG data
126
"""
127
128
def getExtensionArgs(self):
129
"""
130
Get extension arguments for OpenID message.
131
132
Returns:
133
dict, extension arguments
134
"""
135
136
def get(self, field_name, default=None):
137
"""
138
Get profile field value.
139
140
Parameters:
141
- field_name: str, field name
142
- default: default value if field not present
143
144
Returns:
145
str or default, field value
146
"""
147
148
def items(self):
149
"""
150
Get all profile data as key-value pairs.
151
152
Returns:
153
list, [(field_name, value), ...] tuples
154
"""
155
156
def keys(self):
157
"""
158
Get all profile field names.
159
160
Returns:
161
list, field names
162
"""
163
164
def values(self):
165
"""
166
Get all profile field values.
167
168
Returns:
169
list, field values
170
"""
171
```
172
173
### Attribute Exchange Extension (AX)
174
175
Flexible extension for exchanging structured attribute data with support for multiple values and custom attribute types.
176
177
```python { .api }
178
class AttrInfo:
179
"""Attribute information for AX requests."""
180
181
def __init__(self, type_uri, count=1, required=False, alias=None):
182
"""
183
Initialize attribute information.
184
185
Parameters:
186
- type_uri: str, attribute type URI
187
- count: int or 'unlimited', number of values requested
188
- required: bool, whether attribute is required
189
- alias: str, attribute alias (auto-generated if None)
190
"""
191
192
def wantsUnlimitedValues(self):
193
"""
194
Check if unlimited values are requested.
195
196
Returns:
197
bool, True if unlimited values wanted
198
"""
199
200
class FetchRequest:
201
"""AX fetch request for retrieving attributes."""
202
203
def __init__(self, update_url=None):
204
"""
205
Initialize fetch request.
206
207
Parameters:
208
- update_url: str, URL for attribute updates
209
"""
210
211
def add(self, attribute):
212
"""
213
Add attribute to fetch request.
214
215
Parameters:
216
- attribute: AttrInfo object
217
"""
218
219
@classmethod
220
def fromOpenIDRequest(cls, openid_request):
221
"""
222
Create fetch request from OpenID authentication request.
223
224
Parameters:
225
- openid_request: AuthRequest object
226
227
Returns:
228
FetchRequest object or None if no AX data
229
"""
230
231
def parseExtensionArgs(self, ax_args):
232
"""
233
Parse AX extension arguments.
234
235
Parameters:
236
- ax_args: dict, AX extension arguments
237
"""
238
239
def getExtensionArgs(self):
240
"""
241
Get extension arguments for OpenID message.
242
243
Returns:
244
dict, extension arguments
245
"""
246
247
def getRequiredAttrs(self):
248
"""
249
Get required attributes.
250
251
Returns:
252
list, AttrInfo objects for required attributes
253
"""
254
255
def iterAttrs(self):
256
"""
257
Iterate over all requested attributes.
258
259
Returns:
260
iterator, AttrInfo objects
261
"""
262
263
class FetchResponse:
264
"""AX fetch response with attribute data."""
265
266
def __init__(self, request=None, update_url=None):
267
"""
268
Initialize fetch response.
269
270
Parameters:
271
- request: FetchRequest object (optional)
272
- update_url: str, URL for attribute updates
273
"""
274
275
@classmethod
276
def fromSuccessResponse(cls, success_response, signed=True):
277
"""
278
Create fetch response from successful OpenID response.
279
280
Parameters:
281
- success_response: SuccessResponse object
282
- signed: bool, whether to only include signed attributes
283
284
Returns:
285
FetchResponse object or None if no AX data
286
"""
287
288
def getExtensionArgs(self):
289
"""
290
Get extension arguments for OpenID message.
291
292
Returns:
293
dict, extension arguments
294
"""
295
296
def parseExtensionArgs(self, ax_args):
297
"""
298
Parse AX extension arguments.
299
300
Parameters:
301
- ax_args: dict, AX extension arguments
302
"""
303
304
class StoreRequest:
305
"""AX store request for sending attributes to server."""
306
307
def __init__(self, aliases=None):
308
"""
309
Initialize store request.
310
311
Parameters:
312
- aliases: dict, attribute type URI to alias mapping
313
"""
314
315
def getExtensionArgs(self):
316
"""
317
Get extension arguments for OpenID message.
318
319
Returns:
320
dict, extension arguments
321
"""
322
323
class StoreResponse:
324
"""AX store response indicating success or failure."""
325
326
def __init__(self, succeeded=True, error_message=None):
327
"""
328
Initialize store response.
329
330
Parameters:
331
- succeeded: bool, whether store operation succeeded
332
- error_message: str, error message if failed
333
"""
334
335
def succeeded(self):
336
"""
337
Check if store operation succeeded.
338
339
Returns:
340
bool, True if succeeded
341
"""
342
343
def getExtensionArgs(self):
344
"""
345
Get extension arguments for OpenID message.
346
347
Returns:
348
dict, extension arguments
349
"""
350
```
351
352
### AX Base Classes
353
354
Base classes providing common functionality for AX message handling.
355
356
```python { .api }
357
class AXMessage:
358
"""Abstract base class for AX messages."""
359
360
def getExtensionArgs(self):
361
"""Get extension arguments."""
362
363
class AXKeyValueMessage(AXMessage):
364
"""Base class for AX messages with key-value data."""
365
366
def addValue(self, type_uri, value):
367
"""
368
Add single value for attribute type.
369
370
Parameters:
371
- type_uri: str, attribute type URI
372
- value: str, attribute value
373
"""
374
375
def setValues(self, type_uri, values):
376
"""
377
Set multiple values for attribute type.
378
379
Parameters:
380
- type_uri: str, attribute type URI
381
- values: list, attribute values
382
"""
383
384
def getSingle(self, type_uri, default=None):
385
"""
386
Get single value for attribute type.
387
388
Parameters:
389
- type_uri: str, attribute type URI
390
- default: default value if not found
391
392
Returns:
393
str or default, attribute value
394
"""
395
396
def get(self, type_uri):
397
"""
398
Get all values for attribute type.
399
400
Parameters:
401
- type_uri: str, attribute type URI
402
403
Returns:
404
list, attribute values
405
"""
406
407
def count(self, type_uri):
408
"""
409
Count values for attribute type.
410
411
Parameters:
412
- type_uri: str, attribute type URI
413
414
Returns:
415
int, number of values
416
"""
417
```
418
419
### Extension Utilities
420
421
Utility functions for working with OpenID extensions.
422
423
```python { .api }
424
def checkFieldName(field_name):
425
"""
426
Validate SREG field name.
427
428
Parameters:
429
- field_name: str, field name to validate
430
431
Raises:
432
ValueError: if field name is invalid
433
"""
434
435
def getSRegNS(message):
436
"""
437
Get SREG namespace URI from OpenID message.
438
439
Parameters:
440
- message: Message object
441
442
Returns:
443
str, SREG namespace URI or None
444
"""
445
446
def supportsSReg(endpoint):
447
"""
448
Check if endpoint supports SREG extension.
449
450
Parameters:
451
- endpoint: OpenIDServiceEndpoint object
452
453
Returns:
454
bool, True if SREG is supported
455
"""
456
457
def checkAlias(alias):
458
"""
459
Validate AX attribute alias.
460
461
Parameters:
462
- alias: str, alias to validate
463
464
Raises:
465
ValueError: if alias is invalid
466
"""
467
468
def toTypeURIs(namespace_map, alias_list_s):
469
"""
470
Convert comma-separated alias list to type URIs.
471
472
Parameters:
473
- namespace_map: dict, alias to type URI mapping
474
- alias_list_s: str, comma-separated alias list
475
476
Returns:
477
list, type URIs
478
"""
479
```
480
481
## Usage Examples
482
483
### SREG Consumer Usage
484
485
```python
486
from openid.extensions import sreg
487
from openid.consumer import consumer
488
489
# Create consumer and start authentication
490
openid_consumer = consumer.Consumer({}, store)
491
auth_request = openid_consumer.begin(user_url)
492
493
# Add SREG request
494
sreg_request = sreg.SRegRequest(
495
required=['nickname', 'email'], # Required fields
496
optional=['fullname', 'dob'], # Optional fields
497
policy_url="https://mysite.com/privacy"
498
)
499
auth_request.addExtension(sreg_request)
500
501
# Redirect user to identity provider
502
redirect_url = auth_request.redirectURL(realm, return_to)
503
504
# Handle response
505
response = openid_consumer.complete(query, current_url)
506
if response.status == consumer.SUCCESS:
507
sreg_response = sreg.SRegResponse.fromSuccessResponse(response)
508
if sreg_response:
509
nickname = sreg_response.get('nickname')
510
email = sreg_response.get('email')
511
fullname = sreg_response.get('fullname')
512
513
# Use profile data
514
create_user_account(nickname, email, fullname)
515
```
516
517
### SREG Server Usage
518
519
```python
520
from openid.extensions import sreg
521
522
def handle_checkid_request(openid_request):
523
# Check for SREG request
524
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
525
526
if sreg_request:
527
# Get user profile data
528
user_profile = get_user_profile(user_id)
529
530
# Create SREG response with requested fields
531
sreg_response = sreg.SRegResponse.extractResponse(
532
sreg_request,
533
user_profile
534
)
535
536
# Create positive OpenID response
537
openid_response = openid_request.answer(allow=True, ...)
538
openid_response.addExtension(sreg_response)
539
540
return openid_response
541
```
542
543
### AX Consumer Usage
544
545
```python
546
from openid.extensions import ax
547
548
# Create AX fetch request
549
ax_request = ax.FetchRequest()
550
551
# Add specific attributes
552
ax_request.add(ax.AttrInfo(
553
'http://schema.openid.net/contact/email',
554
required=True,
555
alias='email'
556
))
557
ax_request.add(ax.AttrInfo(
558
'http://schema.openid.net/namePerson/friendly',
559
required=False,
560
alias='nickname'
561
))
562
ax_request.add(ax.AttrInfo(
563
'http://schema.openid.net/contact/web/homepage',
564
count='unlimited', # Allow multiple URLs
565
alias='website'
566
))
567
568
# Add to authentication request
569
auth_request.addExtension(ax_request)
570
571
# Handle response
572
response = openid_consumer.complete(query, current_url)
573
if response.status == consumer.SUCCESS:
574
ax_response = ax.FetchResponse.fromSuccessResponse(response)
575
if ax_response:
576
email = ax_response.getSingle('http://schema.openid.net/contact/email')
577
nickname = ax_response.getSingle('http://schema.openid.net/namePerson/friendly')
578
websites = ax_response.get('http://schema.openid.net/contact/web/homepage')
579
```
580
581
### AX Server Usage
582
583
```python
584
from openid.extensions import ax
585
586
def handle_ax_fetch_request(openid_request):
587
ax_request = ax.FetchRequest.fromOpenIDRequest(openid_request)
588
589
if ax_request:
590
ax_response = ax.FetchResponse()
591
592
# Process each requested attribute
593
for attr_info in ax_request.iterAttrs():
594
if attr_info.type_uri == 'http://schema.openid.net/contact/email':
595
if user_allows_email_sharing():
596
ax_response.addValue(attr_info.type_uri, user.email)
597
598
elif attr_info.type_uri == 'http://schema.openid.net/namePerson/friendly':
599
ax_response.addValue(attr_info.type_uri, user.nickname)
600
601
elif attr_info.type_uri == 'http://schema.openid.net/contact/web/homepage':
602
# Multiple values supported
603
for website in user.websites:
604
ax_response.addValue(attr_info.type_uri, website)
605
606
# Add to OpenID response
607
openid_response = openid_request.answer(allow=True, ...)
608
openid_response.addExtension(ax_response)
609
610
return openid_response
611
```
612
613
## Types
614
615
```python { .api }
616
# SREG namespace URIs
617
ns_uri_1_0 = 'http://openid.net/sreg/1.0'
618
ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1'
619
ns_uri = ns_uri_1_1 # Preferred namespace
620
621
# SREG data fields
622
data_fields = {
623
'nickname': 'Any UTF-8 string that the End User wants to use as a nickname.',
624
'email': 'The email address of the End User as specified in section 3.4.1 of [RFC2822]',
625
'fullname': 'UTF-8 string free text representation of the End User\'s full name.',
626
'dob': 'The End User\'s date of birth as YYYY-MM-DD.',
627
'gender': 'The End User\'s gender, "M" for male, "F" for female.',
628
'postcode': 'UTF-8 string free text that SHOULD conform to the End User\'s country\'s postal system.',
629
'country': 'The End User\'s country of residence as specified by ISO3166.',
630
'language': 'End User\'s preferred language as specified by ISO639.',
631
'timezone': 'ASCII string from TimeZone database'
632
}
633
634
# AX constants
635
UNLIMITED_VALUES = "unlimited"
636
MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
637
638
# Common AX attribute type URIs
639
AX_SCHEMA_ATTRS = {
640
'email': 'http://schema.openid.net/contact/email',
641
'nickname': 'http://schema.openid.net/namePerson/friendly',
642
'fullname': 'http://schema.openid.net/namePerson',
643
'first_name': 'http://schema.openid.net/namePerson/first',
644
'last_name': 'http://schema.openid.net/namePerson/last',
645
'website': 'http://schema.openid.net/contact/web/homepage',
646
'image': 'http://schema.openid.net/media/image/aspect11',
647
'country': 'http://schema.openid.net/contact/country/home',
648
'language': 'http://schema.openid.net/pref/language',
649
'timezone': 'http://schema.openid.net/pref/timezone'
650
}
651
652
# Exception types
653
class SRegNamespaceError(Exception):
654
"""SREG namespace error."""
655
656
class AXError(Exception):
657
"""AX extension error."""
658
659
class NotAXMessage(Exception):
660
"""Message is not an AX message."""
661
```