Files
computer-playbook/tests/unit/python/plugins/lookup/test_sso_logout_url.py
T
kevinveenbirkenbachandClaude Opus 5 9d0f618e23 refactor(sso): compute the app logout target once in the sso lookup
The previous commit wrote the same proxy-aware logout expression into
four javascript.js.j2 templates. The rule it encodes - sign out of
oauth2-proxy first when the app is proxy-gated, else go straight to the
provider - belongs where proxy-gating is already decided.

utils/roles/applications/services/sso.py gains logout_url(url,
is_proxy_gated) next to get_sso_config, with OAUTH2_SIGN_OUT_PATH as the
path constant; the rd value is quoted exactly as Jinja's urlencode
filter does it. The sso lookup exposes it as the logout_url term: it
renders OIDC through the templar, takes is_proxy_gated from the same
resolution, and refuses a context without OIDC. Prometheus, Checkmk,
Baserow and Jitsi now render lookup('sso', application_id, 'logout_url').

Behaviour is unchanged: the resolver tests pin the rd encoding against
jinja2's do_urlencode, and the lookup tests cover a proxy-gated app, an
OIDC-native app, a disabled SSO and a missing OIDC.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 13:06:19 +02:00

52 lines
1.9 KiB
Python

import unittest
import unittest.mock as mock
from ansible.errors import AnsibleError
from .test_sso import _apps, _load_module
KEYCLOAK_LOGOUT = "https://auth.example.org/realms/r/protocol/openid-connect/logout"
OIDC = {"CLIENT": {"LOGOUT_URL": KEYCLOAK_LOGOUT}}
class SsoLogoutUrlLookupTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.mod = _load_module("plugins/lookup/sso.py", "sso")
def _run(self, applications, variables):
lk = self.mod.LookupModule()
lk._loader = mock.MagicMock()
lk._templar = mock.MagicMock(template=lambda value: value)
with mock.patch.object(self.mod, "lookup_loader") as loader_mock:
loader_mock.get.return_value = mock.MagicMock(
run=lambda *_a, **_k: [applications]
)
return lk.run(["web-app-x", "logout_url"], variables=variables)
def test_a_proxy_gated_app_signs_out_of_oauth2_proxy_first(self):
result = self._run(_apps(enabled=True, flavor="oauth2"), {"OIDC": OIDC})
self.assertEqual(
result,
[
"/oauth2/sign_out?rd=https%3A//auth.example.org/realms/r/protocol/openid-connect/logout"
],
)
def test_an_oidc_native_app_logs_out_at_the_provider(self):
result = self._run(_apps(enabled=True, flavor="oidc"), {"OIDC": OIDC})
self.assertEqual(result, [KEYCLOAK_LOGOUT])
def test_a_disabled_sso_keeps_the_provider_logout(self):
result = self._run(_apps(enabled=False, flavor="oauth2"), {"OIDC": OIDC})
self.assertEqual(result, [KEYCLOAK_LOGOUT])
def test_a_context_without_oidc_is_refused(self):
with self.assertRaises(AnsibleError) as ctx:
self._run(_apps(enabled=True, flavor="oauth2"), {"applications": {}})
self.assertIn("OIDC", str(ctx.exception))
if __name__ == "__main__":
unittest.main()