mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 03:38:23 +00:00
fix(web-app-keycloak): bring SAML clients to existing realms too
SAML clients were created only by the realm import, and the import runs only
for a missing realm. echoserver keeps its legacy realm cymais.cloud and ran
SuiteCRM behind oauth2-proxy before v16, so the SuiteCRM SAML client
https://<suitecrm>/saml/metadata never existed there: the SAML hop showed
Keycloak's Invalid Request page and Keycloak logged LOGIN_ERROR
error=client_not_found reason=Cannot_match_source_hash.
The existing-realm reconcile now also loops over the clientId of every SAML
client in the rendered realm dictionary, the same source the import uses,
and creates or updates it through kcadm like the application-declared OIDC
clients. The same path already converged the stalwart-webmail and
stalwart-webui clients in echoserver full-10.
Same change as 2b4db90576 on feature/store-i18n, so a later merge of the two
lines resolves without a conflict, plus a unit test that pins the SAML client
selection and its place in the reconcile loop.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5b7284c4a4
commit
754d35c4de
4 files changed
+45
-3
No files matched your search
@@ -50,11 +50,11 @@
|
||||
include_tasks: "{{ role_path }}/tasks/update/08_users/email_verified.yml"
|
||||
when: KEYCLOAK_USER_EMAILS_VERIFIED | bool
|
||||
|
||||
- name: "📂 Ensure application-declared clients (existing realms)"
|
||||
- name: "📂 Ensure application-declared and SAML clients (existing realms)"
|
||||
include_tasks: "{{ role_path }}/tasks/update/10_declared_client.yml"
|
||||
vars:
|
||||
kc_declared_client_id: "{{ declared_client }}"
|
||||
loop: "{{ KEYCLOAK_DECLARED_CLIENT_IDS }}"
|
||||
loop: "{{ KEYCLOAK_DECLARED_CLIENT_IDS + KEYCLOAK_SAML_CLIENT_IDS }}"
|
||||
loop_control:
|
||||
loop_var: declared_client
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
# Realm import only runs for a MISSING realm; this kcadm create-or-update brings
|
||||
# application-declared clients to existing realms too. Desired state is the
|
||||
# application-declared and SAML clients to existing realms too. Desired state is the
|
||||
# import's own client object, so the realm template stays the one SPOT.
|
||||
- name: "✅ Ensure the '{{ kc_declared_client_id }}' client"
|
||||
vars:
|
||||
|
||||
@@ -154,6 +154,7 @@ KEYCLOAK_MOODLE_ENABLED: "{{ 'web-app-moodle' in group_names }}"
|
||||
|
||||
KEYCLOAK_DECLARED_CLIENT_APPS: "{{ lookup('applications') | kc_declared_client_apps(lookup('deployment').groups) }}"
|
||||
KEYCLOAK_DECLARED_CLIENT_IDS: "{{ lookup('applications') | kc_declared_client_ids(lookup('deployment').groups) }}"
|
||||
KEYCLOAK_SAML_CLIENT_IDS: "{{ KEYCLOAK_DICTIONARY_REALM.clients | selectattr('protocol', 'equalto', 'saml') | map(attribute='clientId') | list }}"
|
||||
|
||||
KEYCLOAK_RECAPTCHA_ENABLED: "{{ lookup('config', application_id, 'services.recaptcha.enabled') }}"
|
||||
KEYCLOAK_RECAPTCHA_KEY: "{{ lookup('config', application_id, 'secrets.credentials.recaptcha.key') }}"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import unittest
|
||||
|
||||
from jinja2 import Environment
|
||||
|
||||
from utils.cache.yaml import load_yaml_any
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
ROLE = PROJECT_ROOT / "roles" / "web-app-keycloak"
|
||||
VARS = ROLE / "vars" / "main.yml"
|
||||
ROUTINES = ROLE / "tasks" / "03_manager_ops" / "update_routines.yml"
|
||||
|
||||
REALM = {
|
||||
"clients": [
|
||||
{"clientId": "account", "protocol": "openid-connect"},
|
||||
{"clientId": "cymais.cloud", "protocol": "openid-connect"},
|
||||
{"clientId": "https://crm.example/saml/metadata", "protocol": "saml"},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class TestSamlClientsReachExistingRealms(unittest.TestCase):
|
||||
def test_only_saml_clients_of_the_realm_dictionary_are_selected(self):
|
||||
template = load_yaml_any(str(VARS))["KEYCLOAK_SAML_CLIENT_IDS"]
|
||||
expression = template.strip().removeprefix("{{").removesuffix("}}")
|
||||
selected = Environment(autoescape=True).compile_expression(expression)(
|
||||
KEYCLOAK_DICTIONARY_REALM=REALM
|
||||
)
|
||||
self.assertEqual(selected, ["https://crm.example/saml/metadata"])
|
||||
|
||||
def test_the_existing_realm_reconcile_loops_over_saml_clients(self):
|
||||
task = next(
|
||||
t
|
||||
for t in load_yaml_any(str(ROUTINES))
|
||||
if "10_declared_client.yml" in str(t.get("include_tasks", ""))
|
||||
)
|
||||
self.assertIn("KEYCLOAK_SAML_CLIENT_IDS", task["loop"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user