From 5a5152ffbdbf1dc3e3df5bc4b9da261499683d6d Mon Sep 17 00:00:00 2001 From: Stefan Fleckenstein Date: Sat, 28 Dec 2024 18:37:17 +0000 Subject: [PATCH] chore: enhance performance of loading products (#2385) --- .../core/api/serializers_product.py | 146 +++++++++------- backend/application/core/api/views.py | 2 +- backend/application/core/models.py | 165 ------------------ backend/application/core/queries/product.py | 90 +++++++++- backend/application/core/services/product.py | 40 ++++- .../core/services/security_gate.py | 93 ++++++++-- .../application/licenses/api/serializers.py | 2 +- .../licenses/queries/license_policy.py | 2 +- backend/config/settings/base.py | 12 +- .../api/test_authorization_products.py | 10 +- .../unittests/core/api/test_serializers.py | 88 ---------- backend/unittests/core/test_models.py | 79 --------- 12 files changed, 302 insertions(+), 427 deletions(-) diff --git a/backend/application/core/api/serializers_product.py b/backend/application/core/api/serializers_product.py index 9c98dbe14..f6b84bce2 100644 --- a/backend/application/core/api/serializers_product.py +++ b/backend/application/core/api/serializers_product.py @@ -2,6 +2,7 @@ from typing import Optional from rest_framework.serializers import ( + IntegerField, ModelSerializer, SerializerMethodField, ValidationError, @@ -36,69 +37,30 @@ get_product_authorization_group_member, get_product_member, ) +from application.core.services.product import ( + get_product_group_license_count, + get_product_group_observation_count, +) from application.core.services.risk_acceptance_expiry import ( calculate_risk_acceptance_expiry_date, ) -from application.core.types import Assessment_Status, Status +from application.core.types import Assessment_Status, Severity, Status from application.issue_tracker.types import Issue_Tracker from application.licenses.models import License_Component +from application.licenses.types import License_Policy_Evaluation_Result from application.rules.models import Rule from application.rules.types import Rule_Status class ProductCoreSerializer(ModelSerializer): - open_critical_observation_count = SerializerMethodField() - open_high_observation_count = SerializerMethodField() - open_medium_observation_count = SerializerMethodField() - open_low_observation_count = SerializerMethodField() - open_none_observation_count = SerializerMethodField() - open_unknown_observation_count = SerializerMethodField() - forbidden_licenses_count = SerializerMethodField() - review_required_licenses_count = SerializerMethodField() - unknown_licenses_count = SerializerMethodField() - allowed_licenses_count = SerializerMethodField() - ignored_licenses_count = SerializerMethodField() permissions = SerializerMethodField() - class Meta: - model = Product - fields = "__all__" - - def get_open_critical_observation_count(self, obj: Product) -> int: - return obj.open_critical_observation_count - - def get_open_high_observation_count(self, obj: Product) -> int: - return obj.open_high_observation_count - - def get_open_medium_observation_count(self, obj: Product) -> int: - return obj.open_medium_observation_count - - def get_open_low_observation_count(self, obj: Product) -> int: - return obj.open_low_observation_count - - def get_open_none_observation_count(self, obj: Product) -> int: - return obj.open_none_observation_count - - def get_open_unknown_observation_count(self, obj: Product) -> int: - return obj.open_unknown_observation_count - def get_permissions(self, obj: Product) -> list[Permissions]: return get_permissions_for_role(get_highest_user_role(obj)) - def get_forbidden_licenses_count(self, obj: Product) -> int: - return obj.forbidden_licenses_count - - def get_review_required_licenses_count(self, obj: Product) -> int: - return obj.review_required_licenses_count - - def get_unknown_licenses_count(self, obj: Product) -> int: - return obj.unknown_licenses_count - - def get_allowed_licenses_count(self, obj: Product) -> int: - return obj.allowed_licenses_count - - def get_ignored_licenses_count(self, obj: Product) -> int: - return obj.ignored_licenses_count + class Meta: + model = Product + fields = "__all__" def validate(self, attrs: dict): if attrs.get("repository_branch_housekeeping_active"): @@ -133,9 +95,74 @@ def validate(self, attrs: dict): class ProductGroupSerializer(ProductCoreSerializer): + open_critical_observation_count = SerializerMethodField() + open_high_observation_count = SerializerMethodField() + open_medium_observation_count = SerializerMethodField() + open_low_observation_count = SerializerMethodField() + open_none_observation_count = SerializerMethodField() + open_unknown_observation_count = SerializerMethodField() + forbidden_licenses_count = SerializerMethodField() + review_required_licenses_count = SerializerMethodField() + unknown_licenses_count = SerializerMethodField() + allowed_licenses_count = SerializerMethodField() + ignored_licenses_count = SerializerMethodField() products_count = SerializerMethodField() product_rule_approvals = SerializerMethodField() + def get_open_critical_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_CRITICAL) + + def get_open_high_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_HIGH) + + def get_open_medium_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_MEDIUM) + + def get_open_low_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_LOW) + + def get_open_none_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_NONE) + + def get_open_unknown_observation_count(self, obj: Product) -> int: + return get_product_group_observation_count(obj, Severity.SEVERITY_UNKNOWN) + + def get_forbidden_licenses_count(self, obj: Product) -> int: + return get_product_group_license_count( + obj, License_Policy_Evaluation_Result.RESULT_FORBIDDEN + ) + + def get_review_required_licenses_count(self, obj: Product) -> int: + return get_product_group_license_count( + obj, License_Policy_Evaluation_Result.RESULT_REVIEW_REQUIRED + ) + + def get_unknown_licenses_count(self, obj: Product) -> int: + return get_product_group_license_count( + obj, License_Policy_Evaluation_Result.RESULT_UNKNOWN + ) + + def get_allowed_licenses_count(self, obj: Product) -> int: + return get_product_group_license_count( + obj, License_Policy_Evaluation_Result.RESULT_ALLOWED + ) + + def get_ignored_licenses_count(self, obj: Product) -> int: + return get_product_group_license_count( + obj, License_Policy_Evaluation_Result.RESULT_IGNORED + ) + + def get_products_count(self, obj: Product) -> int: + return obj.products.count() + + def get_product_rule_approvals(self, obj: Product) -> int: + if not obj.product_rules_need_approval: + return 0 + + return Rule.objects.filter( + product=obj, approval_status=Rule_Status.RULE_STATUS_NEEDS_APPROVAL + ).count() + class Meta: model = Product fields = [ @@ -177,17 +204,6 @@ class Meta: "ignored_licenses_count", ] - def get_products_count(self, obj: Product) -> int: - return obj.products.count() - - def get_product_rule_approvals(self, obj: Product) -> int: - if not obj.product_rules_need_approval: - return 0 - - return Rule.objects.filter( - product=obj, approval_status=Rule_Status.RULE_STATUS_NEEDS_APPROVAL - ).count() - def create(self, validated_data: dict) -> Product: product_group = super().create(validated_data) product_group.is_product_group = True @@ -212,6 +228,18 @@ class ProductSerializer( ProductCoreSerializer ): # pylint: disable=too-many-public-methods # all these methods are needed + open_critical_observation_count = IntegerField(read_only=True) + open_high_observation_count = IntegerField(read_only=True) + open_medium_observation_count = IntegerField(read_only=True) + open_low_observation_count = IntegerField(read_only=True) + open_none_observation_count = IntegerField(read_only=True) + open_unknown_observation_count = IntegerField(read_only=True) + forbidden_licenses_count = IntegerField(read_only=True) + review_required_licenses_count = IntegerField(read_only=True) + unknown_licenses_count = IntegerField(read_only=True) + allowed_licenses_count = IntegerField(read_only=True) + ignored_licenses_count = IntegerField(read_only=True) + product_group_name = SerializerMethodField() product_group_repository_branch_housekeeping_active = SerializerMethodField() product_group_security_gate_active = SerializerMethodField() diff --git a/backend/application/core/api/views.py b/backend/application/core/api/views.py index 409648239..2cef74b9b 100644 --- a/backend/application/core/api/views.py +++ b/backend/application/core/api/views.py @@ -164,7 +164,7 @@ class ProductViewSet(ModelViewSet): def get_queryset(self): return ( - get_products(is_product_group=False) + get_products(is_product_group=False, with_annotations=True) .select_related("product_group") .select_related("repository_default_branch") ) diff --git a/backend/application/core/models.py b/backend/application/core/models.py index 1156afbd5..dbcb837b7 100644 --- a/backend/application/core/models.py +++ b/backend/application/core/models.py @@ -141,171 +141,6 @@ class Meta: def __str__(self): return self.name - @property - def open_critical_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_critical_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_CRITICAL, - ).count() - - @property - def open_high_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_high_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_HIGH, - ).count() - - @property - def open_medium_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_medium_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_MEDIUM, - ).count() - - @property - def open_low_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_low_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_LOW, - ).count() - - @property - def open_none_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_none_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_NONE, - ).count() - - @property - def open_unknown_observation_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.open_unknown_observation_count - return count - - return Observation.objects.filter( - product=self, - branch=self.repository_default_branch, - current_status=Status.STATUS_OPEN, - current_severity=Severity.SEVERITY_UNKNOWN, - ).count() - - @property - def forbidden_licenses_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.forbidden_licenses_count - return count - - License_Component = apps.get_model("licenses", "License_Component") - return License_Component.objects.filter( - product=self, - branch=self.repository_default_branch, - evaluation_result=License_Policy_Evaluation_Result.RESULT_FORBIDDEN, - ).count() - - @property - def review_required_licenses_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.review_required_licenses_count - return count - - License_Component = apps.get_model("licenses", "License_Component") - return License_Component.objects.filter( - product=self, - branch=self.repository_default_branch, - evaluation_result=License_Policy_Evaluation_Result.RESULT_REVIEW_REQUIRED, - ).count() - - @property - def unknown_licenses_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.unknown_licenses_count - return count - - License_Component = apps.get_model("licenses", "License_Component") - return License_Component.objects.filter( - product=self, - branch=self.repository_default_branch, - evaluation_result=License_Policy_Evaluation_Result.RESULT_UNKNOWN, - ).count() - - @property - def allowed_licenses_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.allowed_licenses_count - return count - - License_Component = apps.get_model("licenses", "License_Component") - return License_Component.objects.filter( - product=self, - branch=self.repository_default_branch, - evaluation_result=License_Policy_Evaluation_Result.RESULT_ALLOWED, - ).count() - - @property - def ignored_licenses_count(self): - if self.is_product_group: - count = 0 - for product in Product.objects.filter(product_group=self): - count += product.ignored_licenses_count - return count - - License_Component = apps.get_model("licenses", "License_Component") - return License_Component.objects.filter( - product=self, - branch=self.repository_default_branch, - evaluation_result=License_Policy_Evaluation_Result.RESULT_IGNORED, - ).count() - class Branch(Model): product = ForeignKey(Product, on_delete=CASCADE) diff --git a/backend/application/core/queries/product.py b/backend/application/core/queries/product.py index e21223d77..19360ccdb 100644 --- a/backend/application/core/queries/product.py +++ b/backend/application/core/queries/product.py @@ -1,14 +1,19 @@ from typing import Optional -from django.db.models import Exists, OuterRef, Q +from django.db.models import Count, Exists, F, IntegerField, OuterRef, Q, Subquery +from django.db.models.functions import Coalesce from django.db.models.query import QuerySet from application.commons.services.global_request import get_current_user from application.core.models import ( + Observation, Product, Product_Authorization_Group_Member, Product_Member, ) +from application.core.types import Severity, Status +from application.licenses.models import License_Component +from application.licenses.types import License_Policy_Evaluation_Result def get_product_by_id( @@ -31,7 +36,9 @@ def get_product_by_name(name: str, is_product_group: bool = None) -> Optional[Pr return None -def get_products(is_product_group: bool = None) -> QuerySet[Product]: +def get_products( + is_product_group: Optional[bool] = None, with_annotations: Optional[bool] = False +) -> QuerySet[Product]: user = get_current_user() if user is None: @@ -39,6 +46,46 @@ def get_products(is_product_group: bool = None) -> QuerySet[Product]: products = Product.objects.all() + if not is_product_group and with_annotations: + subquery_open_critical = _get_observation_subquery(Severity.SEVERITY_CRITICAL) + subquery_open_high = _get_observation_subquery(Severity.SEVERITY_HIGH) + subquery_open_medium = _get_observation_subquery(Severity.SEVERITY_MEDIUM) + subquery_open_low = _get_observation_subquery(Severity.SEVERITY_LOW) + subquery_open_none = _get_observation_subquery(Severity.SEVERITY_NONE) + subquery_open_unknown = _get_observation_subquery(Severity.SEVERITY_UNKNOWN) + + subquery_license_forbidden = _get_license_subquery( + License_Policy_Evaluation_Result.RESULT_FORBIDDEN + ) + subquery_license_review_required = _get_license_subquery( + License_Policy_Evaluation_Result.RESULT_REVIEW_REQUIRED + ) + subquery_license_unknown = _get_license_subquery( + License_Policy_Evaluation_Result.RESULT_UNKNOWN + ) + subquery_license_allowed = _get_license_subquery( + License_Policy_Evaluation_Result.RESULT_ALLOWED + ) + subquery_license_ignored = _get_license_subquery( + License_Policy_Evaluation_Result.RESULT_IGNORED + ) + + products = products.annotate( + open_critical_observation_count=Coalesce(subquery_open_critical, 0), + open_high_observation_count=Coalesce(subquery_open_high, 0), + open_medium_observation_count=Coalesce(subquery_open_medium, 0), + open_low_observation_count=Coalesce(subquery_open_low, 0), + open_none_observation_count=Coalesce(subquery_open_none, 0), + open_unknown_observation_count=Coalesce(subquery_open_unknown, 0), + forbidden_licenses_count=Coalesce(subquery_license_forbidden, 0), + review_required_licenses_count=Coalesce( + subquery_license_review_required, 0 + ), + unknown_licenses_count=Coalesce(subquery_license_unknown, 0), + allowed_licenses_count=Coalesce(subquery_license_allowed, 0), + ignored_licenses_count=Coalesce(subquery_license_ignored, 0), + ) + if not user.is_superuser: product_members = Product_Member.objects.filter( product=OuterRef("pk"), user=user @@ -80,3 +127,42 @@ def get_products(is_product_group: bool = None) -> QuerySet[Product]: products = products.filter(is_product_group=is_product_group) return products + + +def _get_observation_subquery(severity: str) -> Subquery: + branch_filter = Q(branch=F("product__repository_default_branch")) | ( + Q(branch__isnull=True) & Q(product__repository_default_branch__isnull=True) + ) + + return Subquery( + Observation.objects.filter( + branch_filter, + product=OuterRef("pk"), + current_status=Status.STATUS_OPEN, + current_severity=severity, + ) + .order_by() + .values("product") + .annotate(count=Count("pk")) + .values("count"), + output_field=IntegerField(), + ) + + +def _get_license_subquery(evaluation_result: str) -> Subquery: + branch_filter = Q(branch=F("product__repository_default_branch")) | ( + Q(branch__isnull=True) & Q(product__repository_default_branch__isnull=True) + ) + + return Subquery( + License_Component.objects.filter( + branch_filter, + product=OuterRef("pk"), + evaluation_result=evaluation_result, + ) + .order_by() + .values("product") + .annotate(count=Count("pk")) + .values("count"), + output_field=IntegerField(), + ) diff --git a/backend/application/core/services/product.py b/backend/application/core/services/product.py index 690e7f5ee..6c2effc49 100644 --- a/backend/application/core/services/product.py +++ b/backend/application/core/services/product.py @@ -1,5 +1,7 @@ -from application.core.models import Product +from application.core.models import Observation, Product from application.core.queries.branch import get_branches_by_product +from application.core.types import Status +from application.licenses.models import License_Component def set_repository_default_branch(product: Product) -> None: @@ -21,3 +23,39 @@ def set_repository_default_branch(product: Product) -> None: if new_repository_default_branch != current_repository_default_branch: product.repository_default_branch = new_repository_default_branch product.save() + + +def get_product_group_observation_count(product_group: Product, severity: str) -> int: + if not product_group.is_product_group: + raise ValueError(f"{product_group.name} is not a product group") + + count = 0 + for product in Product.objects.filter(product_group=product_group): + count += get_product_observation_count(product, severity) + return count + + +def get_product_observation_count(product: Product, severity: str) -> int: + return Observation.objects.filter( + product=product, + branch=product.repository_default_branch, + current_status=Status.STATUS_OPEN, + current_severity=severity, + ).count() + + +def get_product_group_license_count( + product_group: Product, evaluation_result: str +) -> int: + count = 0 + for product in Product.objects.filter(product_group=product_group): + count += get_product_license_count(product, evaluation_result) + return count + + +def get_product_license_count(product: Product, evaluation_result: str) -> int: + return License_Component.objects.filter( + product=product, + branch=product.repository_default_branch, + evaluation_result=evaluation_result, + ).count() diff --git a/backend/application/core/services/security_gate.py b/backend/application/core/services/security_gate.py index f92e6915d..1f9ba1998 100644 --- a/backend/application/core/services/security_gate.py +++ b/backend/application/core/services/security_gate.py @@ -5,9 +5,14 @@ send_product_security_gate_notification, ) from application.core.models import Product +from application.core.services.product import get_product_observation_count +from application.core.types import Severity def check_security_gate(product: Product) -> None: + if product.is_product_group: + raise ValueError(f"{product.name} is a product group") + settings = Settings.load() initial_security_gate_passed = product.security_gate_passed @@ -38,36 +43,81 @@ def _calculate_active_product_security_gate(product: Product) -> bool: if product.product_group and product.product_group.security_gate_active is True: security_gate_threshold_critical = ( product.product_group.security_gate_threshold_critical + if product.product_group.security_gate_threshold_critical + else 0 ) security_gate_threshold_high = ( product.product_group.security_gate_threshold_high + if product.product_group.security_gate_threshold_high + else 0 ) security_gate_threshold_medium = ( product.product_group.security_gate_threshold_medium + if product.product_group.security_gate_threshold_medium + else 0 + ) + security_gate_threshold_low = ( + product.product_group.security_gate_threshold_low + if product.product_group.security_gate_threshold_low + else 0 ) - security_gate_threshold_low = product.product_group.security_gate_threshold_low security_gate_threshold_none = ( product.product_group.security_gate_threshold_none + if product.product_group.security_gate_threshold_none + else 0 ) security_gate_threshold_unknown = ( product.product_group.security_gate_threshold_unknown + if product.product_group.security_gate_threshold_unknown + else 0 ) else: - security_gate_threshold_critical = product.security_gate_threshold_critical - security_gate_threshold_high = product.security_gate_threshold_high - security_gate_threshold_medium = product.security_gate_threshold_medium - security_gate_threshold_low = product.security_gate_threshold_low - security_gate_threshold_none = product.security_gate_threshold_none - security_gate_threshold_unknown = product.security_gate_threshold_unknown + security_gate_threshold_critical = ( + product.security_gate_threshold_critical + if product.security_gate_threshold_critical + else 0 + ) + security_gate_threshold_high = ( + product.security_gate_threshold_high + if product.security_gate_threshold_high + else 0 + ) + security_gate_threshold_medium = ( + product.security_gate_threshold_medium + if product.security_gate_threshold_medium + else 0 + ) + security_gate_threshold_low = ( + product.security_gate_threshold_low + if product.security_gate_threshold_low + else 0 + ) + security_gate_threshold_none = ( + product.security_gate_threshold_none + if product.security_gate_threshold_none + else 0 + ) + security_gate_threshold_unknown = ( + product.security_gate_threshold_unknown + if product.security_gate_threshold_unknown + else 0 + ) if ( - product.open_critical_observation_count # pylint: disable=too-many-boolean-expressions + get_product_observation_count( # pylint: disable=too-many-boolean-expressions + product, Severity.SEVERITY_CRITICAL + ) > security_gate_threshold_critical - or product.open_high_observation_count > security_gate_threshold_high - or product.open_medium_observation_count > security_gate_threshold_medium - or product.open_low_observation_count > security_gate_threshold_low - or product.open_none_observation_count > security_gate_threshold_none - or product.open_unknown_observation_count > security_gate_threshold_unknown + or get_product_observation_count(product, Severity.SEVERITY_HIGH) + > security_gate_threshold_high + or get_product_observation_count(product, Severity.SEVERITY_MEDIUM) + > security_gate_threshold_medium + or get_product_observation_count(product, Severity.SEVERITY_LOW) + > security_gate_threshold_low + or get_product_observation_count(product, Severity.SEVERITY_NONE) + > security_gate_threshold_none + or get_product_observation_count(product, Severity.SEVERITY_UNKNOWN) + > security_gate_threshold_unknown ): new_security_gate_passed = False @@ -79,14 +129,19 @@ def _calculate_active_config_security_gate(product: Product) -> bool: new_security_gate_passed = True if ( - product.open_critical_observation_count # pylint: disable=too-many-boolean-expressions + get_product_observation_count( # pylint: disable=too-many-boolean-expressions + product, Severity.SEVERITY_CRITICAL + ) > settings.security_gate_threshold_critical - or product.open_high_observation_count > settings.security_gate_threshold_high - or product.open_medium_observation_count + or get_product_observation_count(product, Severity.SEVERITY_HIGH) + > settings.security_gate_threshold_high + or get_product_observation_count(product, Severity.SEVERITY_MEDIUM) > settings.security_gate_threshold_medium - or product.open_low_observation_count > settings.security_gate_threshold_low - or product.open_none_observation_count > settings.security_gate_threshold_none - or product.open_unknown_observation_count + or get_product_observation_count(product, Severity.SEVERITY_LOW) + > settings.security_gate_threshold_low + or get_product_observation_count(product, Severity.SEVERITY_NONE) + > settings.security_gate_threshold_none + or get_product_observation_count(product, Severity.SEVERITY_UNKNOWN) > settings.security_gate_threshold_unknown ): new_security_gate_passed = False diff --git a/backend/application/licenses/api/serializers.py b/backend/application/licenses/api/serializers.py index 156ef7522..5bc452de9 100644 --- a/backend/application/licenses/api/serializers.py +++ b/backend/application/licenses/api/serializers.py @@ -362,7 +362,7 @@ def get_is_manager(self, obj: License_Policy) -> bool: return False def get_has_products(self, obj: License_Policy) -> bool: - return get_products().filter(license_policy=obj).exists() + return get_products(is_product_group=False).filter(license_policy=obj).exists() def get_has_product_groups(self, obj: License_Policy) -> bool: return get_products(is_product_group=True).filter(license_policy=obj).exists() diff --git a/backend/application/licenses/queries/license_policy.py b/backend/application/licenses/queries/license_policy.py index a305032cd..4545f3260 100644 --- a/backend/application/licenses/queries/license_policy.py +++ b/backend/application/licenses/queries/license_policy.py @@ -26,7 +26,7 @@ def get_license_policies() -> QuerySet[License_Policy]: if user.is_superuser: return license_policies - products = get_products() + products = get_products(is_product_group=False) return license_policies.filter( Q(users=user) diff --git a/backend/config/settings/base.py b/backend/config/settings/base.py index b6f0472af..ea01d08ae 100644 --- a/backend/config/settings/base.py +++ b/backend/config/settings/base.py @@ -336,13 +336,13 @@ def whitenoise_security_headers(headers, path, url): }, "root": {"level": "INFO", "handlers": ["console"]}, "loggers": { - # 'django.db.backends': { - # 'level': 'DEBUG', - # 'handlers': ['console'], + # "django.db.backends": { + # "level": "DEBUG", + # "handlers": ["console"], # }, - # 'django_auth_adfs': { - # 'handlers': ['console'], - # 'level': 'DEBUG', + # "django_auth_adfs": { + # "handlers": ["console"], + # "level": "DEBUG", # }, "werkzeug": { "handlers": ["console"], diff --git a/backend/unittests/access_control/api/test_authorization_products.py b/backend/unittests/access_control/api/test_authorization_products.py index 89547d7dd..36ea5ae4e 100644 --- a/backend/unittests/access_control/api/test_authorization_products.py +++ b/backend/unittests/access_control/api/test_authorization_products.py @@ -16,18 +16,18 @@ def test_authorization_products_product_authorization_group_member(self): self._test_authorization_products() def _test_authorization_products(self): - expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}, {'id': 2, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_external', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_external', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': None, 'security_gate_active': False, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': None, 'repository_default_branch': 3, 'license_policy': None}]}" + expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}, {'id': 2, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_external', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_external', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': None, 'security_gate_active': False, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': None, 'repository_default_branch': 3, 'license_policy': None}]}" self._test_api( APITest("db_admin", "get", "/api/products/", None, 200, expected_data) ) - expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 1, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}]}" + expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}]}" self._test_api( APITest( "db_internal_write", "get", "/api/products/", None, 200, expected_data ) ) - expected_data = "{'id': 1, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}" + expected_data = "{'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}" self._test_api( APITest( "db_internal_write", "get", "/api/products/1/", None, 200, expected_data @@ -63,7 +63,7 @@ def _test_authorization_products(self): expected_data, ) ) - expected_data = "{'id': 5, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': '', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': False, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': False, 'has_licenses': False, 'product_group_license_policy': None, 'name': 'string', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': None, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': None, 'repository_default_branch': None, 'license_policy': None}" + expected_data = "{'id': 5, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': '', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': False, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': False, 'has_licenses': False, 'product_group_license_policy': None, 'name': 'string', 'description': '', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': None, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': None, 'repository_default_branch': None, 'license_policy': None}" self._test_api( APITest( "db_internal_write", @@ -91,7 +91,7 @@ def _test_authorization_products(self): expected_data, ) ) - expected_data = "{'id': 1, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': 'string', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}" + expected_data = "{'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'open_critical_observation_count': 0, 'open_high_observation_count': 0, 'open_medium_observation_count': 0, 'open_low_observation_count': 0, 'open_none_observation_count': 0, 'open_unknown_observation_count': 0, 'forbidden_licenses_count': 0, 'review_required_licenses_count': 0, 'unknown_licenses_count': 0, 'allowed_licenses_count': 0, 'ignored_licenses_count': 0, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'product_group_assessments_need_approval': False, 'repository_default_branch_name': 'db_branch_internal_dev', 'observation_reviews': 0, 'observation_log_approvals': 0, 'has_services': True, 'product_group_product_rules_need_approval': False, 'product_rule_approvals': 0, 'risk_acceptance_expiry_date_calculated': datetime.date(2024, 7, 1), 'product_group_new_observations_in_review': False, 'has_branches': True, 'has_licenses': True, 'product_group_license_policy': None, 'name': 'db_product_internal', 'description': 'string', 'purl': '', 'cpe23': '', 'repository_prefix': '', 'repository_branch_housekeeping_active': None, 'repository_branch_housekeeping_keep_inactive_days': None, 'repository_branch_housekeeping_exempt_branches': '', 'security_gate_passed': True, 'security_gate_active': None, 'security_gate_threshold_critical': None, 'security_gate_threshold_high': None, 'security_gate_threshold_medium': None, 'security_gate_threshold_low': None, 'security_gate_threshold_none': None, 'security_gate_threshold_unknown': None, 'apply_general_rules': True, 'notification_ms_teams_webhook': '', 'notification_slack_webhook': '', 'notification_email_to': '', 'issue_tracker_active': False, 'issue_tracker_type': '', 'issue_tracker_base_url': '', 'issue_tracker_username': '', 'issue_tracker_api_key': '', 'issue_tracker_project_id': '', 'issue_tracker_labels': '', 'issue_tracker_issue_type': '', 'issue_tracker_status_closed': '', 'issue_tracker_minimum_severity': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'assessments_need_approval': False, 'new_observations_in_review': False, 'product_rules_need_approval': False, 'risk_acceptance_expiry_active': None, 'risk_acceptance_expiry_days': None, 'has_cloud_resource': False, 'has_component': False, 'has_docker_image': False, 'has_endpoint': False, 'has_kubernetes_resource': False, 'has_source': False, 'has_potential_duplicates': False, 'product_group': 3, 'repository_default_branch': 1, 'license_policy': None}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/core/api/test_serializers.py b/backend/unittests/core/api/test_serializers.py index 569fbce19..df30bf322 100644 --- a/backend/unittests/core/api/test_serializers.py +++ b/backend/unittests/core/api/test_serializers.py @@ -103,94 +103,6 @@ def test_get_open_unknown_observation_count(self, mock_filter): current_status=Status.STATUS_OPEN, ) - -class TestProductSerializer(BaseTestCase): - @patch("application.core.models.Observation.objects.filter") - def test_get_open_critical_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, - product_serializer.get_open_critical_observation_count(obj=self.product_1), - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_CRITICAL, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_get_open_high_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, product_serializer.get_open_high_observation_count(obj=self.product_1) - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_HIGH, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_get_open_medium_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, product_serializer.get_open_medium_observation_count(obj=self.product_1) - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_MEDIUM, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_get_open_low_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, product_serializer.get_open_low_observation_count(obj=self.product_1) - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_LOW, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_get_open_none_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, product_serializer.get_open_none_observation_count(obj=self.product_1) - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_NONE, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_get_open_unknown_observation_count(self, mock_filter): - mock_filter.return_value.count.return_value = 99 - product_serializer = ProductSerializer() - self.assertEqual( - 99, - product_serializer.get_open_unknown_observation_count(obj=self.product_1), - ) - mock_filter.assert_called_with( - product=self.product_1, - branch=self.branch_1, - current_severity=Severity.SEVERITY_UNKNOWN, - current_status=Status.STATUS_OPEN, - ) - @patch("application.core.api.serializers_product.get_current_user") @patch("application.core.api.serializers_product.get_highest_user_role") @patch("application.core.api.serializers_product.get_permissions_for_role") diff --git a/backend/unittests/core/test_models.py b/backend/unittests/core/test_models.py index d0268afd2..c3047e00b 100644 --- a/backend/unittests/core/test_models.py +++ b/backend/unittests/core/test_models.py @@ -1,89 +1,10 @@ from unittest.mock import patch from application.core.models import Observation, Product -from application.core.types import Severity, Status from application.import_observations.models import Parser from unittests.base_test_case import BaseTestCase -class TestProduct(BaseTestCase): - def test_str(self): - product = Product(name="product_name") - self.assertEqual("product_name", str(product)) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_critical(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_critical_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_CRITICAL, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_high(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_high_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_HIGH, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_medium(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_medium_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_MEDIUM, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_low(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_low_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_LOW, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_none(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_none_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_NONE, - current_status=Status.STATUS_OPEN, - ) - - @patch("application.core.models.Observation.objects.filter") - def test_observation_count_unknown(self, mock): - mock.return_value.count.return_value = 99 - product = Product(name="product_name") - self.assertEqual(99, product.open_unknown_observation_count) - mock.assert_called_with( - product=product, - branch=None, - current_severity=Severity.SEVERITY_UNKNOWN, - current_status=Status.STATUS_OPEN, - ) - - class TestParser(BaseTestCase): def test_str(self): parser = Parser(name="parser_name")