From bb5347e1cd40572b44f4501df06ecf2c6c8d8661 Mon Sep 17 00:00:00 2001 From: SuperAgent BEAM3 Date: Wed, 26 Aug 2026 08:29:49 +0000 Subject: [PATCH] fix: accept pipe-separated trusted-groups header values Authentik's proxy provider on this deployment joins multiple group names with "|" instead of "," (e.g. "admins|developpeur"). The comma-only split in _trusted_groups_header_value() treated that whole string as a single unmatched group name, silently dropping the session to the unbound "default" profile even for identities that are legitimate members of a mapped group. Single-group identities were never affected since there was no separator to parse. Accept comma, pipe, and newline as equivalent separators so the parser works regardless of which the outpost happens to send. --- api/auth.py | 11 ++++++++++- tests/test_trusted_header_auth.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/api/auth.py b/api/auth.py index fef5dc95..b6a30940 100644 --- a/api/auth.py +++ b/api/auth.py @@ -722,7 +722,16 @@ def _trusted_groups_header_value(handler) -> list[str]: if not raw: return [] values = [] - for part in str(raw).replace('\n', ',').split(','): + # Authentik's outpost typically joins multiple group names with a comma, + # but this deployment's proxy provider/property mapping emits a + # pipe-separated list instead (e.g. "admins|developpeur"). A bare comma + # split silently treats a multi-group pipe-joined value as one unmatched + # group name, dropping the session to the unbound "default" profile even + # though the identity is a legitimate member of a mapped group. Accept + # comma, pipe, and newline as equivalent separators so either format + # works regardless of which the outpost happens to send. + normalized = str(raw).replace('\n', ',').replace('|', ',') + for part in normalized.split(','): part = part.strip() if part: values.append(part) diff --git a/tests/test_trusted_header_auth.py b/tests/test_trusted_header_auth.py index 8b07fbc1..c6398a77 100644 --- a/tests/test_trusted_header_auth.py +++ b/tests/test_trusted_header_auth.py @@ -212,6 +212,34 @@ def test_group_map_prefers_mapping_order_over_header_order(monkeypatch): assert auth.ensure_trusted_auth_session(second)["bound_profile"] == "ops" +def test_group_map_accepts_pipe_separated_header_value(monkeypatch): + # Some Authentik proxy provider / property mapping configs join multiple + # group names with "|" instead of ",". Regression case for the identity + # falling back to the unbound "default" profile despite being a member + # of a mapped group: a lone-membership identity works fine (no + # separator to parse), but a second group membership starts producing a + # pipe-joined header value that a comma-only split can't see through. + # + # NOTE: this only covers the concrete-mapped-group case. The + # wildcard-mapped-group case (a "*" mapping value dominating a concrete + # one) is intentionally not exercised here — that precedence rule isn't + # implemented on this branch yet; it ships separately in #6798. See the + # PR description for the full scope note. + _trusted_env( + monkeypatch, + groups_header="Remote-Groups", + group_map={"hermes_devops": "devops"}, + ) + handler = _Handler( + headers={"Remote-User": "alice", "Remote-Groups": "hermes_devops|other_group"} + ) + + info = auth.ensure_trusted_auth_session(handler) + + assert info["bound_profile"] == "devops" + assert auth.trusted_session_allows_active_profile(info) is True + + @pytest.mark.parametrize( "group_map", [