Compare commits
2 commits
e9534ef00f
...
7f1db7189c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f1db7189c | |||
| 468d4c0822 |
4 changed files with 35 additions and 4 deletions
|
|
@ -17,7 +17,9 @@ from urllib.request import HTTPRedirectHandler, Request, build_opener
|
|||
from user_engine.oidc import cookie_value
|
||||
|
||||
_IDENTITY_NAME = re.compile(r"^[A-Za-z0-9._@+-]{1,200}$")
|
||||
_SESSION_TOKEN = re.compile(r"^[A-Za-z0-9._~+/=-]{1,4096}$")
|
||||
# RFC 6265 cookie-octet: printable ASCII except space, '"', ',', ';' and '\'.
|
||||
# Authelia session ids use characters such as '%', '^' and '!'.
|
||||
_SESSION_TOKEN = re.compile(r"^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]{1,4096}$")
|
||||
_PUBLIC_HOST = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+")
|
||||
_CLUSTER_HOST = ".svc.cluster.local"
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,19 @@ def _jsonable(value: Any) -> Any:
|
|||
return value
|
||||
|
||||
|
||||
def _assurance_text(level: object) -> str:
|
||||
"""Name the account-site session's level from its KeyCape token.
|
||||
|
||||
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.
|
||||
"""
|
||||
if level == "aal2":
|
||||
return ' · <span data-assurance="aal2"><code>aal2</code> one-time code verified</span>'
|
||||
if level == "aal1":
|
||||
return ' · <span data-assurance="aal1"><code>aal1</code> password only</span>'
|
||||
return ""
|
||||
|
||||
|
||||
class PortalApplication:
|
||||
"""Small, auditable HTTP adapter over :class:`UserEngineService`."""
|
||||
|
||||
|
|
@ -2343,10 +2356,13 @@ Use the login name they provide; it may differ from your display name.</p></sect
|
|||
"""
|
||||
account_uid = actor.preferred_username or actor.subject
|
||||
authelia_uid = self._netkingdom_name()
|
||||
account_level = _assurance_text(
|
||||
actor.assurance.get("level") if isinstance(actor.assurance, Mapping) else None)
|
||||
lines = (
|
||||
'<dl class="session-uids">'
|
||||
"<dt>Account site session <code>uid</code></dt>"
|
||||
f'<dd data-session="account-site"><strong>{escape(account_uid)}</strong></dd>'
|
||||
f'<dd data-session="account-site"><strong>{escape(account_uid)}</strong>'
|
||||
f'{account_level}</dd>'
|
||||
"<dt>Authelia session <code>uid</code> (shared NetKingdom sign-in)</dt>"
|
||||
+ (
|
||||
f'<dd data-session="authelia"><strong>{escape(authelia_uid)}</strong></dd>'
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class AccountIdentityDisclosureTests(unittest.TestCase):
|
|||
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></dd>', body)
|
||||
self.assertIn(b'<dd data-session="account-site"><strong>sample.user</strong> · <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)
|
||||
|
|
@ -84,7 +84,7 @@ class AccountIdentityDisclosureTests(unittest.TestCase):
|
|||
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></dd>', body)
|
||||
self.assertIn(b'<dd data-session="account-site"><strong>sample.user</strong> · <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)
|
||||
|
|
@ -105,6 +105,11 @@ class AccountIdentityDisclosureTests(unittest.TestCase):
|
|||
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")
|
||||
|
|
|
|||
|
|
@ -83,6 +83,14 @@ class IdentityStateParseTests(unittest.TestCase):
|
|||
self.assertIsNone(authelia_session_token("authelia_session=" + ("a" * 4097)))
|
||||
self.assertIsNone(authelia_session_token(""))
|
||||
|
||||
def test_cookie_token_accepts_authelia_session_id_characters(self):
|
||||
# Synthetic value with the punctuation Authelia session ids contain.
|
||||
value = "Ab3%Xy^Qr!st7Z8&*()[]{}<>?@`|:#$'"
|
||||
self.assertEqual(value, authelia_session_token(f"authelia_session={value}; other=no"))
|
||||
for bad in ('a b', 'a"b', "a\\b", "a\tb", "a\x7fb", "aéb"):
|
||||
with self.subTest(bad=bad):
|
||||
self.assertIsNone(authelia_session_token(f"authelia_session={bad}"))
|
||||
|
||||
def test_lookup_sends_only_the_session_token_and_fails_closed(self):
|
||||
seen = []
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue