user-engine/tests/test_account_identity_disclosure.py
tegwick 468d4c0822 USER-WP-0039: show the account-site session's assurance beside its uid (NK-WP-0051-T03)
The account-site line shows its KeyCape token's aal1/aal2. Authelia's own
authentication_level is not shown: KeyCape checks the one-time code, so
Authelia reports level 1 even for an aal2 sign-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 00:50:52 +02:00

162 lines
8.2 KiB
Python

"""An existing NetKingdom sign-in is shown before the account site continues it."""
import unittest
from urllib.parse import parse_qs, urlparse
import test_portal_navigation
from test_web import invoke
class RecordingLookup:
def __init__(self, result):
self.result = result
self.headers = []
def username(self, header):
self.headers.append(header)
if isinstance(self.result, BaseException):
raise self.result
if callable(self.result):
return self.result(header)
return self.result
class AccountIdentityDisclosureTests(unittest.TestCase):
setUp = test_portal_navigation.PortalNavigationTests.setUp
def test_confirmed_sign_in_is_named_before_the_account_site_continues(self):
def answer(header):
if "authelia_session=super-secret-session" in header:
return "platform-root"
return None
self.app.identity_lookup = RecordingLookup(answer)
cookie = "ue_session=absent; authelia_session=super-secret-session"
response, body = invoke(self.app, "/", cookie=cookie)
self.assertEqual("200 OK", response["status"])
self.assertEqual(1, len(self.app.identity_lookup.headers))
self.assertIn(b"NetKingdom sign-in is <strong>platform-root</strong>", body)
self.assertIn(b"This account site has no session yet.", body)
self.assertIn(b'href="/login">Continue as platform-root', body)
self.assertIn(b'href="/login?fresh=1">Use a different identity', body)
self.assertNotIn(b"Not signed in", body)
self.assertNotIn(b"You are not signed in.", body)
self.assertNotIn(b"Signed in as", body)
self.assertNotIn(b"super-secret-session", body)
self.assertNotIn(b"Active now", body)
_response, body = invoke(self.app, "/")
self.assertIn(b'href="/login">Sign in', body)
self.assertIn(b"Not signed in", body)
self.assertNotIn(b"platform-root", body)
self.assertEqual(2, len(self.app.identity_lookup.headers))
def test_account_session_is_not_replaced_by_the_netkingdom_cookie(self):
self.app.identity_lookup = RecordingLookup("platform-root")
_, body = invoke(
self.app, "/", cookie="ue_session=member; authelia_session=super-secret-session"
)
# The account-site session stays; the other Authelia uid is only shown.
self.assertIn(b"Signed in as <strong>sample.user</strong>", body)
self.assertNotIn(b"Signed in as <strong>platform-root</strong>", body)
self.assertNotIn(b'href="/login"', body)
self.assertNotIn(b"super-secret-session", body)
self.assertEqual(1, len(self.app.identity_lookup.headers))
# NK-WP-0051-T03: two labelled uid lines and a visible mismatch marker.
def _uid_lines(self, path, lookup_result, cookie="ue_session=member; authelia_session=super-secret-session"):
self.app.identity_lookup = RecordingLookup(lookup_result)
response, body = invoke(self.app, path, cookie=cookie)
self.assertEqual("200 OK", response["status"])
return body
def test_signed_in_page_names_both_session_uids(self):
for path in ["/", "/onboarding"]:
with self.subTest(path=path):
body = self._uid_lines(path, "sample.user")
self.assertIn(b'<dd data-session="account-site"><strong>sample.user</strong> &middot; <span data-assurance="aal2">', body)
self.assertIn(b'<dd data-session="authelia"><strong>sample.user</strong></dd>', body)
self.assertIn(b"Account site session <code>uid</code>", body)
self.assertIn(b"Authelia session <code>uid</code>", body)
self.assertNotIn(b"session-uid-mismatch", body)
self.assertEqual(1, len(self.app.identity_lookup.headers))
def test_differing_authelia_uid_is_marked(self):
for path in ["/", "/onboarding"]:
with self.subTest(path=path):
body = self._uid_lines(path, "bernd.worsch-3")
self.assertIn(b'<dd data-session="account-site"><strong>sample.user</strong> &middot; <span data-assurance="aal2">', body)
self.assertIn(b'<dd data-session="authelia"><strong>bernd.worsch-3</strong></dd>', body)
self.assertIn(b'role="alert" class="session-uid-mismatch"', body)
self.assertIn(b"These sessions differ.", body)
self.assertIn(b"https://kc.example/account/logout", body)
self.assertNotIn(b"super-secret-session", body)
def test_unknown_authelia_uid_is_not_guessed(self):
cases = [
(None, "ue_session=member; authelia_session=opaque"),
(TimeoutError("slow"), "ue_session=member; authelia_session=opaque"),
("<script>x</script>", "ue_session=member; authelia_session=opaque"),
("sample.user", "ue_session=member"),
]
for result, cookie in cases:
with self.subTest(result=result, cookie=cookie):
body = self._uid_lines("/", result if cookie.count("authelia") else None, cookie)
self.assertIn(b'<dd data-session="authelia">not signed in / unknown</dd>', body)
self.assertNotIn(b"session-uid-mismatch", body)
self.assertNotIn(b"<script>x", body)
def test_authelia_line_carries_no_level(self):
# KeyCape checks the one-time code; Authelia's level would read aal1.
body = self._uid_lines("/", "sample.user")
self.assertIn(b'<dd data-session="authelia"><strong>sample.user</strong></dd>', body)
def test_without_lookup_authelia_uid_is_unknown(self):
self.app.identity_lookup = None
_, body = invoke(self.app, "/", cookie="ue_session=member; authelia_session=opaque")
self.assertIn(b'<dd data-session="authelia">not signed in / unknown</dd>', body)
self.assertNotIn(b"session-uid-mismatch", body)
self.assertNotIn(b"opaque", body)
def test_lookup_failure_or_unsafe_name_stays_signed_out(self):
for result in [TimeoutError("slow"), "<script>alert(1)</script>", "platform root"]:
with self.subTest(result=result):
self.app.identity_lookup = RecordingLookup(result)
_, body = invoke(self.app, "/", cookie="authelia_session=opaque")
self.assertIn(b'href="/login">Sign in', body)
self.assertIn(b"You are not signed in.", body)
self.assertNotIn(b"Signed in as", body)
self.assertNotIn(b"<script>", body)
self.assertNotIn(b"platform root", body)
def test_logged_out_and_recovery_name_the_same_sign_in(self):
self.app.identity_lookup = RecordingLookup("platform-root")
cookie = "authelia_session=super-secret-session"
_, logged_out = invoke(self.app, "/logged-out", cookie=cookie)
self.assertIn(b"NetKingdom sign-in is <strong>platform-root</strong>", logged_out)
self.assertIn(b"This account site has no session yet.", logged_out)
self.assertNotIn(b"may still be active", logged_out)
self.assertNotIn(b"super-secret-session", logged_out)
self.assertIn(b"https://kc.example/account/logout", logged_out)
_, recovery = invoke(self.app, "/access-recovery", cookie=cookie)
self.assertIn(b"NetKingdom sign-in is <strong>platform-root</strong>", recovery)
self.assertNotIn(b"Signed in as", recovery)
self.assertIn(b"/logout", recovery)
def test_different_identity_requests_a_fresh_sign_in(self):
response, _body = invoke(self.app, "/login", query="fresh=1")
location = response["headers"]["Location"]
query = parse_qs(urlparse(location).query)
self.assertEqual(["login"], query["prompt"])
self.assertEqual(["0"], query["max_age"])
self.assertNotIn("acr_values", query)
def test_query_parameters_do_not_invent_the_shown_identity(self):
self.app.identity_lookup = RecordingLookup("platform-root")
_, body = invoke(
self.app,
"/",
query="username=forged",
cookie="authelia_session=super-secret-session",
)
self.assertIn(b"platform-root", body)
self.assertNotIn(b"forged", body)