Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit 2b76d9d

Browse files
aliciamatsumotoAlicia Matsumoto
andauthored
(security) restrict permissions on task operations (#6198)
* add new perm * add new perm * account for other methods * conflict * revert package * remove debug * nits --------- Co-authored-by: Alicia Matsumoto <amats@netflix.com>
1 parent bb0f4be commit 2b76d9d

2 files changed

Lines changed: 77 additions & 11 deletions

File tree

src/dispatch/auth/permissions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
from abc import ABC, abstractmethod
3+
import json
34

45
from fastapi import HTTPException
56
from starlette.requests import Request
@@ -16,6 +17,7 @@
1617
from dispatch.organization import service as organization_service
1718
from dispatch.organization.models import OrganizationRead
1819
from dispatch.participant_role.enums import ParticipantRoleType
20+
from dispatch.task import service as task_service
1921

2022
log = logging.getLogger(__name__)
2123

@@ -335,6 +337,51 @@ def has_required_permissions(
335337
)
336338

337339

340+
class IncidentTaskCreateEditPermission(BasePermission):
341+
"""
342+
Permissions dependency to apply incident edit permissions to task-based requests.
343+
"""
344+
345+
def has_required_permissions(self, request: Request) -> bool:
346+
incident_id = None
347+
# for task creation, retrieve the incident id from the payload
348+
if request.method == "POST" and hasattr(request, "_body"):
349+
try:
350+
body = json.loads(request._body.decode())
351+
incident_id = body["incident"]["id"]
352+
except (json.JSONDecodeError, KeyError, AttributeError):
353+
log.error(
354+
"Encountered create_task request without expected incident ID. Cannot properly ascertain incident permissions."
355+
)
356+
return False
357+
else: # otherwise, retrieve via the task id
358+
pk = PrimaryKeyModel(id=request.path_params["task_id"])
359+
current_task = task_service.get(db_session=request.state.db, task_id=pk.id)
360+
if not current_task or not current_task.incident:
361+
return False
362+
incident_id = current_task.incident.id
363+
364+
# minimal object with the attributes required for IncidentViewPermission
365+
incident_request = type(
366+
"IncidentRequest",
367+
(),
368+
{
369+
"path_params": {**request.path_params, "incident_id": incident_id},
370+
"state": request.state,
371+
},
372+
)()
373+
374+
# copy necessary request attributes
375+
for attr in ["headers", "method", "url", "query_params"]:
376+
if hasattr(request, attr):
377+
setattr(incident_request, attr, getattr(request, attr))
378+
379+
return any_permission(
380+
permissions=[IncidentEditPermission],
381+
request=incident_request,
382+
)
383+
384+
338385
class IncidentReporterPermission(BasePermission):
339386
def has_required_permissions(
340387
self,

src/dispatch/task/views.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
2-
from fastapi import APIRouter, HTTPException, Query, status
2+
from fastapi import APIRouter, HTTPException, Query, status, Depends
33

44

55
from dispatch.auth.service import CurrentUser
6+
from dispatch.auth.permissions import PermissionsDependency, IncidentTaskCreateEditPermission
67
from dispatch.common.utils.views import create_pydantic_include
78
from dispatch.database.core import DbSession
89
from dispatch.database.service import CommonParameters, search_filter_sort_paginate
@@ -43,7 +44,12 @@ def get_tasks(common: CommonParameters, include: list[str] = Query([], alias="in
4344
return json.loads(TaskPagination(**pagination).json())
4445

4546

46-
@router.post("", response_model=TaskRead, tags=["tasks"])
47+
@router.post(
48+
"",
49+
response_model=TaskRead,
50+
tags=["tasks"],
51+
dependencies=[Depends(PermissionsDependency([IncidentTaskCreateEditPermission]))],
52+
)
4753
def create_task(
4854
db_session: DbSession,
4955
task_in: TaskCreate,
@@ -64,11 +70,12 @@ def create_task(
6470
return task
6571

6672

67-
@router.post("/ticket/{task_id}", tags=["tasks"])
68-
def create_ticket(
69-
db_session: DbSession,
70-
task_id: PrimaryKey,
71-
):
73+
@router.post(
74+
"/ticket/{task_id}",
75+
tags=["tasks"],
76+
dependencies=[Depends(PermissionsDependency([IncidentTaskCreateEditPermission]))],
77+
)
78+
def create_ticket(db_session: DbSession, task_id: PrimaryKey, current_user: CurrentUser):
7279
"""Creates a ticket for an existing task."""
7380
task = get(db_session=db_session, task_id=task_id)
7481
if not task:
@@ -79,8 +86,15 @@ def create_ticket(
7986
return create_task_ticket(task=task, db_session=db_session)
8087

8188

82-
@router.put("/{task_id}", response_model=TaskRead, tags=["tasks"])
83-
def update_task(db_session: DbSession, task_id: PrimaryKey, task_in: TaskUpdate):
89+
@router.put(
90+
"/{task_id}",
91+
response_model=TaskRead,
92+
tags=["tasks"],
93+
dependencies=[Depends(PermissionsDependency([IncidentTaskCreateEditPermission]))],
94+
)
95+
def update_task(
96+
db_session: DbSession, task_id: PrimaryKey, task_in: TaskUpdate, current_user: CurrentUser
97+
):
8498
"""Updates an existing task."""
8599
task = get(db_session=db_session, task_id=task_id)
86100
if not task:
@@ -104,8 +118,13 @@ def update_task(db_session: DbSession, task_id: PrimaryKey, task_in: TaskUpdate)
104118
return task
105119

106120

107-
@router.delete("/{task_id}", response_model=None, tags=["tasks"])
108-
def delete_task(db_session: DbSession, task_id: PrimaryKey):
121+
@router.delete(
122+
"/{task_id}",
123+
response_model=None,
124+
tags=["tasks"],
125+
dependencies=[Depends(PermissionsDependency([IncidentTaskCreateEditPermission]))],
126+
)
127+
def delete_task(db_session: DbSession, task_id: PrimaryKey, current_user: CurrentUser):
109128
"""Deletes an existing task."""
110129
task = get(db_session=db_session, task_id=task_id)
111130
if not task:

0 commit comments

Comments
 (0)