OpenID support for modern servers and consumers with comprehensive authentication functionality.
—
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.
from openid.consumer.discover import (
discover,
OpenIDServiceEndpoint,
normalizeURL,
normalizeXRI,
discoverYadis,
discoverXRI,
discoverURI,
discoverNoYadis,
findOPLocalIdentifier,
arrangeByType,
getOPOrUserServices,
OPENID_1_0_NS,
OPENID_IDP_2_0_TYPE,
OPENID_2_0_TYPE,
OPENID_1_1_TYPE,
OPENID_1_0_TYPE
)The main discovery function that handles both XRI and URL identifiers with automatic protocol detection.
def discover(identifier):
"""
Discover OpenID service endpoints for a user identifier.
Parameters:
- identifier: str, user's OpenID identifier (URL or XRI)
Returns:
tuple: (claimed_id, services) where:
- claimed_id: str, normalized claimed identifier
- services: list of OpenIDServiceEndpoint objects, ordered by preference
Raises:
DiscoveryFailure: when discovery fails for any reason
"""Core class representing an OpenID service endpoint with methods for protocol compatibility and extension support.
class OpenIDServiceEndpoint:
def __init__(self):
"""
Initialize an empty OpenID service endpoint.
Attributes:
- claimed_id: str or None, the claimed identifier
- server_url: str or None, the OpenID server endpoint URL
- type_uris: list, supported OpenID type URIs
- local_id: str or None, the local identifier at the server
- canonicalID: str or None, canonical XRI identifier
- used_yadis: bool, whether this endpoint was discovered via Yadis
- display_identifier: str or None, identifier for display purposes
"""
def usesExtension(self, extension_uri):
"""
Check if this endpoint supports a specific extension.
Parameters:
- extension_uri: str, extension type URI to check
Returns:
bool: True if extension is supported
"""
def preferredNamespace(self):
"""
Get the preferred OpenID namespace for this endpoint.
Returns:
str: OPENID_2_0_MESSAGE_NS or OPENID_1_0_MESSAGE_NS
"""
def supportsType(self, type_uri):
"""
Check if this endpoint supports a specific OpenID type.
Parameters:
- type_uri: str, OpenID type URI to check
Returns:
bool: True if type is supported (considers server endpoints as supporting signon)
"""
def compatibilityMode(self):
"""
Check if this endpoint requires OpenID 1.x compatibility mode.
Returns:
bool: True if OpenID 1.x compatibility is needed
"""
def isOPIdentifier(self):
"""
Check if this is an OP Identifier endpoint (OpenID 2.0 server type).
Returns:
bool: True if this is an OP Identifier endpoint
"""
def getLocalID(self):
"""
Get the identifier to send as openid.identity parameter.
Returns:
str: local identifier or claimed identifier if no local ID
"""
def getDisplayIdentifier(self):
"""
Get the identifier for display purposes.
Returns:
str: display identifier or claimed identifier with fragment removed
"""Factory methods for creating OpenID service endpoints from various sources.
@classmethod
def fromBasicServiceEndpoint(cls, endpoint):
"""
Create OpenIDServiceEndpoint from a basic Yadis service endpoint.
Parameters:
- endpoint: BasicServiceEndpoint object from Yadis discovery
Returns:
OpenIDServiceEndpoint or None if endpoint is not OpenID-compatible
"""
@classmethod
def fromHTML(cls, uri, html):
"""
Parse HTML document for OpenID link rel discovery.
Parameters:
- uri: str, the document URI
- html: str, HTML document content
Returns:
list: OpenIDServiceEndpoint objects found in HTML
"""
@classmethod
def fromXRDS(cls, uri, xrds):
"""
Parse XRDS document for OpenID service endpoints.
Parameters:
- uri: str, the document URI
- xrds: str, XRDS document content
Returns:
list: OpenIDServiceEndpoint objects found in XRDS
Raises:
XRDSError: when XRDS document cannot be parsed
"""
@classmethod
def fromDiscoveryResult(cls, discoveryResult):
"""
Create endpoints from a Yadis DiscoveryResult object.
Parameters:
- discoveryResult: DiscoveryResult object from Yadis discovery
Returns:
list: OpenIDServiceEndpoint objects
Raises:
XRDSError: when XRDS document cannot be parsed
"""
@classmethod
def fromOPEndpointURL(cls, op_endpoint_url):
"""
Create OP Identifier endpoint for a known OP endpoint URL.
Parameters:
- op_endpoint_url: str, the OP endpoint URL
Returns:
OpenIDServiceEndpoint: OP Identifier endpoint
"""Utility functions for normalizing identifiers before discovery.
def normalizeURL(url):
"""
Normalize a URL identifier for OpenID discovery.
Parameters:
- url: str, URL to normalize
Returns:
str: normalized URL with fragment removed
Raises:
DiscoveryFailure: when URL normalization fails
"""
def normalizeXRI(xri):
"""
Normalize an XRI identifier by removing xri:// scheme if present.
Parameters:
- xri: str, XRI identifier to normalize
Returns:
str: normalized XRI without scheme
"""Specific discovery methods for different protocols and fallback scenarios.
def discoverYadis(uri):
"""
Perform Yadis-based OpenID discovery with HTML fallback.
Parameters:
- uri: str, normalized identity URL
Returns:
tuple: (claimed_id, services) where:
- claimed_id: str, discovered claimed identifier
- services: list of OpenIDServiceEndpoint objects
Raises:
DiscoveryFailure: when discovery fails
"""
def discoverXRI(iname):
"""
Perform XRI resolution for OpenID discovery.
Parameters:
- iname: str, XRI identifier to resolve
Returns:
tuple: (claimed_id, services) where:
- claimed_id: str, the normalized XRI
- services: list of OpenIDServiceEndpoint objects with canonicalID set
Raises:
XRDSError: when XRI resolution fails
"""
def discoverURI(uri):
"""
Perform OpenID discovery for URI identifiers.
Parameters:
- uri: str, URI identifier (may need http:// prefix)
Returns:
tuple: (claimed_id, services) where:
- claimed_id: str, normalized claimed identifier
- services: list of OpenIDServiceEndpoint objects
Raises:
DiscoveryFailure: when URI scheme is invalid or discovery fails
"""
def discoverNoYadis(uri):
"""
Perform HTML-only OpenID discovery without Yadis.
Parameters:
- uri: str, identity URL
Returns:
tuple: (claimed_id, services) where:
- claimed_id: str, final URL after redirects
- services: list of OpenIDServiceEndpoint objects from HTML
Raises:
DiscoveryFailure: when HTTP request fails
"""Utility functions for processing and ordering discovered services.
def arrangeByType(service_list, preferred_types):
"""
Reorder services by type preference.
Parameters:
- service_list: list, OpenIDServiceEndpoint objects to reorder
- preferred_types: list, type URIs in order of preference
Returns:
list: reordered services with preferred types first
"""
def getOPOrUserServices(openid_services):
"""
Extract OP Identifier services or return user services ordered by preference.
Parameters:
- openid_services: list, OpenIDServiceEndpoint objects
Returns:
list: OP Identifier services if found, otherwise user services ordered by type preference
"""
def findOPLocalIdentifier(service_element, type_uris):
"""
Find OP-Local Identifier from XRDS service element.
Parameters:
- service_element: ElementTree.Node, xrd:Service element
- type_uris: list, xrd:Type values for this service
Returns:
str or None: OP-Local Identifier if present
Raises:
DiscoveryFailure: when multiple conflicting LocalID tags are found
"""from openid.consumer.discover import discover
try:
claimed_id, services = discover("https://example.com/user")
if services:
# Use the first (highest priority) service
service = services[0]
print(f"Server URL: {service.server_url}")
print(f"OpenID Version: {'2.0' if not service.compatibilityMode() else '1.x'}")
print(f"OP Identifier: {service.isOPIdentifier()}")
else:
print("No OpenID services found")
except DiscoveryFailure as e:
print(f"Discovery failed: {e}")from openid.consumer.discover import discoverXRI
try:
claimed_id, services = discoverXRI("=example*user")
for service in services:
print(f"Canonical ID: {service.canonicalID}")
print(f"Server: {service.server_url}")
print(f"Local ID: {service.getLocalID()}")
except XRDSError as e:
print(f"XRI resolution failed: {e}")from openid.consumer.discover import discover, OPENID_2_0_TYPE
claimed_id, services = discover("https://example.com/user")
for service in services:
# Check OpenID version support
if service.supportsType(OPENID_2_0_TYPE):
print("Supports OpenID 2.0")
# Check for extensions
if service.usesExtension("http://openid.net/extensions/sreg/1.1"):
print("Supports Simple Registration extension")
# Get appropriate namespace
namespace = service.preferredNamespace()
print(f"Preferred namespace: {namespace}")from openid.consumer.discover import OpenIDServiceEndpoint
html_content = '''
<html>
<head>
<link rel="openid2.provider" href="https://example.com/openid">
<link rel="openid2.local_id" href="https://example.com/user/local">
</head>
</html>
'''
services = OpenIDServiceEndpoint.fromHTML("https://example.com/user", html_content)
for service in services:
print(f"Provider: {service.server_url}")
print(f"Local ID: {service.local_id}")# OpenID Namespace URIs
OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
# OpenID Type URIs (in preference order)
OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server' # OP Identifier
OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon' # User Identifier
OPENID_1_1_TYPE = 'http://openid.net/signon/1.1' # OpenID 1.1
OPENID_1_0_TYPE = 'http://openid.net/signon/1.0' # OpenID 1.0
# OpenIDServiceEndpoint class constants
openid_type_uris = [
OPENID_IDP_2_0_TYPE,
OPENID_2_0_TYPE,
OPENID_1_1_TYPE,
OPENID_1_0_TYPE
]# Discovery result tuple
DiscoveryResult = tuple[str, list[OpenIDServiceEndpoint]]
# Exception types
class DiscoveryFailure(Exception):
"""Raised when OpenID discovery fails."""
class XRDSError(Exception):
"""Raised when XRDS parsing fails."""Install with Tessl CLI
npx tessl i tessl/pypi-python3-openid