From 30a5c75e74f64e215751cf27d280bc46c42ce6d6 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 1 Feb 2026 21:49:11 +0100 Subject: [PATCH] refactor: remove backward compatibility layer for permissions - Delete app/core/permissions.py (VendorPermissions enum, PermissionGroups) - Update all code to use permission_discovery_service directly: - app/api/deps.py: get_user_permissions() uses discovery service - app/modules/tenancy/models/vendor.py: get_all_permissions() uses discovery - app/modules/tenancy/routes/api/vendor_team.py: use string literals - app/modules/tenancy/services/vendor_team_service.py: use discovery service - scripts/init_production.py: use discovery service for presets Permissions are now fully module-driven: - Each module defines permissions in definition.py - PermissionDiscoveryService aggregates all permissions - Role presets reference permission IDs directly Co-Authored-By: Claude Opus 4.5 --- app/api/deps.py | 16 +- app/core/permissions.py | 171 ------------------ app/modules/tenancy/models/vendor.py | 8 +- app/modules/tenancy/routes/api/vendor_team.py | 11 +- .../tenancy/services/vendor_team_service.py | 9 +- scripts/init_production.py | 14 +- 6 files changed, 36 insertions(+), 193 deletions(-) delete mode 100644 app/core/permissions.py diff --git a/app/api/deps.py b/app/api/deps.py index 18adcb86..d856189f 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -1027,7 +1027,7 @@ def require_vendor_permission(permission: str): @router.get("/products") def list_products( request: Request, - user: UserContext = Depends(require_vendor_permission(VendorPermissions.PRODUCTS_VIEW.value)) + user: UserContext = Depends(require_vendor_permission("products.view")) ): vendor = request.state.vendor # Vendor is set by this dependency ... @@ -1122,8 +1122,8 @@ def require_any_vendor_permission(*permissions: str): def dashboard( request: Request, user: UserContext = Depends(require_any_vendor_permission( - VendorPermissions.DASHBOARD_VIEW.value, - VendorPermissions.REPORTS_VIEW.value + "dashboard.view", + "reports.view" )) ): vendor = request.state.vendor # Vendor is set by this dependency @@ -1178,8 +1178,8 @@ def require_all_vendor_permissions(*permissions: str): def bulk_delete_products( request: Request, user: UserContext = Depends(require_all_vendor_permissions( - VendorPermissions.PRODUCTS_VIEW.value, - VendorPermissions.PRODUCTS_DELETE.value + "products.view", + "products.delete" )) ): vendor = request.state.vendor # Vendor is set by this dependency @@ -1254,9 +1254,11 @@ def get_user_permissions( # If owner, return all permissions if user_model.is_owner_of(vendor.id): - from app.core.permissions import VendorPermissions + from app.modules.tenancy.services.permission_discovery_service import ( + permission_discovery_service, + ) - return [p.value for p in VendorPermissions] + return list(permission_discovery_service.get_all_permission_ids()) # Get permissions from vendor membership for vm in user_model.vendor_memberships: diff --git a/app/core/permissions.py b/app/core/permissions.py deleted file mode 100644 index 251a8b9f..00000000 --- a/app/core/permissions.py +++ /dev/null @@ -1,171 +0,0 @@ -# app/core/permissions.py -""" -Permission constants and checking logic for RBAC. - -NOTE: This module now uses the module-driven permission system. -Permissions are defined in each module's definition.py file and -discovered by PermissionDiscoveryService. - -This file provides backward-compatible exports for existing code. -New code should use: - from app.modules.tenancy.services.permission_discovery_service import ( - permission_discovery_service - ) -""" - -from enum import Enum - -from app.modules.tenancy.services.permission_discovery_service import ( - permission_discovery_service, -) - - -class VendorPermissions(str, Enum): - """ - All available permissions within a vendor context. - - NOTE: This enum is maintained for backward compatibility. - Permissions are now defined in module definition.py files. - - Naming convention: RESOURCE_ACTION - """ - - # Dashboard (from core module) - DASHBOARD_VIEW = "dashboard.view" - - # Products (from catalog module) - PRODUCTS_VIEW = "products.view" - PRODUCTS_CREATE = "products.create" - PRODUCTS_EDIT = "products.edit" - PRODUCTS_DELETE = "products.delete" - PRODUCTS_IMPORT = "products.import" - PRODUCTS_EXPORT = "products.export" - - # Stock/Inventory (from inventory module) - STOCK_VIEW = "stock.view" - STOCK_EDIT = "stock.edit" - STOCK_TRANSFER = "stock.transfer" - - # Orders (from orders module) - ORDERS_VIEW = "orders.view" - ORDERS_EDIT = "orders.edit" - ORDERS_CANCEL = "orders.cancel" - ORDERS_REFUND = "orders.refund" - - # Customers (from customers module) - CUSTOMERS_VIEW = "customers.view" - CUSTOMERS_EDIT = "customers.edit" - CUSTOMERS_DELETE = "customers.delete" - CUSTOMERS_EXPORT = "customers.export" - - # Marketing (from messaging module - to be added) - MARKETING_VIEW = "marketing.view" - MARKETING_CREATE = "marketing.create" - MARKETING_SEND = "marketing.send" - - # Reports (from analytics module - to be added) - REPORTS_VIEW = "reports.view" - REPORTS_FINANCIAL = "reports.financial" - REPORTS_EXPORT = "reports.export" - - # Settings (from core module) - SETTINGS_VIEW = "settings.view" - SETTINGS_EDIT = "settings.edit" - SETTINGS_THEME = "settings.theme" - SETTINGS_DOMAINS = "settings.domains" - - # Team Management (from tenancy module) - TEAM_VIEW = "team.view" - TEAM_INVITE = "team.invite" - TEAM_EDIT = "team.edit" - TEAM_REMOVE = "team.remove" - - # Marketplace Imports (from marketplace module - to be added) - IMPORTS_VIEW = "imports.view" - IMPORTS_CREATE = "imports.create" - IMPORTS_CANCEL = "imports.cancel" - - -class PermissionGroups: - """ - Pre-defined permission groups for common roles. - - NOTE: These now delegate to permission_discovery_service for consistency. - """ - - @property - def OWNER(self) -> set[str]: - """Full access (for owners) - all permissions.""" - return permission_discovery_service.get_preset_permissions("owner") - - @property - def MANAGER(self) -> set[str]: - """Manager - Can do most things except team management.""" - return permission_discovery_service.get_preset_permissions("manager") - - @property - def STAFF(self) -> set[str]: - """Staff - Can view and edit products/orders but limited access.""" - return permission_discovery_service.get_preset_permissions("staff") - - @property - def SUPPORT(self) -> set[str]: - """Support - Can view and assist with orders/customers.""" - return permission_discovery_service.get_preset_permissions("support") - - @property - def VIEWER(self) -> set[str]: - """Viewer - Read-only access.""" - return permission_discovery_service.get_preset_permissions("viewer") - - @property - def MARKETING(self) -> set[str]: - """Marketing - Focused on marketing and customer communication.""" - return permission_discovery_service.get_preset_permissions("marketing") - - -# Singleton instance for backward compatibility -_permission_groups = PermissionGroups() - - -class PermissionChecker: - """Utility class for permission checking.""" - - @staticmethod - def has_permission(permissions: list[str], required_permission: str) -> bool: - """Check if a permission list contains a required permission.""" - return required_permission in permissions - - @staticmethod - def has_any_permission( - permissions: list[str], required_permissions: list[str] - ) -> bool: - """Check if a permission list contains ANY of the required permissions.""" - return any(perm in permissions for perm in required_permissions) - - @staticmethod - def has_all_permissions( - permissions: list[str], required_permissions: list[str] - ) -> bool: - """Check if a permission list contains ALL of the required permissions.""" - return all(perm in permissions for perm in required_permissions) - - @staticmethod - def get_missing_permissions( - permissions: list[str], required_permissions: list[str] - ) -> list[str]: - """Get list of missing permissions.""" - return [perm for perm in required_permissions if perm not in permissions] - - -def get_preset_permissions(preset_name: str) -> set[str]: - """ - Get permissions for a preset role. - - Args: - preset_name: Name of the preset (owner, manager, staff, support, viewer, marketing) - - Returns: - Set of permission strings - """ - return permission_discovery_service.get_preset_permissions(preset_name) diff --git a/app/modules/tenancy/models/vendor.py b/app/modules/tenancy/models/vendor.py index 566a3241..3a4b2ffd 100644 --- a/app/modules/tenancy/models/vendor.py +++ b/app/modules/tenancy/models/vendor.py @@ -525,10 +525,12 @@ class VendorUser(Base, TimestampMixin): def get_all_permissions(self) -> list: """Get all permissions this user has.""" if self.is_owner: - # Return all possible permissions - from app.core.permissions import VendorPermissions + # Return all possible permissions from discovery service + from app.modules.tenancy.services.permission_discovery_service import ( + permission_discovery_service, + ) - return list(VendorPermissions.__members__.values()) + return list(permission_discovery_service.get_all_permission_ids()) if self.role and self.role.permissions: return self.role.permissions diff --git a/app/modules/tenancy/routes/api/vendor_team.py b/app/modules/tenancy/routes/api/vendor_team.py index 4d641de3..572109a0 100644 --- a/app/modules/tenancy/routes/api/vendor_team.py +++ b/app/modules/tenancy/routes/api/vendor_team.py @@ -22,7 +22,8 @@ from app.api.deps import ( require_vendor_permission, ) from app.core.database import get_db -from app.core.permissions import VendorPermissions +# Permission IDs are now defined in module definition.py files +# and discovered by PermissionDiscoveryService from app.modules.tenancy.services.vendor_team_service import vendor_team_service from models.schema.auth import UserContext from app.modules.tenancy.schemas.team import ( @@ -55,7 +56,7 @@ def list_team_members( include_inactive: bool = False, db: Session = Depends(get_db), current_user: UserContext = Depends( - require_vendor_permission(VendorPermissions.TEAM_VIEW.value) + require_vendor_permission("team.view") ), ): """ @@ -221,7 +222,7 @@ def get_team_member( request: Request, db: Session = Depends(get_db), current_user: UserContext = Depends( - require_vendor_permission(VendorPermissions.TEAM_VIEW.value) + require_vendor_permission("team.view") ), ): """ @@ -370,7 +371,7 @@ def list_roles( request: Request, db: Session = Depends(get_db), current_user: UserContext = Depends( - require_vendor_permission(VendorPermissions.TEAM_VIEW.value) + require_vendor_permission("team.view") ), ): """ @@ -439,7 +440,7 @@ def get_team_statistics( request: Request, db: Session = Depends(get_db), current_user: UserContext = Depends( - require_vendor_permission(VendorPermissions.TEAM_VIEW.value) + require_vendor_permission("team.view") ), ): """ diff --git a/app/modules/tenancy/services/vendor_team_service.py b/app/modules/tenancy/services/vendor_team_service.py index b16a9623..af7622dd 100644 --- a/app/modules/tenancy/services/vendor_team_service.py +++ b/app/modules/tenancy/services/vendor_team_service.py @@ -16,7 +16,14 @@ from typing import Any from sqlalchemy.orm import Session -from app.core.permissions import get_preset_permissions +from app.modules.tenancy.services.permission_discovery_service import ( + permission_discovery_service, +) + + +def get_preset_permissions(preset_name: str) -> set[str]: + """Get permissions for a preset role.""" + return permission_discovery_service.get_preset_permissions(preset_name) from app.modules.tenancy.exceptions import ( CannotRemoveOwnerException, InvalidInvitationTokenException, diff --git a/scripts/init_production.py b/scripts/init_production.py index 33170cf2..50afd2db 100644 --- a/scripts/init_production.py +++ b/scripts/init_production.py @@ -34,7 +34,9 @@ from app.core.config import ( ) from app.core.database import SessionLocal from app.core.environment import is_production -from app.core.permissions import PermissionGroups +from app.modules.tenancy.services.permission_discovery_service import ( + permission_discovery_service, +) from middleware.auth import AuthManager from app.modules.tenancy.models import AdminSetting from app.modules.tenancy.models import User @@ -128,11 +130,11 @@ def create_default_role_templates(db: Session) -> dict: print_success("Role templates ready for vendor onboarding") return { - "manager": list(PermissionGroups.MANAGER), - "staff": list(PermissionGroups.STAFF), - "support": list(PermissionGroups.SUPPORT), - "viewer": list(PermissionGroups.VIEWER), - "marketing": list(PermissionGroups.MARKETING), + "manager": list(permission_discovery_service.get_preset_permissions("manager")), + "staff": list(permission_discovery_service.get_preset_permissions("staff")), + "support": list(permission_discovery_service.get_preset_permissions("support")), + "viewer": list(permission_discovery_service.get_preset_permissions("viewer")), + "marketing": list(permission_discovery_service.get_preset_permissions("marketing")), }