Spaces:
Running
Running
Enforce workspace admin role on collaboration mutations
Browse files- app/projects/api.py +21 -5
app/projects/api.py
CHANGED
|
@@ -39,7 +39,8 @@ router = APIRouter(prefix="/v1/projects", tags=["projects"])
|
|
| 39 |
|
| 40 |
@router.post("/workspace/teams", response_model=TeamResponse)
|
| 41 |
async def create_team(request: Request, payload: TeamBase) -> TeamResponse:
|
| 42 |
-
workspace_id,
|
|
|
|
| 43 |
return await request.app.state.container.collaboration.create_team(workspace_id, payload.name)
|
| 44 |
|
| 45 |
@router.get("/workspace/teams", response_model=list[TeamResponse])
|
|
@@ -71,12 +72,14 @@ async def invite_member(request: Request, payload: InvitationCreate) -> Invitati
|
|
| 71 |
|
| 72 |
@router.patch("/workspace/teams/{team_id}", response_model=TeamResponse)
|
| 73 |
async def update_team(request: Request, team_id: str, payload: TeamBase) -> TeamResponse:
|
| 74 |
-
workspace_id,
|
|
|
|
| 75 |
return await request.app.state.container.collaboration.update_team(workspace_id, team_id, payload.name)
|
| 76 |
|
| 77 |
@router.delete("/workspace/teams/{team_id}", status_code=status.HTTP_204_NO_CONTENT)
|
| 78 |
async def archive_team(request: Request, team_id: str) -> Response:
|
| 79 |
-
workspace_id,
|
|
|
|
| 80 |
await request.app.state.container.collaboration.archive_team(workspace_id, team_id)
|
| 81 |
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
| 82 |
|
|
@@ -128,6 +131,17 @@ async def update_member_role(request: Request, user_id: str, new_role: str) -> M
|
|
| 128 |
return MemberResponse(id=membership.id, workspace_id=membership.workspace_id, user_id=membership.user_id, role=membership.role, created_at=membership.created_at.isoformat())
|
| 129 |
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
def _identity(request: Request) -> tuple[str, str, str, str]:
|
| 132 |
context = request.state.auth
|
| 133 |
if not context.workspace_id or not context.user_id:
|
|
@@ -338,7 +352,8 @@ async def add_project_collaborator(
|
|
| 338 |
user_id: str,
|
| 339 |
role: str
|
| 340 |
) -> MemberResponse:
|
| 341 |
-
workspace_id,
|
|
|
|
| 342 |
return await request.app.state.container.collaboration.add_project_collaborator(
|
| 343 |
workspace_id, str(project_id), user_id, role
|
| 344 |
)
|
|
@@ -349,7 +364,8 @@ async def remove_project_collaborator(
|
|
| 349 |
project_id: UUID,
|
| 350 |
user_id: str
|
| 351 |
) -> Response:
|
| 352 |
-
workspace_id,
|
|
|
|
| 353 |
await request.app.state.container.collaboration.remove_project_collaborator(workspace_id, str(project_id), user_id)
|
| 354 |
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
| 355 |
|
|
|
|
| 39 |
|
| 40 |
@router.post("/workspace/teams", response_model=TeamResponse)
|
| 41 |
async def create_team(request: Request, payload: TeamBase) -> TeamResponse:
|
| 42 |
+
workspace_id, user_id, _, _ = _identity(request)
|
| 43 |
+
await _require_workspace_admin(request, workspace_id, user_id)
|
| 44 |
return await request.app.state.container.collaboration.create_team(workspace_id, payload.name)
|
| 45 |
|
| 46 |
@router.get("/workspace/teams", response_model=list[TeamResponse])
|
|
|
|
| 72 |
|
| 73 |
@router.patch("/workspace/teams/{team_id}", response_model=TeamResponse)
|
| 74 |
async def update_team(request: Request, team_id: str, payload: TeamBase) -> TeamResponse:
|
| 75 |
+
workspace_id, user_id, _, _ = _identity(request)
|
| 76 |
+
await _require_workspace_admin(request, workspace_id, user_id)
|
| 77 |
return await request.app.state.container.collaboration.update_team(workspace_id, team_id, payload.name)
|
| 78 |
|
| 79 |
@router.delete("/workspace/teams/{team_id}", status_code=status.HTTP_204_NO_CONTENT)
|
| 80 |
async def archive_team(request: Request, team_id: str) -> Response:
|
| 81 |
+
workspace_id, user_id, _, _ = _identity(request)
|
| 82 |
+
await _require_workspace_admin(request, workspace_id, user_id)
|
| 83 |
await request.app.state.container.collaboration.archive_team(workspace_id, team_id)
|
| 84 |
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
| 85 |
|
|
|
|
| 131 |
return MemberResponse(id=membership.id, workspace_id=membership.workspace_id, user_id=membership.user_id, role=membership.role, created_at=membership.created_at.isoformat())
|
| 132 |
|
| 133 |
|
| 134 |
+
async def _require_workspace_admin(request: Request, workspace_id: str, user_id: str) -> None:
|
| 135 |
+
"""Authorize collaboration mutations against the resolved active membership."""
|
| 136 |
+
membership = await request.app.state.container.collaboration.get_membership(workspace_id, user_id)
|
| 137 |
+
if (
|
| 138 |
+
membership is None
|
| 139 |
+
or membership.status != "active"
|
| 140 |
+
or membership.role not in {"owner", "admin"}
|
| 141 |
+
):
|
| 142 |
+
raise ForbiddenError
|
| 143 |
+
|
| 144 |
+
|
| 145 |
def _identity(request: Request) -> tuple[str, str, str, str]:
|
| 146 |
context = request.state.auth
|
| 147 |
if not context.workspace_id or not context.user_id:
|
|
|
|
| 352 |
user_id: str,
|
| 353 |
role: str
|
| 354 |
) -> MemberResponse:
|
| 355 |
+
workspace_id, actor_user_id, _, _ = _identity(request)
|
| 356 |
+
await _require_workspace_admin(request, workspace_id, actor_user_id)
|
| 357 |
return await request.app.state.container.collaboration.add_project_collaborator(
|
| 358 |
workspace_id, str(project_id), user_id, role
|
| 359 |
)
|
|
|
|
| 364 |
project_id: UUID,
|
| 365 |
user_id: str
|
| 366 |
) -> Response:
|
| 367 |
+
workspace_id, actor_user_id, _, _ = _identity(request)
|
| 368 |
+
await _require_workspace_admin(request, workspace_id, actor_user_id)
|
| 369 |
await request.app.state.container.collaboration.remove_project_collaborator(workspace_id, str(project_id), user_id)
|
| 370 |
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
| 371 |
|