feat: implement complete RBAC access control with tests
Add 4-layer access control stack (subscription → module → menu → permissions): - P1: Wire requires_permission into menu sidebar filtering - P2: Expose window.USER_PERMISSIONS for Alpine.js client-side gating - P3: Add page-level permission guards on store routes - P4: Role CRUD API endpoints and role editor UI - P5: Audit trail for all role/permission changes Includes unit tests (menu permission filtering, role CRUD service) and integration tests (role API endpoints). All 404 core+tenancy tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -212,6 +212,7 @@ class MenuDiscoveryService:
|
||||
is_super_admin: bool = False,
|
||||
store_code: str | None = None,
|
||||
enabled_module_codes: set[str] | None = None,
|
||||
user_permissions: list[str] | None = None,
|
||||
) -> list[DiscoveredMenuSection]:
|
||||
"""
|
||||
Get filtered menu structure for frontend rendering.
|
||||
@@ -220,7 +221,7 @@ class MenuDiscoveryService:
|
||||
1. Module enablement (disabled modules = hidden items)
|
||||
2. Visibility configuration (AdminMenuConfig preferences)
|
||||
3. Super admin status (hides super_admin_only items for non-super-admins)
|
||||
4. Permission requirements (future: filter by user permissions)
|
||||
4. Permission requirements (filter by user permissions)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
@@ -231,6 +232,9 @@ class MenuDiscoveryService:
|
||||
store_code: Store code for route placeholder replacement
|
||||
enabled_module_codes: If provided, overrides single-platform lookup
|
||||
for module enablement. Passed through to get_menu_sections_for_frontend.
|
||||
user_permissions: List of permission IDs the user has. If provided,
|
||||
items with requires_permission set will be hidden unless the
|
||||
permission is in this list. None = no permission filtering.
|
||||
|
||||
Returns:
|
||||
List of DiscoveredMenuSection with filtered and sorted items
|
||||
@@ -268,6 +272,14 @@ class MenuDiscoveryService:
|
||||
if item.id not in visible_item_ids:
|
||||
continue
|
||||
|
||||
# Apply permission filtering
|
||||
if (
|
||||
user_permissions is not None
|
||||
and item.requires_permission
|
||||
and item.requires_permission not in user_permissions
|
||||
):
|
||||
continue
|
||||
|
||||
# Resolve route placeholders
|
||||
if store_code and "{store_code}" in item.route:
|
||||
item.route = item.route.replace("{store_code}", store_code)
|
||||
|
||||
@@ -229,6 +229,7 @@ class MenuService:
|
||||
is_super_admin: bool = False,
|
||||
store_code: str | None = None,
|
||||
enabled_module_codes: set[str] | None = None,
|
||||
user_permissions: list[str] | None = None,
|
||||
) -> list:
|
||||
"""
|
||||
Get filtered menu structure for frontend rendering.
|
||||
@@ -239,6 +240,7 @@ class MenuService:
|
||||
1. Module enablement (items from disabled modules are removed)
|
||||
2. Visibility configuration
|
||||
3. Super admin status
|
||||
4. User permissions (items with requires_permission hidden if user lacks it)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
@@ -250,6 +252,9 @@ class MenuService:
|
||||
enabled_module_codes: If provided, overrides single-platform lookup
|
||||
for module enablement. Used by merchant portal where a merchant
|
||||
may have subscriptions across multiple platforms.
|
||||
user_permissions: List of permission IDs the user has. If provided,
|
||||
items with requires_permission will be hidden unless the user
|
||||
has the permission. None = no permission filtering.
|
||||
|
||||
Returns:
|
||||
List of DiscoveredMenuSection ready for rendering
|
||||
@@ -262,6 +267,7 @@ class MenuService:
|
||||
is_super_admin=is_super_admin,
|
||||
store_code=store_code,
|
||||
enabled_module_codes=enabled_module_codes,
|
||||
user_permissions=user_permissions,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user