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.
110 lines
3.3 KiB
Python
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
|