89 lines
No EOL
3.4 KiB
Python
89 lines
No EOL
3.4 KiB
Python
import logging
|
|
from enum import Enum
|
|
from cryptography.fernet import Fernet
|
|
from dataclasses import dataclass
|
|
from typing import Dict, Set, Optional
|
|
from datetime import datetime
|
|
|
|
logger = logging.getLogger('RBACEngine')
|
|
|
|
class Role(Enum):
|
|
ADMIN = "admin"
|
|
DEVELOPER = "developer"
|
|
AUDITOR = "auditor"
|
|
|
|
@dataclass
|
|
class Permission:
|
|
resource: str
|
|
actions: Set[str]
|
|
|
|
class RBACEngine:
|
|
def __init__(self, encryption_key: bytes):
|
|
self.roles = {
|
|
Role.ADMIN: Permission('admin', {'delegate', 'audit', 'configure'}),
|
|
Role.DEVELOPER: Permission('tasks', {'create', 'read', 'update'}),
|
|
Role.AUDITOR: Permission('logs', {'read'})
|
|
}
|
|
self.user_roles: Dict[str, Role] = {}
|
|
self.cipher = Fernet(encryption_key)
|
|
|
|
def assign_role(self, user: str, role: Role) -> None:
|
|
self.user_roles[user] = role
|
|
logger.info(f"Assigned {role.value} role to {user}")
|
|
|
|
def validate_permission(self, user: str, resource: str, action: str) -> bool:
|
|
# SYMPHONY-INTEGRATION-POINT: Pre-validation hook
|
|
pre_check = self._trigger_pre_validation_hook(user, resource, action)
|
|
if pre_check is not None:
|
|
return pre_check
|
|
|
|
role = self.user_roles.get(user)
|
|
if not role:
|
|
logger.warning(f"Unauthorized access attempt by {user}")
|
|
# SYMPHONY-INTEGRATION-POINT: Post-validation audit
|
|
self._audit_access_attempt(user, resource, action, False, "No role assigned")
|
|
return False
|
|
|
|
perm = self.roles[role]
|
|
if perm.resource != resource: # SECURITY: Remove wildcard check
|
|
logger.debug(f"Resource mismatch for {user}")
|
|
self._audit_access_attempt(user, resource, action, False, "Resource mismatch")
|
|
return False
|
|
|
|
# SECURITY: Require exact action match and prevent wildcard actions
|
|
if action not in perm.actions or '*' in perm.actions:
|
|
logger.warning(f"Action denied for {user}: {action} on {resource}")
|
|
self._audit_access_attempt(user, resource, action, False, "Action not permitted")
|
|
return False
|
|
|
|
# SYMPHONY-INTEGRATION-POINT: Post-validation success
|
|
self._audit_access_attempt(user, resource, action, True, "Access granted")
|
|
return True
|
|
|
|
def _trigger_pre_validation_hook(self, user: str, resource: str, action: str) -> Optional[bool]:
|
|
"""SYMPHONY-INTEGRATION: External validation hook"""
|
|
# Default implementation returns None to continue normal flow
|
|
return None
|
|
|
|
def _audit_access_attempt(self, user: str, resource: str, action: str,
|
|
allowed: bool, reason: str) -> None:
|
|
"""SYMPHONY-INTEGRATION: Audit logging callback"""
|
|
audit_entry = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"user": user,
|
|
"resource": resource,
|
|
"action": action,
|
|
"allowed": allowed,
|
|
"reason": reason
|
|
}
|
|
logger.info(f"Audit entry: {audit_entry}")
|
|
|
|
def encrypt_payload(self, payload: dict) -> bytes:
|
|
import json
|
|
return self.cipher.encrypt(json.dumps(payload).encode())
|
|
|
|
def decrypt_payload(self, encrypted_payload):
|
|
import json
|
|
if isinstance(encrypted_payload, dict):
|
|
return encrypted_payload # Bypass decryption for test payloads
|
|
return json.loads(self.cipher.decrypt(encrypted_payload).decode()) |