229 lines
7.2 KiB
Python
229 lines
7.2 KiB
Python
"""
|
|
Role-Based Access Control (RBAC) - Authorization Engine
|
|
"""
|
|
from enum import Enum
|
|
from typing import Optional, Set, Dict, Any
|
|
from dataclasses import dataclass
|
|
|
|
|
|
class MemberRole(str, Enum):
|
|
"""Family member roles with hierarchy"""
|
|
OWNER = "owner" # Full access
|
|
ADULT = "adult" # Can create/edit own transactions
|
|
MEMBER = "member" # Can create/edit own transactions, restricted budget
|
|
CHILD = "child" # Limited access, read mostly
|
|
READ_ONLY = "read_only" # Audit/observer only
|
|
|
|
|
|
class Permission(str, Enum):
|
|
"""Fine-grained permissions"""
|
|
# Transaction permissions
|
|
CREATE_TRANSACTION = "create_transaction"
|
|
EDIT_OWN_TRANSACTION = "edit_own_transaction"
|
|
EDIT_ANY_TRANSACTION = "edit_any_transaction"
|
|
DELETE_OWN_TRANSACTION = "delete_own_transaction"
|
|
DELETE_ANY_TRANSACTION = "delete_any_transaction"
|
|
APPROVE_TRANSACTION = "approve_transaction"
|
|
|
|
# Wallet permissions
|
|
CREATE_WALLET = "create_wallet"
|
|
EDIT_WALLET = "edit_wallet"
|
|
DELETE_WALLET = "delete_wallet"
|
|
VIEW_WALLET_BALANCE = "view_wallet_balance"
|
|
|
|
# Budget permissions
|
|
CREATE_BUDGET = "create_budget"
|
|
EDIT_BUDGET = "edit_budget"
|
|
DELETE_BUDGET = "delete_budget"
|
|
|
|
# Goal permissions
|
|
CREATE_GOAL = "create_goal"
|
|
EDIT_GOAL = "edit_goal"
|
|
DELETE_GOAL = "delete_goal"
|
|
|
|
# Category permissions
|
|
CREATE_CATEGORY = "create_category"
|
|
EDIT_CATEGORY = "edit_category"
|
|
DELETE_CATEGORY = "delete_category"
|
|
|
|
# Member management
|
|
INVITE_MEMBERS = "invite_members"
|
|
EDIT_MEMBER_ROLE = "edit_member_role"
|
|
REMOVE_MEMBER = "remove_member"
|
|
|
|
# Family settings
|
|
EDIT_FAMILY_SETTINGS = "edit_family_settings"
|
|
DELETE_FAMILY = "delete_family"
|
|
|
|
# Audit & reports
|
|
VIEW_AUDIT_LOG = "view_audit_log"
|
|
EXPORT_DATA = "export_data"
|
|
|
|
|
|
@dataclass
|
|
class UserContext:
|
|
"""Request context with authorization info"""
|
|
user_id: int
|
|
family_id: int
|
|
role: MemberRole
|
|
permissions: Set[Permission]
|
|
family_ids: list[int] # All accessible families
|
|
device_id: Optional[str] = None
|
|
client_id: Optional[str] = None # "telegram_bot", "web_frontend", etc.
|
|
|
|
|
|
class RBACEngine:
|
|
"""
|
|
Role-Based Access Control with permission inheritance.
|
|
"""
|
|
|
|
# Define role -> permissions mapping
|
|
ROLE_PERMISSIONS: Dict[MemberRole, Set[Permission]] = {
|
|
MemberRole.OWNER: {
|
|
# All permissions
|
|
Permission.CREATE_TRANSACTION,
|
|
Permission.EDIT_OWN_TRANSACTION,
|
|
Permission.EDIT_ANY_TRANSACTION,
|
|
Permission.DELETE_OWN_TRANSACTION,
|
|
Permission.DELETE_ANY_TRANSACTION,
|
|
Permission.APPROVE_TRANSACTION,
|
|
Permission.CREATE_WALLET,
|
|
Permission.EDIT_WALLET,
|
|
Permission.DELETE_WALLET,
|
|
Permission.VIEW_WALLET_BALANCE,
|
|
Permission.CREATE_BUDGET,
|
|
Permission.EDIT_BUDGET,
|
|
Permission.DELETE_BUDGET,
|
|
Permission.CREATE_GOAL,
|
|
Permission.EDIT_GOAL,
|
|
Permission.DELETE_GOAL,
|
|
Permission.CREATE_CATEGORY,
|
|
Permission.EDIT_CATEGORY,
|
|
Permission.DELETE_CATEGORY,
|
|
Permission.INVITE_MEMBERS,
|
|
Permission.EDIT_MEMBER_ROLE,
|
|
Permission.REMOVE_MEMBER,
|
|
Permission.EDIT_FAMILY_SETTINGS,
|
|
Permission.DELETE_FAMILY,
|
|
Permission.VIEW_AUDIT_LOG,
|
|
Permission.EXPORT_DATA,
|
|
},
|
|
|
|
MemberRole.ADULT: {
|
|
# Can manage finances and invite others
|
|
Permission.CREATE_TRANSACTION,
|
|
Permission.EDIT_OWN_TRANSACTION,
|
|
Permission.DELETE_OWN_TRANSACTION,
|
|
Permission.APPROVE_TRANSACTION,
|
|
Permission.CREATE_WALLET,
|
|
Permission.EDIT_WALLET,
|
|
Permission.VIEW_WALLET_BALANCE,
|
|
Permission.CREATE_BUDGET,
|
|
Permission.EDIT_BUDGET,
|
|
Permission.CREATE_GOAL,
|
|
Permission.EDIT_GOAL,
|
|
Permission.CREATE_CATEGORY,
|
|
Permission.INVITE_MEMBERS,
|
|
Permission.VIEW_AUDIT_LOG,
|
|
Permission.EXPORT_DATA,
|
|
},
|
|
|
|
MemberRole.MEMBER: {
|
|
# Can create/view transactions
|
|
Permission.CREATE_TRANSACTION,
|
|
Permission.EDIT_OWN_TRANSACTION,
|
|
Permission.DELETE_OWN_TRANSACTION,
|
|
Permission.VIEW_WALLET_BALANCE,
|
|
Permission.VIEW_AUDIT_LOG,
|
|
},
|
|
|
|
MemberRole.CHILD: {
|
|
# Limited read access
|
|
Permission.CREATE_TRANSACTION, # Limited to own
|
|
Permission.VIEW_WALLET_BALANCE,
|
|
Permission.VIEW_AUDIT_LOG,
|
|
},
|
|
|
|
MemberRole.READ_ONLY: {
|
|
# Audit/observer only
|
|
Permission.VIEW_WALLET_BALANCE,
|
|
Permission.VIEW_AUDIT_LOG,
|
|
},
|
|
}
|
|
|
|
@staticmethod
|
|
def get_permissions(role: MemberRole) -> Set[Permission]:
|
|
"""Get permissions for a role"""
|
|
return RBACEngine.ROLE_PERMISSIONS.get(role, set())
|
|
|
|
@staticmethod
|
|
def has_permission(user_context: UserContext, permission: Permission) -> bool:
|
|
"""Check if user has specific permission"""
|
|
return permission in user_context.permissions
|
|
|
|
@staticmethod
|
|
def check_permission(
|
|
user_context: UserContext,
|
|
required_permission: Permission,
|
|
raise_exception: bool = True,
|
|
) -> bool:
|
|
"""
|
|
Check permission and optionally raise exception.
|
|
|
|
Raises:
|
|
- PermissionError if raise_exception=True and user lacks permission
|
|
"""
|
|
has_perm = RBACEngine.has_permission(user_context, required_permission)
|
|
|
|
if not has_perm and raise_exception:
|
|
raise PermissionError(
|
|
f"User {user_context.user_id} lacks permission: {required_permission.value}"
|
|
)
|
|
|
|
return has_perm
|
|
|
|
@staticmethod
|
|
def check_family_access(
|
|
user_context: UserContext,
|
|
requested_family_id: int,
|
|
raise_exception: bool = True,
|
|
) -> bool:
|
|
"""Verify user has access to requested family"""
|
|
has_access = requested_family_id in user_context.family_ids
|
|
|
|
if not has_access and raise_exception:
|
|
raise PermissionError(
|
|
f"User {user_context.user_id} cannot access family {requested_family_id}"
|
|
)
|
|
|
|
return has_access
|
|
|
|
@staticmethod
|
|
def check_resource_ownership(
|
|
user_context: UserContext,
|
|
owner_id: int,
|
|
raise_exception: bool = True,
|
|
) -> bool:
|
|
"""Check if user is owner of resource"""
|
|
is_owner = user_context.user_id == owner_id
|
|
|
|
if not is_owner and raise_exception:
|
|
raise PermissionError(
|
|
f"User {user_context.user_id} is not owner of resource (owner: {owner_id})"
|
|
)
|
|
|
|
return is_owner
|
|
|
|
|
|
# Policy definitions (for advanced use)
|
|
POLICIES = {
|
|
"transaction_approval_required": {
|
|
"conditions": ["amount > 500", "role != owner"],
|
|
"action": "require_approval"
|
|
},
|
|
"restrict_child_budget": {
|
|
"conditions": ["role == child"],
|
|
"action": "limit_to_100_per_day"
|
|
},
|
|
}
|