mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,735 @@
|
|
1
|
+
"""
|
2
|
+
Permission Manager Module
|
3
|
+
|
4
|
+
This module provides comprehensive role and permission management for the
|
5
|
+
MCP Security Framework. It handles role hierarchies, permission caching,
|
6
|
+
and access validation with support for wildcard permissions.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- Role hierarchy management
|
10
|
+
- Permission caching for performance
|
11
|
+
- Wildcard permission support
|
12
|
+
- JSON-based role configuration
|
13
|
+
- Access validation utilities
|
14
|
+
- Role and permission CRUD operations
|
15
|
+
|
16
|
+
Classes:
|
17
|
+
PermissionManager: Main permission management class
|
18
|
+
RoleHierarchy: Internal role hierarchy representation
|
19
|
+
PermissionCache: Permission caching implementation
|
20
|
+
|
21
|
+
Author: MCP Security Team
|
22
|
+
Version: 1.0.0
|
23
|
+
License: MIT
|
24
|
+
"""
|
25
|
+
|
26
|
+
import json
|
27
|
+
import logging
|
28
|
+
from pathlib import Path
|
29
|
+
from typing import Dict, List, Optional, Set
|
30
|
+
|
31
|
+
from ..schemas.config import PermissionConfig
|
32
|
+
from ..schemas.models import ValidationResult, ValidationStatus
|
33
|
+
from ..utils.validation_utils import validate_configuration_file
|
34
|
+
|
35
|
+
|
36
|
+
class PermissionManager:
|
37
|
+
"""
|
38
|
+
Permission Manager Class
|
39
|
+
|
40
|
+
This class provides comprehensive role and permission management including
|
41
|
+
role hierarchies, permission caching, and access validation.
|
42
|
+
|
43
|
+
The PermissionManager handles:
|
44
|
+
- Loading role configurations from JSON files
|
45
|
+
- Managing role hierarchies and inheritance
|
46
|
+
- Caching permissions for performance optimization
|
47
|
+
- Validating access based on user roles and required permissions
|
48
|
+
- Supporting wildcard permissions and complex permission patterns
|
49
|
+
- CRUD operations for roles and permissions
|
50
|
+
|
51
|
+
Attributes:
|
52
|
+
config (PermissionConfig): Permission configuration settings
|
53
|
+
logger (Logger): Logger instance for permission operations
|
54
|
+
_roles (Dict): Loaded role configurations
|
55
|
+
_hierarchy (Dict): Role hierarchy relationships
|
56
|
+
_permission_cache (Dict): Cache of effective permissions
|
57
|
+
_cache_enabled (bool): Whether permission caching is enabled
|
58
|
+
|
59
|
+
Example:
|
60
|
+
>>> config = PermissionConfig(roles_file="roles.json", cache_enabled=True)
|
61
|
+
>>> perm_manager = PermissionManager(config)
|
62
|
+
>>> result = perm_manager.validate_access(["admin"], ["read:users"])
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
PermissionConfigurationError: When permission configuration is invalid
|
66
|
+
RoleNotFoundError: When specified role is not found
|
67
|
+
PermissionValidationError: When permission validation fails
|
68
|
+
"""
|
69
|
+
|
70
|
+
def __init__(self, config: PermissionConfig):
|
71
|
+
"""
|
72
|
+
Initialize Permission Manager.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
config (PermissionConfig): Permission configuration settings containing
|
76
|
+
roles file path, cache settings, and validation options.
|
77
|
+
Must be a valid PermissionConfig instance with proper roles
|
78
|
+
file path and configuration settings.
|
79
|
+
|
80
|
+
Raises:
|
81
|
+
PermissionConfigurationError: If configuration is invalid or roles
|
82
|
+
file cannot be loaded.
|
83
|
+
|
84
|
+
Example:
|
85
|
+
>>> config = PermissionConfig(roles_file="roles.json")
|
86
|
+
>>> perm_manager = PermissionManager(config)
|
87
|
+
"""
|
88
|
+
self.config = config
|
89
|
+
self.logger = logging.getLogger(__name__)
|
90
|
+
self._roles: Dict = {}
|
91
|
+
self._hierarchy: Dict = {}
|
92
|
+
self._permission_cache: Dict = {}
|
93
|
+
self._cache_enabled = config.permission_cache_enabled
|
94
|
+
|
95
|
+
# Load roles configuration
|
96
|
+
self._load_roles_configuration()
|
97
|
+
|
98
|
+
# Build role hierarchy
|
99
|
+
self._build_role_hierarchy()
|
100
|
+
|
101
|
+
self.logger.info(
|
102
|
+
"PermissionManager initialized successfully",
|
103
|
+
extra={
|
104
|
+
"roles_count": len(self._roles),
|
105
|
+
"cache_enabled": self._cache_enabled,
|
106
|
+
},
|
107
|
+
)
|
108
|
+
|
109
|
+
def validate_access(
|
110
|
+
self, user_roles: List[str], required_permissions: List[str]
|
111
|
+
) -> ValidationResult:
|
112
|
+
"""
|
113
|
+
Validate user access to resource based on roles and required permissions.
|
114
|
+
|
115
|
+
This method checks if the user's roles provide the required permissions
|
116
|
+
for accessing a specific resource. It considers role hierarchies and
|
117
|
+
supports wildcard permissions.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
user_roles (List[str]): List of user role names. Must be valid
|
121
|
+
role names that exist in the loaded configuration.
|
122
|
+
required_permissions (List[str]): List of required permission names.
|
123
|
+
Can include wildcard patterns like "read:*" or "admin:*".
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
ValidationResult: Validation result containing:
|
127
|
+
- is_valid (bool): True if access is granted
|
128
|
+
- user_roles (List[str]): User roles used for validation
|
129
|
+
- required_permissions (List[str]): Required permissions
|
130
|
+
- effective_permissions (Set[str]): Effective permissions
|
131
|
+
- missing_permissions (Set[str]): Missing permissions
|
132
|
+
- error_code (int): Error code if validation failed
|
133
|
+
- error_message (str): Human-readable error message
|
134
|
+
|
135
|
+
Raises:
|
136
|
+
RoleNotFoundError: When any user role is not found in configuration
|
137
|
+
PermissionValidationError: When permission validation fails
|
138
|
+
|
139
|
+
Example:
|
140
|
+
>>> result = perm_manager.validate_access(
|
141
|
+
... ["admin", "user"],
|
142
|
+
... ["read:users", "write:posts"]
|
143
|
+
... )
|
144
|
+
>>> if result.is_valid:
|
145
|
+
... print("Access granted")
|
146
|
+
>>> else:
|
147
|
+
... print(f"Access denied: {result.error_message}")
|
148
|
+
"""
|
149
|
+
try:
|
150
|
+
# Validate input parameters
|
151
|
+
if not user_roles:
|
152
|
+
return ValidationResult(
|
153
|
+
is_valid=False,
|
154
|
+
status=ValidationStatus.INVALID,
|
155
|
+
error_code=-32001,
|
156
|
+
error_message="No user roles provided",
|
157
|
+
)
|
158
|
+
|
159
|
+
if not required_permissions:
|
160
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
161
|
+
|
162
|
+
# Validate that all user roles exist
|
163
|
+
invalid_roles = [role for role in user_roles if role not in self._roles]
|
164
|
+
if invalid_roles:
|
165
|
+
return ValidationResult(
|
166
|
+
is_valid=False,
|
167
|
+
status=ValidationStatus.INVALID,
|
168
|
+
error_code=-32002,
|
169
|
+
error_message=f"Invalid roles: {invalid_roles}",
|
170
|
+
)
|
171
|
+
|
172
|
+
# Get effective permissions for user roles
|
173
|
+
effective_permissions = self.get_effective_permissions(user_roles)
|
174
|
+
|
175
|
+
# Check if all required permissions are satisfied
|
176
|
+
missing_permissions = set()
|
177
|
+
for required_perm in required_permissions:
|
178
|
+
if not self._permission_matches(required_perm, effective_permissions):
|
179
|
+
missing_permissions.add(required_perm)
|
180
|
+
|
181
|
+
is_valid = len(missing_permissions) == 0
|
182
|
+
|
183
|
+
if is_valid:
|
184
|
+
return ValidationResult(
|
185
|
+
is_valid=True,
|
186
|
+
status=ValidationStatus.VALID,
|
187
|
+
granted_permissions=list(effective_permissions),
|
188
|
+
denied_permissions=[]
|
189
|
+
)
|
190
|
+
else:
|
191
|
+
return ValidationResult(
|
192
|
+
is_valid=False,
|
193
|
+
status=ValidationStatus.INVALID,
|
194
|
+
error_code=-32003,
|
195
|
+
error_message=f"Missing permissions: {missing_permissions}",
|
196
|
+
granted_permissions=list(effective_permissions),
|
197
|
+
denied_permissions=list(missing_permissions)
|
198
|
+
)
|
199
|
+
|
200
|
+
except Exception as e:
|
201
|
+
self.logger.error(
|
202
|
+
"Permission validation failed",
|
203
|
+
extra={
|
204
|
+
"user_roles": user_roles,
|
205
|
+
"required_permissions": required_permissions,
|
206
|
+
"error": str(e),
|
207
|
+
},
|
208
|
+
)
|
209
|
+
raise PermissionValidationError(f"Permission validation failed: {str(e)}")
|
210
|
+
|
211
|
+
def get_effective_permissions(self, user_roles: List[str]) -> Set[str]:
|
212
|
+
"""
|
213
|
+
Get effective permissions for user roles including inherited permissions.
|
214
|
+
|
215
|
+
This method calculates the effective permissions for a set of user roles,
|
216
|
+
taking into account role hierarchies and inheritance.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
user_roles (List[str]): List of user role names
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
Set[str]: Set of effective permissions for the user roles
|
223
|
+
|
224
|
+
Raises:
|
225
|
+
RoleNotFoundError: When any user role is not found
|
226
|
+
"""
|
227
|
+
if not user_roles:
|
228
|
+
return set()
|
229
|
+
|
230
|
+
# Validate that all roles exist
|
231
|
+
invalid_roles = [role for role in user_roles if role not in self._roles]
|
232
|
+
if invalid_roles:
|
233
|
+
raise RoleNotFoundError(f"Invalid roles: {invalid_roles}")
|
234
|
+
|
235
|
+
# Create cache key
|
236
|
+
cache_key = tuple(sorted(user_roles))
|
237
|
+
|
238
|
+
# Check cache first
|
239
|
+
if self._cache_enabled and cache_key in self._permission_cache:
|
240
|
+
return self._permission_cache[cache_key]
|
241
|
+
|
242
|
+
# Calculate effective permissions
|
243
|
+
effective_permissions = set()
|
244
|
+
for role in user_roles:
|
245
|
+
role_permissions = self._get_role_permissions_with_inheritance(role)
|
246
|
+
effective_permissions.update(role_permissions)
|
247
|
+
|
248
|
+
# Cache the result
|
249
|
+
if self._cache_enabled:
|
250
|
+
self._permission_cache[cache_key] = effective_permissions
|
251
|
+
|
252
|
+
return effective_permissions
|
253
|
+
|
254
|
+
def check_role_hierarchy(self, child_role: str, parent_role: str) -> bool:
|
255
|
+
"""
|
256
|
+
Check if child role inherits from parent role through hierarchy.
|
257
|
+
|
258
|
+
This method checks if a child role inherits from a parent role,
|
259
|
+
either directly or through the role hierarchy.
|
260
|
+
|
261
|
+
Args:
|
262
|
+
child_role (str): Child role name
|
263
|
+
parent_role (str): Parent role name
|
264
|
+
|
265
|
+
Returns:
|
266
|
+
bool: True if child role inherits from parent role
|
267
|
+
|
268
|
+
Raises:
|
269
|
+
RoleNotFoundError: When either role is not found
|
270
|
+
"""
|
271
|
+
if child_role not in self._roles:
|
272
|
+
raise RoleNotFoundError(f"Child role not found: {child_role}")
|
273
|
+
|
274
|
+
if parent_role not in self._roles:
|
275
|
+
raise RoleNotFoundError(f"Parent role not found: {parent_role}")
|
276
|
+
|
277
|
+
if child_role == parent_role:
|
278
|
+
return True
|
279
|
+
|
280
|
+
return self._check_hierarchy_inheritance(child_role, parent_role)
|
281
|
+
|
282
|
+
def add_role_permission(self, role: str, permission: str) -> bool:
|
283
|
+
"""
|
284
|
+
Add permission to role.
|
285
|
+
|
286
|
+
This method adds a permission to a specific role and clears
|
287
|
+
the permission cache to ensure consistency.
|
288
|
+
|
289
|
+
Args:
|
290
|
+
role (str): Role name
|
291
|
+
permission (str): Permission to add
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
bool: True if permission was added successfully
|
295
|
+
|
296
|
+
Raises:
|
297
|
+
RoleNotFoundError: When role is not found
|
298
|
+
"""
|
299
|
+
if role not in self._roles:
|
300
|
+
raise RoleNotFoundError(f"Role not found: {role}")
|
301
|
+
|
302
|
+
try:
|
303
|
+
if "permissions" not in self._roles[role]:
|
304
|
+
self._roles[role]["permissions"] = []
|
305
|
+
|
306
|
+
if permission not in self._roles[role]["permissions"]:
|
307
|
+
self._roles[role]["permissions"].append(permission)
|
308
|
+
|
309
|
+
# Clear cache for this role
|
310
|
+
self._clear_role_cache(role)
|
311
|
+
|
312
|
+
self.logger.info(
|
313
|
+
"Permission added to role",
|
314
|
+
extra={"role": role, "permission": permission},
|
315
|
+
)
|
316
|
+
return True
|
317
|
+
else:
|
318
|
+
self.logger.warning(
|
319
|
+
"Permission already exists in role",
|
320
|
+
extra={"role": role, "permission": permission},
|
321
|
+
)
|
322
|
+
return False
|
323
|
+
|
324
|
+
except Exception as e:
|
325
|
+
self.logger.error(
|
326
|
+
"Failed to add permission to role",
|
327
|
+
extra={"role": role, "permission": permission, "error": str(e)},
|
328
|
+
)
|
329
|
+
return False
|
330
|
+
|
331
|
+
def remove_role_permission(self, role: str, permission: str) -> bool:
|
332
|
+
"""
|
333
|
+
Remove permission from role.
|
334
|
+
|
335
|
+
This method removes a permission from a specific role and clears
|
336
|
+
the permission cache to ensure consistency.
|
337
|
+
|
338
|
+
Args:
|
339
|
+
role (str): Role name
|
340
|
+
permission (str): Permission to remove
|
341
|
+
|
342
|
+
Returns:
|
343
|
+
bool: True if permission was removed successfully
|
344
|
+
|
345
|
+
Raises:
|
346
|
+
RoleNotFoundError: When role is not found
|
347
|
+
"""
|
348
|
+
if role not in self._roles:
|
349
|
+
raise RoleNotFoundError(f"Role not found: {role}")
|
350
|
+
|
351
|
+
try:
|
352
|
+
if "permissions" in self._roles[role]:
|
353
|
+
if permission in self._roles[role]["permissions"]:
|
354
|
+
self._roles[role]["permissions"].remove(permission)
|
355
|
+
|
356
|
+
# Clear cache for this role
|
357
|
+
self._clear_role_cache(role)
|
358
|
+
|
359
|
+
self.logger.info(
|
360
|
+
"Permission removed from role",
|
361
|
+
extra={"role": role, "permission": permission},
|
362
|
+
)
|
363
|
+
return True
|
364
|
+
else:
|
365
|
+
self.logger.warning(
|
366
|
+
"Permission not found in role",
|
367
|
+
extra={"role": role, "permission": permission},
|
368
|
+
)
|
369
|
+
return False
|
370
|
+
else:
|
371
|
+
self.logger.warning("Role has no permissions", extra={"role": role})
|
372
|
+
return False
|
373
|
+
|
374
|
+
except Exception as e:
|
375
|
+
self.logger.error(
|
376
|
+
"Failed to remove permission from role",
|
377
|
+
extra={"role": role, "permission": permission, "error": str(e)},
|
378
|
+
)
|
379
|
+
return False
|
380
|
+
|
381
|
+
def reload_roles_configuration(self) -> bool:
|
382
|
+
"""
|
383
|
+
Reload roles configuration from file.
|
384
|
+
|
385
|
+
This method reloads the roles configuration from the configured
|
386
|
+
file and rebuilds the role hierarchy.
|
387
|
+
|
388
|
+
Returns:
|
389
|
+
bool: True if configuration was reloaded successfully
|
390
|
+
"""
|
391
|
+
try:
|
392
|
+
# Clear existing data
|
393
|
+
self._roles.clear()
|
394
|
+
self._hierarchy.clear()
|
395
|
+
self._permission_cache.clear()
|
396
|
+
|
397
|
+
# Reload configuration
|
398
|
+
self._load_roles_configuration()
|
399
|
+
self._build_role_hierarchy()
|
400
|
+
|
401
|
+
self.logger.info("Roles configuration reloaded successfully")
|
402
|
+
return True
|
403
|
+
|
404
|
+
except Exception as e:
|
405
|
+
self.logger.error(
|
406
|
+
"Failed to reload roles configuration", extra={"error": str(e)}
|
407
|
+
)
|
408
|
+
return False
|
409
|
+
|
410
|
+
def get_user_roles(self, username: str) -> List[str]:
|
411
|
+
"""
|
412
|
+
Get roles for a specific user.
|
413
|
+
|
414
|
+
This method retrieves the roles assigned to a specific user.
|
415
|
+
It checks the user-role mapping in the configuration and
|
416
|
+
returns the associated roles.
|
417
|
+
|
418
|
+
Args:
|
419
|
+
username (str): Username to get roles for
|
420
|
+
|
421
|
+
Returns:
|
422
|
+
List[str]: List of roles assigned to the user
|
423
|
+
|
424
|
+
Raises:
|
425
|
+
RoleNotFoundError: When user is not found in configuration
|
426
|
+
"""
|
427
|
+
try:
|
428
|
+
# Check if user-role mapping exists in configuration
|
429
|
+
if hasattr(self.config, "user_roles") and self.config.user_roles:
|
430
|
+
if username in self.config.user_roles:
|
431
|
+
return self.config.user_roles[username]
|
432
|
+
|
433
|
+
# Check if user has default role mapping
|
434
|
+
if hasattr(self.config, "default_user_role"):
|
435
|
+
return [self.config.default_user_role]
|
436
|
+
|
437
|
+
# If no explicit mapping, check if user exists in any role
|
438
|
+
for role_name, role_config in self._roles.items():
|
439
|
+
if hasattr(role_config, "users") and username in role_config.get(
|
440
|
+
"users", []
|
441
|
+
):
|
442
|
+
return [role_name]
|
443
|
+
|
444
|
+
# Return empty list if no roles found
|
445
|
+
self.logger.warning(
|
446
|
+
f"No roles found for user '{username}'", extra={"username": username}
|
447
|
+
)
|
448
|
+
return []
|
449
|
+
|
450
|
+
except Exception as e:
|
451
|
+
self.logger.error(
|
452
|
+
f"Failed to get roles for user '{username}'",
|
453
|
+
extra={"username": username, "error": str(e)},
|
454
|
+
)
|
455
|
+
raise RoleNotFoundError(
|
456
|
+
f"Failed to get roles for user '{username}': {str(e)}"
|
457
|
+
)
|
458
|
+
|
459
|
+
def get_role_permissions(self, role: str) -> List[str]:
|
460
|
+
"""
|
461
|
+
Get direct permissions for a specific role.
|
462
|
+
|
463
|
+
This method returns the direct permissions assigned to a role,
|
464
|
+
excluding inherited permissions from parent roles.
|
465
|
+
|
466
|
+
Args:
|
467
|
+
role (str): Role name to get permissions for
|
468
|
+
|
469
|
+
Returns:
|
470
|
+
List[str]: List of direct permissions for the role
|
471
|
+
|
472
|
+
Raises:
|
473
|
+
RoleNotFoundError: When role is not found in configuration
|
474
|
+
"""
|
475
|
+
if role not in self._roles:
|
476
|
+
raise RoleNotFoundError(f"Role not found: {role}")
|
477
|
+
|
478
|
+
return self._roles[role].get("permissions", []).copy()
|
479
|
+
|
480
|
+
def get_all_roles(self) -> List[str]:
|
481
|
+
"""
|
482
|
+
Get list of all available roles.
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
List[str]: List of all role names in the configuration
|
486
|
+
"""
|
487
|
+
return list(self._roles.keys())
|
488
|
+
|
489
|
+
def get_role_hierarchy(self) -> Dict[str, List[str]]:
|
490
|
+
"""
|
491
|
+
Get complete role hierarchy.
|
492
|
+
|
493
|
+
Returns:
|
494
|
+
Dict[str, List[str]]: Dictionary mapping roles to their parent roles
|
495
|
+
"""
|
496
|
+
return self._hierarchy.copy()
|
497
|
+
|
498
|
+
def export_roles_config(self) -> Dict:
|
499
|
+
"""
|
500
|
+
Export current roles configuration.
|
501
|
+
|
502
|
+
This method exports the current roles configuration including
|
503
|
+
all roles, permissions, and hierarchy relationships.
|
504
|
+
|
505
|
+
Returns:
|
506
|
+
Dict: Complete roles configuration dictionary containing:
|
507
|
+
- roles: Dictionary of role definitions
|
508
|
+
- permissions: Dictionary of permission descriptions
|
509
|
+
- hierarchy: Role hierarchy relationships
|
510
|
+
|
511
|
+
Example:
|
512
|
+
>>> config = perm_manager.export_roles_config()
|
513
|
+
>>> with open('exported_roles.json', 'w') as f:
|
514
|
+
... json.dump(config, f, indent=2)
|
515
|
+
"""
|
516
|
+
exported_config = {
|
517
|
+
"roles": {},
|
518
|
+
"permissions": {},
|
519
|
+
"hierarchy": self._hierarchy.copy()
|
520
|
+
}
|
521
|
+
|
522
|
+
# Export roles with their permissions
|
523
|
+
for role_name, role_data in self._roles.items():
|
524
|
+
exported_config["roles"][role_name] = {
|
525
|
+
"description": role_data.get("description", ""),
|
526
|
+
"permissions": role_data.get("permissions", []),
|
527
|
+
"parent_roles": self._hierarchy.get(role_name, [])
|
528
|
+
}
|
529
|
+
|
530
|
+
# Collect all unique permissions
|
531
|
+
all_permissions = set()
|
532
|
+
for role_data in self._roles.values():
|
533
|
+
permissions = role_data.get("permissions", [])
|
534
|
+
all_permissions.update(permissions)
|
535
|
+
|
536
|
+
# Create permission descriptions
|
537
|
+
for permission in all_permissions:
|
538
|
+
if permission not in exported_config["permissions"]:
|
539
|
+
# Generate default description based on permission name
|
540
|
+
description = permission.replace(":", " ").replace("_", " ").title()
|
541
|
+
exported_config["permissions"][permission] = description
|
542
|
+
|
543
|
+
self.logger.info(
|
544
|
+
"Roles configuration exported",
|
545
|
+
extra={
|
546
|
+
"roles_count": len(exported_config["roles"]),
|
547
|
+
"permissions_count": len(exported_config["permissions"])
|
548
|
+
}
|
549
|
+
)
|
550
|
+
|
551
|
+
return exported_config
|
552
|
+
|
553
|
+
def clear_cache(self) -> None:
|
554
|
+
"""Clear permission cache."""
|
555
|
+
self._permission_cache.clear()
|
556
|
+
self.logger.info("Permission cache cleared")
|
557
|
+
|
558
|
+
def clear_permission_cache(self) -> None:
|
559
|
+
"""Clear permission cache."""
|
560
|
+
self._permission_cache.clear()
|
561
|
+
self.logger.info("Permission cache cleared")
|
562
|
+
|
563
|
+
def _load_roles_configuration(self) -> None:
|
564
|
+
"""Load roles configuration from file."""
|
565
|
+
try:
|
566
|
+
roles_file = Path(self.config.roles_file)
|
567
|
+
|
568
|
+
if not roles_file.exists():
|
569
|
+
raise PermissionConfigurationError(
|
570
|
+
f"Roles file not found: {roles_file}"
|
571
|
+
)
|
572
|
+
|
573
|
+
with open(roles_file, "r", encoding="utf-8") as f:
|
574
|
+
roles_data = json.load(f)
|
575
|
+
# Handle both direct roles dict and nested "roles" key
|
576
|
+
if "roles" in roles_data:
|
577
|
+
self._roles = roles_data["roles"]
|
578
|
+
else:
|
579
|
+
self._roles = roles_data
|
580
|
+
|
581
|
+
# Validate roles structure
|
582
|
+
self._validate_roles_structure()
|
583
|
+
|
584
|
+
self.logger.info(
|
585
|
+
"Roles configuration loaded", extra={"roles_count": len(self._roles)}
|
586
|
+
)
|
587
|
+
|
588
|
+
except json.JSONDecodeError as e:
|
589
|
+
raise PermissionConfigurationError(f"Invalid JSON in roles file: {str(e)}")
|
590
|
+
except Exception as e:
|
591
|
+
raise PermissionConfigurationError(
|
592
|
+
f"Failed to load roles configuration: {str(e)}"
|
593
|
+
)
|
594
|
+
|
595
|
+
def _validate_roles_structure(self) -> None:
|
596
|
+
"""Validate roles configuration structure."""
|
597
|
+
for role_name, role_data in self._roles.items():
|
598
|
+
if not isinstance(role_data, dict):
|
599
|
+
raise PermissionConfigurationError(f"Invalid role data for {role_name}")
|
600
|
+
|
601
|
+
if "permissions" in role_data and not isinstance(
|
602
|
+
role_data["permissions"], list
|
603
|
+
):
|
604
|
+
raise PermissionConfigurationError(
|
605
|
+
f"Invalid permissions for role {role_name}"
|
606
|
+
)
|
607
|
+
|
608
|
+
if "inherits" in role_data and not isinstance(role_data["inherits"], list):
|
609
|
+
raise PermissionConfigurationError(
|
610
|
+
f"Invalid inheritance for role {role_name}"
|
611
|
+
)
|
612
|
+
|
613
|
+
def _build_role_hierarchy(self) -> None:
|
614
|
+
"""Build role hierarchy from configuration."""
|
615
|
+
self._hierarchy.clear()
|
616
|
+
|
617
|
+
for role_name, role_data in self._roles.items():
|
618
|
+
parent_roles = role_data.get("inherits", [])
|
619
|
+
self._hierarchy[role_name] = parent_roles.copy()
|
620
|
+
|
621
|
+
def _get_role_permissions_with_inheritance(self, role: str) -> Set[str]:
|
622
|
+
"""Get permissions for role including inherited permissions."""
|
623
|
+
permissions = set()
|
624
|
+
|
625
|
+
# Add direct permissions
|
626
|
+
direct_permissions = self._roles[role].get("permissions", [])
|
627
|
+
permissions.update(direct_permissions)
|
628
|
+
|
629
|
+
# Add inherited permissions
|
630
|
+
parent_roles = self._hierarchy.get(role, [])
|
631
|
+
for parent_role in parent_roles:
|
632
|
+
if parent_role in self._roles:
|
633
|
+
parent_permissions = self._get_role_permissions_with_inheritance(
|
634
|
+
parent_role
|
635
|
+
)
|
636
|
+
permissions.update(parent_permissions)
|
637
|
+
|
638
|
+
return permissions
|
639
|
+
|
640
|
+
def _check_hierarchy_inheritance(self, child_role: str, parent_role: str) -> bool:
|
641
|
+
"""Check if child role inherits from parent role through hierarchy."""
|
642
|
+
parent_roles = self._hierarchy.get(child_role, [])
|
643
|
+
|
644
|
+
# Check direct parents
|
645
|
+
if parent_role in parent_roles:
|
646
|
+
return True
|
647
|
+
|
648
|
+
# Check indirect parents
|
649
|
+
for direct_parent in parent_roles:
|
650
|
+
if self._check_hierarchy_inheritance(direct_parent, parent_role):
|
651
|
+
return True
|
652
|
+
|
653
|
+
return False
|
654
|
+
|
655
|
+
def _permission_matches(
|
656
|
+
self, required_perm: str, available_permissions: Set[str]
|
657
|
+
) -> bool:
|
658
|
+
"""Check if required permission matches any available permission."""
|
659
|
+
# Direct match
|
660
|
+
if required_perm in available_permissions:
|
661
|
+
return True
|
662
|
+
|
663
|
+
# Wildcard match
|
664
|
+
if "*" in required_perm:
|
665
|
+
perm_parts = required_perm.split(":")
|
666
|
+
if len(perm_parts) == 2:
|
667
|
+
action, resource = perm_parts
|
668
|
+
|
669
|
+
# Check action wildcard
|
670
|
+
if action == "*":
|
671
|
+
for perm in available_permissions:
|
672
|
+
if perm.endswith(f":{resource}"):
|
673
|
+
return True
|
674
|
+
|
675
|
+
# Check resource wildcard
|
676
|
+
elif resource == "*":
|
677
|
+
for perm in available_permissions:
|
678
|
+
if perm.startswith(f"{action}:"):
|
679
|
+
return True
|
680
|
+
|
681
|
+
return False
|
682
|
+
|
683
|
+
def _clear_role_cache(self, role: str) -> None:
|
684
|
+
"""Clear cache entries that include the specified role."""
|
685
|
+
if not self._cache_enabled:
|
686
|
+
return
|
687
|
+
|
688
|
+
keys_to_remove = []
|
689
|
+
for cache_key in self._permission_cache.keys():
|
690
|
+
if role in cache_key:
|
691
|
+
keys_to_remove.append(cache_key)
|
692
|
+
|
693
|
+
for key in keys_to_remove:
|
694
|
+
del self._permission_cache[key]
|
695
|
+
|
696
|
+
def _load_external_permissions(self) -> Dict[str, List[str]]:
|
697
|
+
"""
|
698
|
+
Load permissions from external systems.
|
699
|
+
|
700
|
+
This is a placeholder method for external permission loading.
|
701
|
+
In a real implementation, this would connect to external systems
|
702
|
+
like LDAP, Active Directory, or other identity providers.
|
703
|
+
|
704
|
+
Returns:
|
705
|
+
Dict[str, List[str]]: Dictionary mapping role names to permission lists
|
706
|
+
"""
|
707
|
+
# Placeholder implementation - return empty dict
|
708
|
+
return {}
|
709
|
+
|
710
|
+
|
711
|
+
class PermissionConfigurationError(Exception):
|
712
|
+
"""Raised when permission configuration is invalid."""
|
713
|
+
|
714
|
+
def __init__(self, message: str, error_code: int = -32001):
|
715
|
+
self.message = message
|
716
|
+
self.error_code = error_code
|
717
|
+
super().__init__(self.message)
|
718
|
+
|
719
|
+
|
720
|
+
class RoleNotFoundError(Exception):
|
721
|
+
"""Raised when a specified role is not found."""
|
722
|
+
|
723
|
+
def __init__(self, message: str, error_code: int = -32002):
|
724
|
+
self.message = message
|
725
|
+
self.error_code = error_code
|
726
|
+
super().__init__(self.message)
|
727
|
+
|
728
|
+
|
729
|
+
class PermissionValidationError(Exception):
|
730
|
+
"""Raised when permission validation fails."""
|
731
|
+
|
732
|
+
def __init__(self, message: str, error_code: int = -32003):
|
733
|
+
self.message = message
|
734
|
+
self.error_code = error_code
|
735
|
+
super().__init__(self.message)
|