Skip to content

Commit b7f0f99

Browse files
authored
Asset/Organizations Endpoints: Patches, permission checking, and API tests (#14080)
* Update AssetSerializer fields to allow null values and set defaults * Refactor authorization functions to use type hints for better clarity and maintainability * Enhance permission checks to support multiple primary key attributes in post requests * Refactor check_post_permission to use list type for post_pk parameter * Refactor Organization serializers to handle default values for critical and key assets, and update OrganizationViewSet to use OrganizationFilterSet for filtering. * Refactor API tests to include asset and organization endpoints, enhancing coverage for asset-related functionalities. * Refactor permission classes to use asset and organization-specific permissions, enhancing clarity and maintainability. * Add blank line before UserHasOrganizationGroupPermission class for improved readability
1 parent 09c9821 commit b7f0f99

8 files changed

Lines changed: 388 additions & 54 deletions

File tree

dojo/api_v2/permissions.py

Lines changed: 136 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import re
22

3+
from django.db.models import Model
34
from django.shortcuts import get_object_or_404
45
from rest_framework import permissions, serializers
56
from rest_framework.exceptions import (
67
ParseError,
78
PermissionDenied,
89
ValidationError,
910
)
11+
from rest_framework.request import Request
1012

1113
from dojo.authorization.authorization import (
1214
user_has_configuration_permission,
@@ -29,7 +31,7 @@
2931
)
3032

3133

32-
def check_post_permission(request, post_model, post_pk, post_permission):
34+
def check_post_permission(request: Request, post_model: Model, post_pk: str | list[str], post_permission: int) -> bool:
3335
if request.method == "POST":
3436
if request.data.get(post_pk) is None:
3537
msg = f"Unable to check for permissions: Attribute '{post_pk}' is required"
@@ -40,13 +42,13 @@ def check_post_permission(request, post_model, post_pk, post_permission):
4042

4143

4244
def check_object_permission(
43-
request,
44-
obj,
45-
get_permission,
46-
put_permission,
47-
delete_permission,
48-
post_permission=None,
49-
):
45+
request: Request,
46+
obj: Model,
47+
get_permission: int,
48+
put_permission: int,
49+
delete_permission: int,
50+
post_permission: int | None = None,
51+
) -> bool:
5052
if request.method == "GET":
5153
return user_has_permission(request.user, obj, get_permission)
5254
if request.method in {"PUT", "PATCH"}:
@@ -507,6 +509,25 @@ def has_object_permission(self, request, view, obj):
507509
)
508510

509511

512+
class UserHasAssetPermission(permissions.BasePermission):
513+
def has_permission(self, request, view):
514+
return check_post_permission(
515+
request,
516+
Product_Type,
517+
"organization",
518+
Permissions.Product_Type_Add_Product,
519+
)
520+
521+
def has_object_permission(self, request, view, obj):
522+
return check_object_permission(
523+
request,
524+
obj,
525+
Permissions.Product_View,
526+
Permissions.Product_Edit,
527+
Permissions.Product_Delete,
528+
)
529+
530+
510531
class UserHasProductMemberPermission(permissions.BasePermission):
511532
def has_permission(self, request, view):
512533
return check_post_permission(
@@ -523,6 +544,22 @@ def has_object_permission(self, request, view, obj):
523544
)
524545

525546

547+
class UserHasAssetMemberPermission(permissions.BasePermission):
548+
def has_permission(self, request, view):
549+
return check_post_permission(
550+
request, Product, "asset", Permissions.Product_Manage_Members,
551+
)
552+
553+
def has_object_permission(self, request, view, obj):
554+
return check_object_permission(
555+
request,
556+
obj,
557+
Permissions.Product_View,
558+
Permissions.Product_Manage_Members,
559+
Permissions.Product_Member_Delete,
560+
)
561+
562+
526563
class UserHasProductGroupPermission(permissions.BasePermission):
527564
def has_permission(self, request, view):
528565
return check_post_permission(
@@ -539,6 +576,22 @@ def has_object_permission(self, request, view, obj):
539576
)
540577

541578

579+
class UserHasAssetGroupPermission(permissions.BasePermission):
580+
def has_permission(self, request, view):
581+
return check_post_permission(
582+
request, Product, "asset", Permissions.Product_Group_Add,
583+
)
584+
585+
def has_object_permission(self, request, view, obj):
586+
return check_object_permission(
587+
request,
588+
obj,
589+
Permissions.Product_Group_View,
590+
Permissions.Product_Group_Edit,
591+
Permissions.Product_Group_Delete,
592+
)
593+
594+
542595
class UserHasProductTypePermission(permissions.BasePermission):
543596
def has_permission(self, request, view):
544597
if request.method == "POST":
@@ -557,6 +610,24 @@ def has_object_permission(self, request, view, obj):
557610
)
558611

559612

613+
class UserHasOrganizationPermission(permissions.BasePermission):
614+
def has_permission(self, request, view):
615+
if request.method == "POST":
616+
return user_has_global_permission(
617+
request.user, Permissions.Product_Type_Add,
618+
)
619+
return True
620+
621+
def has_object_permission(self, request, view, obj):
622+
return check_object_permission(
623+
request,
624+
obj,
625+
Permissions.Product_Type_View,
626+
Permissions.Product_Type_Edit,
627+
Permissions.Product_Type_Delete,
628+
)
629+
630+
560631
class UserHasProductTypeMemberPermission(permissions.BasePermission):
561632
def has_permission(self, request, view):
562633
return check_post_permission(
@@ -576,6 +647,25 @@ def has_object_permission(self, request, view, obj):
576647
)
577648

578649

650+
class UserHasOrganizationMemberPermission(permissions.BasePermission):
651+
def has_permission(self, request, view):
652+
return check_post_permission(
653+
request,
654+
Product_Type,
655+
"organization",
656+
Permissions.Product_Type_Manage_Members,
657+
)
658+
659+
def has_object_permission(self, request, view, obj):
660+
return check_object_permission(
661+
request,
662+
obj,
663+
Permissions.Product_Type_View,
664+
Permissions.Product_Type_Manage_Members,
665+
Permissions.Product_Type_Member_Delete,
666+
)
667+
668+
579669
class UserHasProductTypeGroupPermission(permissions.BasePermission):
580670
def has_permission(self, request, view):
581671
return check_post_permission(
@@ -595,6 +685,25 @@ def has_object_permission(self, request, view, obj):
595685
)
596686

597687

688+
class UserHasOrganizationGroupPermission(permissions.BasePermission):
689+
def has_permission(self, request, view):
690+
return check_post_permission(
691+
request,
692+
Product_Type,
693+
"organization",
694+
Permissions.Product_Type_Group_Add,
695+
)
696+
697+
def has_object_permission(self, request, view, obj):
698+
return check_object_permission(
699+
request,
700+
obj,
701+
Permissions.Product_Type_Group_View,
702+
Permissions.Product_Type_Group_Edit,
703+
Permissions.Product_Type_Group_Delete,
704+
)
705+
706+
598707
class UserHasReimportPermission(permissions.BasePermission):
599708
def has_permission(self, request, view):
600709
# permission check takes place before validation, so we don't have access to serializer.validated_data()
@@ -739,6 +848,25 @@ def has_object_permission(self, request, view, obj):
739848
)
740849

741850

851+
class UserHasAssetAPIScanConfigurationPermission(permissions.BasePermission):
852+
def has_permission(self, request, view):
853+
return check_post_permission(
854+
request,
855+
Product,
856+
"asset",
857+
Permissions.Product_API_Scan_Configuration_Add,
858+
)
859+
860+
def has_object_permission(self, request, view, obj):
861+
return check_object_permission(
862+
request,
863+
obj,
864+
Permissions.Product_API_Scan_Configuration_View,
865+
Permissions.Product_API_Scan_Configuration_Edit,
866+
Permissions.Product_API_Scan_Configuration_Delete,
867+
)
868+
869+
742870
class UserHasJiraProductPermission(permissions.BasePermission):
743871
def has_permission(self, request, view):
744872
if request.method == "POST":

dojo/asset/api/serializers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ class AssetSerializer(serializers.ModelSerializer):
3737
# V3 fields
3838
asset_meta = ProductMetaSerializer(source="product_meta", read_only=True, many=True)
3939
organization = RelatedOrganizationField(source="prod_type")
40-
asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade")
41-
enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance")
40+
asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade", required=False, allow_null=True)
41+
enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance", required=False, default=False)
4242
asset_managers = serializers.PrimaryKeyRelatedField(
4343
source="product_manager",
44-
queryset=Dojo_User.objects.exclude(is_active=False))
44+
queryset=Dojo_User.objects.exclude(is_active=False),
45+
required=False, allow_null=True,
46+
)
4547

4648
class Meta:
4749
model = Product

dojo/asset/api/views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class AssetAPIScanConfigurationViewSet(
4343
filterset_class = AssetAPIScanConfigurationFilterSet
4444
permission_classes = (
4545
IsAuthenticated,
46-
permissions.UserHasProductAPIScanConfigurationPermission,
46+
permissions.UserHasAssetAPIScanConfigurationPermission,
4747
)
4848

4949
def get_queryset(self):
@@ -68,7 +68,7 @@ class AssetViewSet(
6868
filterset_class = ApiAssetFilter
6969
permission_classes = (
7070
IsAuthenticated,
71-
permissions.UserHasProductPermission,
71+
permissions.UserHasAssetPermission,
7272
)
7373

7474
def get_queryset(self):
@@ -138,7 +138,7 @@ class AssetMemberViewSet(
138138
filterset_class = AssetMemberFilterSet
139139
permission_classes = (
140140
IsAuthenticated,
141-
permissions.UserHasProductMemberPermission,
141+
permissions.UserHasAssetMemberPermission,
142142
)
143143

144144
def get_queryset(self):
@@ -166,7 +166,7 @@ class AssetGroupViewSet(
166166
filterset_class = AssetGroupFilterSet
167167
permission_classes = (
168168
IsAuthenticated,
169-
permissions.UserHasProductGroupPermission,
169+
permissions.UserHasAssetGroupPermission,
170170
)
171171

172172
def get_queryset(self):

0 commit comments

Comments
 (0)