woodglue.apps.identity_providers¶
OpenID Connect and GitHub sign-in.
Signing in through Identity Providers: OpenID Connect, GitHub, and trusted reverse proxies.
woodglue is its own OAuth client (see docs/adr/0001). A sign-in is the
authorization code flow with PKCE:
/auth/start/<provider>records a pending sign-in (state, nonce, PKCE verifier, and where to return), binds it to the browser with a short-livedwgl_logincookie, and redirects to the provider./auth/callback/<provider>checks that the returnedstatematches the cookie, exchanges the code, and turns the provider's answer into anExternalIdentity. For OIDC that is the validated id_token (issuer, audience, expiry, nonce, signature against the provider's JWKS); for GitHub it is the user, verified primary email, orgs, and teams from the API.- The person is then checked against the provider's
admitrules, signed in as a local User (AuthStore.sign_in_person), and given a Session.
Admission and claim-to-Role mapping are re-evaluated at every sign-in.
A trusted proxy (JwtProxy, HeaderProxy) has no round trip: it asserts the
identity on every request, and woodglue.apps.authn turns the first valid
assertion into a Session.
Pending sign-ins live in memory for ten minutes, so a restart in the middle of one only means signing in again.
SignInError
¶
ExternalIdentity
dataclass
¶
What an Identity Provider asserts about a person.
Source code in src/woodglue/apps/identity_providers.py
PendingSignIns
¶
In-memory pending sign-ins keyed by OAuth state.
Source code in src/woodglue/apps/identity_providers.py
ProviderClient
¶
Bases: ABC
Talks to one Identity Provider. Subclasses implement one protocol.
Source code in src/woodglue/apps/identity_providers.py
GitHubClient
¶
Bases: ProviderClient
Source code in src/woodglue/apps/identity_providers.py
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
TrustedProxy
¶
Bases: ABC
Reads the identity a trusted reverse proxy asserts on each request.
Source code in src/woodglue/apps/identity_providers.py
JwksCache
¶
Signing keys from a JWKS URL, refreshed hourly and when a token names an unknown key id, but at most once a minute, so forged key ids cannot make woodglue hammer the URL.
https:// keys load in the background (load, which create_app starts),
so request handling never waits on the network: a token checked before
they arrive, or naming a key not seen yet, is refused while a refresh runs.
file:// keys, for providers whose keys are deployed as files, are read
in place.
Source code in src/woodglue/apps/identity_providers.py
load()
async
¶
Fetch the keys now. Failures are logged; a later check retries.
Source code in src/woodglue/apps/identity_providers.py
JwtProxy
¶
Bases: TrustedProxy
Verifies a JWT the proxy puts in a header, against keys from jwks_url.
Source code in src/woodglue/apps/identity_providers.py
HeaderProxy
¶
Bases: TrustedProxy
Trusts plain identity headers, but only from trusted_sources.
Source code in src/woodglue/apps/identity_providers.py
admits(rules, who)
¶
Whether rules admit who. Email rules only consider a verified email.
>>> ann = ExternalIdentity("okta", "1", "ann@x.com", True, "Ann", ("ops",))
>>> admits(AdmitRules(email_domains=["x.com"]), ann), admits(AdmitRules(groups=["ops"]), ann)
(True, True)
>>> eve = ExternalIdentity("okta", "2", "eve@x.com", False, "Eve")
>>> admits(AdmitRules(email_domains=["x.com"]), eve)
False
Source code in src/woodglue/apps/identity_providers.py
role_sources(provider, who, admins)
¶
The Role assignments a sign-in sets, keyed by source: the provider's default
and group-mapped Roles, and admin for configured admins.
>>> p = ProviderConfig(id="okta", kind="oidc", issuer="https://i", client_id="c",
... client_secret="s", admit=AdmitRules(anyone=True), default_roles=["viewer"],
... group_roles={"ops": ["operator"]})
>>> ann = ExternalIdentity("okta", "1", "ann@x.com", True, "Ann", ("ops",))
>>> role_sources(p, ann, ["ANN@x.com"])
{'provider:okta': ['operator', 'viewer'], 'admins': ['admin']}
Source code in src/woodglue/apps/identity_providers.py
pkce_challenge(verifier)
¶
The S256 PKCE challenge for a verifier (RFC 7636).
make_clients(providers)
¶
Clients for the providers people sign in with through the login page.
Source code in src/woodglue/apps/identity_providers.py
make_trusted_proxies(providers)
¶
Verifiers for the trusted-proxy providers among providers.