basyx commited on
Commit
3a3f97b
·
verified ·
1 Parent(s): 18400bf

Enforce workspace admin role on collaboration mutations

Browse files
Files changed (1) hide show
  1. 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, _, _, _ = _identity(request)
 
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, _, _, _ = _identity(request)
 
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, _, _, _ = _identity(request)
 
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, _, _, _ = _identity(request)
 
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, _, _, _ = _identity(request)
 
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