93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from fastapi import Depends, Header, HTTPException, status
|
|
from fastapi.security import HTTPAuthorizationCredentials
|
|
|
|
from app.core.dependencies import bearer, get_current_user
|
|
from app.core.security import decode_access_token
|
|
from app.modules.sync import repository as sync_repo
|
|
from app.modules.sync.schemas import HubAuthContext, TenantContext
|
|
from app.modules.users.models import User
|
|
|
|
|
|
@dataclass
|
|
class HubPrincipal:
|
|
farm_hub_id: str
|
|
hub_site_id: str
|
|
enterprise_id: str
|
|
|
|
|
|
def get_hub_auth(authorization: str | None = Header(default=None)) -> HubPrincipal:
|
|
if not authorization or not authorization.startswith("Hub "):
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED")
|
|
token = authorization[4:].strip()
|
|
if ":" not in token:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED")
|
|
hub_site_id, api_key = token.split(":", 1)
|
|
hub = sync_repo.verify_hub_credential(hub_site_id.strip(), api_key.strip())
|
|
if not hub:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED")
|
|
return HubPrincipal(farm_hub_id=hub.id, hub_site_id=hub.hub_site_id, enterprise_id=hub.enterprise_id)
|
|
|
|
|
|
def get_tenant_context(
|
|
enterprise_id: str,
|
|
user: User = Depends(get_current_user),
|
|
credentials: HTTPAuthorizationCredentials | None = Depends(bearer),
|
|
) -> TenantContext:
|
|
if user.is_superuser:
|
|
return TenantContext(
|
|
enterprise_id=enterprise_id,
|
|
enterprise_role="admin",
|
|
user_id=user.id,
|
|
farm_hub_ids=None,
|
|
)
|
|
if credentials:
|
|
try:
|
|
payload = decode_access_token(credentials.credentials)
|
|
if payload.get("enterprise_id") == enterprise_id:
|
|
farm_ids = payload.get("farm_ids")
|
|
return TenantContext(
|
|
enterprise_id=enterprise_id,
|
|
enterprise_role=payload.get("enterprise_role", "viewer"), # type: ignore[arg-type]
|
|
user_id=user.id,
|
|
farm_hub_ids=farm_ids,
|
|
)
|
|
except Exception:
|
|
pass
|
|
member = sync_repo.get_member(user.id, enterprise_id)
|
|
if not member:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN")
|
|
farm_ids = None if member.role == "admin" else sync_repo.list_farm_access(user.id, enterprise_id)
|
|
return TenantContext(
|
|
enterprise_id=enterprise_id,
|
|
enterprise_role=member.role, # type: ignore[arg-type]
|
|
user_id=user.id,
|
|
farm_hub_ids=farm_ids,
|
|
)
|
|
|
|
|
|
def require_farm_access(
|
|
farm_hub_id: str,
|
|
enterprise_id: str,
|
|
tenant: TenantContext,
|
|
) -> None:
|
|
if tenant.enterprise_role == "admin" or tenant.farm_hub_ids is None:
|
|
return
|
|
if farm_hub_id not in (tenant.farm_hub_ids or []):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="FARM_ACCESS_FORBIDDEN")
|
|
|
|
|
|
def require_enterprise_admin(tenant: TenantContext = Depends(get_tenant_context)) -> TenantContext:
|
|
if tenant.enterprise_role != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ADMIN_ONLY")
|
|
return tenant
|
|
|
|
|
|
def require_enterprise_zootech(tenant: TenantContext = Depends(get_tenant_context)) -> TenantContext:
|
|
if tenant.enterprise_role not in ("admin", "zootech"):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ZOOTECH_ONLY")
|
|
return tenant
|