Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2104,8 +2104,14 @@ class CommonImportScanSerializer(serializers.Serializer):
required=False,
validators=[ImporterFileExtensionValidator()],
)
product_type_name = serializers.CharField(required=False)
product_name = serializers.CharField(required=False)
product_type_name = serializers.CharField(
required=False,
help_text=_("Also referred to as 'Organization' name."),
)
product_name = serializers.CharField(
required=False,
help_text=_("Also referred to as 'Asset' name."),
)
engagement_name = serializers.CharField(required=False)
engagement_end_date = serializers.DateField(
required=False,
Expand Down Expand Up @@ -2160,8 +2166,14 @@ class CommonImportScanSerializer(serializers.Serializer):
# confused
test_id = serializers.IntegerField(read_only=True)
engagement_id = serializers.IntegerField(read_only=True)
product_id = serializers.IntegerField(read_only=True)
product_type_id = serializers.IntegerField(read_only=True)
product_id = serializers.IntegerField(
read_only=True,
help_text=_("Also referred to as 'Asset' ID."),
)
product_type_id = serializers.IntegerField(
read_only=True,
help_text=_("Also referred to as 'Organization' ID."),
)
statistics = ImportStatisticsSerializer(read_only=True, required=False)
pro = serializers.ListField(read_only=True, required=False)
apply_tags_to_findings = serializers.BooleanField(
Expand Down
8 changes: 6 additions & 2 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
get_authorized_jira_issues,
get_authorized_jira_projects,
)
from dojo.labels import get_labels
from dojo.models import (
Announcement,
Answer,
Expand Down Expand Up @@ -179,6 +180,9 @@
logger = logging.getLogger(__name__)


labels = get_labels()


def schema_with_prefetch() -> dict:
return {
"list": extend_schema(
Expand Down Expand Up @@ -2725,7 +2729,7 @@ def report_generate(request, obj, options):
if type(obj).__name__ == "Product_Type":
product_type = obj

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

findings = report_finding_filter_class(
request.GET,
Expand Down Expand Up @@ -2754,7 +2758,7 @@ def report_generate(request, obj, options):
elif type(obj).__name__ == "Product":
product = obj

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

findings = report_finding_filter_class(
request.GET,
Expand Down
Empty file added dojo/asset/__init__.py
Empty file.
Empty file added dojo/asset/api/__init__.py
Empty file.
121 changes: 121 additions & 0 deletions dojo/asset/api/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from django_filters import BooleanFilter, CharFilter, NumberFilter, OrderingFilter
from django_filters.rest_framework import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field

from dojo.filters import (
CharFieldFilterANDExpression,
CharFieldInFilter,
DateRangeFilter,
DojoFilter,
NumberInFilter,
ProductSLAFilter,
custom_filter,
)
from dojo.labels import get_labels
from dojo.models import (
Product_API_Scan_Configuration,
Product_Group,
Product_Member,
)

labels = get_labels()


class AssetAPIScanConfigurationFilterSet(FilterSet):
asset = NumberFilter(field_name="product")

class Meta:
model = Product_API_Scan_Configuration
fields = ("id", "tool_configuration", "service_key_1", "service_key_2", "service_key_3")


class ApiAssetFilter(DojoFilter):
# BooleanFilter
external_audience = BooleanFilter(field_name="external_audience")
internet_accessible = BooleanFilter(field_name="internet_accessible")
# CharFilter
name = CharFilter(lookup_expr="icontains")
name_exact = CharFilter(field_name="name", lookup_expr="iexact")
description = CharFilter(lookup_expr="icontains")
business_criticality = CharFilter(method=custom_filter, field_name="business_criticality")
platform = CharFilter(method=custom_filter, field_name="platform")
lifecycle = CharFilter(method=custom_filter, field_name="lifecycle")
origin = CharFilter(method=custom_filter, field_name="origin")
# NumberInFilter
id = NumberInFilter(field_name="id", lookup_expr="in")
asset_manager = NumberInFilter(field_name="product_manager", lookup_expr="in")
technical_contact = NumberInFilter(field_name="technical_contact", lookup_expr="in")
team_manager = NumberInFilter(field_name="team_manager", lookup_expr="in")
prod_type = NumberInFilter(field_name="prod_type", lookup_expr="in")
tid = NumberInFilter(field_name="tid", lookup_expr="in")
prod_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in")
user_records = NumberInFilter(field_name="user_records", lookup_expr="in")
regulations = NumberInFilter(field_name="regulations", lookup_expr="in")

tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Tag name contains")
tags = CharFieldInFilter(
field_name="tags__name",
lookup_expr="in",
help_text="Comma separated list of exact tags (uses OR for multiple values)")
tags__and = CharFieldFilterANDExpression(
field_name="tags__name",
help_text="Comma separated list of exact tags to match with an AND expression")
not_tag = CharFilter(field_name="tags__name", lookup_expr="icontains", help_text="Not Tag name contains", exclude="True")
not_tags = CharFieldInFilter(field_name="tags__name", lookup_expr="in",
help_text=labels.ASSET_FILTERS_CSV_TAGS_NOT_HELP, exclude="True")
has_tags = BooleanFilter(field_name="tags", lookup_expr="isnull", exclude=True, label="Has tags")
outside_of_sla = extend_schema_field(OpenApiTypes.NUMBER)(ProductSLAFilter())

# DateRangeFilter
created = DateRangeFilter()
updated = DateRangeFilter()
# NumberFilter
revenue = NumberFilter()

o = OrderingFilter(
# tuple-mapping retains order
fields=(
("id", "id"),
("tid", "tid"),
("name", "name"),
("created", "created"),
("prod_numeric_grade", "prod_numeric_grade"),
("business_criticality", "business_criticality"),
("platform", "platform"),
("lifecycle", "lifecycle"),
("origin", "origin"),
("revenue", "revenue"),
("external_audience", "external_audience"),
("internet_accessible", "internet_accessible"),
("product_manager", "asset_manager"),
("product_manager__first_name", "asset_manager__first_name"),
("product_manager__last_name", "asset_manager__last_name"),
("technical_contact", "technical_contact"),
("technical_contact__first_name", "technical_contact__first_name"),
("technical_contact__last_name", "technical_contact__last_name"),
("team_manager", "team_manager"),
("team_manager__first_name", "team_manager__first_name"),
("team_manager__last_name", "team_manager__last_name"),
("prod_type", "prod_type"),
("prod_type__name", "prod_type__name"),
("updated", "updated"),
("user_records", "user_records"),
),
)


class AssetMemberFilterSet(FilterSet):
asset_id = NumberFilter(field_name="product_id")

class Meta:
model = Product_Member
fields = ("id", "user_id")


class AssetGroupFilterSet(FilterSet):
asset_id = NumberFilter(field_name="product_id")

class Meta:
model = Product_Group
fields = ("id", "group_id")
160 changes: 160 additions & 0 deletions dojo/asset/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied, ValidationError

from dojo.api_v2.serializers import ProductMetaSerializer, TagListSerializerField
from dojo.authorization.authorization import user_has_permission
from dojo.authorization.roles_permissions import Permissions
from dojo.models import (
Dojo_User,
Product,
Product_API_Scan_Configuration,
Product_Group,
Product_Member,
)
from dojo.organization.api.serializers import RelatedOrganizationField
from dojo.product.queries import get_authorized_products


class RelatedAssetField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return get_authorized_products(Permissions.Product_View)


class AssetAPIScanConfigurationSerializer(serializers.ModelSerializer):
asset = RelatedAssetField(source="product")

class Meta:
model = Product_API_Scan_Configuration
exclude = ("product",)


class AssetSerializer(serializers.ModelSerializer):
findings_count = serializers.SerializerMethodField()
findings_list = serializers.SerializerMethodField()

tags = TagListSerializerField(required=False)

# V3 fields
asset_meta = ProductMetaSerializer(source="product_meta", read_only=True, many=True)
organization = RelatedOrganizationField(source="prod_type")
asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade")
enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance")
asset_managers = serializers.PrimaryKeyRelatedField(
source="product_manager",
queryset=Dojo_User.objects.exclude(is_active=False))

class Meta:
model = Product
exclude = (
"tid",
"updated",
"async_updating",
# Below here excluded for V3 migration
"prod_type",
"prod_numeric_grade",
"enable_product_tag_inheritance",
"product_manager",
)

def validate(self, data):
async_updating = getattr(self.instance, "async_updating", None)
if async_updating:
new_sla_config = data.get("sla_configuration", None)
old_sla_config = getattr(self.instance, "sla_configuration", None)
if new_sla_config and old_sla_config and new_sla_config != old_sla_config:
msg = "Finding SLA expiration dates are currently being recalculated. The SLA configuration for this asset cannot be changed until the calculation is complete."
raise serializers.ValidationError(msg)
return data

def get_findings_count(self, obj) -> int:
return obj.findings_count

# TODO: maybe extend_schema_field is needed here?
def get_findings_list(self, obj) -> list[int]:
return obj.open_findings_list()


class AssetMemberSerializer(serializers.ModelSerializer):
asset = RelatedAssetField(source="product")

class Meta:
model = Product_Member
exclude = ("product",)

def validate(self, data):
if (
self.instance is not None
and data.get("asset") != self.instance.product
and not user_has_permission(
self.context["request"].user,
data.get("asset"),
Permissions.Product_Manage_Members,
)
):
msg = "You are not permitted to add a member to this Asset"
raise PermissionDenied(msg)

if (
self.instance is None
or data.get("asset") != self.instance.product
or data.get("user") != self.instance.user
):
members = Product_Member.objects.filter(
product=data.get("asset"), user=data.get("user"),
)
if members.count() > 0:
msg = "Asset Member already exists"
raise ValidationError(msg)

if data.get("role").is_owner and not user_has_permission(
self.context["request"].user,
data.get("asset"),
Permissions.Product_Member_Add_Owner,
):
msg = "You are not permitted to add a member as Owner to this Asset"
raise PermissionDenied(msg)

return data


class AssetGroupSerializer(serializers.ModelSerializer):
asset = RelatedAssetField(source="product")

class Meta:
model = Product_Group
exclude = ("product",)

def validate(self, data):
if (
self.instance is not None
and data.get("asset") != self.instance.product
and not user_has_permission(
self.context["request"].user,
data.get("asset"),
Permissions.Product_Group_Add,
)
):
msg = "You are not permitted to add a group to this Asset"
raise PermissionDenied(msg)

if (
self.instance is None
or data.get("asset") != self.instance.product
or data.get("group") != self.instance.group
):
members = Product_Group.objects.filter(
product=data.get("asset"), group=data.get("group"),
)
if members.count() > 0:
msg = "Asset Group already exists"
raise ValidationError(msg)

if data.get("role").is_owner and not user_has_permission(
self.context["request"].user,
data.get("asset"),
Permissions.Product_Group_Add_Owner,
):
msg = "You are not permitted to add a group as Owner to this Asset"
raise PermissionDenied(msg)

return data
15 changes: 15 additions & 0 deletions dojo/asset/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dojo.asset.api.views import (
AssetAPIScanConfigurationViewSet,
AssetGroupViewSet,
AssetMemberViewSet,
AssetViewSet,
)


def add_asset_urls(router):
router.register(r"assets", AssetViewSet, basename="asset")
router.register(r"asset_api_scan_configurations", AssetAPIScanConfigurationViewSet,
basename="asset_api_scan_configuration")
router.register(r"asset_groups", AssetGroupViewSet, basename="asset_group")
router.register(r"asset_members", AssetMemberViewSet, basename="asset_member")
return router
Loading