progress in permisssion
This commit is contained in:
parent
c1da9cd0bd
commit
31defb7eff
|
|
@ -0,0 +1,63 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlalchemy import select, and_
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from adapters.db.models.business_permission import BusinessPermission
|
||||||
|
from adapters.db.repositories.base_repo import BaseRepository
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessPermissionRepository(BaseRepository[BusinessPermission]):
|
||||||
|
def __init__(self, db: Session) -> None:
|
||||||
|
super().__init__(db, BusinessPermission)
|
||||||
|
|
||||||
|
def get_by_user_and_business(self, user_id: int, business_id: int) -> Optional[BusinessPermission]:
|
||||||
|
"""دریافت دسترسیهای کاربر برای کسب و کار خاص"""
|
||||||
|
stmt = select(BusinessPermission).where(
|
||||||
|
and_(
|
||||||
|
BusinessPermission.user_id == user_id,
|
||||||
|
BusinessPermission.business_id == business_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self.db.execute(stmt).scalars().first()
|
||||||
|
|
||||||
|
def create_or_update(self, user_id: int, business_id: int, permissions: dict) -> BusinessPermission:
|
||||||
|
"""ایجاد یا بهروزرسانی دسترسیهای کاربر برای کسب و کار"""
|
||||||
|
existing = self.get_by_user_and_business(user_id, business_id)
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
existing.business_permissions = permissions
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(existing)
|
||||||
|
return existing
|
||||||
|
else:
|
||||||
|
new_permission = BusinessPermission(
|
||||||
|
user_id=user_id,
|
||||||
|
business_id=business_id,
|
||||||
|
business_permissions=permissions
|
||||||
|
)
|
||||||
|
self.db.add(new_permission)
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(new_permission)
|
||||||
|
return new_permission
|
||||||
|
|
||||||
|
def delete_by_user_and_business(self, user_id: int, business_id: int) -> bool:
|
||||||
|
"""حذف دسترسیهای کاربر برای کسب و کار"""
|
||||||
|
existing = self.get_by_user_and_business(user_id, business_id)
|
||||||
|
if existing:
|
||||||
|
self.db.delete(existing)
|
||||||
|
self.db.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_user_businesses(self, user_id: int) -> list[BusinessPermission]:
|
||||||
|
"""دریافت تمام کسب و کارهایی که کاربر دسترسی دارد"""
|
||||||
|
stmt = select(BusinessPermission).where(BusinessPermission.user_id == user_id)
|
||||||
|
return self.db.execute(stmt).scalars().all()
|
||||||
|
|
||||||
|
def get_business_users(self, business_id: int) -> list[BusinessPermission]:
|
||||||
|
"""دریافت تمام کاربرانی که دسترسی به کسب و کار دارند"""
|
||||||
|
stmt = select(BusinessPermission).where(BusinessPermission.business_id == business_id)
|
||||||
|
return self.db.execute(stmt).scalars().all()
|
||||||
|
|
@ -24,7 +24,8 @@ class AuthContext:
|
||||||
calendar_type: CalendarType = "jalali",
|
calendar_type: CalendarType = "jalali",
|
||||||
timezone: Optional[str] = None,
|
timezone: Optional[str] = None,
|
||||||
business_id: Optional[int] = None,
|
business_id: Optional[int] = None,
|
||||||
fiscal_year_id: Optional[int] = None
|
fiscal_year_id: Optional[int] = None,
|
||||||
|
db: Optional[Session] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.user = user
|
self.user = user
|
||||||
self.api_key_id = api_key_id
|
self.api_key_id = api_key_id
|
||||||
|
|
@ -33,6 +34,13 @@ class AuthContext:
|
||||||
self.timezone = timezone
|
self.timezone = timezone
|
||||||
self.business_id = business_id
|
self.business_id = business_id
|
||||||
self.fiscal_year_id = fiscal_year_id
|
self.fiscal_year_id = fiscal_year_id
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
# دسترسیهای اپلیکیشن
|
||||||
|
self.app_permissions = user.app_permissions or {}
|
||||||
|
|
||||||
|
# دسترسیهای کسب و کار (در صورت وجود business_id)
|
||||||
|
self.business_permissions = self._get_business_permissions() if business_id and db else {}
|
||||||
|
|
||||||
# ایجاد translator برای زبان تشخیص داده شده
|
# ایجاد translator برای زبان تشخیص داده شده
|
||||||
self._translator = Translator(language)
|
self._translator = Translator(language)
|
||||||
|
|
@ -71,6 +79,138 @@ class AuthContext:
|
||||||
"""بررسی فعال بودن کاربر"""
|
"""بررسی فعال بودن کاربر"""
|
||||||
return self.user.is_active
|
return self.user.is_active
|
||||||
|
|
||||||
|
def _get_business_permissions(self) -> dict:
|
||||||
|
"""دریافت دسترسیهای کسب و کار از دیتابیس"""
|
||||||
|
if not self.business_id or not self.db:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
from adapters.db.repositories.business_permission_repo import BusinessPermissionRepository
|
||||||
|
repo = BusinessPermissionRepository(self.db)
|
||||||
|
permission_obj = repo.get_by_user_and_business(self.user.id, self.business_id)
|
||||||
|
|
||||||
|
if permission_obj and permission_obj.business_permissions:
|
||||||
|
return permission_obj.business_permissions
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# بررسی دسترسیهای اپلیکیشن
|
||||||
|
def has_app_permission(self, permission: str) -> bool:
|
||||||
|
"""بررسی دسترسی در سطح اپلیکیشن"""
|
||||||
|
# SuperAdmin تمام دسترسیهای اپلیکیشن را دارد
|
||||||
|
if self.app_permissions.get("superadmin", False):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.app_permissions.get(permission, False)
|
||||||
|
|
||||||
|
def is_superadmin(self) -> bool:
|
||||||
|
"""بررسی superadmin بودن"""
|
||||||
|
return self.has_app_permission("superadmin")
|
||||||
|
|
||||||
|
def can_manage_users(self) -> bool:
|
||||||
|
"""بررسی دسترسی مدیریت کاربران در سطح اپلیکیشن"""
|
||||||
|
return self.has_app_permission("user_management")
|
||||||
|
|
||||||
|
def can_manage_businesses(self) -> bool:
|
||||||
|
"""بررسی دسترسی مدیریت کسب و کارها"""
|
||||||
|
return self.has_app_permission("business_management")
|
||||||
|
|
||||||
|
def can_access_system_settings(self) -> bool:
|
||||||
|
"""بررسی دسترسی به تنظیمات سیستم"""
|
||||||
|
return self.has_app_permission("system_settings")
|
||||||
|
|
||||||
|
def is_business_owner(self) -> bool:
|
||||||
|
"""بررسی اینکه آیا کاربر مالک کسب و کار است یا نه"""
|
||||||
|
if not self.business_id or not self.db:
|
||||||
|
return False
|
||||||
|
|
||||||
|
from adapters.db.models.business import Business
|
||||||
|
business = self.db.get(Business, self.business_id)
|
||||||
|
return business and business.owner_id == self.user.id
|
||||||
|
|
||||||
|
# بررسی دسترسیهای کسب و کار
|
||||||
|
def has_business_permission(self, section: str, action: str) -> bool:
|
||||||
|
"""بررسی دسترسی در سطح کسب و کار"""
|
||||||
|
if not self.business_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# SuperAdmin تمام دسترسیها را دارد
|
||||||
|
if self.is_superadmin():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# مالک کسب و کار تمام دسترسیها را دارد
|
||||||
|
if self.is_business_owner():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# بررسی دسترسیهای عادی
|
||||||
|
if not self.business_permissions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# بررسی وجود بخش
|
||||||
|
if section not in self.business_permissions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
section_perms = self.business_permissions[section]
|
||||||
|
|
||||||
|
# اگر بخش خالی است، فقط خواندن
|
||||||
|
if not section_perms:
|
||||||
|
return action == "read"
|
||||||
|
|
||||||
|
# بررسی دسترسی خاص
|
||||||
|
return section_perms.get(action, False)
|
||||||
|
|
||||||
|
def can_read_section(self, section: str) -> bool:
|
||||||
|
"""بررسی دسترسی خواندن بخش در کسب و کار"""
|
||||||
|
if not self.business_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# SuperAdmin و مالک کسب و کار دسترسی کامل دارند
|
||||||
|
if self.is_superadmin() or self.is_business_owner():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return section in self.business_permissions
|
||||||
|
|
||||||
|
def can_write_section(self, section: str) -> bool:
|
||||||
|
"""بررسی دسترسی نوشتن در بخش"""
|
||||||
|
return self.has_business_permission(section, "write")
|
||||||
|
|
||||||
|
def can_delete_section(self, section: str) -> bool:
|
||||||
|
"""بررسی دسترسی حذف در بخش"""
|
||||||
|
return self.has_business_permission(section, "delete")
|
||||||
|
|
||||||
|
def can_approve_section(self, section: str) -> bool:
|
||||||
|
"""بررسی دسترسی تأیید در بخش"""
|
||||||
|
return self.has_business_permission(section, "approve")
|
||||||
|
|
||||||
|
def can_export_section(self, section: str) -> bool:
|
||||||
|
"""بررسی دسترسی صادرات در بخش"""
|
||||||
|
return self.has_business_permission(section, "export")
|
||||||
|
|
||||||
|
def can_manage_business_users(self) -> bool:
|
||||||
|
"""بررسی دسترسی مدیریت کاربران کسب و کار"""
|
||||||
|
return self.has_business_permission("settings", "manage_users")
|
||||||
|
|
||||||
|
# ترکیب دسترسیها
|
||||||
|
def has_any_permission(self, section: str, action: str) -> bool:
|
||||||
|
"""بررسی دسترسی در هر دو سطح"""
|
||||||
|
# SuperAdmin دسترسی کامل دارد
|
||||||
|
if self.is_superadmin():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# بررسی دسترسی کسب و کار
|
||||||
|
return self.has_business_permission(section, action)
|
||||||
|
|
||||||
|
def can_access_business(self, business_id: int) -> bool:
|
||||||
|
"""بررسی دسترسی به کسب و کار خاص"""
|
||||||
|
# SuperAdmin دسترسی به همه کسب و کارها دارد
|
||||||
|
if self.is_superadmin():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# اگر مالک کسب و کار است، دسترسی دارد
|
||||||
|
if self.is_business_owner() and business_id == self.business_id:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# بررسی دسترسیهای کسب و کار
|
||||||
|
return business_id == self.business_id
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"""تبدیل به dictionary برای استفاده در API"""
|
"""تبدیل به dictionary برای استفاده در API"""
|
||||||
return {
|
return {
|
||||||
|
|
@ -82,10 +222,17 @@ class AuthContext:
|
||||||
"mobile": self.user.mobile,
|
"mobile": self.user.mobile,
|
||||||
"referral_code": getattr(self.user, "referral_code", None),
|
"referral_code": getattr(self.user, "referral_code", None),
|
||||||
"is_active": self.user.is_active,
|
"is_active": self.user.is_active,
|
||||||
|
"app_permissions": self.app_permissions,
|
||||||
"created_at": self.user.created_at.isoformat() if self.user.created_at else None,
|
"created_at": self.user.created_at.isoformat() if self.user.created_at else None,
|
||||||
"updated_at": self.user.updated_at.isoformat() if self.user.updated_at else None,
|
"updated_at": self.user.updated_at.isoformat() if self.user.updated_at else None,
|
||||||
},
|
},
|
||||||
"api_key_id": self.api_key_id,
|
"api_key_id": self.api_key_id,
|
||||||
|
"permissions": {
|
||||||
|
"app_permissions": self.app_permissions,
|
||||||
|
"business_permissions": self.business_permissions,
|
||||||
|
"is_superadmin": self.is_superadmin(),
|
||||||
|
"is_business_owner": self.is_business_owner(),
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"language": self.language,
|
"language": self.language,
|
||||||
"calendar_type": self.calendar_type,
|
"calendar_type": self.calendar_type,
|
||||||
|
|
@ -139,7 +286,8 @@ def get_current_user(
|
||||||
calendar_type=calendar_type,
|
calendar_type=calendar_type,
|
||||||
timezone=timezone,
|
timezone=timezone,
|
||||||
business_id=business_id,
|
business_id=business_id,
|
||||||
fiscal_year_id=fiscal_year_id
|
fiscal_year_id=fiscal_year_id,
|
||||||
|
db=db
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
130
hesabixAPI/app/core/permissions.py
Normal file
130
hesabixAPI/app/core/permissions.py
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from typing import Callable, Any
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from app.core.auth_dependency import get_current_user, AuthContext
|
||||||
|
from app.core.responses import ApiError
|
||||||
|
|
||||||
|
|
||||||
|
def require_app_permission(permission: str):
|
||||||
|
"""Decorator برای بررسی دسترسی در سطح اپلیکیشن"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
ctx = get_current_user()
|
||||||
|
if not ctx.has_app_permission(permission):
|
||||||
|
raise ApiError("FORBIDDEN", f"Missing app permission: {permission}", http_status=403)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def require_business_permission(section: str, action: str):
|
||||||
|
"""Decorator برای بررسی دسترسی در سطح کسب و کار"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
ctx = get_current_user()
|
||||||
|
if not ctx.has_business_permission(section, action):
|
||||||
|
raise ApiError("FORBIDDEN", f"Missing business permission: {section}.{action}", http_status=403)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def require_any_permission(section: str, action: str):
|
||||||
|
"""Decorator برای بررسی دسترسی در هر دو سطح (app یا business)"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
ctx = get_current_user()
|
||||||
|
if not ctx.has_any_permission(section, action):
|
||||||
|
raise ApiError("FORBIDDEN", f"Missing permission: {section}.{action}", http_status=403)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def require_superadmin():
|
||||||
|
"""Decorator برای بررسی superadmin بودن"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
ctx = get_current_user()
|
||||||
|
if not ctx.is_superadmin():
|
||||||
|
raise ApiError("FORBIDDEN", "Superadmin access required", http_status=403)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def require_business_access(business_id_param: str = "business_id"):
|
||||||
|
"""Decorator برای بررسی دسترسی به کسب و کار خاص"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
ctx = get_current_user()
|
||||||
|
business_id = kwargs.get(business_id_param)
|
||||||
|
if business_id and not ctx.can_access_business(business_id):
|
||||||
|
raise ApiError("FORBIDDEN", f"No access to business {business_id}", http_status=403)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
# Decorator های ترکیبی برای استفاده آسان
|
||||||
|
def require_sales_write():
|
||||||
|
"""دسترسی نوشتن در بخش فروش"""
|
||||||
|
return require_any_permission("sales", "write")
|
||||||
|
|
||||||
|
|
||||||
|
def require_sales_delete():
|
||||||
|
"""دسترسی حذف در بخش فروش"""
|
||||||
|
return require_any_permission("sales", "delete")
|
||||||
|
|
||||||
|
|
||||||
|
def require_sales_approve():
|
||||||
|
"""دسترسی تأیید در بخش فروش"""
|
||||||
|
return require_any_permission("sales", "approve")
|
||||||
|
|
||||||
|
|
||||||
|
def require_purchases_write():
|
||||||
|
"""دسترسی نوشتن در بخش خرید"""
|
||||||
|
return require_any_permission("purchases", "write")
|
||||||
|
|
||||||
|
|
||||||
|
def require_accounting_write():
|
||||||
|
"""دسترسی نوشتن در بخش حسابداری"""
|
||||||
|
return require_any_permission("accounting", "write")
|
||||||
|
|
||||||
|
|
||||||
|
def require_inventory_write():
|
||||||
|
"""دسترسی نوشتن در بخش موجودی"""
|
||||||
|
return require_any_permission("inventory", "write")
|
||||||
|
|
||||||
|
|
||||||
|
def require_reports_export():
|
||||||
|
"""دسترسی صادرات گزارش"""
|
||||||
|
return require_any_permission("reports", "export")
|
||||||
|
|
||||||
|
|
||||||
|
def require_settings_manage_users():
|
||||||
|
"""دسترسی مدیریت کاربران کسب و کار"""
|
||||||
|
return require_any_permission("settings", "manage_users")
|
||||||
|
|
||||||
|
|
||||||
|
def require_user_management():
|
||||||
|
"""دسترسی مدیریت کاربران در سطح اپلیکیشن"""
|
||||||
|
return require_app_permission("user_management")
|
||||||
|
|
||||||
|
|
||||||
|
def require_business_management():
|
||||||
|
"""دسترسی مدیریت کسب و کارها"""
|
||||||
|
return require_app_permission("business_management")
|
||||||
|
|
||||||
|
|
||||||
|
def require_system_settings():
|
||||||
|
"""دسترسی تنظیمات سیستم"""
|
||||||
|
return require_app_permission("system_settings")
|
||||||
37
hesabixAPI/app/core/permissions/__init__.py
Normal file
37
hesabixAPI/app/core/permissions/__init__.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from .permissions import (
|
||||||
|
require_app_permission,
|
||||||
|
require_business_permission,
|
||||||
|
require_any_permission,
|
||||||
|
require_superadmin,
|
||||||
|
require_business_access,
|
||||||
|
require_sales_write,
|
||||||
|
require_sales_delete,
|
||||||
|
require_sales_approve,
|
||||||
|
require_purchases_write,
|
||||||
|
require_accounting_write,
|
||||||
|
require_inventory_write,
|
||||||
|
require_reports_export,
|
||||||
|
require_settings_manage_users,
|
||||||
|
require_user_management,
|
||||||
|
require_business_management,
|
||||||
|
require_system_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"require_app_permission",
|
||||||
|
"require_business_permission",
|
||||||
|
"require_any_permission",
|
||||||
|
"require_superadmin",
|
||||||
|
"require_business_access",
|
||||||
|
"require_sales_write",
|
||||||
|
"require_sales_delete",
|
||||||
|
"require_sales_approve",
|
||||||
|
"require_purchases_write",
|
||||||
|
"require_accounting_write",
|
||||||
|
"require_inventory_write",
|
||||||
|
"require_reports_export",
|
||||||
|
"require_settings_manage_users",
|
||||||
|
"require_user_management",
|
||||||
|
"require_business_management",
|
||||||
|
"require_system_settings",
|
||||||
|
]
|
||||||
259
hesabixAPI/docs/PERMISSIONS_SYSTEM.md
Normal file
259
hesabixAPI/docs/PERMISSIONS_SYSTEM.md
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
# سیستم دسترسی دو سطحی
|
||||||
|
|
||||||
|
این سیستم دسترسیها را در دو سطح جداگانه مدیریت میکند:
|
||||||
|
|
||||||
|
## 1. دسترسیهای اپلیکیشن (App-Level Permissions)
|
||||||
|
|
||||||
|
در `users.app_permissions` ذخیره میشود و شامل:
|
||||||
|
|
||||||
|
### دسترسیهای موجود:
|
||||||
|
- `superadmin`: دسترسی کامل به سیستم
|
||||||
|
- `user_management`: مدیریت کاربران در سطح اپلیکیشن
|
||||||
|
- `business_management`: مدیریت کسب و کارها
|
||||||
|
- `system_settings`: دسترسی به تنظیمات سیستم
|
||||||
|
|
||||||
|
### مثال JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"superadmin": true,
|
||||||
|
"user_management": true,
|
||||||
|
"business_management": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. دسترسیهای کسب و کار (Business-Level Permissions)
|
||||||
|
|
||||||
|
در `business_permissions.business_permissions` ذخیره میشود و شامل:
|
||||||
|
|
||||||
|
### بخشهای موجود:
|
||||||
|
- `sales`: فروش
|
||||||
|
- `purchases`: خرید
|
||||||
|
- `accounting`: حسابداری
|
||||||
|
- `inventory`: موجودی
|
||||||
|
- `reports`: گزارشها
|
||||||
|
- `settings`: تنظیمات کسب و کار
|
||||||
|
- `marketing`: بازاریابی
|
||||||
|
|
||||||
|
### عملیاتهای موجود:
|
||||||
|
- `read`: خواندن
|
||||||
|
- `write`: نوشتن
|
||||||
|
- `delete`: حذف
|
||||||
|
- `approve`: تأیید
|
||||||
|
- `export`: صادرات
|
||||||
|
- `manage_users`: مدیریت کاربران (فقط در settings)
|
||||||
|
|
||||||
|
### مثال JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sales": {
|
||||||
|
"write": true,
|
||||||
|
"delete": true,
|
||||||
|
"approve": true
|
||||||
|
},
|
||||||
|
"accounting": {
|
||||||
|
"write": true
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"export": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"manage_users": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## نحوه استفاده
|
||||||
|
|
||||||
|
### 1. بررسی دسترسی در AuthContext:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# دسترسیهای اپلیکیشن
|
||||||
|
ctx.has_app_permission("superadmin")
|
||||||
|
ctx.is_superadmin()
|
||||||
|
ctx.can_manage_users()
|
||||||
|
ctx.can_manage_businesses()
|
||||||
|
|
||||||
|
# دسترسیهای کسب و کار
|
||||||
|
ctx.has_business_permission("sales", "write")
|
||||||
|
ctx.can_read_section("sales")
|
||||||
|
ctx.can_write_section("sales")
|
||||||
|
ctx.can_delete_section("sales")
|
||||||
|
ctx.can_approve_section("sales")
|
||||||
|
ctx.can_export_section("reports")
|
||||||
|
ctx.can_manage_business_users()
|
||||||
|
|
||||||
|
# ترکیب دسترسیها
|
||||||
|
ctx.has_any_permission("sales", "write") # app یا business
|
||||||
|
ctx.can_access_business(business_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. استفاده از Decorator ها:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.permissions import (
|
||||||
|
require_superadmin,
|
||||||
|
require_user_management,
|
||||||
|
require_sales_write,
|
||||||
|
require_business_access
|
||||||
|
)
|
||||||
|
|
||||||
|
# دسترسی اپلیکیشن
|
||||||
|
@require_superadmin()
|
||||||
|
def admin_function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@require_user_management()
|
||||||
|
def manage_users():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# دسترسی کسب و کار
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_sales_write()
|
||||||
|
def create_sale(business_id: int):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. بررسی دسترسی در API:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@router.post("/business/{business_id}/sales")
|
||||||
|
def create_sale(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
# بررسی دسترسی به کسب و کار
|
||||||
|
if not ctx.can_access_business(business_id):
|
||||||
|
raise ApiError("FORBIDDEN", "No access to this business")
|
||||||
|
|
||||||
|
# بررسی دسترسی نوشتن فروش
|
||||||
|
if not ctx.has_business_permission("sales", "write"):
|
||||||
|
raise ApiError("FORBIDDEN", "No permission to create sales")
|
||||||
|
|
||||||
|
# ایجاد فروش
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## قوانین دسترسی
|
||||||
|
|
||||||
|
### 1. SuperAdmin:
|
||||||
|
- **دسترسی خودکار**: تمام دسترسیهای اپلیکیشن را خودکار دارد
|
||||||
|
- **دسترسی کامل**: به تمام بخشهای سیستم دسترسی دارد
|
||||||
|
- **دسترسی کسب و کار**: میتواند به هر کسب و کاری دسترسی داشته باشد
|
||||||
|
- **تمام عملیات**: تمام عملیات را میتواند انجام دهد
|
||||||
|
|
||||||
|
### 2. مالک کسب و کار:
|
||||||
|
- **دسترسی اپلیکیشن**: فقط دسترسیهای مشخص شده در `app_permissions`
|
||||||
|
- **دسترسی خودکار کسب و کار**: تمام دسترسیهای کسب و کار خود را خودکار دارد
|
||||||
|
- **دسترسی کامل**: تمام عملیات در کسب و کار خود را میتواند انجام دهد
|
||||||
|
- **مدیریت کاربران**: میتواند کاربران کسب و کار خود را مدیریت کند
|
||||||
|
|
||||||
|
### 3. کاربران عادی:
|
||||||
|
- **دسترسی اپلیکیشن**: فقط دسترسیهای مشخص شده در `app_permissions`
|
||||||
|
- **دسترسی کسب و کار**: دسترسیهای مشخص شده در `business_permissions`
|
||||||
|
- **دسترسی محدود**: فقط به کسب و کارهای خود دسترسی دارند
|
||||||
|
- **قوانین بخش**: اگر بخش در دسترسیها وجود دارد اما خالی است، فقط خواندن مجاز است
|
||||||
|
|
||||||
|
### 3. ذخیرهسازی بهینه:
|
||||||
|
- فقط دسترسیهای موجود ذخیره میشود
|
||||||
|
- `false` یا `null` ذخیره نمیشود
|
||||||
|
- کاهش حجم داده و بهبود عملکرد
|
||||||
|
|
||||||
|
## مثالهای عملی
|
||||||
|
|
||||||
|
### کاربر مدیر فروش:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_permissions": {},
|
||||||
|
"business_permissions": {
|
||||||
|
"sales": {
|
||||||
|
"write": true,
|
||||||
|
"delete": true,
|
||||||
|
"approve": true
|
||||||
|
},
|
||||||
|
"inventory": {
|
||||||
|
"write": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### کاربر کارمند حسابداری:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_permissions": {},
|
||||||
|
"business_permissions": {
|
||||||
|
"accounting": {
|
||||||
|
"write": true
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"export": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SuperAdmin:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_permissions": {
|
||||||
|
"superadmin": true
|
||||||
|
},
|
||||||
|
"business_permissions": {} // دسترسی کامل به همه
|
||||||
|
}
|
||||||
|
|
||||||
|
// نتایج:
|
||||||
|
// - has_app_permission("user_management") → True (خودکار)
|
||||||
|
// - has_app_permission("business_management") → True (خودکار)
|
||||||
|
// - has_business_permission("sales", "write") → True (برای هر کسب و کار)
|
||||||
|
// - is_superadmin() → True
|
||||||
|
// - is_business_owner() → False (مگر اینکه مالک کسب و کار باشد)
|
||||||
|
```
|
||||||
|
|
||||||
|
### مالک کسب و کار:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_permissions": {},
|
||||||
|
"business_permissions": {
|
||||||
|
"sales": {"write": true, "delete": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// نتایج:
|
||||||
|
// - has_app_permission("user_management") → False
|
||||||
|
// - has_business_permission("sales", "write") → True (از JSON)
|
||||||
|
// - has_business_permission("accounting", "write") → True (خودکار - مالک)
|
||||||
|
// - has_business_permission("reports", "export") → True (خودکار - مالک)
|
||||||
|
// - is_business_owner() → True
|
||||||
|
```
|
||||||
|
|
||||||
|
## توسعه سیستم
|
||||||
|
|
||||||
|
### اضافه کردن بخش جدید:
|
||||||
|
```python
|
||||||
|
# فقط در business_permissions اضافه میشود
|
||||||
|
{
|
||||||
|
"new_section": {
|
||||||
|
"write": true,
|
||||||
|
"approve": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### اضافه کردن عملیات جدید:
|
||||||
|
```python
|
||||||
|
# در هر بخش قابل اضافه کردن
|
||||||
|
{
|
||||||
|
"sales": {
|
||||||
|
"write": true,
|
||||||
|
"new_action": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### اضافه کردن دسترسی اپلیکیشن جدید:
|
||||||
|
```python
|
||||||
|
# در app_permissions اضافه میشود
|
||||||
|
{
|
||||||
|
"new_app_permission": true
|
||||||
|
}
|
||||||
|
```
|
||||||
188
hesabixAPI/examples/permission_usage.py
Normal file
188
hesabixAPI/examples/permission_usage.py
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
"""
|
||||||
|
مثالهایی از استفاده از سیستم دسترسی دو سطحی
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.core.auth_dependency import get_current_user, AuthContext
|
||||||
|
from app.core.permissions import (
|
||||||
|
require_superadmin,
|
||||||
|
require_user_management,
|
||||||
|
require_business_management,
|
||||||
|
require_sales_write,
|
||||||
|
require_sales_delete,
|
||||||
|
require_accounting_write,
|
||||||
|
require_reports_export,
|
||||||
|
require_settings_manage_users,
|
||||||
|
require_any_permission,
|
||||||
|
require_business_access
|
||||||
|
)
|
||||||
|
from app.core.responses import ApiError
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 1: دسترسیهای اپلیکیشن
|
||||||
|
@router.get("/admin/users")
|
||||||
|
@require_user_management()
|
||||||
|
def list_users(ctx: AuthContext = Depends(get_current_user)):
|
||||||
|
"""لیست کاربران - نیاز به دسترسی مدیریت کاربران در سطح اپلیکیشن"""
|
||||||
|
return {"message": "User list", "user_id": ctx.get_user_id()}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/admin/businesses")
|
||||||
|
@require_business_management()
|
||||||
|
def list_businesses(ctx: AuthContext = Depends(get_current_user)):
|
||||||
|
"""لیست کسب و کارها - نیاز به دسترسی مدیریت کسب و کارها"""
|
||||||
|
return {"message": "Business list"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/admin/system-settings")
|
||||||
|
@require_superadmin()
|
||||||
|
def get_system_settings(ctx: AuthContext = Depends(get_current_user)):
|
||||||
|
"""تنظیمات سیستم - فقط superadmin"""
|
||||||
|
return {"message": "System settings"}
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 2: دسترسیهای کسب و کار
|
||||||
|
@router.post("/business/{business_id}/sales")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_sales_write()
|
||||||
|
def create_sale(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""ایجاد فروش - نیاز به دسترسی نوشتن در بخش فروش"""
|
||||||
|
return {"message": f"Sale created for business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/business/{business_id}/sales/{sale_id}")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_sales_delete()
|
||||||
|
def delete_sale(
|
||||||
|
business_id: int,
|
||||||
|
sale_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""حذف فروش - نیاز به دسترسی حذف در بخش فروش"""
|
||||||
|
return {"message": f"Sale {sale_id} deleted from business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/business/{business_id}/accounting/entries")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_accounting_write()
|
||||||
|
def create_accounting_entry(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""ایجاد سند حسابداری - نیاز به دسترسی نوشتن در بخش حسابداری"""
|
||||||
|
return {"message": f"Accounting entry created for business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/business/{business_id}/reports/export")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_reports_export()
|
||||||
|
def export_reports(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""صادرات گزارش - نیاز به دسترسی صادرات گزارش"""
|
||||||
|
return {"message": f"Reports exported for business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/business/{business_id}/users")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
@require_settings_manage_users()
|
||||||
|
def add_business_user(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""اضافه کردن کاربر به کسب و کار - نیاز به دسترسی مدیریت کاربران کسب و کار"""
|
||||||
|
return {"message": f"User added to business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 3: بررسی دسترسیهای ترکیبی
|
||||||
|
@router.get("/business/{business_id}/dashboard")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
def get_business_dashboard(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""داشبورد کسب و کار - بررسی دسترسیهای مختلف"""
|
||||||
|
dashboard_data = {
|
||||||
|
"business_id": business_id,
|
||||||
|
"user_permissions": {
|
||||||
|
"can_read_sales": ctx.can_read_section("sales"),
|
||||||
|
"can_write_sales": ctx.can_write_section("sales"),
|
||||||
|
"can_delete_sales": ctx.can_delete_section("sales"),
|
||||||
|
"can_read_accounting": ctx.can_read_section("accounting"),
|
||||||
|
"can_write_accounting": ctx.can_write_section("accounting"),
|
||||||
|
"can_export_reports": ctx.can_export_section("reports"),
|
||||||
|
"can_manage_users": ctx.can_manage_business_users(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboard_data
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 4: بررسی دسترسیهای پویا
|
||||||
|
@router.get("/business/{business_id}/permissions")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
def get_user_permissions(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""دریافت دسترسیهای کاربر برای کسب و کار"""
|
||||||
|
return {
|
||||||
|
"app_permissions": ctx.app_permissions,
|
||||||
|
"business_permissions": ctx.business_permissions,
|
||||||
|
"is_superadmin": ctx.is_superadmin(),
|
||||||
|
"is_business_owner": ctx.is_business_owner(),
|
||||||
|
"can_access_business": ctx.can_access_business(business_id),
|
||||||
|
"permissions_info": {
|
||||||
|
"has_automatic_app_permissions": ctx.is_superadmin(),
|
||||||
|
"has_automatic_business_permissions": ctx.is_business_owner(),
|
||||||
|
"effective_permissions": {
|
||||||
|
"can_manage_users": ctx.can_manage_users(),
|
||||||
|
"can_manage_businesses": ctx.can_manage_businesses(),
|
||||||
|
"can_write_sales": ctx.can_write_section("sales"),
|
||||||
|
"can_delete_sales": ctx.can_delete_section("sales"),
|
||||||
|
"can_approve_sales": ctx.can_approve_section("sales"),
|
||||||
|
"can_export_reports": ctx.can_export_section("reports"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 5: بررسی دسترسیهای پیچیده
|
||||||
|
@router.get("/business/{business_id}/sales/analytics")
|
||||||
|
@require_business_access("business_id")
|
||||||
|
def get_sales_analytics(
|
||||||
|
business_id: int,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""تحلیل فروش - نیاز به دسترسی خواندن فروش و صادرات گزارش"""
|
||||||
|
if not ctx.has_any_permission("sales", "read"):
|
||||||
|
raise ApiError("FORBIDDEN", "No permission to read sales data")
|
||||||
|
|
||||||
|
if not ctx.has_any_permission("reports", "export"):
|
||||||
|
raise ApiError("FORBIDDEN", "No permission to export analytics")
|
||||||
|
|
||||||
|
return {"message": f"Sales analytics for business {business_id}"}
|
||||||
|
|
||||||
|
|
||||||
|
# مثال 6: مدیریت دسترسیها (فقط superadmin)
|
||||||
|
@router.post("/admin/business/{business_id}/users/{user_id}/permissions")
|
||||||
|
@require_superadmin()
|
||||||
|
def update_user_business_permissions(
|
||||||
|
business_id: int,
|
||||||
|
user_id: int,
|
||||||
|
permissions: dict,
|
||||||
|
ctx: AuthContext = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""بهروزرسانی دسترسیهای کاربر در کسب و کار - فقط superadmin"""
|
||||||
|
# اینجا باید منطق بهروزرسانی دسترسیها پیادهسازی شود
|
||||||
|
return {
|
||||||
|
"message": f"Permissions updated for user {user_id} in business {business_id}",
|
||||||
|
"permissions": permissions
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ adapters/db/models/password_reset.py
|
||||||
adapters/db/models/user.py
|
adapters/db/models/user.py
|
||||||
adapters/db/repositories/api_key_repo.py
|
adapters/db/repositories/api_key_repo.py
|
||||||
adapters/db/repositories/base_repo.py
|
adapters/db/repositories/base_repo.py
|
||||||
|
adapters/db/repositories/business_permission_repo.py
|
||||||
adapters/db/repositories/business_repo.py
|
adapters/db/repositories/business_repo.py
|
||||||
adapters/db/repositories/password_reset_repo.py
|
adapters/db/repositories/password_reset_repo.py
|
||||||
adapters/db/repositories/user_repo.py
|
adapters/db/repositories/user_repo.py
|
||||||
|
|
@ -31,10 +32,12 @@ app/core/error_handlers.py
|
||||||
app/core/i18n.py
|
app/core/i18n.py
|
||||||
app/core/i18n_catalog.py
|
app/core/i18n_catalog.py
|
||||||
app/core/logging.py
|
app/core/logging.py
|
||||||
|
app/core/permissions.py
|
||||||
app/core/responses.py
|
app/core/responses.py
|
||||||
app/core/security.py
|
app/core/security.py
|
||||||
app/core/settings.py
|
app/core/settings.py
|
||||||
app/core/smart_normalizer.py
|
app/core/smart_normalizer.py
|
||||||
|
app/core/permissions/__init__.py
|
||||||
app/services/api_key_service.py
|
app/services/api_key_service.py
|
||||||
app/services/auth_service.py
|
app/services/auth_service.py
|
||||||
app/services/captcha_service.py
|
app/services/captcha_service.py
|
||||||
|
|
@ -58,4 +61,5 @@ migrations/versions/20250117_000007_create_business_permissions_table.py
|
||||||
migrations/versions/20250915_000001_init_auth_tables.py
|
migrations/versions/20250915_000001_init_auth_tables.py
|
||||||
migrations/versions/20250916_000002_add_referral_fields.py
|
migrations/versions/20250916_000002_add_referral_fields.py
|
||||||
tests/__init__.py
|
tests/__init__.py
|
||||||
tests/test_health.py
|
tests/test_health.py
|
||||||
|
tests/test_permissions.py
|
||||||
226
hesabixAPI/tests/test_permissions.py
Normal file
226
hesabixAPI/tests/test_permissions.py
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
"""
|
||||||
|
تستهای سیستم دسترسی دو سطحی
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import Mock
|
||||||
|
from app.core.auth_dependency import AuthContext
|
||||||
|
from adapters.db.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthContextPermissions:
|
||||||
|
"""تست کلاس AuthContext برای بررسی دسترسیها"""
|
||||||
|
|
||||||
|
def test_app_permissions(self):
|
||||||
|
"""تست دسترسیهای اپلیکیشن"""
|
||||||
|
# ایجاد کاربر با دسترسی superadmin
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {"superadmin": True}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1)
|
||||||
|
|
||||||
|
# تست دسترسیهای اپلیکیشن - SuperAdmin باید تمام دسترسیها را داشته باشد
|
||||||
|
assert ctx.has_app_permission("superadmin") == True
|
||||||
|
assert ctx.has_app_permission("user_management") == True # خودکار
|
||||||
|
assert ctx.has_app_permission("business_management") == True # خودکار
|
||||||
|
assert ctx.has_app_permission("system_settings") == True # خودکار
|
||||||
|
assert ctx.is_superadmin() == True
|
||||||
|
assert ctx.can_manage_users() == True
|
||||||
|
assert ctx.can_manage_businesses() == True
|
||||||
|
|
||||||
|
def test_app_permissions_normal_user(self):
|
||||||
|
"""تست دسترسیهای اپلیکیشن برای کاربر عادی"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {"user_management": True}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1)
|
||||||
|
|
||||||
|
# تست دسترسیهای اپلیکیشن
|
||||||
|
assert ctx.has_app_permission("superadmin") == False
|
||||||
|
assert ctx.has_app_permission("user_management") == True
|
||||||
|
assert ctx.has_app_permission("business_management") == False
|
||||||
|
assert ctx.is_superadmin() == False
|
||||||
|
assert ctx.can_manage_users() == True
|
||||||
|
assert ctx.can_manage_businesses() == False
|
||||||
|
|
||||||
|
def test_business_permissions(self):
|
||||||
|
"""تست دسترسیهای کسب و کار"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
|
||||||
|
# Mock دیتابیس
|
||||||
|
db = Mock()
|
||||||
|
business_permission_repo = Mock()
|
||||||
|
business_permission_repo.get_by_user_and_business.return_value = Mock(
|
||||||
|
business_permissions={
|
||||||
|
"sales": {"write": True, "delete": True},
|
||||||
|
"accounting": {"write": True}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx = AuthContext(
|
||||||
|
user=user,
|
||||||
|
api_key_id=1,
|
||||||
|
business_id=1,
|
||||||
|
db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock کردن repository
|
||||||
|
with pytest.MonkeyPatch().context() as m:
|
||||||
|
m.setattr("app.core.auth_dependency.BusinessPermissionRepository",
|
||||||
|
lambda db: business_permission_repo)
|
||||||
|
|
||||||
|
# تست دسترسیهای کسب و کار
|
||||||
|
assert ctx.has_business_permission("sales", "write") == True
|
||||||
|
assert ctx.has_business_permission("sales", "delete") == True
|
||||||
|
assert ctx.has_business_permission("sales", "approve") == False
|
||||||
|
assert ctx.has_business_permission("accounting", "write") == True
|
||||||
|
assert ctx.has_business_permission("purchases", "read") == False
|
||||||
|
assert ctx.can_read_section("sales") == True
|
||||||
|
assert ctx.can_write_section("sales") == True
|
||||||
|
assert ctx.can_delete_section("sales") == True
|
||||||
|
assert ctx.can_approve_section("sales") == False
|
||||||
|
|
||||||
|
def test_empty_business_permissions(self):
|
||||||
|
"""تست دسترسیهای خالی کسب و کار"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1)
|
||||||
|
ctx.business_permissions = {}
|
||||||
|
|
||||||
|
# اگر دسترسیها خالی باشد، فقط خواندن مجاز است
|
||||||
|
assert ctx.has_business_permission("sales", "read") == False
|
||||||
|
assert ctx.has_business_permission("sales", "write") == False
|
||||||
|
assert ctx.can_read_section("sales") == False
|
||||||
|
|
||||||
|
def test_section_with_empty_permissions(self):
|
||||||
|
"""تست بخش با دسترسیهای خالی"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1)
|
||||||
|
ctx.business_permissions = {
|
||||||
|
"sales": {}, # بخش خالی
|
||||||
|
"accounting": {"write": True}
|
||||||
|
}
|
||||||
|
|
||||||
|
# بخش خالی فقط خواندن مجاز است
|
||||||
|
assert ctx.has_business_permission("sales", "read") == True
|
||||||
|
assert ctx.has_business_permission("sales", "write") == False
|
||||||
|
assert ctx.has_business_permission("accounting", "write") == True
|
||||||
|
|
||||||
|
def test_superadmin_override(self):
|
||||||
|
"""تست override کردن دسترسیها توسط superadmin"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {"superadmin": True}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1)
|
||||||
|
ctx.business_permissions = {} # بدون دسترسی کسب و کار
|
||||||
|
|
||||||
|
# SuperAdmin باید دسترسی کامل داشته باشد
|
||||||
|
assert ctx.has_any_permission("sales", "write") == True
|
||||||
|
assert ctx.has_any_permission("accounting", "delete") == True
|
||||||
|
assert ctx.can_access_business(999) == True # هر کسب و کاری
|
||||||
|
|
||||||
|
def test_business_access_control(self):
|
||||||
|
"""تست کنترل دسترسی به کسب و کار"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1)
|
||||||
|
|
||||||
|
# فقط به کسب و کار خود دسترسی دارد
|
||||||
|
assert ctx.can_access_business(1) == True
|
||||||
|
assert ctx.can_access_business(2) == False
|
||||||
|
|
||||||
|
# SuperAdmin به همه دسترسی دارد
|
||||||
|
user.app_permissions = {"superadmin": True}
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1)
|
||||||
|
assert ctx.can_access_business(999) == True
|
||||||
|
|
||||||
|
def test_business_owner_permissions(self):
|
||||||
|
"""تست دسترسیهای مالک کسب و کار"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
user.id = 1
|
||||||
|
|
||||||
|
# Mock دیتابیس و کسب و کار
|
||||||
|
db = Mock()
|
||||||
|
business = Mock()
|
||||||
|
business.owner_id = 1 # کاربر مالک است
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1, db=db)
|
||||||
|
|
||||||
|
# Mock کردن Business model
|
||||||
|
with pytest.MonkeyPatch().context() as m:
|
||||||
|
m.setattr("app.core.auth_dependency.Business", Mock)
|
||||||
|
db.get.return_value = business
|
||||||
|
|
||||||
|
# مالک کسب و کار باید تمام دسترسیها را داشته باشد
|
||||||
|
assert ctx.is_business_owner() == True
|
||||||
|
assert ctx.has_business_permission("sales", "write") == True
|
||||||
|
assert ctx.has_business_permission("sales", "delete") == True
|
||||||
|
assert ctx.has_business_permission("accounting", "write") == True
|
||||||
|
assert ctx.has_business_permission("reports", "export") == True
|
||||||
|
assert ctx.can_read_section("sales") == True
|
||||||
|
assert ctx.can_write_section("sales") == True
|
||||||
|
assert ctx.can_delete_section("sales") == True
|
||||||
|
assert ctx.can_approve_section("sales") == True
|
||||||
|
|
||||||
|
def test_business_owner_override(self):
|
||||||
|
"""تست override کردن دسترسیها توسط مالک کسب و کار"""
|
||||||
|
user = Mock(spec=User)
|
||||||
|
user.app_permissions = {}
|
||||||
|
user.id = 1
|
||||||
|
|
||||||
|
# Mock دیتابیس و کسب و کار
|
||||||
|
db = Mock()
|
||||||
|
business = Mock()
|
||||||
|
business.owner_id = 1
|
||||||
|
|
||||||
|
ctx = AuthContext(user=user, api_key_id=1, business_id=1, db=db)
|
||||||
|
ctx.business_permissions = {} # بدون دسترسی کسب و کار
|
||||||
|
|
||||||
|
# Mock کردن Business model
|
||||||
|
with pytest.MonkeyPatch().context() as m:
|
||||||
|
m.setattr("app.core.auth_dependency.Business", Mock)
|
||||||
|
db.get.return_value = business
|
||||||
|
|
||||||
|
# مالک کسب و کار باید دسترسی کامل داشته باشد حتی بدون business_permissions
|
||||||
|
assert ctx.is_business_owner() == True
|
||||||
|
assert ctx.has_business_permission("sales", "write") == True
|
||||||
|
assert ctx.has_business_permission("accounting", "delete") == True
|
||||||
|
assert ctx.can_read_section("purchases") == True
|
||||||
|
assert ctx.can_write_section("inventory") == True
|
||||||
|
|
||||||
|
|
||||||
|
class TestPermissionDecorators:
|
||||||
|
"""تست decorator های دسترسی"""
|
||||||
|
|
||||||
|
def test_require_app_permission(self):
|
||||||
|
"""تست decorator دسترسی اپلیکیشن"""
|
||||||
|
from app.core.permissions import require_app_permission
|
||||||
|
|
||||||
|
@require_app_permission("user_management")
|
||||||
|
def test_function():
|
||||||
|
return "success"
|
||||||
|
|
||||||
|
# این تست نیاز به mock کردن get_current_user دارد
|
||||||
|
# که در محیط تست پیچیدهتر است
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_require_business_permission(self):
|
||||||
|
"""تست decorator دسترسی کسب و کار"""
|
||||||
|
from app.core.permissions import require_business_permission
|
||||||
|
|
||||||
|
@require_business_permission("sales", "write")
|
||||||
|
def test_function():
|
||||||
|
return "success"
|
||||||
|
|
||||||
|
# این تست نیاز به mock کردن get_current_user دارد
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__])
|
||||||
Loading…
Reference in a new issue