Skip to content

permissions: role→permission grants stay global, but must support mapping the synthetic tenant:<role> principal roles #377

Description

@antosubash

Why

RolePermission/UserPermission (modules/permissions/permissions/models.py:37,56) are keyed by role_name/user_id with no tenant dimension, and per the tenancy design doc that's correct — permission grants are a platform-level policy ("what can an editor do"), while tenant membership roles (owner/admin/member) are per-tenant facts about a person, not a platform policy. So this module is a near no-op for MultiTenantMixin adoption, but it needs a concrete change to work with the design doc's tenant:<role> principal-augmentation scheme, since today it can only assign permissions to role names backed by a users.models.Role row.

Tables

  • RolePermission (models.py:37) — adopt MultiTenantMixin: no. Composite PK (role_name, permission_key) (models.py:42-43) stays global; a role's permission set is platform policy, not per-tenant data.
  • UserPermission (models.py:56) — adopt MultiTenantMixin: no. Composite PK (user_id, permission_key) (models.py:61-62) — a direct grant to a specific user account is also platform-level (per the users issue, a User is not itself tenant-scoped).
  • Neither table has a unique constraint missing tenant_id to fix; both PKs are intentionally global. No SM024 concern.

Code paths

  • The real gap: PermissionService._resolve_role_permissions/_resolve_role_sources (service.py:219-248) resolve permissions by looking up role_name in self.registry.role_map (simple_module_core.permissions.PermissionRegistry), and the admin UI's role list/edit (list_roles_with_counts, get_role, set_role_permissions — service.py:61-120) only enumerates roles backed by a users.models.Role row (_load_roles, service.py:55-59). Per the design doc, the active tenant's membership role is added to the request principal as tenant:<role> (e.g. tenant:owner), a synthetic string with no corresponding Role row — so there is currently no admin screen or service method to map platform permissions onto tenant:owner/tenant:admin/tenant:member, and _resolve_role_permissions would silently resolve an unmapped tenant:owner to an empty permission set (not an error, just quietly no permissions) unless tenants itself maps those roles onto its own permissions as the design doc says it will ("the module maps those roles onto its own permissions").
  • sync_admin_all_permissions (service.py:278-297) grants the platform ADMIN_ROLE_NAME role every registered key via the registry's wildcard — must never be reachable from a tenant:admin principal role; confirm (in tenants/auth, not here) that the string "admin" used as a tenant membership role is never compared equal to the platform ADMIN_ROLE_NAME without the tenant: prefix, since the design doc's own words — "a tenant admin is never the platform admin" — describe exactly the collision this naming scheme is built to avoid; this module's ADMIN_ROLE_NAME wildcard grant is the blast radius if that prefix is ever dropped somewhere in the resolution chain.
  • resolve_effective_permissions (service.py:250-257) combines a user's direct grants with role-inherited ones from user.roles (the UserRole join table) — this path never sees tenant:<role> strings at all today, since those are added to the request principal elsewhere (outside this module), not to user.roles. Confirm the actual request-time permission check (RequiresPermission, simple_module_hosting.permissions) merges both sources — this module's DB-backed roles and the tenant module's session-injected tenant:<role> — rather than only calling resolve_effective_permissions and missing tenant-role grants entirely.
  • No caches, no file storage, no public routes in this module.

Migration

  • No schema change.
  • If tenant roles need to be admin-editable through the same UI as platform roles, that's a tenants or permissions extension (e.g. PermissionService accepting a role name not backed by a Role row) — not a migration, a feature addition, and only needed if product wants tenant-role permissions configurable rather than fixed in tenants' own code.

Tests

  • A user's effective permissions for a request combine platform role grants (RolePermission) and their tenant:<role> grant for the active tenant, and the two are additive, not one overriding the other.
  • tenant:admin never resolves to the same permission set as the platform admin role's wildcard, even if tenants_membership.role happens to store the bare string "admin" — assert the tenant: prefix is preserved end-to-end from membership row to principal to permission check.
  • sync_admin_all_permissions only ever affects the platform ADMIN_ROLE_NAME row, never a tenant:* entry, regardless of how many tenants exist.
  • Switching active tenant mid-session changes the effective tenant:<role> permissions on the next request without requiring re-login (exercises whatever mechanism refreshes the principal's tenant-role claim).

Out of scope / open questions

  • Whether tenant-role-to-permission mapping ever becomes admin-configurable through this module's UI, or stays fixed in tenants' own code as the design doc currently describes — this issue only flags that permissions cannot express it today, not that it must.
  • The actual merge point between permissions' DB-backed resolution and the tenant module's principal augmentation lives outside this module (likely simple_module_hosting.permissions / RequiresPermission) and should be verified there, not here.

Context: part of the SaaS tenancy work in #370 (fail-closed isolation, tenant_context()/all_tenants(), the tenants module). Design: docs/plans/2026-09-27-saas-tenancy-design.md and docs/framework/multi-tenancy.md on that branch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions