Comprehensive SAML 2.0 toolkit for Python applications enabling SSO and SLO functionality with Service Provider support
—
Comprehensive utilities for cryptographic operations, XML processing, URL handling, certificate management, and security validation. These utilities provide the foundational security and processing capabilities that underpin all SAML operations.
Essential utility functions for SAML operations including cryptography, XML processing, URL handling, and time utilities.
class OneLogin_Saml2_Utils:
# Class constants for XPath and time handling
RESPONSE_SIGNATURE_XPATH = '/samlp:Response/ds:Signature'
ASSERTION_SIGNATURE_XPATH = '/samlp:Response/saml:Assertion/ds:Signature'
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
ALLOWED_CLOCK_DRIFT = 300 # secondsSecure URL processing and parameter management for SAML redirects and requests.
@staticmethod
def redirect(url: str, parameters: dict = {}, request_data: dict = {}) -> str:
"""
Execute redirection with validation and parameter encoding.
Parameters:
- url: Target URL for redirection
- parameters: Query parameters to append
- request_data: HTTP request context
Returns:
Complete redirection URL with encoded parameters
"""
@staticmethod
def get_self_url_host(request_data: dict) -> str:
"""
Get protocol + current host + port.
Parameters:
- request_data: HTTP request information
Returns:
Base URL (e.g., "https://example.com:8443")
"""
@staticmethod
def get_self_url_no_query(request_data: dict) -> str:
"""
Get current URL without query string.
Parameters:
- request_data: HTTP request information
Returns:
URL without query parameters
"""
@staticmethod
def normalize_url(url: str) -> str:
"""
Normalize URL for comparison (lowercase netloc).
Parameters:
- url: URL to normalize
Returns:
Normalized URL string
"""
@staticmethod
def escape_url(url: str, lowercase_urlencoding: bool = False) -> str:
"""
Escape non-safe symbols in URLs.
Parameters:
- url: URL to escape
- lowercase_urlencoding: Use lowercase encoding for ADFS compatibility
Returns:
URL-encoded string
"""Encoding and compression utilities for SAML message processing.
@staticmethod
def b64encode(data: str) -> str:
"""
Base64 encoding with string compatibility.
Parameters:
- data: Data to encode
Returns:
Base64 encoded string
"""
@staticmethod
def b64decode(data: str) -> str:
"""
Base64 decoding with proper padding.
Parameters:
- data: Base64 encoded data
Returns:
Decoded string
"""
@staticmethod
def deflate_and_base64_encode(value: str) -> str:
"""
Zlib deflate and base64 encode (for HTTP-Redirect binding).
Parameters:
- value: String to compress and encode
Returns:
Deflated and base64 encoded string
"""
@staticmethod
def decode_base64_and_inflate(value: str, ignore_zip: bool = False) -> str:
"""
Base64 decode and zlib inflate (RFC1951).
Parameters:
- value: Encoded and compressed string
- ignore_zip: Skip inflation if True
Returns:
Decoded and inflated string
"""X.509 certificate handling and cryptographic key management for SAML security operations.
@staticmethod
def format_cert(cert: str, heads: bool = True) -> str:
"""
Format X.509 certificate with optional headers/footers.
Parameters:
- cert: Certificate string or PEM data
- heads: Whether to include BEGIN/END CERTIFICATE headers
Returns:
Properly formatted certificate string
"""
@staticmethod
def format_private_key(key: str, heads: bool = True) -> str:
"""
Format private key (RSA or PKCS#8) with headers.
Parameters:
- key: Private key string or PEM data
- heads: Whether to include BEGIN/END headers
Returns:
Properly formatted private key string
"""
@staticmethod
def calculate_x509_fingerprint(x509_cert: str, alg: str = 'sha1') -> str:
"""
Calculate certificate fingerprint.
Parameters:
- x509_cert: X.509 certificate string
- alg: Hash algorithm (sha1, sha256, sha384, sha512)
Returns:
Lowercase fingerprint string without colons
"""
@staticmethod
def format_finger_print(fingerprint: str) -> str:
"""
Format fingerprint by removing colons and converting to lowercase.
Parameters:
- fingerprint: Fingerprint with or without colons
Returns:
Formatted fingerprint string
"""XML digital signature creation and validation with comprehensive security checks.
@staticmethod
def add_sign(xml: str, key: str, cert: str, debug: bool = False, sign_algorithm: str = OneLogin_Saml2_Constants.RSA_SHA256, digest_algorithm: str = OneLogin_Saml2_Constants.SHA256) -> str:
"""
Add XML digital signature to SAML documents.
Parameters:
- xml: XML document to sign
- key: Private key for signing
- cert: X.509 certificate
- debug: Enable debug mode
- sign_algorithm: Signature algorithm (default: RSA-SHA256)
- digest_algorithm: Digest algorithm (default: SHA256)
Returns:
Signed XML document
"""
@staticmethod
def validate_sign(xml: str, cert: str = None, fingerprint: str = None, fingerprintalg: str = 'sha1', validatecert: bool = False, debug: bool = False, xpath: str = None, multicerts: list = None, raise_exceptions: bool = False) -> bool:
"""
Validate XML signatures with multiple certificate support.
Parameters:
- xml: XML document to validate
- cert: X.509 certificate for validation
- fingerprint: Certificate fingerprint
- fingerprintalg: Fingerprint algorithm
- validatecert: Whether to validate certificate
- debug: Enable debug mode
- xpath: Specific XPath for signature element
- multicerts: List of certificates to try
- raise_exceptions: Whether to raise validation exceptions
Returns:
True if signature is valid
"""
@staticmethod
def validate_binary_sign(signed_query: str, signature: str, cert: str = None, algorithm: str = OneLogin_Saml2_Constants.RSA_SHA256, debug: bool = False) -> bool:
"""
Validate binary signatures (for GET requests).
Parameters:
- signed_query: Query string that was signed
- signature: Base64 encoded signature
- cert: X.509 certificate
- algorithm: Signature algorithm
- debug: Enable debug mode
Returns:
True if signature is valid
"""XML encryption handling for assertions and NameID protection.
@staticmethod
def decrypt_element(encrypted_data: str, key: str, debug: bool = False, inplace: bool = False) -> str:
"""
Decrypt XML encrypted elements.
Parameters:
- encrypted_data: Encrypted XML element
- key: Private key for decryption
- debug: Enable debug mode
- inplace: Whether to decrypt in-place
Returns:
Decrypted XML element
"""
@staticmethod
def generate_name_id(value: str, sp_nq: str, sp_format: str = None, cert: str = None, debug: bool = False, nq: str = None) -> str:
"""
Generate NameID with optional encryption.
Parameters:
- value: NameID value
- sp_nq: SP Name Qualifier
- sp_format: NameID format
- cert: Certificate for encryption
- debug: Enable debug mode
- nq: Name Qualifier
Returns:
NameID XML element (encrypted if cert provided)
"""SAML timestamp handling with proper format conversion and validation.
@staticmethod
def parse_time_to_SAML(time: int) -> str:
"""
Convert UNIX timestamp to SAML2 timestamp format.
Parameters:
- time: UNIX timestamp
Returns:
SAML timestamp string (ISO 8601 format)
"""
@staticmethod
def parse_SAML_to_time(timestr: str) -> int:
"""
Convert SAML2 timestamp to UNIX timestamp.
Parameters:
- timestr: SAML timestamp string
Returns:
UNIX timestamp integer
"""
@staticmethod
def now() -> int:
"""
Get current UNIX timestamp.
Returns:
Current timestamp as integer
"""
@staticmethod
def parse_duration(duration: str, timestamp: int = None) -> int:
"""
Interpret ISO8601 duration relative to timestamp.
Parameters:
- duration: ISO8601 duration string (e.g., "PT30M")
- timestamp: Base timestamp (default: current time)
Returns:
Resulting timestamp after adding duration
"""Additional utility functions for ID generation and session management.
@staticmethod
def generate_unique_id() -> str:
"""
Generate unique string for assertions using UUID and SHA1.
Returns:
Unique identifier string
"""
@staticmethod
def get_status(dom: str) -> dict:
"""
Extract status information from SAML Response.
Parameters:
- dom: SAML Response XML document
Returns:
Dictionary with status code and message
"""
@staticmethod
def delete_local_session(callback: callable = None) -> None:
"""
Delete local session with optional callback.
Parameters:
- callback: Optional function to call during session deletion
"""Secure XML processing utilities with protection against XML vulnerabilities and comprehensive XPath operations.
class OneLogin_Saml2_XML:
@staticmethod
def to_string(xml: object, **kwargs) -> str:
"""
Serialize XML element to encoded string with namespace cleanup.
Parameters:
- xml: XML element or document
- **kwargs: Additional serialization options
Returns:
Encoded XML string
"""
@staticmethod
def to_etree(xml: str) -> object:
"""
Parse XML from string/bytes to lxml Element with security.
Parameters:
- xml: XML string or bytes
Returns:
lxml Element object
Security Features:
- Forbids DTD processing
- Forbids entity resolution
- Strips comments and processing instructions
"""
@staticmethod
def validate_xml(xml: str, schema: str, debug: bool = False) -> bool:
"""
Validate XML against XSD schema files.
Parameters:
- xml: XML document to validate
- schema: XSD schema file path
- debug: Enable debug output
Returns:
True if XML is valid against schema
"""Execute XPath queries with SAML namespace support and security features.
@staticmethod
def query(dom: object, query: str, context: object = None, tagid: str = None) -> list:
"""
Execute XPath queries with SAML namespace support.
Parameters:
- dom: XML document or element
- query: XPath expression
- context: XPath context element
- tagid: Specific tag ID for context
Returns:
List of matching XML elements
"""
@staticmethod
def extract_tag_text(xml: str, tagname: str) -> str:
"""
Extract specific tag content as text.
Parameters:
- xml: XML document string
- tagname: Tag name to extract
Returns:
Tag text content
"""
@staticmethod
def element_text(node: object) -> str:
"""
Get element text content with comment stripping.
Parameters:
- node: XML element node
Returns:
Clean text content
"""Comprehensive constants for SAML 2.0 specifications, algorithms, and URIs.
class OneLogin_Saml2_Constants:
# Clock drift allowance for timestamp validation
ALLOWED_CLOCK_DRIFT = 300 # secondsStandard NameID format identifiers for different identity representations.
# NameID Format Constants
NAMEID_EMAIL_ADDRESS = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
NAMEID_X509_SUBJECT_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName'
NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName'
NAMEID_UNSPECIFIED = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
NAMEID_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos'
NAMEID_ENTITY = 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
NAMEID_TRANSIENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
NAMEID_PERSISTENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
NAMEID_ENCRYPTED = 'urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted'Standard attribute name format identifiers for SAML attributes.
# Attribute Name Format Constants
ATTRNAME_FORMAT_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified'
ATTRNAME_FORMAT_URI = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
ATTRNAME_FORMAT_BASIC = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'Protocol bindings for SAML message transport.
# SAML Binding Constants
BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'
BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP'
BINDING_DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE'Standard XML namespace URIs for SAML processing.
# SAML Namespaces
NS_SAML = 'urn:oasis:names:tc:SAML:2.0:assertion'
NS_SAMLP = 'urn:oasis:names:tc:SAML:2.0:protocol'
NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'
NS_MD = 'urn:oasis:names:tc:SAML:2.0:metadata'
NS_XS = 'http://www.w3.org/2001/XMLSchema'
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
NS_XENC = 'http://www.w3.org/2001/04/xmlenc#'
NS_DS = 'http://www.w3.org/2000/09/xmldsig#'
# Namespace Prefixes
NS_PREFIX_SAML = 'saml'
NS_PREFIX_SAMLP = 'samlp'
NS_PREFIX_MD = 'md'
NS_PREFIX_XS = 'xs'
NS_PREFIX_XSI = 'xsi'
NS_PREFIX_XSD = 'xsd'
NS_PREFIX_XENC = 'xenc'
NS_PREFIX_DS = 'ds'
# Namespace Map (Prefix:Namespace Mappings)
NSMAP = {
NS_PREFIX_SAMLP: NS_SAMLP,
NS_PREFIX_SAML: NS_SAML,
NS_PREFIX_DS: NS_DS,
NS_PREFIX_XENC: NS_XENC,
NS_PREFIX_MD: NS_MD
}Authentication context class references for different authentication methods.
# Authentication Context Constants
AC_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified'
AC_PASSWORD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
AC_PASSWORD_PROTECTED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
AC_X509 = 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'
AC_SMARTCARD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard'
AC_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos'Subject confirmation method identifiers for assertion validation.
# Subject Confirmation Methods
CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'
CM_HOLDER_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key'
CM_SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches'SAML response status codes for success and error conditions.
# Status Code Constants
STATUS_SUCCESS = 'urn:oasis:names:tc:SAML:2.0:status:Success'
STATUS_REQUESTER = 'urn:oasis:names:tc:SAML:2.0:status:Requester'
STATUS_RESPONDER = 'urn:oasis:names:tc:SAML:2.0:status:Responder'
STATUS_VERSION_MISMATCH = 'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch'
STATUS_NO_PASSIVE = 'urn:oasis:names:tc:SAML:2.0:status:NoPassive'
STATUS_PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout'
STATUS_PROXY_COUNT_EXCEEDED = 'urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded'Algorithm identifiers for digital signatures, encryption, and hashing.
# Hash Algorithms
SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
# Signature Algorithms
RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
# Encryption Algorithms
AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'
AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
# Deprecated Algorithms (security warnings)
DEPRECATED_ALGORITHMS = [DSA_SHA1, RSA_SHA1, SHA1]Comprehensive error classes with detailed error codes for different failure scenarios.
class OneLogin_Saml2_Error(Exception):
def __init__(self, message: str, code: int = 0, errors: list = None):
"""
General SAML error with categorized error codes.
Parameters:
- message: Error description
- code: Numeric error code
- errors: List of additional error details
"""
class OneLogin_Saml2_ValidationError(OneLogin_Saml2_Error):
def __init__(self, message: str, code: int = 0, errors: list = None):
"""
SAML validation error with specific validation codes.
Parameters:
- message: Validation error description
- code: Validation error code
- errors: List of validation failure details
"""General Error Codes:
SETTINGS_FILE_NOT_FOUND - Configuration file missingSETTINGS_INVALID - Invalid configuration valuesCERT_NOT_FOUND - Certificate not foundSAML_RESPONSE_NOT_FOUND - SAML Response not foundSAML_SINGLE_LOGOUT_NOT_SUPPORTED - SLO not supportedREDIRECT_INVALID_URL - Invalid redirect URLValidation Error Codes:
UNSUPPORTED_SAML_VERSION - Unsupported SAML versionMISSING_ID - Missing ID attributeWRONG_NUMBER_OF_ASSERTIONS - Incorrect assertion countSTATUS_CODE_IS_NOT_SUCCESS - Non-success statusINVALID_SIGNATURE - Invalid digital signatureASSERTION_EXPIRED - Assertion expiredWRONG_AUDIENCE - Wrong audience restrictionWRONG_ISSUER - Wrong issuer verificationRESPONSE_EXPIRED - SAML Response expiredDEPRECATED_SIGNATURE_METHOD - Deprecated signature method usedDEPRECATED_DIGEST_METHOD - Deprecated digest method usedXML parsing functions with comprehensive security protections against XML vulnerabilities.
def parse(source: str, parser: object = None, base_url: str = None, forbid_dtd: bool = True, forbid_entities: bool = True) -> object:
"""
Secure XML document parsing with vulnerability protection.
Parameters:
- source: XML source (file, URL, or string)
- parser: Custom XML parser
- base_url: Base URL for relative references
- forbid_dtd: Prevent DTD processing (security)
- forbid_entities: Prevent entity expansion (security)
Returns:
Parsed XML document tree
Security Features:
- DTD processing disabled
- Entity expansion disabled
- Network access disabled
- Comment and PI removal
"""
def fromstring(text: str, parser: object = None, base_url: str = None, forbid_dtd: bool = True, forbid_entities: bool = True) -> object:
"""
Secure XML string parsing with security protections.
Parameters:
- text: XML string to parse
- parser: Custom XML parser
- base_url: Base URL for relative references
- forbid_dtd: Prevent DTD processing
- forbid_entities: Prevent entity expansion
Returns:
Parsed XML element
"""Python 2/3 compatibility utilities for consistent string and byte handling across versions.
def utf8(data: str) -> str:
"""
Convert data to UTF-8 string with version compatibility.
Parameters:
- data: String or bytes data
Returns:
UTF-8 encoded string (Python version appropriate)
"""
def to_string(data: str) -> str:
"""
Convert data to string with proper encoding.
Parameters:
- data: String or bytes data
Returns:
String in appropriate format for Python version
"""
def to_bytes(data: str) -> bytes:
"""
Convert data to bytes with UTF-8 encoding.
Parameters:
- data: String data
Returns:
UTF-8 encoded bytes
"""from onelogin.saml2.utils import OneLogin_Saml2_Utils
# Validate XML signature with certificate
is_valid = OneLogin_Saml2_Utils.validate_sign(
xml_document,
cert=idp_certificate,
validatecert=True,
raise_exceptions=True
)
# Calculate certificate fingerprint
fingerprint = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
certificate,
alg='sha256'
)# Convert timestamps between formats
saml_time = OneLogin_Saml2_Utils.parse_time_to_SAML(time.time())
unix_time = OneLogin_Saml2_Utils.parse_SAML_to_time(saml_timestamp)
# Parse ISO8601 duration
expiry_time = OneLogin_Saml2_Utils.parse_duration("PT30M") # 30 minutes from nowfrom onelogin.saml2.xml_utils import OneLogin_Saml2_XML
from onelogin.saml2.xmlparser import fromstring
# Secure XML parsing
xml_element = fromstring(xml_string, forbid_dtd=True, forbid_entities=True)
# XPath queries with SAML namespaces
assertions = OneLogin_Saml2_XML.query(xml_element, '//saml:Assertion')
# XML validation against schema
is_valid = OneLogin_Saml2_XML.validate_xml(xml_doc, 'saml-schema-assertion-2.0.xsd')from onelogin.saml2.errors import OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError
try:
auth.process_response()
except OneLogin_Saml2_ValidationError as e:
print(f"Validation failed: {e} (Code: {e.code})")
except OneLogin_Saml2_Error as e:
print(f"SAML error: {e} (Code: {e.code})")Install with Tessl CLI
npx tessl i tessl/pypi-python3-saml