0
# OpenID Discovery
1
2
Complete OpenID service discovery implementation that enables consumers to discover OpenID service endpoints from user identifiers. The discovery system supports both Yadis-based XRDS discovery and HTML-based discovery with automatic fallback mechanisms, handling OpenID 1.0, 1.1, and 2.0 protocols.
3
4
## Core Imports
5
6
```python
7
from openid.consumer.discover import (
8
discover,
9
OpenIDServiceEndpoint,
10
normalizeURL,
11
normalizeXRI,
12
discoverYadis,
13
discoverXRI,
14
discoverURI,
15
discoverNoYadis,
16
findOPLocalIdentifier,
17
arrangeByType,
18
getOPOrUserServices,
19
OPENID_1_0_NS,
20
OPENID_IDP_2_0_TYPE,
21
OPENID_2_0_TYPE,
22
OPENID_1_1_TYPE,
23
OPENID_1_0_TYPE
24
)
25
```
26
27
## Capabilities
28
29
### Primary Discovery Interface
30
31
The main discovery function that handles both XRI and URL identifiers with automatic protocol detection.
32
33
```python { .api }
34
def discover(identifier):
35
"""
36
Discover OpenID service endpoints for a user identifier.
37
38
Parameters:
39
- identifier: str, user's OpenID identifier (URL or XRI)
40
41
Returns:
42
tuple: (claimed_id, services) where:
43
- claimed_id: str, normalized claimed identifier
44
- services: list of OpenIDServiceEndpoint objects, ordered by preference
45
46
Raises:
47
DiscoveryFailure: when discovery fails for any reason
48
"""
49
```
50
51
### Service Endpoint Representation
52
53
Core class representing an OpenID service endpoint with methods for protocol compatibility and extension support.
54
55
```python { .api }
56
class OpenIDServiceEndpoint:
57
def __init__(self):
58
"""
59
Initialize an empty OpenID service endpoint.
60
61
Attributes:
62
- claimed_id: str or None, the claimed identifier
63
- server_url: str or None, the OpenID server endpoint URL
64
- type_uris: list, supported OpenID type URIs
65
- local_id: str or None, the local identifier at the server
66
- canonicalID: str or None, canonical XRI identifier
67
- used_yadis: bool, whether this endpoint was discovered via Yadis
68
- display_identifier: str or None, identifier for display purposes
69
"""
70
71
def usesExtension(self, extension_uri):
72
"""
73
Check if this endpoint supports a specific extension.
74
75
Parameters:
76
- extension_uri: str, extension type URI to check
77
78
Returns:
79
bool: True if extension is supported
80
"""
81
82
def preferredNamespace(self):
83
"""
84
Get the preferred OpenID namespace for this endpoint.
85
86
Returns:
87
str: OPENID_2_0_MESSAGE_NS or OPENID_1_0_MESSAGE_NS
88
"""
89
90
def supportsType(self, type_uri):
91
"""
92
Check if this endpoint supports a specific OpenID type.
93
94
Parameters:
95
- type_uri: str, OpenID type URI to check
96
97
Returns:
98
bool: True if type is supported (considers server endpoints as supporting signon)
99
"""
100
101
def compatibilityMode(self):
102
"""
103
Check if this endpoint requires OpenID 1.x compatibility mode.
104
105
Returns:
106
bool: True if OpenID 1.x compatibility is needed
107
"""
108
109
def isOPIdentifier(self):
110
"""
111
Check if this is an OP Identifier endpoint (OpenID 2.0 server type).
112
113
Returns:
114
bool: True if this is an OP Identifier endpoint
115
"""
116
117
def getLocalID(self):
118
"""
119
Get the identifier to send as openid.identity parameter.
120
121
Returns:
122
str: local identifier or claimed identifier if no local ID
123
"""
124
125
def getDisplayIdentifier(self):
126
"""
127
Get the identifier for display purposes.
128
129
Returns:
130
str: display identifier or claimed identifier with fragment removed
131
"""
132
```
133
134
### Service Endpoint Creation Methods
135
136
Factory methods for creating OpenID service endpoints from various sources.
137
138
```python { .api }
139
@classmethod
140
def fromBasicServiceEndpoint(cls, endpoint):
141
"""
142
Create OpenIDServiceEndpoint from a basic Yadis service endpoint.
143
144
Parameters:
145
- endpoint: BasicServiceEndpoint object from Yadis discovery
146
147
Returns:
148
OpenIDServiceEndpoint or None if endpoint is not OpenID-compatible
149
"""
150
151
@classmethod
152
def fromHTML(cls, uri, html):
153
"""
154
Parse HTML document for OpenID link rel discovery.
155
156
Parameters:
157
- uri: str, the document URI
158
- html: str, HTML document content
159
160
Returns:
161
list: OpenIDServiceEndpoint objects found in HTML
162
"""
163
164
@classmethod
165
def fromXRDS(cls, uri, xrds):
166
"""
167
Parse XRDS document for OpenID service endpoints.
168
169
Parameters:
170
- uri: str, the document URI
171
- xrds: str, XRDS document content
172
173
Returns:
174
list: OpenIDServiceEndpoint objects found in XRDS
175
176
Raises:
177
XRDSError: when XRDS document cannot be parsed
178
"""
179
180
@classmethod
181
def fromDiscoveryResult(cls, discoveryResult):
182
"""
183
Create endpoints from a Yadis DiscoveryResult object.
184
185
Parameters:
186
- discoveryResult: DiscoveryResult object from Yadis discovery
187
188
Returns:
189
list: OpenIDServiceEndpoint objects
190
191
Raises:
192
XRDSError: when XRDS document cannot be parsed
193
"""
194
195
@classmethod
196
def fromOPEndpointURL(cls, op_endpoint_url):
197
"""
198
Create OP Identifier endpoint for a known OP endpoint URL.
199
200
Parameters:
201
- op_endpoint_url: str, the OP endpoint URL
202
203
Returns:
204
OpenIDServiceEndpoint: OP Identifier endpoint
205
"""
206
```
207
208
### URL and XRI Normalization
209
210
Utility functions for normalizing identifiers before discovery.
211
212
```python { .api }
213
def normalizeURL(url):
214
"""
215
Normalize a URL identifier for OpenID discovery.
216
217
Parameters:
218
- url: str, URL to normalize
219
220
Returns:
221
str: normalized URL with fragment removed
222
223
Raises:
224
DiscoveryFailure: when URL normalization fails
225
"""
226
227
def normalizeXRI(xri):
228
"""
229
Normalize an XRI identifier by removing xri:// scheme if present.
230
231
Parameters:
232
- xri: str, XRI identifier to normalize
233
234
Returns:
235
str: normalized XRI without scheme
236
"""
237
```
238
239
### Discovery Protocol Implementations
240
241
Specific discovery methods for different protocols and fallback scenarios.
242
243
```python { .api }
244
def discoverYadis(uri):
245
"""
246
Perform Yadis-based OpenID discovery with HTML fallback.
247
248
Parameters:
249
- uri: str, normalized identity URL
250
251
Returns:
252
tuple: (claimed_id, services) where:
253
- claimed_id: str, discovered claimed identifier
254
- services: list of OpenIDServiceEndpoint objects
255
256
Raises:
257
DiscoveryFailure: when discovery fails
258
"""
259
260
def discoverXRI(iname):
261
"""
262
Perform XRI resolution for OpenID discovery.
263
264
Parameters:
265
- iname: str, XRI identifier to resolve
266
267
Returns:
268
tuple: (claimed_id, services) where:
269
- claimed_id: str, the normalized XRI
270
- services: list of OpenIDServiceEndpoint objects with canonicalID set
271
272
Raises:
273
XRDSError: when XRI resolution fails
274
"""
275
276
def discoverURI(uri):
277
"""
278
Perform OpenID discovery for URI identifiers.
279
280
Parameters:
281
- uri: str, URI identifier (may need http:// prefix)
282
283
Returns:
284
tuple: (claimed_id, services) where:
285
- claimed_id: str, normalized claimed identifier
286
- services: list of OpenIDServiceEndpoint objects
287
288
Raises:
289
DiscoveryFailure: when URI scheme is invalid or discovery fails
290
"""
291
292
def discoverNoYadis(uri):
293
"""
294
Perform HTML-only OpenID discovery without Yadis.
295
296
Parameters:
297
- uri: str, identity URL
298
299
Returns:
300
tuple: (claimed_id, services) where:
301
- claimed_id: str, final URL after redirects
302
- services: list of OpenIDServiceEndpoint objects from HTML
303
304
Raises:
305
DiscoveryFailure: when HTTP request fails
306
"""
307
```
308
309
### Service Processing Utilities
310
311
Utility functions for processing and ordering discovered services.
312
313
```python { .api }
314
def arrangeByType(service_list, preferred_types):
315
"""
316
Reorder services by type preference.
317
318
Parameters:
319
- service_list: list, OpenIDServiceEndpoint objects to reorder
320
- preferred_types: list, type URIs in order of preference
321
322
Returns:
323
list: reordered services with preferred types first
324
"""
325
326
def getOPOrUserServices(openid_services):
327
"""
328
Extract OP Identifier services or return user services ordered by preference.
329
330
Parameters:
331
- openid_services: list, OpenIDServiceEndpoint objects
332
333
Returns:
334
list: OP Identifier services if found, otherwise user services ordered by type preference
335
"""
336
337
def findOPLocalIdentifier(service_element, type_uris):
338
"""
339
Find OP-Local Identifier from XRDS service element.
340
341
Parameters:
342
- service_element: ElementTree.Node, xrd:Service element
343
- type_uris: list, xrd:Type values for this service
344
345
Returns:
346
str or None: OP-Local Identifier if present
347
348
Raises:
349
DiscoveryFailure: when multiple conflicting LocalID tags are found
350
"""
351
```
352
353
## Usage Examples
354
355
### Basic Discovery
356
357
```python
358
from openid.consumer.discover import discover
359
360
try:
361
claimed_id, services = discover("https://example.com/user")
362
363
if services:
364
# Use the first (highest priority) service
365
service = services[0]
366
print(f"Server URL: {service.server_url}")
367
print(f"OpenID Version: {'2.0' if not service.compatibilityMode() else '1.x'}")
368
print(f"OP Identifier: {service.isOPIdentifier()}")
369
else:
370
print("No OpenID services found")
371
372
except DiscoveryFailure as e:
373
print(f"Discovery failed: {e}")
374
```
375
376
### XRI Discovery
377
378
```python
379
from openid.consumer.discover import discoverXRI
380
381
try:
382
claimed_id, services = discoverXRI("=example*user")
383
384
for service in services:
385
print(f"Canonical ID: {service.canonicalID}")
386
print(f"Server: {service.server_url}")
387
print(f"Local ID: {service.getLocalID()}")
388
389
except XRDSError as e:
390
print(f"XRI resolution failed: {e}")
391
```
392
393
### Service Endpoint Analysis
394
395
```python
396
from openid.consumer.discover import discover, OPENID_2_0_TYPE
397
398
claimed_id, services = discover("https://example.com/user")
399
400
for service in services:
401
# Check OpenID version support
402
if service.supportsType(OPENID_2_0_TYPE):
403
print("Supports OpenID 2.0")
404
405
# Check for extensions
406
if service.usesExtension("http://openid.net/extensions/sreg/1.1"):
407
print("Supports Simple Registration extension")
408
409
# Get appropriate namespace
410
namespace = service.preferredNamespace()
411
print(f"Preferred namespace: {namespace}")
412
```
413
414
### HTML Link Discovery
415
416
```python
417
from openid.consumer.discover import OpenIDServiceEndpoint
418
419
html_content = '''
420
<html>
421
<head>
422
<link rel="openid2.provider" href="https://example.com/openid">
423
<link rel="openid2.local_id" href="https://example.com/user/local">
424
</head>
425
</html>
426
'''
427
428
services = OpenIDServiceEndpoint.fromHTML("https://example.com/user", html_content)
429
430
for service in services:
431
print(f"Provider: {service.server_url}")
432
print(f"Local ID: {service.local_id}")
433
```
434
435
## Constants
436
437
```python { .api }
438
# OpenID Namespace URIs
439
OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
440
441
# OpenID Type URIs (in preference order)
442
OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server' # OP Identifier
443
OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon' # User Identifier
444
OPENID_1_1_TYPE = 'http://openid.net/signon/1.1' # OpenID 1.1
445
OPENID_1_0_TYPE = 'http://openid.net/signon/1.0' # OpenID 1.0
446
447
# OpenIDServiceEndpoint class constants
448
openid_type_uris = [
449
OPENID_IDP_2_0_TYPE,
450
OPENID_2_0_TYPE,
451
OPENID_1_1_TYPE,
452
OPENID_1_0_TYPE
453
]
454
```
455
456
## Types
457
458
```python { .api }
459
# Discovery result tuple
460
DiscoveryResult = tuple[str, list[OpenIDServiceEndpoint]]
461
462
# Exception types
463
class DiscoveryFailure(Exception):
464
"""Raised when OpenID discovery fails."""
465
466
class XRDSError(Exception):
467
"""Raised when XRDS parsing fails."""
468
```