Files
finance_bot/.history/app/security/rbac_20251210210308.py
2025-12-10 22:09:31 +09:00

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