Protocol bindings

The pygamlastan.bindings module encodes and decodes SAML messages for the HTTP-Redirect, HTTP-POST, and Artifact bindings. The functions work on plain Python data (bytes, strings, and name/value pairs), so they fit any web framework: you do the HTTP I/O, pygamlastan does the SAML encoding.

HTTP-Redirect

Encode a message into a redirect URL (DEFLATE + base64 + URL-encoding), then issue a 302:

from pygamlastan import bindings

url = bindings.redirect_encode(
    message_xml.encode(),
    is_request=True,                       # SAMLRequest vs SAMLResponse
    destination="https://idp.example.org/sso",
    relay_state="opaque-state",
)

To decode, pass the raw query string exactly as received. Do not URL-decode it first: gamlastan decodes internally, and for signed redirects the signature is computed over the raw encoded parameters.

from urllib.parse import urlparse

query = urlparse(request_url).query          # "SAMLRequest=...&RelayState=..."
decoded = bindings.redirect_decode(query)
decoded.is_request        # bool
decoded.saml_text         # the message as text
decoded.relay_state       # echoed RelayState
decoded.sig_alg           # signature algorithm, if signed
decoded.signature         # raw signature bytes, if signed

Signed redirects

Pass a signer and algorithm URI to sign the outgoing query, and verify an incoming one with the detached-signature verifier:

url = bindings.redirect_encode(
    message_xml.encode(), is_request=True,
    destination="https://idp.example.org/sso",
    relay_state="state",
    signer=saml_signer,
    sig_alg="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
)

HTTP-POST

Encode a self-submitting HTML form; render it in the browser to auto-post:

html = bindings.post_encode(
    message_xml.encode(), is_request=False,
    destination="https://sp.example.org/acs",
    relay_state="state",
)

Decode from duplicate-preserving, already form-decoded POST parameters (POST fields are plain base64, not DEFLATE-compressed):

decoded = bindings.post_decode([
    ("SAMLResponse", form["SAMLResponse"]),
    ("RelayState", form.get("RelayState", "")),
])

RelayState

RelayState is limited to 80 bytes by the SAML profile. Validate it before use:

bindings.validate_relay_state(value)   # raises SamlBindingError if too long/unsafe

Artifact

pygamlastan.bindings.SamlArtifact builds and parses type 0x0004 artifacts:

import os
artifact = bindings.SamlArtifact(0, "https://idp.example.org", os.urandom(20))
token = artifact.encode()                 # base64 to put in the URL

decoded = bindings.SamlArtifact.decode(token)
decoded.matches_entity("https://idp.example.org")   # True