init commit
This commit is contained in:
228
app/security/rbac.py
Normal file
228
app/security/rbac.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
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"
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user