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.
Files changed (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. 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)