fix(workspace): authorize tenant members via Core membership
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a846b3e7f1
commit
d0c1d296b2
@ -1,10 +1,14 @@
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Request
|
||||
import httpx
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import get_current_user
|
||||
from app.core.security import STATE_ACCESS_TOKEN, get_current_user
|
||||
from shared.exceptions import ForbiddenError
|
||||
from shared.tenant import STATE_TENANT_ID
|
||||
|
||||
_ADMINS = {"platform_admin", "tenant_owner", "tenant_admin"}
|
||||
_EDITORS = {"tenant_editor"}
|
||||
_READ_ONLY = {"tenant_viewer"}
|
||||
|
||||
|
||||
def user_has_permission(user, permission: str) -> bool:
|
||||
@ -17,12 +21,68 @@ def user_has_permission(user, permission: str) -> bool:
|
||||
return len(parts) >= 3 and f"{parts[0]}.{parts[1]}.manage" in roles
|
||||
|
||||
|
||||
def _roles_allow(roles: set[str], permission: str) -> bool:
|
||||
if roles & _ADMINS or "workspace.*" in roles:
|
||||
return True
|
||||
if roles & _EDITORS:
|
||||
return True
|
||||
if roles & _READ_ONLY:
|
||||
return permission.endswith(".read") or permission.endswith(".enabled")
|
||||
if permission in roles:
|
||||
return True
|
||||
parts = permission.split(".")
|
||||
return len(parts) >= 3 and f"{parts[0]}.{parts[1]}.manage" in roles
|
||||
|
||||
|
||||
async def _core_membership_roles(request: Request) -> set[str]:
|
||||
cached = getattr(request.state, "workspace_core_roles", None)
|
||||
if cached is not None:
|
||||
return cached
|
||||
token = getattr(request.state, STATE_ACCESS_TOKEN, None)
|
||||
tenant_id = getattr(request.state, STATE_TENANT_ID, None)
|
||||
if not token or not tenant_id:
|
||||
request.state.workspace_core_roles = set()
|
||||
return set()
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get(
|
||||
f"{settings.core_service_url.rstrip('/')}/api/v1/me",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
except Exception:
|
||||
request.state.workspace_core_roles = set()
|
||||
return set()
|
||||
if response.status_code != 200:
|
||||
request.state.workspace_core_roles = set()
|
||||
return set()
|
||||
data = response.json()
|
||||
roles: set[str] = set()
|
||||
if data.get("platform_role"):
|
||||
roles.add(str(data["platform_role"]))
|
||||
for membership in data.get("memberships") or []:
|
||||
if str(membership.get("tenant_id")) != str(tenant_id):
|
||||
continue
|
||||
if str(membership.get("status") or "active") not in {"active", "ACTIVE"}:
|
||||
continue
|
||||
if membership.get("role"):
|
||||
roles.add(str(membership["role"]))
|
||||
if membership.get("is_owner"):
|
||||
roles.add("tenant_owner")
|
||||
request.state.workspace_core_roles = roles
|
||||
return roles
|
||||
|
||||
|
||||
def require_permissions(*permissions: str):
|
||||
async def dependency(user=Depends(get_current_user)):
|
||||
if not settings.auth_required or any(user_has_permission(user, permission) for permission in permissions):
|
||||
async def dependency(request: Request, user=Depends(get_current_user)):
|
||||
if not settings.auth_required:
|
||||
return user
|
||||
if any(user_has_permission(user, permission) for permission in permissions):
|
||||
return user
|
||||
core_roles = await _core_membership_roles(request)
|
||||
if any(_roles_allow(core_roles, permission) for permission in permissions):
|
||||
return user
|
||||
raise ForbiddenError(
|
||||
"Permission denied",
|
||||
"این عملیات برای نقش فعلی فضای کاری مجاز نیست.",
|
||||
error_code="permission_denied",
|
||||
details={"required": list(permissions)},
|
||||
)
|
||||
@ -30,10 +90,13 @@ def require_permissions(*permissions: str):
|
||||
return dependency
|
||||
|
||||
|
||||
async def require_workspace_access(user=Depends(get_current_user)):
|
||||
async def require_workspace_access(request: Request, user=Depends(get_current_user)):
|
||||
if not settings.auth_required:
|
||||
return user
|
||||
roles = set(user.roles)
|
||||
if roles & _ADMINS or any(role.startswith("workspace.") for role in roles):
|
||||
return user
|
||||
raise ForbiddenError("Permission denied", error_code="permission_denied")
|
||||
core_roles = await _core_membership_roles(request)
|
||||
if core_roles & (_ADMINS | _EDITORS | _READ_ONLY):
|
||||
return user
|
||||
raise ForbiddenError("دسترسی به فضای کاری برای این سازمان مجاز نیست.", error_code="permission_denied")
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from app.core.config import settings
|
||||
@ -9,6 +9,7 @@ from shared.exceptions import UnauthorizedError
|
||||
from shared.security import CurrentUser
|
||||
|
||||
bearer = HTTPBearer(auto_error=False)
|
||||
STATE_ACCESS_TOKEN = "workspace_access_token"
|
||||
|
||||
|
||||
@lru_cache
|
||||
@ -26,9 +27,13 @@ def get_jwt_validator() -> JWTValidator:
|
||||
)
|
||||
|
||||
|
||||
async def get_current_user(credentials: HTTPAuthorizationCredentials | None = Depends(bearer)):
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(bearer),
|
||||
):
|
||||
if not settings.auth_required:
|
||||
return CurrentUser(user_id="test-user", username="test", roles=["tenant_admin"])
|
||||
if credentials is None or not credentials.credentials:
|
||||
raise UnauthorizedError("توکن احراز هویت ارائه نشده است")
|
||||
setattr(request.state, STATE_ACCESS_TOKEN, credentials.credentials)
|
||||
return await get_jwt_validator().validate(credentials.credentials)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user