""" 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" }, }