[FEATURE]: Complete Permissions for Groups, Membership Fees, and User Role Assignment #404

Closed
opened 2026-02-03 17:16:28 +01:00 by moritz · 0 comments
Owner

Description

The current roles-and-permissions implementation covers User, Member, CustomFieldValue, CustomField, and Role with resource policies and PermissionSets. Several resources and one UI flow are missing:

  1. Group and MemberGroup have no Ash policies; only the page plug protects routes. Direct Ash calls (e.g. from code or a future API) are not authorized.
  2. MembershipFeeType and MembershipFeeCycle have no resource-level permissions; access is effectively admin-only via the page wildcard.
  3. Settings pages (/settings, /membership_fee_settings) are not explicitly listed in PermissionSets (only covered by admin wildcard *).
  4. User role assignment cannot be done in the UI; there is no way for an admin to assign or change a user’s role.

This issue implements resource policies and PermissionSets for Group, MemberGroup, MembershipFeeType, and MembershipFeeCycle, adds explicit settings page entries, implements the User role-assignment UI (admin-only), and enforces “at least one admin” when changing roles. Documentation (roles-and-permissions architecture and related docs) must be updated to reflect the new resources and behaviour.

Permission design (agreed):

  • MemberGroup: Kassenwart (normal_user) and Admin may create/destroy (assign/remove members to/from groups). Read for all who can read Member/Group.
  • MembershipFeeType: All authenticated roles may read (e.g. for member form dropdown). Only Admin may create/update/destroy.
  • MembershipFeeCycle: Kassenwart and Admin may read, update status, and edit notes. Vorstand/Buchhaltung (read_only) may only read.
  • Settings pages: Explicit admin-only page entries for /settings and /membership_fee_settings in PermissionSets.
  • User role assignment: Admin-only. Validation: at least one user must keep the Admin role (forbid changing the last admin’s role to a non-admin role).

Tasks

  1. PermissionSets

    • Add resource permissions for MemberGroup: read for own_data (linked member only), read for read_only/normal_user/admin; create/destroy for normal_user and admin (scope: all). Decide linked scope for own_data (e.g. member_id in actor’s linked member).
    • Add resource permissions for MembershipFeeType: read scope all for all four permission sets; create/update/destroy scope all for admin only.
    • Add resource permissions for MembershipFeeCycle: read for all (scope all or linked as needed); update (including custom actions mark_as_paid, mark_as_suspended, mark_as_unpaid) for normal_user and admin; create/destroy for admin only (if applicable).
    • Add explicit pages for /settings and /membership_fee_settings to the admin permission set only (so they are documented and can be restricted later without relying on *).
  2. Group resource

    • Add a policies block to lib/membership/group.ex using Mv.Authorization.Checks.HasPermission. Rely on existing PermissionSets entries (read for own_data/read_only/normal_user, full CRUD for admin).
  3. MemberGroup resource

    • Add a policies block to lib/membership/member_group.ex using HasPermission (and any bypass for read if needed for linked scope). Ensure create/destroy are allowed only for normal_user and admin per PermissionSets.
  4. MembershipFeeType resource

    • Add a policies block to lib/membership_fees/membership_fee_type.ex using HasPermission. Read for all roles; create/update/destroy for admin only.
  5. MembershipFeeCycle resource

    • Add a policies block to lib/membership_fees/membership_fee_cycle.ex using HasPermission. Read for all; update (and custom actions) for normal_user and admin; create/destroy for admin only. Align action names with PermissionSets (e.g. treat custom update actions as :update or add explicit permissions if needed).
  6. User resource and role assignment

    • Ensure the User action used for admin editing accepts role_id (or add a dedicated action for assigning role, admin-only). Add a validation: when changing role_id away from the Admin role, forbid if the user is the last user with the Admin role (query count of users with Admin role; if this user is one of them and count is 1, return validation error).
    • No “self-demote” rule: an admin may change their own role, but the “last admin” check still applies.
  7. User role assignment UI

    • In lib/mv_web/live/user_live/form.ex, add a role dropdown (list roles from Mv.Authorization.Role). Show only when the current user is admin (e.g. can?(@current_user, :update, Mv.Authorization.Role) or equivalent). Include role_id in the form params and pass it to the User update action when saving. Ensure the form is only used for the admin path (e.g. user index/edit) and not for profile edit if profile must not change role.
  8. Documentation

    • Update docs/roles-and-permissions-architecture.md: add Group to the resource list and permission matrix; add MemberGroup, MembershipFeeType, MembershipFeeCycle; document resource and page permissions; add “User role assignment” (admin-only, last-admin validation) and explicit settings pages.
    • Update docs/roles-and-permissions-implementation-plan.md (or equivalent) with completed/added items for Group, MemberGroup, MembershipFee*, settings pages, and User role UI.
    • Update docs/groups-architecture.md authorization section to state that Group (and MemberGroup) policies are implemented and reference the roles-and-permissions architecture.
    • Update docs/membership-fee-architecture.md to state that MembershipFeeType and MembershipFeeCycle are integrated with PermissionSets and policies, with a short summary of who may read/update.
  9. HasPermission / resource names

    • Confirm that Mv.Authorization.Checks.HasPermission (and any UI helper) resolve resource names correctly for MemberGroup, MembershipFeeType, MembershipFeeCycle (e.g. Module.split() |> List.last()). Add or adjust mapping if needed (e.g. “MembershipFeeType” vs “MembershipFeeCycle” in PermissionSets).

Acceptance Criteria

  • Group: Only users with Group read permission can read groups; only admin can create/update/destroy groups. Direct Ash.read / Ash.create etc. with an actor respect policies.
  • MemberGroup: Only normal_user and admin can create or destroy member-group links; read is allowed according to PermissionSets (e.g. own_data via linked member). Unauthorized create/destroy returns forbidden.
  • MembershipFeeType: All authenticated roles can read membership fee types; only admin can create/update/destroy. Unauthorized write returns forbidden.
  • MembershipFeeCycle: read_only can only read cycles; normal_user and admin can read and update (including status and notes). Only admin can create/destroy cycles if exposed. Unauthorized actions return forbidden.
  • Settings pages: PermissionSets explicitly list /settings and /membership_fee_settings for admin; CheckPagePermission denies non-admin access even if wildcard behaviour is changed later.
  • User role assignment: In the user form (admin context), an admin can select a role from a dropdown and save; role_id is persisted. Non-admins do not see the role dropdown. When changing a user’s role from Admin to a non-admin role, the operation is rejected with a clear error if that user is the last user with the Admin role.
  • Documentation: Architecture and implementation docs describe Group, MemberGroup, MembershipFeeType, MembershipFeeCycle, settings pages, and User role assignment (including last-admin rule).

Test Strategy

  1. PermissionSets

    • Unit tests: get_permissions(:normal_user) (and others) include MemberGroup, MembershipFeeType, MembershipFeeCycle with expected actions/scopes; admin pages list includes /settings and /membership_fee_settings.
  2. Group

    • With actor per role (own_data, read_only, normal_user, admin): read allowed/forbidden as per matrix; create/update/destroy only for admin. At least one test per role for read; one test for admin create/update/destroy; one test for non-admin create/update/destroy (expect forbidden).
  3. MemberGroup

    • own_data: cannot create/destroy (or can only for linked member if specified). read_only: can read, cannot create/destroy. normal_user and admin: can create and destroy. Tests with multiple actors and assert forbidden when not allowed.
  4. MembershipFeeType

    • All roles can read (e.g. list or get). Only admin can create/update/destroy. Non-admin create/update/destroy returns forbidden.
  5. MembershipFeeCycle

    • read_only: can read; update (or mark_as_) returns forbidden. normal_user and admin: can read and update (and mark_as_). Admin can create/destroy if applicable. Cover at least one custom action (e.g. mark_as_paid) with normal_user and read_only.
  6. Page permission

    • Unit or integration: user with read_only or normal_user cannot access /settings or /membership_fee_settings (redirect or 403); admin can.
  7. User role assignment

    • Integration/UI or domain: as admin, open user edit form, select a different role, save; user’s role_id is updated. As non-admin, open user form (if possible); role dropdown is not present or not submittable.
    • Validation: create or use a scenario with exactly one admin; attempt to change that user’s role to a non-admin role; expect validation error (e.g. “At least one user must keep the Admin role” or equivalent). With two admins, changing one to non-admin succeeds.
  8. Documentation

    • Manual or light checklist: roles-and-permissions-architecture.md contains Group, MemberGroup, MembershipFeeType, MembershipFeeCycle, settings pages, and User role assignment; groups-architecture and membership-fee-architecture reference the permission system and implementation status.
## Description The current roles-and-permissions implementation covers User, Member, CustomFieldValue, CustomField, and Role with resource policies and PermissionSets. Several resources and one UI flow are missing: 1. **Group** and **MemberGroup** have no Ash policies; only the page plug protects routes. Direct Ash calls (e.g. from code or a future API) are not authorized. 2. **MembershipFeeType** and **MembershipFeeCycle** have no resource-level permissions; access is effectively admin-only via the page wildcard. 3. **Settings pages** (`/settings`, `/membership_fee_settings`) are not explicitly listed in PermissionSets (only covered by admin wildcard `*`). 4. **User role assignment** cannot be done in the UI; there is no way for an admin to assign or change a user’s role. This issue implements resource policies and PermissionSets for Group, MemberGroup, MembershipFeeType, and MembershipFeeCycle, adds explicit settings page entries, implements the User role-assignment UI (admin-only), and enforces “at least one admin” when changing roles. Documentation (roles-and-permissions architecture and related docs) must be updated to reflect the new resources and behaviour. **Permission design (agreed):** - **MemberGroup:** Kassenwart (normal_user) and Admin may create/destroy (assign/remove members to/from groups). Read for all who can read Member/Group. - **MembershipFeeType:** All authenticated roles may read (e.g. for member form dropdown). Only Admin may create/update/destroy. - **MembershipFeeCycle:** Kassenwart and Admin may read, update status, and edit notes. Vorstand/Buchhaltung (read_only) may only read. - **Settings pages:** Explicit admin-only page entries for `/settings` and `/membership_fee_settings` in PermissionSets. - **User role assignment:** Admin-only. Validation: at least one user must keep the Admin role (forbid changing the last admin’s role to a non-admin role). --- ## Tasks 1. **PermissionSets** - Add resource permissions for **MemberGroup**: read for own_data (linked member only), read for read_only/normal_user/admin; create/destroy for normal_user and admin (scope: all). Decide linked scope for own_data (e.g. member_id in actor’s linked member). - Add resource permissions for **MembershipFeeType**: read scope all for all four permission sets; create/update/destroy scope all for admin only. - Add resource permissions for **MembershipFeeCycle**: read for all (scope all or linked as needed); update (including custom actions mark_as_paid, mark_as_suspended, mark_as_unpaid) for normal_user and admin; create/destroy for admin only (if applicable). - Add explicit **pages** for `/settings` and `/membership_fee_settings` to the admin permission set only (so they are documented and can be restricted later without relying on `*`). 2. **Group resource** - Add a `policies` block to `lib/membership/group.ex` using `Mv.Authorization.Checks.HasPermission`. Rely on existing PermissionSets entries (read for own_data/read_only/normal_user, full CRUD for admin). 3. **MemberGroup resource** - Add a `policies` block to `lib/membership/member_group.ex` using HasPermission (and any bypass for read if needed for linked scope). Ensure create/destroy are allowed only for normal_user and admin per PermissionSets. 4. **MembershipFeeType resource** - Add a `policies` block to `lib/membership_fees/membership_fee_type.ex` using HasPermission. Read for all roles; create/update/destroy for admin only. 5. **MembershipFeeCycle resource** - Add a `policies` block to `lib/membership_fees/membership_fee_cycle.ex` using HasPermission. Read for all; update (and custom actions) for normal_user and admin; create/destroy for admin only. Align action names with PermissionSets (e.g. treat custom update actions as :update or add explicit permissions if needed). 6. **User resource and role assignment** - Ensure the User action used for admin editing accepts `role_id` (or add a dedicated action for assigning role, admin-only). Add a validation: when changing `role_id` away from the Admin role, forbid if the user is the last user with the Admin role (query count of users with Admin role; if this user is one of them and count is 1, return validation error). - No “self-demote” rule: an admin may change their own role, but the “last admin” check still applies. 7. **User role assignment UI** - In `lib/mv_web/live/user_live/form.ex`, add a role dropdown (list roles from `Mv.Authorization.Role`). Show only when the current user is admin (e.g. `can?(@current_user, :update, Mv.Authorization.Role)` or equivalent). Include `role_id` in the form params and pass it to the User update action when saving. Ensure the form is only used for the admin path (e.g. user index/edit) and not for profile edit if profile must not change role. 8. **Documentation** - Update `docs/roles-and-permissions-architecture.md`: add Group to the resource list and permission matrix; add MemberGroup, MembershipFeeType, MembershipFeeCycle; document resource and page permissions; add “User role assignment” (admin-only, last-admin validation) and explicit settings pages. - Update `docs/roles-and-permissions-implementation-plan.md` (or equivalent) with completed/added items for Group, MemberGroup, MembershipFee*, settings pages, and User role UI. - Update `docs/groups-architecture.md` authorization section to state that Group (and MemberGroup) policies are implemented and reference the roles-and-permissions architecture. - Update `docs/membership-fee-architecture.md` to state that MembershipFeeType and MembershipFeeCycle are integrated with PermissionSets and policies, with a short summary of who may read/update. 9. **HasPermission / resource names** - Confirm that `Mv.Authorization.Checks.HasPermission` (and any UI helper) resolve resource names correctly for `MemberGroup`, `MembershipFeeType`, `MembershipFeeCycle` (e.g. `Module.split() |> List.last()`). Add or adjust mapping if needed (e.g. “MembershipFeeType” vs “MembershipFeeCycle” in PermissionSets). --- ## Acceptance Criteria - **Group:** Only users with Group read permission can read groups; only admin can create/update/destroy groups. Direct `Ash.read` / `Ash.create` etc. with an actor respect policies. - **MemberGroup:** Only normal_user and admin can create or destroy member-group links; read is allowed according to PermissionSets (e.g. own_data via linked member). Unauthorized create/destroy returns forbidden. - **MembershipFeeType:** All authenticated roles can read membership fee types; only admin can create/update/destroy. Unauthorized write returns forbidden. - **MembershipFeeCycle:** read_only can only read cycles; normal_user and admin can read and update (including status and notes). Only admin can create/destroy cycles if exposed. Unauthorized actions return forbidden. - **Settings pages:** PermissionSets explicitly list `/settings` and `/membership_fee_settings` for admin; CheckPagePermission denies non-admin access even if wildcard behaviour is changed later. - **User role assignment:** In the user form (admin context), an admin can select a role from a dropdown and save; `role_id` is persisted. Non-admins do not see the role dropdown. When changing a user’s role from Admin to a non-admin role, the operation is rejected with a clear error if that user is the last user with the Admin role. - **Documentation:** Architecture and implementation docs describe Group, MemberGroup, MembershipFeeType, MembershipFeeCycle, settings pages, and User role assignment (including last-admin rule). --- ## Test Strategy 1. **PermissionSets** - Unit tests: `get_permissions(:normal_user)` (and others) include MemberGroup, MembershipFeeType, MembershipFeeCycle with expected actions/scopes; admin pages list includes `/settings` and `/membership_fee_settings`. 2. **Group** - With actor per role (own_data, read_only, normal_user, admin): read allowed/forbidden as per matrix; create/update/destroy only for admin. At least one test per role for read; one test for admin create/update/destroy; one test for non-admin create/update/destroy (expect forbidden). 3. **MemberGroup** - own_data: cannot create/destroy (or can only for linked member if specified). read_only: can read, cannot create/destroy. normal_user and admin: can create and destroy. Tests with multiple actors and assert forbidden when not allowed. 4. **MembershipFeeType** - All roles can read (e.g. list or get). Only admin can create/update/destroy. Non-admin create/update/destroy returns forbidden. 5. **MembershipFeeCycle** - read_only: can read; update (or mark_as_*) returns forbidden. normal_user and admin: can read and update (and mark_as_*). Admin can create/destroy if applicable. Cover at least one custom action (e.g. mark_as_paid) with normal_user and read_only. 6. **Page permission** - Unit or integration: user with read_only or normal_user cannot access `/settings` or `/membership_fee_settings` (redirect or 403); admin can. 7. **User role assignment** - Integration/UI or domain: as admin, open user edit form, select a different role, save; user’s `role_id` is updated. As non-admin, open user form (if possible); role dropdown is not present or not submittable. - Validation: create or use a scenario with exactly one admin; attempt to change that user’s role to a non-admin role; expect validation error (e.g. “At least one user must keep the Admin role” or equivalent). With two admins, changing one to non-admin succeeds. 8. **Documentation** - Manual or light checklist: roles-and-permissions-architecture.md contains Group, MemberGroup, MembershipFeeType, MembershipFeeCycle, settings pages, and User role assignment; groups-architecture and membership-fee-architecture reference the permission system and implementation status.
moritz added this to the We have different roles and permissions milestone 2026-02-03 17:16:28 +01:00
moritz self-assigned this 2026-02-03 17:16:28 +01:00
moritz added this to the Sprint 12: 29.01.- 19.02 project 2026-02-03 17:16:29 +01:00
moritz added the
L
label 2026-02-03 17:16:37 +01:00
Sign in to join this conversation.
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: local-it/mitgliederverwaltung#404
No description provided.