Coverage for netrun_rbac\models.py: 100%

1"""

2 RBAC Models - Role and Permission definitions

3

4 Extracted from: Intirkast middleware/rbac.py

5 Generalized for multi-project reuse

6"""

7

8 from enum import Enum

9 from typing import Dict, List

10 from pydantic import BaseModel, Field

11

12

13 class Role(str, Enum):

14 """

15 Role hierarchy for multi-tenant SaaS platforms

16

17 Hierarchy (lowest to highest):

18 - viewer: Read-only access (view content, analytics)

19 - member: Create/edit own content (schedule posts, generate videos)

20 - admin: Manage team (invite users, edit all content)

21 - owner: Full control (billing, tenant settings, delete tenant)

22 """

23

24 VIEWER = "viewer"

25 MEMBER = "member"

26 ADMIN = "admin"

27 OWNER = "owner"

28

29

30 class Permission(str, Enum):

31 """

32 Fine-grained permissions for resource access control

33

34 Pattern: {resource}:{action}

35 Example: posts:create, users:delete, billing:read

36 """

37

38 # User management

39 USERS_READ = "users:read"

40 USERS_CREATE = "users:create"

41 USERS_UPDATE = "users:update"

42 USERS_DELETE = "users:delete"

43

44 # Tenant management

45 TENANT_READ = "tenant:read"

46 TENANT_UPDATE = "tenant:update"

47 TENANT_DELETE = "tenant:delete"

48

49 # Content management (generic)

50 CONTENT_READ = "content:read"

51 CONTENT_CREATE = "content:create"

52 CONTENT_UPDATE = "content:update"

53 CONTENT_DELETE = "content:delete"

54

55 # Billing

56 BILLING_READ = "billing:read"

57 BILLING_UPDATE = "billing:update"

58

59 # Invitations

60 INVITATIONS_CREATE = "invitations:create"

61 INVITATIONS_DELETE = "invitations:delete"

62

63

64 class RoleHierarchy:

65 """

66 Role hierarchy and permission mapping

67

68 Provides:

69 - Hierarchical role comparison

70 - Permission-to-role mapping

71 - Role validation utilities

72 """

73

74 # Role hierarchy (higher number = more permissions)

75 HIERARCHY: Dict[Role, int] = {

76 Role.VIEWER: 0,

77 Role.MEMBER: 1,

78 Role.ADMIN: 2,

79 Role.OWNER: 3,

80 }

81

82 # Permission mappings (role -> list of permissions)

83 PERMISSIONS: Dict[Role, List[Permission]] = {

84 Role.VIEWER: [

85 Permission.USERS_READ,

86 Permission.TENANT_READ,

87 Permission.CONTENT_READ,

88 Permission.BILLING_READ,

89 ],

90 Role.MEMBER: [

91 # Inherits viewer permissions

92 Permission.USERS_READ,

93 Permission.TENANT_READ,

94 Permission.CONTENT_READ,

95 Permission.BILLING_READ,

96 # Additional member permissions

97 Permission.CONTENT_CREATE,

98 Permission.CONTENT_UPDATE, # Own content only

99 ],

100 Role.ADMIN: [

101 # Inherits member permissions

102 Permission.USERS_READ,

103 Permission.TENANT_READ,

104 Permission.CONTENT_READ,

105 Permission.BILLING_READ,

106 Permission.CONTENT_CREATE,

107 Permission.CONTENT_UPDATE, # All content

108 Permission.CONTENT_DELETE,

109 # Additional admin permissions

110 Permission.USERS_CREATE,

111 Permission.USERS_UPDATE,

112 Permission.INVITATIONS_CREATE,

113 Permission.INVITATIONS_DELETE,

114 ],

115 Role.OWNER: [

116 # All permissions

117 Permission.USERS_READ,

118 Permission.USERS_CREATE,

119 Permission.USERS_UPDATE,

120 Permission.USERS_DELETE,

121 Permission.TENANT_READ,

122 Permission.TENANT_UPDATE,

123 Permission.TENANT_DELETE,

124 Permission.CONTENT_READ,

125 Permission.CONTENT_CREATE,

126 Permission.CONTENT_UPDATE,

127 Permission.CONTENT_DELETE,

128 Permission.BILLING_READ,

129 Permission.BILLING_UPDATE,

130 Permission.INVITATIONS_CREATE,

131 Permission.INVITATIONS_DELETE,

132 ],

133 }

134

135 @classmethod

136 def has_permission(cls, user_role: str, required_permission: Permission) -> bool:

137 """

138 Check if a role has a specific permission

139

140 Args:

141 user_role: User's role (string)

142 required_permission: Required permission

143

144 Returns:

145 True if role has permission

146 """

147 try:

148 role = Role(user_role)

149 except ValueError:

150 return False

151

152 return required_permission in cls.PERMISSIONS.get(role, [])

153

154 @classmethod

155 def check_role_permission(cls, user_role: str, required_role: str) -> bool:

156 """

157 Check if user role has sufficient permissions (hierarchical)

158

159 Args:

160 user_role: User's current role

161 required_role: Minimum required role

162

163 Returns:

164 True if user has sufficient permissions

165 """

166 try:

167 user_role_enum = Role(user_role)

168 required_role_enum = Role(required_role)

169 except ValueError:

170 return False

171

172 user_level = cls.HIERARCHY.get(user_role_enum, -1)

173 required_level = cls.HIERARCHY.get(required_role_enum, 999)

174

175 return user_level >= required_level

176

177 @classmethod

178 def get_role_level(cls, role: str) -> int:

179 """

180 Get numeric level for a role

181

182 Args:

183 role: Role name

184

185 Returns:

186 Numeric level (0-3), or -1 if invalid

187 """

188 try:

189 role_enum = Role(role)

190 return cls.HIERARCHY.get(role_enum, -1)

191 except ValueError:

192 return -1

193

194

195 class RoleAssignment(BaseModel):

196 """

197 Pydantic model for role assignment

198

199 Used for API requests/responses

200 """

201

202 user_id: str = Field(..., description="User ID (UUID)")

203 tenant_id: str = Field(..., description="Tenant ID (UUID)")

204 role: Role = Field(..., description="Assigned role")

205 assigned_by: str | None = Field(None, description="User ID who assigned the role")

206

207 class Config:

208 json_schema_extra = {

209 "example": {

210 "user_id": "550e8400-e29b-41d4-a716-446655440000",

211 "tenant_id": "660e8400-e29b-41d4-a716-446655440001",

212 "role": "admin",

213 "assigned_by": "770e8400-e29b-41d4-a716-446655440002",

214 }

215 }