Skip to content

Commit 4e2b322

Browse files
committed
Refactor permission classes: introduce UserHasDevelopmentEnvironmentPermission, UserHasRegulationPermission, and UserHasSLAPermission; update views accordingly
1 parent 963d4a3 commit 4e2b322

2 files changed

Lines changed: 106 additions & 35 deletions

File tree

dojo/api_v2/permissions.py

Lines changed: 98 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
from dojo.importers.auto_create_context import AutoCreateContextManager
2020
from dojo.models import (
2121
Cred_Mapping,
22+
Development_Environment,
2223
Dojo_Group,
2324
Endpoint,
2425
Engagement,
2526
Finding,
2627
Finding_Group,
2728
Product,
2829
Product_Type,
30+
Regulation,
31+
SLA_Configuration,
2932
Test,
3033
)
3134

@@ -59,6 +62,71 @@ def check_object_permission(
5962
return False
6063

6164

65+
class BaseRelatedObjectPermission(permissions.BasePermission):
66+
67+
"""
68+
An "abstract" base class for related object permissions (like notes, metadata, etc.)
69+
that only need object permissions, not general permissions. This class will serve as
70+
the base class for other more aptly named permission classes.
71+
"""
72+
73+
permission_map: dict[str, int] = {
74+
"get_permission": None,
75+
"put_permission": None,
76+
"delete_permission": None,
77+
"post_permission": None,
78+
}
79+
80+
def has_permission(self, request: Request, view):
81+
# related object only need object permission
82+
return True
83+
84+
def has_object_permission(self, request: Request, view, obj):
85+
return check_object_permission(
86+
request,
87+
obj,
88+
**self.permission_map,
89+
)
90+
91+
92+
class BaseDjangoModelPermission(permissions.BasePermission):
93+
94+
"""
95+
An "abstract" base class for Django model permissions.
96+
This class will serve as the base class for other more aptly named permission classes.
97+
"""
98+
99+
django_model: Model = None
100+
request_method_permission_map: dict[str, str] = {
101+
"GET": "view",
102+
"POST": "add",
103+
"PUT": "change",
104+
"PATCH": "change",
105+
"DELETE": "delete",
106+
}
107+
108+
def _evaluate_permissions(self, request: Request, permissions: dict[str, str]) -> bool:
109+
for method, permission in permissions.items():
110+
if request.method == method:
111+
return user_has_configuration_permission(
112+
request.user,
113+
f"{self.django_model._meta.app_label}.{permission}_{self.django_model._meta.model_name}",
114+
)
115+
return False
116+
117+
def has_permission(self, request: Request, view):
118+
# First restrict the mapping got GET/POST only
119+
expected_request_method_permission_map = {k: v for k, v in self.request_method_permission_map.items() if k in {"GET", "POST"}}
120+
# Short circuit if the request method is not in the expected methods
121+
if request.method not in expected_request_method_permission_map:
122+
return True
123+
# Evaluate the permissions
124+
return self._evaluate_permissions(request, expected_request_method_permission_map)
125+
126+
def has_object_permission(self, request: Request, view, obj):
127+
return self._evaluate_permissions(request, self.request_method_permission_map)
128+
129+
62130
class UserHasAppAnalysisPermission(permissions.BasePermission):
63131
def has_permission(self, request, view):
64132
return check_post_permission(
@@ -277,33 +345,6 @@ def has_object_permission(self, request, view, obj):
277345
)
278346

279347

280-
class BaseRelatedObjectPermission(permissions.BasePermission):
281-
282-
"""
283-
An "abstract" base class for related object permissions (like notes, metadata, etc.)
284-
that only need object permissions, not general permissions. This class will serve as
285-
the base class for other more aptly named permission classes.
286-
"""
287-
288-
permission_map = {
289-
"get_permission": None,
290-
"put_permission": None,
291-
"delete_permission": None,
292-
"post_permission": None,
293-
}
294-
295-
def has_permission(self, request, view):
296-
# related object only need object permission
297-
return True
298-
299-
def has_object_permission(self, request, view, obj):
300-
return check_object_permission(
301-
request,
302-
obj,
303-
**self.permission_map,
304-
)
305-
306-
307348
class UserHasEngagementPermission(permissions.BasePermission):
308349
def has_permission(self, request, view):
309350
return check_post_permission(
@@ -987,6 +1028,36 @@ def has_object_permission(self, request, view, obj):
9871028
)
9881029

9891030

1031+
class UserHasSLAPermission(BaseDjangoModelPermission):
1032+
django_model = SLA_Configuration
1033+
1034+
1035+
class UserHasDevelopmentEnvironmentPermission(BaseDjangoModelPermission):
1036+
django_model = Development_Environment
1037+
# https://github.com/DefectDojo/django-DefectDojo/blob/963d4a35bfd8f5138330f0d70595a755fa4999b0/dojo/user/utils.py#L93
1038+
# It looks like view permission was explicitly not supported, so I assume
1039+
# reading these endpoints are not necessarily restricted (unless you're auth'd of course)
1040+
request_method_permission_map = {
1041+
"POST": "add",
1042+
"PUT": "change",
1043+
"PATCH": "change",
1044+
"DELETE": "delete",
1045+
}
1046+
1047+
1048+
class UserHasRegulationPermission(BaseDjangoModelPermission):
1049+
django_model = Regulation
1050+
# https://github.com/DefectDojo/django-DefectDojo/blob/963d4a35bfd8f5138330f0d70595a755fa4999b0/dojo/user/utils.py#L104
1051+
# It looks like view permission was explicitly not supported, so I assume
1052+
# reading these endpoints are not necessarily restricted (unless you're auth'd of course)
1053+
request_method_permission_map = {
1054+
"POST": "add",
1055+
"PUT": "change",
1056+
"PATCH": "change",
1057+
"DELETE": "delete",
1058+
}
1059+
1060+
9901061
def raise_no_auto_create_import_validation_error(
9911062
test_title,
9921063
scan_type,

dojo/api_v2/views.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,7 +2039,7 @@ class DevelopmentEnvironmentViewSet(
20392039
serializer_class = serializers.DevelopmentEnvironmentSerializer
20402040
queryset = Development_Environment.objects.none()
20412041
filter_backends = (DjangoFilterBackend,)
2042-
permission_classes = (IsAuthenticated, DjangoModelPermissions)
2042+
permission_classes = (IsAuthenticated, permissions.UserHasDevelopmentEnvironmentPermission)
20432043

20442044
def get_queryset(self):
20452045
return Development_Environment.objects.all().order_by("id")
@@ -2392,7 +2392,7 @@ class RegulationsViewSet(
23922392
queryset = Regulation.objects.none()
23932393
filter_backends = (DjangoFilterBackend,)
23942394
filterset_fields = ["id", "name", "description"]
2395-
permission_classes = (IsAuthenticated, DjangoModelPermissions)
2395+
permission_classes = (IsAuthenticated, permissions.UserHasRegulationPermission)
23962396

23972397
def get_queryset(self):
23982398
return Regulation.objects.all().order_by("id")
@@ -3133,7 +3133,7 @@ class SLAConfigurationViewset(
31333133
serializer_class = serializers.SLAConfigurationSerializer
31343134
queryset = SLA_Configuration.objects.none()
31353135
filter_backends = (DjangoFilterBackend,)
3136-
permission_classes = (IsAuthenticated, DjangoModelPermissions)
3136+
permission_classes = (IsAuthenticated, permissions.UserHasSLAPermission)
31373137

31383138
def get_queryset(self):
31393139
return SLA_Configuration.objects.all().order_by("id")
@@ -3147,7 +3147,7 @@ class QuestionnaireQuestionViewSet(
31473147
queryset = Question.objects.none()
31483148
filter_backends = (DjangoFilterBackend,)
31493149
permission_classes = (
3150-
permissions.BaseRelatedObjectPermission,
3150+
permissions.UserHasEngagementRelatedObjectPermission,
31513151
DjangoModelPermissions,
31523152
)
31533153

@@ -3163,7 +3163,7 @@ class QuestionnaireAnswerViewSet(
31633163
queryset = Answer.objects.none()
31643164
filter_backends = (DjangoFilterBackend,)
31653165
permission_classes = (
3166-
permissions.BaseRelatedObjectPermission,
3166+
permissions.UserHasEngagementRelatedObjectPermission,
31673167
DjangoModelPermissions,
31683168
)
31693169

@@ -3178,7 +3178,7 @@ class QuestionnaireGeneralSurveyViewSet(
31783178
queryset = General_Survey.objects.none()
31793179
filter_backends = (DjangoFilterBackend,)
31803180
permission_classes = (
3181-
permissions.BaseRelatedObjectPermission,
3181+
permissions.UserHasEngagementRelatedObjectPermission,
31823182
DjangoModelPermissions,
31833183
)
31843184

@@ -3193,7 +3193,7 @@ class QuestionnaireEngagementSurveyViewSet(
31933193
queryset = Engagement_Survey.objects.none()
31943194
filter_backends = (DjangoFilterBackend,)
31953195
permission_classes = (
3196-
permissions.BaseRelatedObjectPermission,
3196+
permissions.UserHasEngagementRelatedObjectPermission,
31973197
DjangoModelPermissions,
31983198
)
31993199

@@ -3234,7 +3234,7 @@ class QuestionnaireAnsweredSurveyViewSet(
32343234
queryset = Answered_Survey.objects.none()
32353235
filter_backends = (DjangoFilterBackend,)
32363236
permission_classes = (
3237-
permissions.BaseRelatedObjectPermission,
3237+
permissions.UserHasEngagementRelatedObjectPermission,
32383238
DjangoModelPermissions,
32393239
)
32403240

0 commit comments

Comments
 (0)