mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
feat(web-app-stalwart): let the platform administrator administer Stalwart over SSO
OIDC principals received only the built-in User role, so the platform administrator reached the web administration and was bounced to self-service half a second later. Three design specs asserted on a table that is not hers; in run 38482429514 they failed on clearnet exactly as on tor, which is what ruled out the onion CDN as the cause. The Keycloak client already carries the groups scope and the RBAC mapper already sends full paths, so the token held the groups all along. The directory simply never read them: claimGroups was unset. Setting it, and giving one group account the System Administrator role, grants through membership instead of through a default. defaultUserRoleIds and defaultGroupRoleIds stay untouched, so no other principal inherits anything. The group is named as a local part because a Stalwart account is addressed as name@domain; the Keycloak path lives in externalId. Creating it follows 04_manage_user.yml: create, read the id back out of notCreated on conflict, then update. Verified on a local compose deploy of variant 0: the group was created, and the suite went from three failing design specs to one failing and one flaky. The remaining one is a stylesheet gap, not a permission: style.css hangs its table rules on body[class*=task-] .table while WebUI v1.0.11 renders a bare table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2605de7d90
commit
199f2a6d41
3 files changed
+48
No files matched your search
@@ -35,6 +35,12 @@
|
||||
stalwart_jmap_body: "{{ lookup('template', 'jmap/directory/clear_audience.json.j2') }}"
|
||||
when: stalwart_oidc_dir_id | length > 0
|
||||
|
||||
- name: "👥 Let the OIDC directory read the RBAC groups the token carries"
|
||||
include_tasks: jmap_call.yml
|
||||
vars:
|
||||
stalwart_jmap_body: "{{ lookup('template', 'jmap/directory/set_claim_groups.json.j2') }}"
|
||||
when: stalwart_oidc_dir_id | length > 0
|
||||
|
||||
- name: "📖 List Stalwart built-in roles"
|
||||
include_tasks: jmap_call.yml
|
||||
vars:
|
||||
@@ -66,6 +72,42 @@
|
||||
defaultAdminRoleIds: "{{ {stalwart_role_admin: true} | combine({stalwart_role_user: true}) if stalwart_role_admin | length > 0 else {stalwart_role_user: true}
|
||||
}}"
|
||||
|
||||
- name: "📁 Create the group that carries System Administrator to its OIDC members"
|
||||
include_tasks: jmap_call.yml
|
||||
vars:
|
||||
stalwart_jmap_tolerate_rejected: true
|
||||
stalwart_jmap_body: >-
|
||||
{{ {'using': ['urn:ietf:params:jmap:core', 'urn:stalwart:jmap'],
|
||||
'methodCalls': [['x:Account/set', {'create': {'g': {
|
||||
'@type': 'Group',
|
||||
'name': STALWART_ADMIN_GROUP_NAME,
|
||||
'domainId': stalwart_domain_id,
|
||||
'description': 'Keycloak ' ~ STALWART_ADMIN_GROUP_PATH,
|
||||
'externalId': STALWART_ADMIN_GROUP_PATH,
|
||||
'roles': {'@type': 'Custom', 'roleIds': {stalwart_role_admin: true}}
|
||||
}}}, 'c0']]} | to_json }}
|
||||
when: stalwart_role_admin | length > 0
|
||||
|
||||
- name: "🔍 Resolve the group id when it already existed"
|
||||
ansible.builtin.set_fact:
|
||||
stalwart_admin_group_id: >-
|
||||
{{ ((stalwart_jmap.methodResponses[0][1].notCreated | default({}))
|
||||
.get('g', {}).get('objectId', {}) or {}).get('id', '') }}
|
||||
when: stalwart_role_admin | length > 0
|
||||
|
||||
- name: "🔄 Keep the existing group's external id and role in step"
|
||||
include_tasks: jmap_call.yml
|
||||
vars:
|
||||
stalwart_jmap_body: >-
|
||||
{{ {'using': ['urn:ietf:params:jmap:core', 'urn:stalwart:jmap'],
|
||||
'methodCalls': [['x:Account/set', {'update': {stalwart_admin_group_id: {
|
||||
'externalId': STALWART_ADMIN_GROUP_PATH,
|
||||
'roles': {'@type': 'Custom', 'roleIds': {stalwart_role_admin: true}}
|
||||
}}}, 'c0']]} | to_json }}
|
||||
when:
|
||||
- stalwart_role_admin | length > 0
|
||||
- stalwart_admin_group_id | length > 0
|
||||
|
||||
- name: "📖 Read the Stalwart authentication singleton"
|
||||
include_tasks: jmap_call.yml
|
||||
vars:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"using": ["urn:ietf:params:jmap:core", "urn:stalwart:jmap"],
|
||||
"methodCalls": [["x:Directory/set", {"update": { {{ stalwart_oidc_dir_id | to_json }}: {"claimGroups": {{ RBAC.GROUP.CLAIM | to_json }}} }}, "c0"]]
|
||||
}
|
||||
@@ -60,6 +60,8 @@ STALWART_IP4_PUBLIC: "{{ DOCKER_PUBLIC_BIND_HOST }}"
|
||||
STALWART_ACTIVE_PROVIDER: "{{ lookup('mail_provider') == application_id }}"
|
||||
|
||||
STALWART_ADMIN_USER: "{{ lookup('users', 'stalwartadmin').username }}"
|
||||
STALWART_ADMIN_GROUP_NAME: administrators
|
||||
STALWART_ADMIN_GROUP_PATH: "/{{ lookup('rbac_group_path', application_id='web-app-stalwart', role='administrator') }}"
|
||||
STALWART_ADMIN_PASSWORD: "{{ lookup('users', 'stalwartadmin').password }}"
|
||||
|
||||
STALWART_USERS: "{{ lookup('stalwart_users') }}"
|
||||
|
||||
Reference in new issue
Block a user