coulomb-social/tests/test_oidc_claims.py
tegwick 29a9ff735e 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.
2026-08-09 22:42:51 +02:00

110 lines
3.3 KiB
Python

"""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