Skip to content

Commit 2a575d6

Browse files
authored
Adjust several permissions for API and UI (#5672)
* initial commit * documentation of method * intermediate commit * API for Notes restricted to superusers * documentation for notes api * fix unit tests * flake8
1 parent 43e199b commit 2a575d6

File tree

16 files changed

+457
-92
lines changed

16 files changed

+457
-92
lines changed

dojo/api_v2/permissions.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dojo.importers.reimporter.utils import get_target_engagement_if_exists, get_target_product_by_id_if_exists, \
66
get_target_product_if_exists, get_target_test_if_exists, \
77
get_target_product_type_if_exists
8-
from dojo.models import Endpoint, Engagement, Finding, Product_Type, Product, Test, Dojo_Group
8+
from dojo.models import Endpoint, Engagement, Finding, Finding_Group, Product_Type, Product, Test, Dojo_Group
99
from django.shortcuts import get_object_or_404
1010
from rest_framework import permissions, serializers
1111
from dojo.authorization.authorization import user_has_global_permission, user_has_permission, user_has_configuration_permission
@@ -109,6 +109,14 @@ def has_object_permission(self, request, view, obj):
109109
return has_permission_result
110110

111111

112+
class UserHasToolProductSettingsPermission(permissions.BasePermission):
113+
def has_permission(self, request, view):
114+
return check_post_permission(request, Product, 'product', Permissions.Product_Edit)
115+
116+
def has_object_permission(self, request, view, obj):
117+
return check_object_permission(request, obj.product, Permissions.Product_View, Permissions.Product_Edit, Permissions.Product_Edit)
118+
119+
112120
class UserHasEndpointPermission(permissions.BasePermission):
113121
def has_permission(self, request, view):
114122
return check_post_permission(request, Product, 'product', Permissions.Endpoint_Add)
@@ -347,6 +355,77 @@ def has_object_permission(self, request, view, obj):
347355
return check_object_permission(request, obj, Permissions.Product_API_Scan_Configuration_View, Permissions.Product_API_Scan_Configuration_Edit, Permissions.Product_API_Scan_Configuration_Delete)
348356

349357

358+
class UserHasJiraProductPermission(permissions.BasePermission):
359+
def has_permission(self, request, view):
360+
if request.method == 'POST':
361+
has_permission_result = True
362+
engagement_id = request.data.get('engagement', None)
363+
if engagement_id:
364+
object = get_object_or_404(Engagement, pk=engagement_id)
365+
has_permission_result = has_permission_result and \
366+
user_has_permission(request.user, object, Permissions.Engagement_Edit)
367+
product_id = request.data.get('product', None)
368+
if product_id:
369+
object = get_object_or_404(Product, pk=product_id)
370+
has_permission_result = has_permission_result and \
371+
user_has_permission(request.user, object, Permissions.Product_Edit)
372+
return has_permission_result
373+
else:
374+
return True
375+
376+
def has_object_permission(self, request, view, obj):
377+
has_permission_result = True
378+
engagement = obj.engagement
379+
if engagement:
380+
has_permission_result = has_permission_result and \
381+
check_object_permission(request, engagement, Permissions.Engagement_View, Permissions.Engagement_Edit, Permissions.Engagement_Edit)
382+
product = obj.product
383+
if product:
384+
has_permission_result = has_permission_result and \
385+
check_object_permission(request, product, Permissions.Product_View, Permissions.Product_Edit, Permissions.Product_Edit)
386+
return has_permission_result
387+
388+
389+
class UserHasJiraIssuePermission(permissions.BasePermission):
390+
def has_permission(self, request, view):
391+
if request.method == 'POST':
392+
has_permission_result = True
393+
engagement_id = request.data.get('engagement', None)
394+
if engagement_id:
395+
object = get_object_or_404(Engagement, pk=engagement_id)
396+
has_permission_result = has_permission_result and \
397+
user_has_permission(request.user, object, Permissions.Engagement_Edit)
398+
finding_id = request.data.get('finding', None)
399+
if finding_id:
400+
object = get_object_or_404(Finding, pk=finding_id)
401+
has_permission_result = has_permission_result and \
402+
user_has_permission(request.user, object, Permissions.Finding_Edit)
403+
finding_group_id = request.data.get('finding_group', None)
404+
if finding_group_id:
405+
object = get_object_or_404(Finding_Group, pk=finding_group_id)
406+
has_permission_result = has_permission_result and \
407+
user_has_permission(request.user, object, Permissions.Finding_Group_Edit)
408+
return has_permission_result
409+
else:
410+
return True
411+
412+
def has_object_permission(self, request, view, obj):
413+
has_permission_result = True
414+
engagement = obj.engagement
415+
if engagement:
416+
has_permission_result = has_permission_result and \
417+
check_object_permission(request, engagement, Permissions.Engagement_View, Permissions.Engagement_Edit, Permissions.Engagement_Edit)
418+
finding = obj.finding
419+
if finding:
420+
has_permission_result = has_permission_result and \
421+
check_object_permission(request, finding, Permissions.Finding_View, Permissions.Finding_Edit, Permissions.Finding_Edit)
422+
finding_group = obj.finding_group
423+
if finding_group:
424+
has_permission_result = has_permission_result and \
425+
check_object_permission(request, finding_group, Permissions.Finding_Group_View, Permissions.Finding_Group_Edit, Permissions.Finding_Group_Edit)
426+
return has_permission_result
427+
428+
350429
class IsSuperUser(permissions.BasePermission):
351430
def has_permission(self, request, view):
352431
return request.user and request.user.is_superuser

dojo/api_v2/serializers.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,25 @@ class Meta:
780780
def get_url(self, obj) -> str:
781781
return jira_helper.get_jira_issue_url(obj)
782782

783+
def validate(self, data):
784+
if self.context['request'].method == 'PATCH':
785+
engagement = data.get('engagement', self.instance.engagement)
786+
finding = data.get('finding', self.instance.finding)
787+
finding_group = data.get('finding_group', self.instance.finding_group)
788+
else:
789+
engagement = data.get('engagement', None)
790+
finding = data.get('finding', None)
791+
finding_group = data.get('finding_group', None)
792+
793+
if ((engagement and not finding and not finding_group) or
794+
(finding and not engagement and not finding_group) or
795+
(finding_group and not engagement and not finding)):
796+
pass
797+
else:
798+
raise serializers.ValidationError('Either engagement or finding or finding_group has to be set.')
799+
800+
return data
801+
783802

784803
class JIRAInstanceSerializer(serializers.ModelSerializer):
785804
class Meta:
@@ -795,6 +814,19 @@ class Meta:
795814
model = JIRA_Project
796815
fields = '__all__'
797816

817+
def validate(self, data):
818+
if self.context['request'].method == 'PATCH':
819+
engagement = data.get('engagement', self.instance.engagement)
820+
product = data.get('product', self.instance.product)
821+
else:
822+
engagement = data.get('engagement', None)
823+
product = data.get('product', None)
824+
825+
if ((engagement and product) or (not engagement and not product)):
826+
raise serializers.ValidationError('Either engagement or product has to be set.')
827+
828+
return data
829+
798830

799831
class SonarqubeIssueSerializer(serializers.ModelSerializer):
800832
class Meta:

dojo/api_v2/views.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
from dojo.finding.queries import get_authorized_findings, get_authorized_stub_findings
5757
from dojo.endpoint.queries import get_authorized_endpoints, get_authorized_endpoint_status
5858
from dojo.group.queries import get_authorized_groups, get_authorized_group_members
59+
from dojo.jira_link.queries import get_authorized_jira_projects, get_authorized_jira_issues
60+
from dojo.tool_product.queries import get_authorized_tool_product_settings
5961
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema, extend_schema_view
6062
from dojo.authorization.roles_permissions import Permissions
6163

@@ -1015,34 +1017,40 @@ class JiraInstanceViewSet(mixins.ListModelMixin,
10151017
permission_classes = (permissions.IsSuperUser, DjangoModelPermissions)
10161018

10171019

1018-
# Authorization: staff
1020+
# Authorization: object-based
10191021
class JiraIssuesViewSet(mixins.ListModelMixin,
10201022
mixins.RetrieveModelMixin,
10211023
mixins.DestroyModelMixin,
10221024
mixins.CreateModelMixin,
10231025
mixins.UpdateModelMixin,
10241026
viewsets.GenericViewSet):
10251027
serializer_class = serializers.JIRAIssueSerializer
1026-
queryset = JIRA_Issue.objects.all()
1028+
queryset = JIRA_Issue.objects.none()
10271029
filter_backends = (DjangoFilterBackend,)
1028-
filter_fields = ('id', 'jira_id', 'jira_key', 'finding_id')
1029-
permission_classes = (IsAdminUser, DjangoModelPermissions)
1030+
filter_fields = ('id', 'jira_id', 'jira_key', 'finding', 'engagement', 'finding_group')
1031+
permission_classes = (IsAuthenticated, permissions.UserHasJiraIssuePermission)
10301032

1033+
def get_queryset(self):
1034+
return get_authorized_jira_issues(Permissions.Product_View)
10311035

1032-
# Authorization: staff
1036+
1037+
# Authorization: object-based
10331038
class JiraProjectViewSet(mixins.ListModelMixin,
10341039
mixins.RetrieveModelMixin,
10351040
mixins.DestroyModelMixin,
10361041
mixins.UpdateModelMixin,
10371042
mixins.CreateModelMixin,
10381043
viewsets.GenericViewSet):
10391044
serializer_class = serializers.JIRAProjectSerializer
1040-
queryset = JIRA_Project.objects.all()
1045+
queryset = JIRA_Project.objects.none()
10411046
filter_backends = (DjangoFilterBackend,)
1042-
filter_fields = ('id', 'jira_instance', 'product', 'component', 'project_key',
1047+
filter_fields = ('id', 'jira_instance', 'product', 'engagement', 'component', 'project_key',
10431048
'push_all_issues', 'enable_engagement_epic_mapping',
10441049
'push_notes')
1045-
permission_classes = (IsAdminUser, DjangoModelPermissions)
1050+
permission_classes = (IsAuthenticated, permissions.UserHasJiraProductPermission)
1051+
1052+
def get_queryset(self):
1053+
return get_authorized_jira_projects(Permissions.Product_View)
10461054

10471055

10481056
# Authorization: superuser
@@ -1754,19 +1762,22 @@ class ToolConfigurationsViewSet(mixins.ListModelMixin,
17541762
permission_classes = (permissions.IsSuperUser, DjangoModelPermissions)
17551763

17561764

1757-
# Authorization: staff
1765+
# Authorization: object-based
17581766
class ToolProductSettingsViewSet(mixins.ListModelMixin,
17591767
mixins.RetrieveModelMixin,
17601768
mixins.DestroyModelMixin,
17611769
mixins.CreateModelMixin,
17621770
mixins.UpdateModelMixin,
17631771
viewsets.GenericViewSet):
17641772
serializer_class = serializers.ToolProductSettingsSerializer
1765-
queryset = Tool_Product_Settings.objects.all()
1773+
queryset = Tool_Product_Settings.objects.none()
17661774
filter_backends = (DjangoFilterBackend,)
17671775
filter_fields = ('id', 'name', 'product', 'tool_configuration',
17681776
'tool_project_id', 'url')
1769-
permission_classes = (IsAdminUser, DjangoModelPermissions)
1777+
permission_classes = (IsAuthenticated, permissions.UserHasToolProductSettingsPermission)
1778+
1779+
def get_queryset(self):
1780+
return get_authorized_tool_product_settings(Permissions.Product_View)
17701781

17711782

17721783
# Authorization: configuration
@@ -2078,7 +2089,7 @@ class NoteTypeViewSet(mixins.ListModelMixin,
20782089
permission_classes = (IsAdminUser, DjangoModelPermissions)
20792090

20802091

2081-
# Authorization: staff
2092+
# Authorization: superuser
20822093
class NotesViewSet(mixins.ListModelMixin,
20832094
mixins.RetrieveModelMixin,
20842095
mixins.UpdateModelMixin,
@@ -2089,7 +2100,7 @@ class NotesViewSet(mixins.ListModelMixin,
20892100
filter_fields = ('id', 'entry', 'author',
20902101
'private', 'date', 'edited',
20912102
'edit_time', 'editor')
2092-
permission_classes = (IsAdminUser, DjangoModelPermissions)
2103+
permission_classes = (permissions.IsSuperUser, DjangoModelPermissions)
20932104

20942105

20952106
def report_generate(request, obj, options):

dojo/authorization/roles_permissions.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ class Permissions(IntEnum):
114114
Product_API_Scan_Configuration_Edit = 2506
115115
Product_API_Scan_Configuration_Delete = 2507
116116

117+
Product_Tracking_Files_View = 2602
118+
Product_Tracking_Files_Add = 2603
119+
Product_Tracking_Files_Edit = 2606
120+
Product_Tracking_Files_Delete = 2607
121+
117122
@classmethod
118123
def has_value(cls, value):
119124
try:
@@ -212,7 +217,8 @@ def get_roles_with_permissions():
212217
Permissions.Group_View,
213218
Permissions.Language_View,
214219
Permissions.Technology_View,
215-
Permissions.Product_API_Scan_Configuration_View
220+
Permissions.Product_API_Scan_Configuration_View,
221+
Permissions.Product_Tracking_Files_View,
216222
},
217223
Roles.API_Importer: {
218224
Permissions.Product_Type_View,
@@ -279,7 +285,9 @@ def get_roles_with_permissions():
279285
Permissions.Technology_Add,
280286
Permissions.Technology_Edit,
281287

282-
Permissions.Product_API_Scan_Configuration_View
288+
Permissions.Product_API_Scan_Configuration_View,
289+
290+
Permissions.Product_Tracking_Files_View,
283291
},
284292
Roles.Maintainer: {
285293
Permissions.Product_Type_Add_Product,
@@ -359,7 +367,12 @@ def get_roles_with_permissions():
359367
Permissions.Product_API_Scan_Configuration_View,
360368
Permissions.Product_API_Scan_Configuration_Add,
361369
Permissions.Product_API_Scan_Configuration_Edit,
362-
Permissions.Product_API_Scan_Configuration_Delete
370+
Permissions.Product_API_Scan_Configuration_Delete,
371+
372+
Permissions.Product_Tracking_Files_View,
373+
Permissions.Product_Tracking_Files_Add,
374+
Permissions.Product_Tracking_Files_Edit,
375+
Permissions.Product_Tracking_Files_Delete,
363376
},
364377
Roles.Owner: {
365378
Permissions.Product_Type_Add_Product,
@@ -447,7 +460,12 @@ def get_roles_with_permissions():
447460
Permissions.Product_API_Scan_Configuration_View,
448461
Permissions.Product_API_Scan_Configuration_Add,
449462
Permissions.Product_API_Scan_Configuration_Edit,
450-
Permissions.Product_API_Scan_Configuration_Delete
463+
Permissions.Product_API_Scan_Configuration_Delete,
464+
465+
Permissions.Product_Tracking_Files_View,
466+
Permissions.Product_Tracking_Files_Add,
467+
Permissions.Product_Tracking_Files_Edit,
468+
Permissions.Product_Tracking_Files_Delete,
451469
}
452470
}
453471

dojo/github_issue_link/views.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,7 @@ def webhook(request):
2525
return HttpResponse('')
2626

2727

28-
@user_passes_test(lambda u: u.is_staff)
29-
def express_new_github(request):
30-
return HttpResponse('')
31-
32-
33-
@user_passes_test(lambda u: u.is_staff)
28+
@user_passes_test(lambda u: u.is_superuser)
3429
def new_github(request):
3530
if request.method == 'POST':
3631
gform = GITHUBForm(request.POST, instance=GITHUB_Conf())
@@ -63,7 +58,7 @@ def new_github(request):
6358
{'gform': gform})
6459

6560

66-
@user_passes_test(lambda u: u.is_staff)
61+
@user_passes_test(lambda u: u.is_superuser)
6762
def github(request):
6863
confs = GITHUB_Conf.objects.all()
6964
add_breadcrumb(title="Github List", top_level=not len(request.GET), request=request)
@@ -73,7 +68,7 @@ def github(request):
7368
})
7469

7570

76-
@user_passes_test(lambda u: u.is_staff)
71+
@user_passes_test(lambda u: u.is_superuser)
7772
def delete_github(request, tid):
7873
github_instance = get_object_or_404(GITHUB_Conf, pk=tid)
7974
# eng = test.engagement

0 commit comments

Comments
 (0)