--- # URI de retour APRES DECONNEXION, par client. # # Keycloak les valide separement des URI de rappel : un client peut donc se connecter # parfaitement et echouer a la deconnexion, sur un « We are sorry… invalid redirect uri » # qui ne dit pas de quelle liste il parle. Constate le 2026-08-08 sur Nextcloud — et # AUCUN des quatre clients ne declarait cet attribut ; Nextcloud etait seulement le seul # a envoyer une URI de retour. # # DERIVEE de `web_origins`, qui porte deja l'URL de base du service : la connaissance # existe, il n'y a pas a la reecrire. `/*` couvre la page d'accueil et les chemins de # retour que chaque application choisit. # # Par l'API et non `kcadm -s` : `attributes` est une MAP, et sur une map kcadm accepte # la commande, sort en succes et n'ecrit rien (mesure du meme jour sur `smtpServer`). # On relit systematiquement apres avoir pose. # Jeton FRAIS : celui de `politique-mdp.yml` a plus de six fichiers d'age, et il vit # 60 s. C'est ce qui faisait echouer cette lecture sur toute construction NEUVE — et # passer au rejeu, une fois la flotte convergee. Voir `jeton-admin.yml`. - name: Reprendre un jeton d'administration avant de lire les clients ansible.builtin.include_tasks: jeton-admin.yml - name: Lire les clients du realm ansible.builtin.uri: url: "http://localhost:8080/admin/realms/{{ serveur_keycloak_realm }}/clients" headers: Authorization: "Bearer {{ serveur_keycloak_pol_jeton.json.access_token }}" status_code: [200] register: serveur_keycloak_clients_actuels # `no_log` protege le jeton porte par l'en-tete. Il masquait aussi la CAUSE : le # 2026-08-10, cet appel a echoue deux fois sur « the output has been hidden », et il a # fallu remonter jusqu'a l'age du jeton pour comprendre. On extrait donc le verdict. no_log: true failed_when: false - name: Dire pourquoi la lecture des clients a échoué, si elle a échoué ansible.builtin.fail: msg: >- Keycloak a refuse la lecture des clients du realme « {{ serveur_keycloak_realm }} » (HTTP {{ serveur_keycloak_clients_actuels.status | default('?') }}). Un 401 signifie un jeton perime — verifier que `jeton-admin.yml` est bien inclus juste avant cet appel. when: serveur_keycloak_clients_actuels.status | default(0) != 200 - name: Composer les URI de déconnexion attendues ansible.builtin.set_fact: serveur_keycloak_deconnexion: >- {{ dict(serveur_keycloak_clients | map(attribute='clientId') | list | zip(serveur_keycloak_clients | map(attribute='web_origins') | map('map', 'regex_replace', '/?$', '/*') | map('join', '##') | list)) }} - name: Déclarer l'URI de retour après déconnexion ansible.builtin.uri: url: >- http://localhost:8080/admin/realms/{{ serveur_keycloak_realm }}/clients/{{ item.id }} method: PUT status_code: [204] headers: Authorization: "Bearer {{ serveur_keycloak_pol_jeton.json.access_token }}" body_format: json body: >- {{ item | combine({'attributes': (item.attributes | default({})) | combine({'post.logout.redirect.uris': serveur_keycloak_deconnexion[item.clientId]})}) }} loop: "{{ serveur_keycloak_clients_actuels.json }}" loop_control: label: "{{ item.clientId }}" # `no_log` protege le JETON porte par l'en-tete — on ne peut pas simplement l'enlever. # Mais il masquait aussi la CAUSE : le 2026-08-10, l'echec s'est lu « the output has # been hidden » sur le tout dernier appel d'un deploiement de 157 taches. On journalise # donc le verdict a part, en n'extrayant que ce qui ne revele rien. no_log: true register: serveur_keycloak_deconnexion_res failed_when: false changed_when: true when: - item.clientId in serveur_keycloak_deconnexion - (item.attributes | default({})).get('post.logout.redirect.uris', '') != serveur_keycloak_deconnexion[item.clientId] - name: Dire quels clients Keycloak a refusés, et pourquoi ansible.builtin.fail: msg: >- Keycloak a refuse la mise a jour des URI de deconnexion pour : {{ refuses | map(attribute='item.clientId') | list | join(', ') }}. Codes rendus : {{ refuses | map(attribute='status') | list }}. Messages : {{ refuses | map(attribute='msg') | map('string') | map('truncate', 160) | list }}. vars: refuses: >- {{ (serveur_keycloak_deconnexion_res.results | default([])) | rejectattr('skipped', 'defined') | selectattr('status', 'defined') | rejectattr('status', 'equalto', 204) | list }} when: refuses | length > 0