Complete identity smoke path: id_token claims, registration entry, cutover docs
Prefer verified KeyCape id_token claims when /userinfo returns 401; soft-fail userinfo. Add CSOC-WP-0003 registration entry (disabled until NetKingdom URL), AAL step-up hooks, smoke/cutover evidence for tegwick OIDC without MFA.
This commit is contained in:
parent
3bc16b581b
commit
29a9ff735e
14 changed files with 513 additions and 41 deletions
110
tests/test_oidc_claims.py
Normal file
110
tests/test_oidc_claims.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"""Unit tests for OIDC claim assembly (no live issuer)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.test import override_settings
|
||||
|
||||
from coulomb_social.apps.identity import oidc
|
||||
|
||||
|
||||
@override_settings(
|
||||
OIDC_ENABLED=True,
|
||||
OIDC_ISSUER="https://kc.example.test",
|
||||
OIDC_CLIENT_ID="coulomb-social",
|
||||
OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/",
|
||||
)
|
||||
def test_claims_prefer_id_token_when_userinfo_empty():
|
||||
token = {"id_token": "header.payload.sig"}
|
||||
fake_claims = {
|
||||
"iss": "https://kc.example.test",
|
||||
"sub": "platform-root",
|
||||
"aud": "coulomb-social",
|
||||
"name": "Platform Root",
|
||||
"preferred_username": "platform-root",
|
||||
"tenant": "tenant:coulomb",
|
||||
}
|
||||
with patch.object(oidc, "decode_id_token", return_value=fake_claims) as dec:
|
||||
out = oidc.claims_from_token_response(token, {})
|
||||
dec.assert_called_once_with("header.payload.sig")
|
||||
assert out["sub"] == "platform-root"
|
||||
assert out["name"] == "Platform Root"
|
||||
|
||||
|
||||
@override_settings(
|
||||
OIDC_ENABLED=True,
|
||||
OIDC_ISSUER="https://kc.example.test",
|
||||
OIDC_CLIENT_ID="coulomb-social",
|
||||
OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/",
|
||||
)
|
||||
def test_userinfo_overlays_id_token():
|
||||
token = {"id_token": "h.p.s"}
|
||||
with patch.object(
|
||||
oidc,
|
||||
"decode_id_token",
|
||||
return_value={"sub": "u1", "name": "From Token", "email": ""},
|
||||
):
|
||||
out = oidc.claims_from_token_response(
|
||||
token, {"name": "From Userinfo", "email": "a@b.c"}
|
||||
)
|
||||
assert out["sub"] == "u1"
|
||||
assert out["name"] == "From Userinfo"
|
||||
assert out["email"] == "a@b.c"
|
||||
|
||||
|
||||
@override_settings(
|
||||
OIDC_ENABLED=True,
|
||||
OIDC_ISSUER="https://kc.example.test",
|
||||
OIDC_CLIENT_ID="coulomb-social",
|
||||
OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/",
|
||||
)
|
||||
def test_missing_sub_raises():
|
||||
with patch.object(oidc, "decode_id_token", return_value={}):
|
||||
with pytest.raises(oidc.OIDCConfigurationError, match="no subject"):
|
||||
oidc.claims_from_token_response({"id_token": "h.p.s"}, {})
|
||||
|
||||
|
||||
@override_settings(
|
||||
OIDC_ENABLED=True,
|
||||
OIDC_ISSUER="https://kc.example.test",
|
||||
OIDC_CLIENT_ID="coulomb-social",
|
||||
OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/",
|
||||
)
|
||||
def test_fetch_userinfo_soft_fails_on_401(httpx_mock=None):
|
||||
import httpx
|
||||
|
||||
class FakeResp:
|
||||
status_code = 401
|
||||
|
||||
def json(self):
|
||||
return {"error": "invalid_token"}
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
oidc,
|
||||
"discovery_document",
|
||||
return_value={"userinfo_endpoint": "https://kc.example.test/userinfo"},
|
||||
),
|
||||
patch.object(httpx, "get", return_value=FakeResp()),
|
||||
):
|
||||
assert oidc.fetch_userinfo("opaque-or-jwt") == {}
|
||||
|
||||
|
||||
@override_settings(
|
||||
OIDC_ENABLED=True,
|
||||
OIDC_ISSUER="https://kc.example.test",
|
||||
OIDC_CLIENT_ID="coulomb-social",
|
||||
OIDC_REDIRECT_URI="https://coulomb.example.test/auth/callback/",
|
||||
)
|
||||
def test_authorization_url_can_request_aal2():
|
||||
with patch.object(
|
||||
oidc,
|
||||
"discovery_document",
|
||||
return_value={"authorization_endpoint": "https://kc.example.test/authorize"},
|
||||
):
|
||||
url = oidc.build_authorization_url(
|
||||
state="state", code_verifier="verifier", acr_values="aal2"
|
||||
)
|
||||
assert "acr_values=aal2" in url
|
||||
|
|
@ -20,6 +20,16 @@ def test_landing_public(client):
|
|||
assert b"Sign in" in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_registration_link_uses_only_configured_destination(client, settings):
|
||||
settings.NETKINGDOM_REGISTRATION_URL = (
|
||||
"https://users.coulomb.social/register?client_id=coulomb-social"
|
||||
)
|
||||
r = client.get(reverse("identity:register") + "?next=https://evil.example")
|
||||
assert r.status_code == 302
|
||||
assert r["Location"] == settings.NETKINGDOM_REGISTRATION_URL
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_app_home_requires_login(client):
|
||||
r = client.get(reverse("core:app_home"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue