Skip to main content

wsfed-caep

Server-side WS-Federation relying party for Python. It accepts SAML 2.0 bearer assertions, reads identity-provider federation metadata, writes relying-party metadata, verifies XML signatures, decrypts encrypted assertions, and remembers assertion ids so a bearer token cannot be replayed. A Shared Signals poll receiver for CAEP and RISC is included. The relying party does not start that receiver.

Version 0.1.1. License: MIT. A pull request merged into main publishes the package to PyPI after the test workflow succeeds on that commit. A direct push does not. There is no API token: PyPI Trusted Publishing trades the workflow's GitHub OIDC token for a short-lived upload token, from the pypi environment, and PyPI holds the publisher for this repository and publish.yml. A version already on PyPI stays there; the next release is a new version in pyproject.toml (and VERSION). develop and main are the same commit when this tree is published; nothing here creates a remote.

Usage

from wsfed_caep import RelyingParty

rp = RelyingParty.from_file("wsfed.json")
request = rp.login(relay_state="/")
who = rp.handle_reply({"method": "POST", "url": "/wsfed", "body": body}, request.id)

JSON keys match the Node configuration: realm, replyUrl, idpMetadata, privateKey, certificate, idpCa, idpEntityId, clockSkewSeconds, wantAssertionsEncrypted, allowUnsolicited, requestTokenType, signals, ssf. file: and env: prefixes are accepted. The default subject attribute is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier.

signals omitted or true is on. The resolved configuration omits the key in that case. false is kept, does not start a receiver, and does not require transmitter metadata. Sign-in is unchanged either way: WS-Federation does not send OAuth scopes. Any other value raises a config error whose message is signals must be true or false.

docker compose build
docker run --rm --user "$(id -u):$(id -g)" \
  -v "$PWD/demo:/app/demo" \
  wsfed-caep-python-demo \
  wsfed-caep generate-keys --config demo/wsfed.json
docker run --rm --user "$(id -u):$(id -g)" \
  -v "$PWD/demo:/app/demo" \
  wsfed-caep-python-demo \
  wsfed-caep metadata --config demo/wsfed.json

The generated certificate's common name defaults to wsfed-caep-nodejs relying party plus the realm, cut at 64 characters.

Plain http (#13). Federation metadata from a URL and every Shared Signals transmitter call (configuration, JWKS, stream and poll) use https. A plain http:// URL is refused before any connection unless its host is loopback (127.0.0.0/8, ::1, localhost), because metadata carries the signing keys and Shared Signals carry SETs and the bearer token. A URL with userinfo is never loopback, and a URL with whitespace or a control character is refused. The WsFedError keeps the fetch's code (metadata, ssf) and names the scheme and host only. There is no opt-out. The relying party's own realm and reply URL may stay http; they are never fetched.

Tests

docker build --target test .

The suite runs in a container.

The suite includes a stub identity provider. Those tests do not start iya-sts.

python3 scripts/live_iya_sts.py is the live half. It uses iya-sts (the latest main branch, in memory mode with persistence and clustering off, when IYA_STS_URL is unset; IYA_STS_IMAGE starts a fresh container from an image), copies this checkout to a scratch build context, builds the demo image, runs the install script inside it (which registers the demo, generates the key pair and writes the idpCa chain), then builds the demo image again so it carries the chain, checks the image holds the chain and no key, starts the demo with no CA mount, signs in through GET /login, and requires a credential-change event and a session-revoked event on GET /live-status. The second event ends the local session. It uses the host's Python and Docker and does not import this library. It uses two high ports (defaults 18556 and 18656, LIVE_STS_PORT and LIVE_DEMO_PORT) and never binds 3000, 8081 or 18481-18495. No secret appears on a docker command line, and sign-in requests are logged by fixed labels, not URLs. The nightly workflow (06:35 UTC) runs the unit suite and this script once every 24 hours.

Layout

  • src/wsfed_caep/ library
  • tests/ pytest, fixtures, stub identity provider
  • demo/app.py stdlib HTTP demo
  • scripts/configure_iya_sts.py registers the demo with iya-sts and writes the listener CA trust chain to the file idpCa names
  • scripts/trust_chain.py holds save_trust_chain() and refreshes that file
  • scripts/live_iya_sts.py live sign-in against iya-sts
  • .github/workflows/nightly.yml unit suite and live script, once every 24 hours
  • Dockerfile, docker-compose.yml (network_mode: host, PORT=3000)

The iya-sts trust chain (idpCa)

idpCa in demo/wsfed.json names the iya-sts TLS listener CA trust chain the library trusts for the IdP metadata URL and the Shared Signals transmitter. The default is file:generated/idp-ca.pem, relative to demo/. The value may also be a plain path, env:<NAME>, or the PEM itself. The library loads every certificate in it as a trust anchor (ssl load_verify_locations(cadata=...)), so the whole chain works and OpenSSL ends the path at the self-signed Root. The presented leaf must still verify to one of those anchors (a forged leaf followed by the real CA certificates is refused), and the host name is checked against the leaf's subject alternative names (a DNS name, or an IP literal such as 127.0.0.1).

scripts/configure_iya_sts.py, the install script, writes that file on every run (scripts/trust_chain.py, save_trust_chain()). It saves the whole chain iya-sts publishes at GET /tls/server-certificate (leaf, TLS Issuing CA, Intermediate, Root), or the chain from the TLS handshake when that path does not answer. It prints the path, the trust anchor's subject and its SHA-256 fingerprint. Compare the anchor with the Root CA on iya-sts's PKI page. A password iya-sts generates for the test user is never printed: it goes to demo/generated/<user>.password, mode 0600.

The demo image build copies that file into the image at /app/demo/generated/idp-ca.pem, so the image trusts that iya-sts with no bind mount and no fetch. Run the install script before you build the image, and rebuild the image after running the install script against a different iya-sts. .dockerignore admits only that file from demo/generated: the relying party's key, certificate and the IdP metadata stay out of the image (compose still mounts demo/generated read-only for them). A build with no chain file yet (a fresh clone, CI) still succeeds; the image then has no idpCa file.

The mechanism is COPY demo ./demo plus .dockerignore's demo/generated/* and !demo/generated/idp-ca.pem. Do not replace it with a COPY that names the file or globs for it: when the file is absent, both BuildKit and the classic builder fail. A directory COPY of demo/ (which always exists) succeeds in both. python scripts/trust_chain.py --config demo/wsfed.json --idp <iya-sts URL> refreshes the file by hand.

Demo

PORT defaults to 3000. A loopback realm and reply URL follow that port. Any other host stays as written. PUBLIC_URL or --public-url replaces the origin and keeps the path. The default http://localhost:3000 is not an explicit public URL. GET /healthz returns 200 and the body ok even when generated keys are absent. GET /login redirects to the identity provider with wa=wsignin1.0, wtrealm, wreply, and wctx. The reply URL accepts the sign-in POST and wsignoutcleanup1.0. POST /logout signs out, including at the identity provider. GET /live-status is JSON: signedIn, subject, the receiver state, and the Shared Signals events this process has accepted (type and jti only). No token is in that body.

Demo JSON uses file: paths under demo/generated/. Those keys are not in the tree. "signals" is omitted, so the receiver is on when a transmitter is configured.

PORT=40123 docker compose up --build

Metadata

Release files for wsfed-caep 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wsfed-caep 0.1.1
File Size Uploaded Provenance Actions
wsfed_caep-0.1.1.tar.gz 54.9 kB With provenance Details for wsfed_caep-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for wsfed-caep 0.1.1
File Interpreter ABI Platform Actions
wsfed_caep-0.1.1-py3-none-any.whl Python 3 none any Details for wsfed_caep-0.1.1-py3-none-any.whl

Total release size: 97.4 kB

Release files / wsfed_caep-0.1.1.tar.gz

Download URL wsfed_caep-0.1.1.tar.gz
Size 54.9 kB
Tags Source
SHA-256 checksum
How to use checksums
12ef4713c75097d0863a264ae741d63aa1b5c07cba00bde7beca87912b3ec186
BLAKE2b-256 checksum
How to use checksums
dc0d9f6b92b678126c5c4fd3a0710dac52506b953d5c4c0c0317417f610d6244
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / wsfed_caep-0.1.1-py3-none-any.whl

Download URL wsfed_caep-0.1.1-py3-none-any.whl
Size 42.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c86f3c1092ac18f81a781996f8dce46fdb25171ead737812350493c28c36efb5
BLAKE2b-256 checksum
How to use checksums
a0e375e0ffeb0ab4fe7b9c3ea62f5e424482d8fe5e851e74940583ba8f3fdec7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for wsfed-caep 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for wsfed_caep-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page