Skip to content

Commit 62b53d6

Browse files
dogboatMaffooch
andauthored
Introduce text labels for v3 (#13155)
* v3 labels * refactor/rework text copy labels and urls * Update dojo/settings/settings.dist.py * (re)import serializer help for product (type) name change * update tests --------- Co-authored-by: Cody Maffucci <[email protected]>
1 parent fc44e8b commit 62b53d6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2698
-548
lines changed

dojo/api_v2/serializers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,8 +2104,14 @@ class CommonImportScanSerializer(serializers.Serializer):
21042104
required=False,
21052105
validators=[ImporterFileExtensionValidator()],
21062106
)
2107-
product_type_name = serializers.CharField(required=False)
2108-
product_name = serializers.CharField(required=False)
2107+
product_type_name = serializers.CharField(
2108+
required=False,
2109+
help_text=_("Also referred to as 'Organization' name."),
2110+
)
2111+
product_name = serializers.CharField(
2112+
required=False,
2113+
help_text=_("Also referred to as 'Asset' name."),
2114+
)
21092115
engagement_name = serializers.CharField(required=False)
21102116
engagement_end_date = serializers.DateField(
21112117
required=False,
@@ -2160,8 +2166,14 @@ class CommonImportScanSerializer(serializers.Serializer):
21602166
# confused
21612167
test_id = serializers.IntegerField(read_only=True)
21622168
engagement_id = serializers.IntegerField(read_only=True)
2163-
product_id = serializers.IntegerField(read_only=True)
2164-
product_type_id = serializers.IntegerField(read_only=True)
2169+
product_id = serializers.IntegerField(
2170+
read_only=True,
2171+
help_text=_("Also referred to as 'Asset' ID."),
2172+
)
2173+
product_type_id = serializers.IntegerField(
2174+
read_only=True,
2175+
help_text=_("Also referred to as 'Organization' ID."),
2176+
)
21652177
statistics = ImportStatisticsSerializer(read_only=True, required=False)
21662178
pro = serializers.ListField(read_only=True, required=False)
21672179
apply_tags_to_findings = serializers.BooleanField(

dojo/api_v2/views.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
get_authorized_jira_issues,
8686
get_authorized_jira_projects,
8787
)
88+
from dojo.labels import get_labels
8889
from dojo.models import (
8990
Announcement,
9091
Answer,
@@ -179,6 +180,9 @@
179180
logger = logging.getLogger(__name__)
180181

181182

183+
labels = get_labels()
184+
185+
182186
def schema_with_prefetch() -> dict:
183187
return {
184188
"list": extend_schema(
@@ -2725,7 +2729,7 @@ def report_generate(request, obj, options):
27252729
if type(obj).__name__ == "Product_Type":
27262730
product_type = obj
27272731

2728-
report_name = "Product Type Report: " + str(product_type)
2732+
report_name = labels.ORG_REPORT_WITH_NAME_TITLE % {"name": str(product_type)}
27292733

27302734
findings = report_finding_filter_class(
27312735
request.GET,
@@ -2754,7 +2758,7 @@ def report_generate(request, obj, options):
27542758
elif type(obj).__name__ == "Product":
27552759
product = obj
27562760

2757-
report_name = "Product Report: " + str(product)
2761+
report_name = labels.ASSET_REPORT_WITH_NAME_TITLE % {"name": str(product)}
27582762

27592763
findings = report_finding_filter_class(
27602764
request.GET,

dojo/asset/__init__.py

Whitespace-only changes.

dojo/asset/api/__init__.py

Whitespace-only changes.

dojo/asset/api/filters.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from django_filters import BooleanFilter, CharFilter, NumberFilter, OrderingFilter
2+
from django_filters.rest_framework import FilterSet
3+
from drf_spectacular.types import OpenApiTypes
4+
from drf_spectacular.utils import extend_schema_field
5+
6+
from dojo.filters import (
7+
CharFieldFilterANDExpression,
8+
CharFieldInFilter,
9+
DateRangeFilter,
10+
DojoFilter,
11+
NumberInFilter,
12+
ProductSLAFilter,
13+
custom_filter,
14+
)
15+
from dojo.labels import get_labels
16+
from dojo.models import (
17+
Product_API_Scan_Configuration,
18+
Product_Group,
19+
Product_Member,
20+
)
21+
22+
labels = get_labels()
23+
24+
25+
class AssetAPIScanConfigurationFilterSet(FilterSet):
26+
asset = NumberFilter(field_name="product")
27+
28+
class Meta:
29+
model = Product_API_Scan_Configuration
30+
fields = ("id", "tool_configuration", "service_key_1", "service_key_2", "service_key_3")
31+
32+
33+
class ApiAssetFilter(DojoFilter):
34+
# BooleanFilter
35+
external_audience = BooleanFilter(field_name="external_audience")
36+
internet_accessible = BooleanFilter(field_name="internet_accessible")
37+
# CharFilter
38+
name = CharFilter(lookup_expr="icontains")
39+
name_exact = CharFilter(field_name="name", lookup_expr="iexact")
40+
description = CharFilter(lookup_expr="icontains")
41+
business_criticality = CharFilter(method=custom_filter, field_name="business_criticality")
42+
platform = CharFilter(method=custom_filter, field_name="platform")
43+
lifecycle = CharFilter(method=custom_filter, field_name="lifecycle")
44+
origin = CharFilter(method=custom_filter, field_name="origin")
45+
# NumberInFilter
46+
id = NumberInFilter(field_name="id", lookup_expr="in")
47+
asset_manager = NumberInFilter(field_name="product_manager", lookup_expr="in")
48+
technical_contact = NumberInFilter(field_name="technical_contact", lookup_expr="in")
49+
team_manager = NumberInFilter(field_name="team_manager", lookup_expr="in")
50+
prod_type = NumberInFilter(field_name="prod_type", lookup_expr="in")
51+
tid = NumberInFilter(field_name="tid", lookup_expr="in")
52+
prod_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in")
53+
user_records = NumberInFilter(field_name="user_records", lookup_expr="in")
54+
regulations = NumberInFilter(field_name="regulations", lookup_expr="in")
55+
56+
tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Tag name contains")
57+
tags = CharFieldInFilter(
58+
field_name="tags__name",
59+
lookup_expr="in",
60+
help_text="Comma separated list of exact tags (uses OR for multiple values)")
61+
tags__and = CharFieldFilterANDExpression(
62+
field_name="tags__name",
63+
help_text="Comma separated list of exact tags to match with an AND expression")
64+
not_tag = CharFilter(field_name="tags__name", lookup_expr="icontains", help_text="Not Tag name contains", exclude="True")
65+
not_tags = CharFieldInFilter(field_name="tags__name", lookup_expr="in",
66+
help_text=labels.ASSET_FILTERS_CSV_TAGS_NOT_HELP, exclude="True")
67+
has_tags = BooleanFilter(field_name="tags", lookup_expr="isnull", exclude=True, label="Has tags")
68+
outside_of_sla = extend_schema_field(OpenApiTypes.NUMBER)(ProductSLAFilter())
69+
70+
# DateRangeFilter
71+
created = DateRangeFilter()
72+
updated = DateRangeFilter()
73+
# NumberFilter
74+
revenue = NumberFilter()
75+
76+
o = OrderingFilter(
77+
# tuple-mapping retains order
78+
fields=(
79+
("id", "id"),
80+
("tid", "tid"),
81+
("name", "name"),
82+
("created", "created"),
83+
("prod_numeric_grade", "prod_numeric_grade"),
84+
("business_criticality", "business_criticality"),
85+
("platform", "platform"),
86+
("lifecycle", "lifecycle"),
87+
("origin", "origin"),
88+
("revenue", "revenue"),
89+
("external_audience", "external_audience"),
90+
("internet_accessible", "internet_accessible"),
91+
("product_manager", "asset_manager"),
92+
("product_manager__first_name", "asset_manager__first_name"),
93+
("product_manager__last_name", "asset_manager__last_name"),
94+
("technical_contact", "technical_contact"),
95+
("technical_contact__first_name", "technical_contact__first_name"),
96+
("technical_contact__last_name", "technical_contact__last_name"),
97+
("team_manager", "team_manager"),
98+
("team_manager__first_name", "team_manager__first_name"),
99+
("team_manager__last_name", "team_manager__last_name"),
100+
("prod_type", "prod_type"),
101+
("prod_type__name", "prod_type__name"),
102+
("updated", "updated"),
103+
("user_records", "user_records"),
104+
),
105+
)
106+
107+
108+
class AssetMemberFilterSet(FilterSet):
109+
asset_id = NumberFilter(field_name="product_id")
110+
111+
class Meta:
112+
model = Product_Member
113+
fields = ("id", "user_id")
114+
115+
116+
class AssetGroupFilterSet(FilterSet):
117+
asset_id = NumberFilter(field_name="product_id")
118+
119+
class Meta:
120+
model = Product_Group
121+
fields = ("id", "group_id")

dojo/asset/api/serializers.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
from rest_framework import serializers
2+
from rest_framework.exceptions import PermissionDenied, ValidationError
3+
4+
from dojo.api_v2.serializers import ProductMetaSerializer, TagListSerializerField
5+
from dojo.authorization.authorization import user_has_permission
6+
from dojo.authorization.roles_permissions import Permissions
7+
from dojo.models import (
8+
Dojo_User,
9+
Product,
10+
Product_API_Scan_Configuration,
11+
Product_Group,
12+
Product_Member,
13+
)
14+
from dojo.organization.api.serializers import RelatedOrganizationField
15+
from dojo.product.queries import get_authorized_products
16+
17+
18+
class RelatedAssetField(serializers.PrimaryKeyRelatedField):
19+
def get_queryset(self):
20+
return get_authorized_products(Permissions.Product_View)
21+
22+
23+
class AssetAPIScanConfigurationSerializer(serializers.ModelSerializer):
24+
asset = RelatedAssetField(source="product")
25+
26+
class Meta:
27+
model = Product_API_Scan_Configuration
28+
exclude = ("product",)
29+
30+
31+
class AssetSerializer(serializers.ModelSerializer):
32+
findings_count = serializers.SerializerMethodField()
33+
findings_list = serializers.SerializerMethodField()
34+
35+
tags = TagListSerializerField(required=False)
36+
37+
# V3 fields
38+
asset_meta = ProductMetaSerializer(source="product_meta", read_only=True, many=True)
39+
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")
42+
asset_managers = serializers.PrimaryKeyRelatedField(
43+
source="product_manager",
44+
queryset=Dojo_User.objects.exclude(is_active=False))
45+
46+
class Meta:
47+
model = Product
48+
exclude = (
49+
"tid",
50+
"updated",
51+
"async_updating",
52+
# Below here excluded for V3 migration
53+
"prod_type",
54+
"prod_numeric_grade",
55+
"enable_product_tag_inheritance",
56+
"product_manager",
57+
)
58+
59+
def validate(self, data):
60+
async_updating = getattr(self.instance, "async_updating", None)
61+
if async_updating:
62+
new_sla_config = data.get("sla_configuration", None)
63+
old_sla_config = getattr(self.instance, "sla_configuration", None)
64+
if new_sla_config and old_sla_config and new_sla_config != old_sla_config:
65+
msg = "Finding SLA expiration dates are currently being recalculated. The SLA configuration for this asset cannot be changed until the calculation is complete."
66+
raise serializers.ValidationError(msg)
67+
return data
68+
69+
def get_findings_count(self, obj) -> int:
70+
return obj.findings_count
71+
72+
# TODO: maybe extend_schema_field is needed here?
73+
def get_findings_list(self, obj) -> list[int]:
74+
return obj.open_findings_list()
75+
76+
77+
class AssetMemberSerializer(serializers.ModelSerializer):
78+
asset = RelatedAssetField(source="product")
79+
80+
class Meta:
81+
model = Product_Member
82+
exclude = ("product",)
83+
84+
def validate(self, data):
85+
if (
86+
self.instance is not None
87+
and data.get("asset") != self.instance.product
88+
and not user_has_permission(
89+
self.context["request"].user,
90+
data.get("asset"),
91+
Permissions.Product_Manage_Members,
92+
)
93+
):
94+
msg = "You are not permitted to add a member to this Asset"
95+
raise PermissionDenied(msg)
96+
97+
if (
98+
self.instance is None
99+
or data.get("asset") != self.instance.product
100+
or data.get("user") != self.instance.user
101+
):
102+
members = Product_Member.objects.filter(
103+
product=data.get("asset"), user=data.get("user"),
104+
)
105+
if members.count() > 0:
106+
msg = "Asset Member already exists"
107+
raise ValidationError(msg)
108+
109+
if data.get("role").is_owner and not user_has_permission(
110+
self.context["request"].user,
111+
data.get("asset"),
112+
Permissions.Product_Member_Add_Owner,
113+
):
114+
msg = "You are not permitted to add a member as Owner to this Asset"
115+
raise PermissionDenied(msg)
116+
117+
return data
118+
119+
120+
class AssetGroupSerializer(serializers.ModelSerializer):
121+
asset = RelatedAssetField(source="product")
122+
123+
class Meta:
124+
model = Product_Group
125+
exclude = ("product",)
126+
127+
def validate(self, data):
128+
if (
129+
self.instance is not None
130+
and data.get("asset") != self.instance.product
131+
and not user_has_permission(
132+
self.context["request"].user,
133+
data.get("asset"),
134+
Permissions.Product_Group_Add,
135+
)
136+
):
137+
msg = "You are not permitted to add a group to this Asset"
138+
raise PermissionDenied(msg)
139+
140+
if (
141+
self.instance is None
142+
or data.get("asset") != self.instance.product
143+
or data.get("group") != self.instance.group
144+
):
145+
members = Product_Group.objects.filter(
146+
product=data.get("asset"), group=data.get("group"),
147+
)
148+
if members.count() > 0:
149+
msg = "Asset Group already exists"
150+
raise ValidationError(msg)
151+
152+
if data.get("role").is_owner and not user_has_permission(
153+
self.context["request"].user,
154+
data.get("asset"),
155+
Permissions.Product_Group_Add_Owner,
156+
):
157+
msg = "You are not permitted to add a group as Owner to this Asset"
158+
raise PermissionDenied(msg)
159+
160+
return data

dojo/asset/api/urls.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from dojo.asset.api.views import (
2+
AssetAPIScanConfigurationViewSet,
3+
AssetGroupViewSet,
4+
AssetMemberViewSet,
5+
AssetViewSet,
6+
)
7+
8+
9+
def add_asset_urls(router):
10+
router.register(r"assets", AssetViewSet, basename="asset")
11+
router.register(r"asset_api_scan_configurations", AssetAPIScanConfigurationViewSet,
12+
basename="asset_api_scan_configuration")
13+
router.register(r"asset_groups", AssetGroupViewSet, basename="asset_group")
14+
router.register(r"asset_members", AssetMemberViewSet, basename="asset_member")
15+
return router

0 commit comments

Comments
 (0)