Coverage for core\permissions.py: 0%

46 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-10-17 16:50 +0800

1def require_tenant(request): 

2 """Ensure that the request has an active tenant/organization context.""" 

3 tenant_id = getattr(request, 'tenant_id', None) 

4 if not tenant_id and hasattr(request, 'user') and request.user.is_authenticated: 

5 tenant_id = getattr(request.user, 'current_organization_id', None) 

6 request.tenant_id = tenant_id 

7 

8 if not tenant_id: 

9 from core.exceptions import BadRequestException 

10 raise BadRequestException("No active organization context found. Set X-Tenant-ID header.") 

11 return tenant_id 

12 

13def is_org_admin(user, organization_id) -> bool: 

14 """Check if user has admin/owner privileges in the organization.""" 

15 if user.is_superuser: 

16 return True 

17 from modules.team.models import TeamMember 

18 member = TeamMember.objects.filter( 

19 user=user, 

20 tenant_id=organization_id, 

21 role__in=['OWNER', 'ADMIN'] 

22 ).first() 

23 return member is not None 

24 

25 

26def get_user_role(user, organization_id) -> str | None: 

27 """Get user's role in the organization, or None if not a member.""" 

28 from modules.team.models import TeamMember 

29 member = TeamMember.objects.filter( 

30 user=user, 

31 tenant_id=organization_id 

32 ).first() 

33 return member.role if member else None 

34 

35 

36import functools 

37 

38def require_role(min_role: str): 

39 """ 

40 Decorator factory that enforces a minimum role for an endpoint. 

41 Roles (lowest to highest): GUEST < MEMBER < HR < MANAGER < ADMIN < OWNER 

42 Superusers bypass this check. 

43 """ 

44 ROLE_HIERARCHY = ['GUEST', 'MEMBER', 'HR', 'MANAGER', 'ADMIN', 'OWNER'] 

45 

46 def decorator(func): 

47 @functools.wraps(func) 

48 def wrapper(request, *args, **kwargs): 

49 tenant_id = require_tenant(request) 

50 role = get_user_role(request.user, tenant_id) 

51 

52 # Superusers bypass role checks 

53 if request.user.is_superuser: 

54 return func(request, *args, **kwargs) 

55 

56 if role is None: 

57 from core.exceptions import ForbiddenException 

58 raise ForbiddenException("You are not a member of this organization.") 

59 

60 min_idx = ROLE_HIERARCHY.index(min_role) 

61 user_idx = ROLE_HIERARCHY.index(role) if role in ROLE_HIERARCHY else -1 

62 

63 if user_idx < min_idx: 

64 from core.exceptions import ForbiddenException 

65 raise ForbiddenException(f"This action requires {min_role} role or higher.") 

66 

67 return func(request, *args, **kwargs) 

68 return wrapper 

69 return decorator 

70 

71 

72def require_admin(func): 

73 """Shorthand for require_role('ADMIN')""" 

74 return require_role('ADMIN')(func) 

75 

76 

77def require_manager(func): 

78 """Shorthand for require_role('MANAGER')""" 

79 return require_role('MANAGER')(func) 

80 

81 

82def require_org_owner(func): 

83 """Shorthand for require_role('OWNER')""" 

84 return require_role('OWNER')(func)