diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index 9dd5264cd..dc671a44f 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - chore/doc-about + - fix/doc_api_base_url permissions: contents: write diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..89640d319 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Reporting a Vulnerability + +Please use the ["Report a vulnerability"](https://github.com/MaibornWolff/SecObserve/security/advisories/new) button in the GitHub repository (under the "Security" tab) to report a vulnerability. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. diff --git a/backend/application/__init__.py b/backend/application/__init__.py index 5b6018861..e4adfb83d 100644 --- a/backend/application/__init__.py +++ b/backend/application/__init__.py @@ -1 +1 @@ -__version__ = "1.5.0" +__version__ = "1.6.0" diff --git a/backend/application/commons/api/permissions.py b/backend/application/commons/api/permissions.py new file mode 100644 index 000000000..ece3e6ffd --- /dev/null +++ b/backend/application/commons/api/permissions.py @@ -0,0 +1,21 @@ +from rest_framework.permissions import BasePermission + +from application.access_control.api.permissions import check_object_permission +from application.access_control.services.roles_permissions import Permissions + + +class UserHasNotificationPermission(BasePermission): + def has_object_permission(self, request, view, obj): + if obj.product: + return check_object_permission( + request, + obj.product, + Permissions.Product_View, + None, + Permissions.Product_Delete, + ) + + if request.user and request.user.is_superuser: + return True + + return False diff --git a/backend/application/commons/api/views.py b/backend/application/commons/api/views.py index d34530196..6b513cc71 100644 --- a/backend/application/commons/api/views.py +++ b/backend/application/commons/api/views.py @@ -4,12 +4,14 @@ from rest_framework.exceptions import ValidationError from rest_framework.filters import SearchFilter from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.status import HTTP_204_NO_CONTENT from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet from application.commons.api.filters import NotificationFilter +from application.commons.api.permissions import UserHasNotificationPermission from application.commons.api.serializers import ( NotificationBulkSerializer, NotificationSerializer, @@ -49,6 +51,7 @@ class NotificationViewSet( ): serializer_class = NotificationSerializer filterset_class = NotificationFilter + permission_classes = (IsAuthenticated, UserHasNotificationPermission) queryset = Notification.objects.all() filter_backends = [SearchFilter, DjangoFilterBackend] search_fields = ["name"] diff --git a/backend/application/commons/services/notification.py b/backend/application/commons/services/notification.py index 44fe24cf9..39d164e5f 100644 --- a/backend/application/commons/services/notification.py +++ b/backend/application/commons/services/notification.py @@ -1,8 +1,11 @@ from django.db.models.query import QuerySet from rest_framework.exceptions import ValidationError +from application.access_control.services.authorization import user_has_permission +from application.access_control.services.roles_permissions import Permissions from application.commons.models import Notification from application.commons.queries.notification import get_notifications +from application.commons.services.global_request import get_current_user def bulk_delete(notification_ids: list[int]) -> None: @@ -15,4 +18,17 @@ def _check_notifications(notification_ids: list[int]) -> QuerySet[Notification]: if len(notifications) != len(notification_ids): raise ValidationError("Some notifications do not exist") + user = get_current_user() + if not user: + raise ValidationError("No user in backend request") + + if not user.is_superuser: + for notification in notifications: + if not notification.product or not user_has_permission( + notification.product, Permissions.Product_Delete + ): + raise ValidationError( + "User does not have permission to delete some notifications" + ) + return notifications diff --git a/backend/application/core/api/filters.py b/backend/application/core/api/filters.py index 2ec9618b6..ea3dfdd8e 100644 --- a/backend/application/core/api/filters.py +++ b/backend/application/core/api/filters.py @@ -20,6 +20,8 @@ Product_Member, Service, ) +from application.core.types import Status +from application.import_observations.types import Parser_Source, Parser_Type AGE_DAY = "Today" AGE_WEEK = "Past 7 days" @@ -138,8 +140,8 @@ class Meta: class ParserFilter(FilterSet): name = CharFilter(field_name="name", lookup_expr="icontains") - type = ChoiceFilter(field_name="type", choices=Parser.TYPE_CHOICES) - source = ChoiceFilter(field_name="source", choices=Parser.SOURCE_CHOICES) + type = ChoiceFilter(field_name="type", choices=Parser_Type.TYPE_CHOICES) + source = ChoiceFilter(field_name="source", choices=Parser_Source.SOURCE_CHOICES) ordering = OrderingFilter( # tuple-mapping retains order @@ -250,7 +252,7 @@ class Meta: class PotentialDuplicateFilter(FilterSet): status = ChoiceFilter( field_name="potential_duplicate_observation__current_status", - choices=Observation.STATUS_CHOICES, + choices=Status.STATUS_CHOICES, ) class Meta: diff --git a/backend/application/core/api/serializers.py b/backend/application/core/api/serializers.py index e0d0f75af..06632b94f 100644 --- a/backend/application/core/api/serializers.py +++ b/backend/application/core/api/serializers.py @@ -36,10 +36,13 @@ from application.core.queries.product_member import get_product_member from application.core.services.observation_log import create_observation_log from application.core.services.security_gate import check_security_gate +from application.core.types import Severity, Status +from application.import_observations.types import Parser_Type from application.issue_tracker.services.issue_tracker import ( issue_tracker_factory, push_observation_to_issue_tracker, ) +from application.issue_tracker.types import Issue_Tracker class ProductCoreSerializer(ModelSerializer): @@ -199,7 +202,7 @@ def get_repository_default_branch_name(self, obj: Product) -> str: def validate(self, attrs: dict): # pylint: disable=too-many-branches # There are quite a lot of branches, but at least they are not nested too much - if attrs.get("issue_tracker_type") == Product.ISSUE_TRACKER_GITHUB: + if attrs.get("issue_tracker_type") == Issue_Tracker.ISSUE_TRACKER_GITHUB: attrs["issue_tracker_base_url"] = "https://api.github.com" if not ( @@ -222,7 +225,7 @@ def validate(self, attrs: dict): # pylint: disable=too-many-branches "Issue tracker data must be set when issue tracking is active" ) - if attrs.get("issue_tracker_type") == Product.ISSUE_TRACKER_JIRA: + if attrs.get("issue_tracker_type") == Issue_Tracker.ISSUE_TRACKER_JIRA: if not attrs.get("issue_tracker_username"): raise ValidationError( "Username must be set when issue tracker type is Jira" @@ -238,7 +241,7 @@ def validate(self, attrs: dict): # pylint: disable=too-many-branches if ( attrs.get("issue_tracker_type") - and attrs.get("issue_tracker_type") != Product.ISSUE_TRACKER_JIRA + and attrs.get("issue_tracker_type") != Issue_Tracker.ISSUE_TRACKER_JIRA ): if attrs.get("issue_tracker_username"): raise ValidationError( @@ -563,7 +566,7 @@ def get_origin_component_name_version(self, observation: Observation) -> str: class ObservationUpdateSerializer(ModelSerializer): def validate(self, attrs: dict): self.instance: Observation - if self.instance and self.instance.parser.type != Parser.TYPE_MANUAL: + if self.instance and self.instance.parser.type != Parser_Type.TYPE_MANUAL: raise ValidationError("Only manual observations can be updated") attrs["import_last_seen"] = timezone.now() @@ -586,6 +589,7 @@ def update(self, instance: Observation, validated_data: dict): instance.origin_docker_image_name = "" instance.origin_docker_image_tag = "" + instance.origin_docker_image_digest = "" observation: Observation = super().update(instance, validated_data) @@ -629,7 +633,11 @@ class Meta: "parser_severity", "parser_status", "origin_component_name_version", + "origin_component_name", + "origin_component_version", "origin_docker_image_name_tag", + "origin_docker_image_name", + "origin_docker_image_tag", "origin_endpoint_url", "origin_service_name", "origin_source_file", @@ -644,8 +652,8 @@ class Meta: class ObservationCreateSerializer(ModelSerializer): def validate(self, attrs): - attrs["parser"] = Parser.objects.get(type=Parser.TYPE_MANUAL) - attrs["scanner"] = Parser.TYPE_MANUAL + attrs["parser"] = Parser.objects.get(type=Parser_Type.TYPE_MANUAL) + attrs["scanner"] = Parser_Type.TYPE_MANUAL attrs["import_last_seen"] = timezone.now() if attrs.get("branch"): @@ -689,7 +697,11 @@ class Meta: "parser_severity", "parser_status", "origin_component_name_version", + "origin_component_name", + "origin_component_version", "origin_docker_image_name_tag", + "origin_docker_image_name", + "origin_docker_image_tag", "origin_endpoint_url", "origin_service_name", "origin_source_file", @@ -703,8 +715,8 @@ class Meta: class ObservationAssessmentSerializer(Serializer): - severity = ChoiceField(choices=Observation.SEVERITY_CHOICES, required=False) - status = ChoiceField(choices=Observation.STATUS_CHOICES, required=False) + severity = ChoiceField(choices=Severity.SEVERITY_CHOICES, required=False) + status = ChoiceField(choices=Status.STATUS_CHOICES, required=False) comment = CharField(max_length=255, required=True) @@ -719,8 +731,8 @@ class ObservationBulkDeleteSerializer(Serializer): class ObservationBulkAssessmentSerializer(Serializer): - severity = ChoiceField(choices=Observation.SEVERITY_CHOICES, required=False) - status = ChoiceField(choices=Observation.STATUS_CHOICES, required=False) + severity = ChoiceField(choices=Severity.SEVERITY_CHOICES, required=False) + status = ChoiceField(choices=Status.STATUS_CHOICES, required=False) comment = CharField(max_length=255, required=True) observations = ListField( child=IntegerField(min_value=1), min_length=0, max_length=100, required=True diff --git a/backend/application/core/api/views.py b/backend/application/core/api/views.py index 25accf295..a43e0bf54 100644 --- a/backend/application/core/api/views.py +++ b/backend/application/core/api/views.py @@ -89,6 +89,7 @@ set_potential_duplicate_both_ways, ) from application.core.services.security_gate import check_security_gate +from application.core.types import Status from application.issue_tracker.services.issue_tracker import ( push_deleted_observation_to_issue_tracker, push_observations_to_issue_tracker, @@ -135,7 +136,7 @@ def export_observations_excel(self, request, pk=None): product = self.__get_product(pk) status = self.request.query_params.get("status") - if status and (status, status) not in Observation.STATUS_CHOICES: + if status and (status, status) not in Status.STATUS_CHOICES: raise ValidationError(f"Status {status} is not a valid choice") workbook = export_observations_excel(product, status) @@ -168,7 +169,7 @@ def export_observations_csv(self, request, pk=None): product = self.__get_product(pk) status = self.request.query_params.get("status") - if status and (status, status) not in Observation.STATUS_CHOICES: + if status and (status, status) not in Status.STATUS_CHOICES: raise ValidationError(f"Status {status} is not a valid choice") response = HttpResponse(content_type="text/csv") diff --git a/backend/application/core/migrations/0030_observation_origin_docker_image_digest.py b/backend/application/core/migrations/0030_observation_origin_docker_image_digest.py new file mode 100644 index 000000000..b8776899e --- /dev/null +++ b/backend/application/core/migrations/0030_observation_origin_docker_image_digest.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.9 on 2024-01-28 08:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0029_adjust_observation_hashes"), + ] + + operations = [ + migrations.AddField( + model_name="observation", + name="origin_docker_image_digest", + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/backend/application/core/migrations/0031_observation_issue_tracker_issue_closed_and_more.py b/backend/application/core/migrations/0031_observation_issue_tracker_issue_closed_and_more.py new file mode 100644 index 000000000..1b5056c8d --- /dev/null +++ b/backend/application/core/migrations/0031_observation_issue_tracker_issue_closed_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.9 on 2024-02-03 07:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0030_observation_origin_docker_image_digest"), + ] + + operations = [ + migrations.AddField( + model_name="observation", + name="issue_tracker_issue_closed", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="product", + name="issue_tracker_minimum_severity", + field=models.CharField( + blank=True, + choices=[ + ("Unkown", "Unkown"), + ("None", "None"), + ("Low", "Low"), + ("Medium", "Medium"), + ("High", "High"), + ("Critical", "Critical"), + ], + max_length=12, + ), + ), + ] diff --git a/backend/application/core/models.py b/backend/application/core/models.py index 6f98fd549..d139d017c 100644 --- a/backend/application/core/models.py +++ b/backend/application/core/models.py @@ -22,19 +22,12 @@ get_identity_hash, normalize_observation_fields, ) +from application.core.types import Severity, Status +from application.import_observations.types import Parser_Source, Parser_Type +from application.issue_tracker.types import Issue_Tracker class Product(Model): - ISSUE_TRACKER_GITHUB = "GitHub" - ISSUE_TRACKER_GITLAB = "GitLab" - ISSUE_TRACKER_JIRA = "Jira" - - ISSUE_TRACKER_TYPE_CHOICES = [ - (ISSUE_TRACKER_GITHUB, ISSUE_TRACKER_GITHUB), - (ISSUE_TRACKER_GITLAB, ISSUE_TRACKER_GITLAB), - (ISSUE_TRACKER_JIRA, ISSUE_TRACKER_JIRA), - ] - name = CharField(max_length=255, unique=True) description = TextField(max_length=2048, blank=True) @@ -90,7 +83,7 @@ class Product(Model): issue_tracker_active = BooleanField(default=False) issue_tracker_type = CharField( - max_length=12, choices=ISSUE_TRACKER_TYPE_CHOICES, blank=True + max_length=12, choices=Issue_Tracker.ISSUE_TRACKER_TYPE_CHOICES, blank=True ) issue_tracker_base_url = CharField(max_length=255, blank=True) issue_tracker_username = CharField(max_length=255, blank=True) @@ -99,6 +92,9 @@ class Product(Model): issue_tracker_labels = CharField(max_length=255, blank=True) issue_tracker_issue_type = CharField(max_length=255, blank=True) issue_tracker_status_closed = CharField(max_length=255, blank=True) + issue_tracker_minimum_severity = CharField( + max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True + ) last_observation_change = DateTimeField(default=timezone.now) @@ -121,8 +117,8 @@ def open_critical_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, ).count() @property @@ -136,8 +132,8 @@ def open_high_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, ).count() @property @@ -151,8 +147,8 @@ def open_medium_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, ).count() @property @@ -166,8 +162,8 @@ def open_low_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, ).count() @property @@ -181,8 +177,8 @@ def open_none_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, ).count() @property @@ -196,8 +192,8 @@ def open_unkown_observation_count(self): return Observation.objects.filter( product=self, branch=self.repository_default_branch, - current_status=Observation.STATUS_OPEN, - current_severity=Observation.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, ).count() @@ -223,48 +219,48 @@ def __str__(self): def open_critical_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_CRITICAL, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, ).count() @property def open_high_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_HIGH, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, ).count() @property def open_medium_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_MEDIUM, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, ).count() @property def open_low_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_LOW, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, ).count() @property def open_none_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_NONE, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, ).count() @property def open_unkown_observation_count(self): return Observation.objects.filter( branch=self, - current_severity=Observation.SEVERITY_UNKOWN, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, ).count() @@ -289,8 +285,8 @@ def open_critical_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_CRITICAL, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, ).count() @property @@ -298,8 +294,8 @@ def open_high_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_HIGH, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, ).count() @property @@ -307,8 +303,8 @@ def open_medium_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_MEDIUM, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, ).count() @property @@ -316,8 +312,8 @@ def open_low_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_LOW, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, ).count() @property @@ -325,8 +321,8 @@ def open_none_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_NONE, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, ).count() @property @@ -334,8 +330,8 @@ def open_unkown_observation_count(self): return Observation.objects.filter( origin_service=self, branch=self.product.repository_default_branch, - current_severity=Observation.SEVERITY_UNKOWN, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, ).count() @@ -352,41 +348,9 @@ class Meta: class Parser(Model): - TYPE_SCA = "SCA" - TYPE_SAST = "SAST" - TYPE_DAST = "DAST" - TYPE_IAST = "IAST" - TYPE_SECRETS = "Secrets" - TYPE_INFRASTRUCTURE = "Infrastructure" - TYPE_OTHER = "Other" - TYPE_MANUAL = "Manual" - - TYPE_CHOICES = [ - (TYPE_SCA, TYPE_SCA), - (TYPE_SAST, TYPE_SAST), - (TYPE_DAST, TYPE_DAST), - (TYPE_IAST, TYPE_IAST), - (TYPE_SECRETS, TYPE_SECRETS), - (TYPE_INFRASTRUCTURE, TYPE_INFRASTRUCTURE), - (TYPE_OTHER, TYPE_OTHER), - (TYPE_MANUAL, TYPE_MANUAL), - ] - - SOURCE_API = "API" - SOURCE_FILE = "File" - SOURCE_MANUAL = "Manual" - SOURCE_UNKOWN = "Unkown" - - SOURCE_CHOICES = [ - (SOURCE_API, SOURCE_API), - (SOURCE_FILE, SOURCE_FILE), - (SOURCE_MANUAL, SOURCE_MANUAL), - (SOURCE_UNKOWN, SOURCE_UNKOWN), - ] - name = CharField(max_length=255, unique=True) - type = CharField(max_length=16, choices=TYPE_CHOICES) - source = CharField(max_length=16, choices=SOURCE_CHOICES) + type = CharField(max_length=16, choices=Parser_Type.TYPE_CHOICES) + source = CharField(max_length=16, choices=Parser_Source.SOURCE_CHOICES) class Meta: indexes = [ @@ -398,68 +362,31 @@ def __str__(self): class Observation(Model): - SEVERITY_UNKOWN = "Unkown" - SEVERITY_NONE = "None" - SEVERITY_LOW = "Low" - SEVERITY_HIGH = "High" - SEVERITY_MEDIUM = "Medium" - SEVERITY_CRITICAL = "Critical" - - SEVERITY_CHOICES = [ - (SEVERITY_UNKOWN, SEVERITY_UNKOWN), - (SEVERITY_NONE, SEVERITY_NONE), - (SEVERITY_LOW, SEVERITY_LOW), - (SEVERITY_MEDIUM, SEVERITY_MEDIUM), - (SEVERITY_HIGH, SEVERITY_HIGH), - (SEVERITY_CRITICAL, SEVERITY_CRITICAL), - ] - - NUMERICAL_SEVERITIES = { - SEVERITY_UNKOWN: 6, - SEVERITY_NONE: 5, - SEVERITY_LOW: 4, - SEVERITY_MEDIUM: 3, - SEVERITY_HIGH: 2, - SEVERITY_CRITICAL: 1, - } - - STATUS_OPEN = "Open" - STATUS_RESOLVED = "Resolved" - STATUS_DUPLICATE = "Duplicate" - STATUS_FALSE_POSITIVE = "False positive" - STATUS_IN_REVIEW = "In review" - STATUS_NOT_AFFECTED = "Not affected" - STATUS_NOT_SECURITY = "Not security" - STATUS_RISK_ACCEPTED = "Risk accepted" - - STATUS_CHOICES = [ - (STATUS_OPEN, STATUS_OPEN), - (STATUS_RESOLVED, STATUS_RESOLVED), - (STATUS_DUPLICATE, STATUS_DUPLICATE), - (STATUS_FALSE_POSITIVE, STATUS_FALSE_POSITIVE), - (STATUS_IN_REVIEW, STATUS_IN_REVIEW), - (STATUS_NOT_AFFECTED, STATUS_NOT_AFFECTED), - (STATUS_NOT_SECURITY, STATUS_NOT_SECURITY), - (STATUS_RISK_ACCEPTED, STATUS_RISK_ACCEPTED), - ] - product = ForeignKey(Product, on_delete=PROTECT) branch = ForeignKey(Branch, on_delete=CASCADE, null=True) parser = ForeignKey(Parser, on_delete=PROTECT) title = CharField(max_length=255) description = TextField(max_length=2048, blank=True) recommendation = TextField(max_length=2048, blank=True) - current_severity = CharField(max_length=12, choices=SEVERITY_CHOICES) + current_severity = CharField(max_length=12, choices=Severity.SEVERITY_CHOICES) numerical_severity = IntegerField( validators=[MinValueValidator(1), MaxValueValidator(6)] ) - parser_severity = CharField(max_length=12, choices=SEVERITY_CHOICES, blank=True) - rule_severity = CharField(max_length=12, choices=SEVERITY_CHOICES, blank=True) - assessment_severity = CharField(max_length=12, choices=SEVERITY_CHOICES, blank=True) - current_status = CharField(max_length=16, choices=STATUS_CHOICES) - parser_status = CharField(max_length=16, choices=STATUS_CHOICES, blank=True) - rule_status = CharField(max_length=16, choices=STATUS_CHOICES, blank=True) - assessment_status = CharField(max_length=16, choices=STATUS_CHOICES, blank=True) + parser_severity = CharField( + max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True + ) + rule_severity = CharField( + max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True + ) + assessment_severity = CharField( + max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True + ) + current_status = CharField(max_length=16, choices=Status.STATUS_CHOICES) + parser_status = CharField(max_length=16, choices=Status.STATUS_CHOICES, blank=True) + rule_status = CharField(max_length=16, choices=Status.STATUS_CHOICES, blank=True) + assessment_status = CharField( + max_length=16, choices=Status.STATUS_CHOICES, blank=True + ) scanner_observation_id = CharField(max_length=255, blank=True) vulnerability_id = CharField(max_length=255, blank=True) origin_component_name = CharField(max_length=255, blank=True) @@ -472,6 +399,7 @@ class Observation(Model): origin_docker_image_tag = CharField(max_length=255, blank=True) origin_docker_image_name_tag = CharField(max_length=513, blank=True) origin_docker_image_name_tag_short = CharField(max_length=513, blank=True) + origin_docker_image_digest = CharField(max_length=255, blank=True) origin_endpoint_url = TextField(max_length=2048, blank=True) origin_endpoint_scheme = CharField(max_length=255, blank=True) origin_endpoint_hostname = CharField(max_length=255, blank=True) @@ -537,6 +465,7 @@ class Observation(Model): on_delete=PROTECT, ) issue_tracker_issue_id = CharField(max_length=255, blank=True) + issue_tracker_issue_closed = BooleanField(default=False) issue_tracker_jira_initial_status = CharField(max_length=255, blank=True) has_potential_duplicates = BooleanField(default=False) @@ -582,10 +511,8 @@ class Observation_Log(Model): user = ForeignKey( "access_control.User", related_name="observation_logs", on_delete=PROTECT ) - severity = CharField( - max_length=12, choices=Observation.SEVERITY_CHOICES, blank=True - ) - status = CharField(max_length=16, choices=Observation.STATUS_CHOICES, blank=True) + severity = CharField(max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True) + status = CharField(max_length=16, choices=Status.STATUS_CHOICES, blank=True) comment = CharField(max_length=255) created = DateTimeField(auto_now_add=True) diff --git a/backend/application/core/services/observation.py b/backend/application/core/services/observation.py index 0f85271e2..71c6aa36d 100644 --- a/backend/application/core/services/observation.py +++ b/backend/application/core/services/observation.py @@ -4,6 +4,8 @@ from django.apps import apps from django.db.models.fields import CharField, TextField +from application.core.types import Severity, Status + # Parameter observation cannot be typed, because some methods are used in the model class @@ -66,30 +68,26 @@ def get_current_severity(observation) -> str: def _get_cvss3_severity(cvss3_score: int): - Observation = apps.get_model("core", "Observation") - if cvss3_score is None: - return Observation.SEVERITY_UNKOWN + return Severity.SEVERITY_UNKOWN if cvss3_score >= 9: - return Observation.SEVERITY_CRITICAL + return Severity.SEVERITY_CRITICAL if cvss3_score >= 7: - return Observation.SEVERITY_HIGH + return Severity.SEVERITY_HIGH if cvss3_score >= 4: - return Observation.SEVERITY_MEDIUM + return Severity.SEVERITY_MEDIUM if cvss3_score >= 0.1: - return Observation.SEVERITY_LOW + return Severity.SEVERITY_LOW - return Observation.SEVERITY_NONE + return Severity.SEVERITY_NONE def get_current_status(observation) -> str: - Observation = apps.get_model("core", "Observation") - - if observation.parser_status == Observation.STATUS_RESOLVED: + if observation.parser_status == Status.STATUS_RESOLVED: return observation.parser_status if observation.assessment_status: @@ -101,7 +99,7 @@ def get_current_status(observation) -> str: if observation.parser_status: return observation.parser_status - return Observation.STATUS_OPEN + return Status.STATUS_OPEN def normalize_observation_fields(observation) -> None: @@ -192,25 +190,10 @@ def normalize_origin_component(observation): # pylint: disable=too-many-branche def normalize_origin_docker(observation): if not observation.origin_docker_image_name_tag: - if observation.origin_docker_image_name and observation.origin_docker_image_tag: - observation.origin_docker_image_name_tag = ( - observation.origin_docker_image_name - + ":" - + observation.origin_docker_image_tag - ) - elif observation.origin_docker_image_name: - observation.origin_docker_image_name_tag = ( - observation.origin_docker_image_name - ) + _normalize_origin_docker_image_name(observation) else: - docker_image_parts = observation.origin_docker_image_name_tag.split(":") - if len(docker_image_parts) == 2: - observation.origin_docker_image_name = docker_image_parts[0] - observation.origin_docker_image_tag = docker_image_parts[1] - else: - observation.origin_docker_image_name = ( - observation.origin_docker_image_name_tag - ) + _normalize_origin_docker_image_name_tag(observation) + if observation.origin_docker_image_name_tag: origin_docker_image_name_tag_parts = ( observation.origin_docker_image_name_tag.split("/") @@ -229,6 +212,34 @@ def normalize_origin_docker(observation): observation.origin_docker_image_name = "" if observation.origin_docker_image_tag is None: observation.origin_docker_image_tag = "" + if observation.origin_docker_image_digest is None: + observation.origin_docker_image_digest = "" + + +def _normalize_origin_docker_image_name(observation): + if observation.origin_docker_image_name and not observation.origin_docker_image_tag: + docker_image_parts = observation.origin_docker_image_name.split(":") + if len(docker_image_parts) == 2: + observation.origin_docker_image_name = docker_image_parts[0] + observation.origin_docker_image_tag = docker_image_parts[1] + + if observation.origin_docker_image_name and observation.origin_docker_image_tag: + observation.origin_docker_image_name_tag = ( + observation.origin_docker_image_name + + ":" + + observation.origin_docker_image_tag + ) + else: + observation.origin_docker_image_name_tag = observation.origin_docker_image_name + + +def _normalize_origin_docker_image_name_tag(observation): + docker_image_parts = observation.origin_docker_image_name_tag.split(":") + if len(docker_image_parts) == 2: + observation.origin_docker_image_name = docker_image_parts[0] + observation.origin_docker_image_tag = docker_image_parts[1] + else: + observation.origin_docker_image_name = observation.origin_docker_image_name_tag def normalize_origin_endpoint(observation): @@ -309,12 +320,12 @@ def normalize_severity(observation): if ( observation.parser_severity, observation.parser_severity, - ) not in observation.SEVERITY_CHOICES: - observation.parser_severity = observation.SEVERITY_UNKOWN + ) not in Severity.SEVERITY_CHOICES: + observation.parser_severity = Severity.SEVERITY_UNKOWN observation.current_severity = get_current_severity(observation) - observation.numerical_severity = observation.NUMERICAL_SEVERITIES.get( + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( observation.current_severity ) diff --git a/backend/application/core/services/observations_bulk_actions.py b/backend/application/core/services/observations_bulk_actions.py index f77ad5ed7..ed36647fa 100644 --- a/backend/application/core/services/observations_bulk_actions.py +++ b/backend/application/core/services/observations_bulk_actions.py @@ -7,6 +7,7 @@ from application.core.services.assessment import save_assessment from application.core.services.potential_duplicates import set_potential_duplicate from application.core.services.security_gate import check_security_gate +from application.core.types import Status from application.issue_tracker.services.issue_tracker import ( push_deleted_observation_to_issue_tracker, ) @@ -77,7 +78,7 @@ def observations_bulk_mark_duplicates( for duplicate in duplicates: duplicate.has_potential_duplicates = False - save_assessment(duplicate, None, Observation.STATUS_DUPLICATE, comment) + save_assessment(duplicate, None, Status.STATUS_DUPLICATE, comment) set_potential_duplicate(observation) diff --git a/backend/application/core/services/potential_duplicates.py b/backend/application/core/services/potential_duplicates.py index d10663a1f..1cf5a7b87 100644 --- a/backend/application/core/services/potential_duplicates.py +++ b/backend/application/core/services/potential_duplicates.py @@ -5,6 +5,7 @@ from application.commons.services.tasks import handle_task_exception from application.core.models import Branch, Observation, Potential_Duplicate, Product +from application.core.types import Status @db_task() @@ -31,12 +32,11 @@ def _handle_observation(observation: Observation, observations: QuerySet[Observa Potential_Duplicate.objects.filter(observation=observation).delete() initial_has_potential_duplicates = observation.has_potential_duplicates observation.has_potential_duplicates = False - if observation.current_status == Observation.STATUS_OPEN: + if observation.current_status == Status.STATUS_OPEN: for potential_duplicate_observation in observations: if ( observation != potential_duplicate_observation - and potential_duplicate_observation.current_status - == Observation.STATUS_OPEN + and potential_duplicate_observation.current_status == Status.STATUS_OPEN ): potential_duplicate_type = None if ( @@ -83,10 +83,10 @@ def set_potential_duplicate_both_ways(observation: Observation) -> None: def set_potential_duplicate(observation: Observation) -> None: initial_has_potential_duplicates = observation.has_potential_duplicates - if observation.current_status == Observation.STATUS_OPEN: + if observation.current_status == Status.STATUS_OPEN: open_potential_duplicates = Potential_Duplicate.objects.filter( observation=observation, - potential_duplicate_observation__current_status=Observation.STATUS_OPEN, + potential_duplicate_observation__current_status=Status.STATUS_OPEN, ).count() if open_potential_duplicates == 0: observation.has_potential_duplicates = False diff --git a/backend/application/core/types.py b/backend/application/core/types.py new file mode 100644 index 000000000..24fe832fc --- /dev/null +++ b/backend/application/core/types.py @@ -0,0 +1,47 @@ +class Severity: + SEVERITY_UNKOWN = "Unkown" + SEVERITY_NONE = "None" + SEVERITY_LOW = "Low" + SEVERITY_HIGH = "High" + SEVERITY_MEDIUM = "Medium" + SEVERITY_CRITICAL = "Critical" + + SEVERITY_CHOICES = [ + (SEVERITY_UNKOWN, SEVERITY_UNKOWN), + (SEVERITY_NONE, SEVERITY_NONE), + (SEVERITY_LOW, SEVERITY_LOW), + (SEVERITY_MEDIUM, SEVERITY_MEDIUM), + (SEVERITY_HIGH, SEVERITY_HIGH), + (SEVERITY_CRITICAL, SEVERITY_CRITICAL), + ] + + NUMERICAL_SEVERITIES = { + SEVERITY_UNKOWN: 6, + SEVERITY_NONE: 5, + SEVERITY_LOW: 4, + SEVERITY_MEDIUM: 3, + SEVERITY_HIGH: 2, + SEVERITY_CRITICAL: 1, + } + + +class Status: + STATUS_OPEN = "Open" + STATUS_RESOLVED = "Resolved" + STATUS_DUPLICATE = "Duplicate" + STATUS_FALSE_POSITIVE = "False positive" + STATUS_IN_REVIEW = "In review" + STATUS_NOT_AFFECTED = "Not affected" + STATUS_NOT_SECURITY = "Not security" + STATUS_RISK_ACCEPTED = "Risk accepted" + + STATUS_CHOICES = [ + (STATUS_OPEN, STATUS_OPEN), + (STATUS_RESOLVED, STATUS_RESOLVED), + (STATUS_DUPLICATE, STATUS_DUPLICATE), + (STATUS_FALSE_POSITIVE, STATUS_FALSE_POSITIVE), + (STATUS_IN_REVIEW, STATUS_IN_REVIEW), + (STATUS_NOT_AFFECTED, STATUS_NOT_AFFECTED), + (STATUS_NOT_SECURITY, STATUS_NOT_SECURITY), + (STATUS_RISK_ACCEPTED, STATUS_RISK_ACCEPTED), + ] diff --git a/backend/application/import_observations/parsers/azure_defender/parser.py b/backend/application/import_observations/parsers/azure_defender/parser.py index ea25ef26e..04b72bb38 100644 --- a/backend/application/import_observations/parsers/azure_defender/parser.py +++ b/backend/application/import_observations/parsers/azure_defender/parser.py @@ -5,11 +5,12 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type class AzureDefenderParser(BaseParser, BaseFileParser): @@ -19,7 +20,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_INFRASTRUCTURE + return Parser_Type.TYPE_INFRASTRUCTURE def check_format(self, file: File) -> tuple[bool, list[str], dict | list]: if file.name and not file.name.endswith(".csv"): diff --git a/backend/application/import_observations/parsers/cryptolyzer/parser.py b/backend/application/import_observations/parsers/cryptolyzer/parser.py index f2d7917ea..b96680e4f 100644 --- a/backend/application/import_observations/parsers/cryptolyzer/parser.py +++ b/backend/application/import_observations/parsers/cryptolyzer/parser.py @@ -3,11 +3,13 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type # Recommended cipher suites, curves and signature algorithms according to German BSI as of 2023 TLS12_RECOMMENDED_CIPHERS = [ @@ -120,7 +122,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_DAST # pylint: disable=duplicate-code + return Parser_Type.TYPE_DAST # pylint: disable=duplicate-code def check_format(self, file: File) -> tuple[bool, list[str], dict]: try: @@ -184,7 +186,7 @@ def check_weak_protocols(self, data: dict) -> Optional[Observation]: observation = Observation( title="Weak protocols detected", description=description, - parser_severity=Observation.SEVERITY_HIGH, + parser_severity=Severity.SEVERITY_HIGH, origin_endpoint_url=endpoint_url, scanner=self.get_name(), ) @@ -224,7 +226,7 @@ def check_ciphers( observation = Observation( title="Unrecommended " + protocol_name + " cipher suites", description=description, - parser_severity=Observation.SEVERITY_MEDIUM, + parser_severity=Severity.SEVERITY_MEDIUM, origin_endpoint_url=endpoint_url, scanner=self.get_name(), ) @@ -262,7 +264,7 @@ def check_curves( observation = Observation( title="Unrecommended elliptic curves", description=description, - parser_severity=Observation.SEVERITY_MEDIUM, + parser_severity=Severity.SEVERITY_MEDIUM, origin_endpoint_url=endpoint_url, scanner=self.get_name(), ) @@ -301,7 +303,7 @@ def check_signature_algorithms( observation = Observation( title="Unrecommended signature algorithms", description=description, - parser_severity=Observation.SEVERITY_MEDIUM, + parser_severity=Severity.SEVERITY_MEDIUM, origin_endpoint_url=endpoint_url, scanner=self.get_name(), ) diff --git a/backend/application/import_observations/parsers/cyclone_dx/parser.py b/backend/application/import_observations/parsers/cyclone_dx/parser.py index b9da99066..122e98fa2 100644 --- a/backend/application/import_observations/parsers/cyclone_dx/parser.py +++ b/backend/application/import_observations/parsers/cyclone_dx/parser.py @@ -4,11 +4,13 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type @dataclass @@ -25,7 +27,9 @@ class Component: @dataclass class Metadata: scanner: str - container: str + container_name: str + container_tag: str + container_digest: str file: str @@ -36,7 +40,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_SCA + return Parser_Type.TYPE_SCA def check_format(self, file: File) -> tuple[bool, list[str], dict]: try: @@ -139,7 +143,9 @@ def _create_observations( # pylint: disable=too-many-locals cvss3_vector=cvss3_vector, cwe=cwe, scanner=metadata.scanner, - origin_docker_image_name_tag=metadata.container, + origin_docker_image_name=metadata.container_name, + origin_docker_image_tag=metadata.container_tag, + origin_docker_image_digest=metadata.container_digest, origin_source_file=metadata.file, ) @@ -180,7 +186,9 @@ def _get_component_dependencies(self, data, components, component): def _get_metadata(self, data: dict) -> Metadata: scanner = "" - container = "" + container_name = "" + container_tag = "" + container_digest = "" file = "" tools = data.get("metadata", {}).get("tools") @@ -206,13 +214,21 @@ def _get_metadata(self, data: dict) -> Metadata: data.get("metadata", {}).get("component", {}).get("version", "") ) if component_type == "container": - container = component_name - if component_version and ":" not in container: - container += f":{component_version}" + container_name = component_name + if component_version and component_version.startswith("sha256:"): + container_digest = component_version + elif component_version: + container_tag = component_version if component_type == "file": file = component_name - return Metadata(scanner=scanner, container=container, file=file) + return Metadata( + scanner=scanner, + container_name=container_name, + container_tag=container_tag, + container_digest=container_digest, + file=file, + ) def _get_cvss3(self, vulnerability): ratings = vulnerability.get("ratings", []) @@ -230,15 +246,13 @@ def _get_cvss3(self, vulnerability): return None, None def _get_highest_severity(self, vulnerability): - current_severity = Observation.SEVERITY_UNKOWN + current_severity = Severity.SEVERITY_UNKOWN current_numerical_severity = 999 ratings = vulnerability.get("ratings", []) if ratings: for rating in ratings: - severity = rating.get( - "severity", Observation.SEVERITY_UNKOWN - ).capitalize() - numerical_severity = Observation.NUMERICAL_SEVERITIES.get(severity, 99) + severity = rating.get("severity", Severity.SEVERITY_UNKOWN).capitalize() + numerical_severity = Severity.NUMERICAL_SEVERITIES.get(severity, 99) if numerical_severity < current_numerical_severity: current_severity = severity return current_severity diff --git a/backend/application/import_observations/parsers/dependency_track/parser.py b/backend/application/import_observations/parsers/dependency_track/parser.py index fd7901bc7..32a6c108c 100644 --- a/backend/application/import_observations/parsers/dependency_track/parser.py +++ b/backend/application/import_observations/parsers/dependency_track/parser.py @@ -3,20 +3,22 @@ import requests -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity, Status from application.import_observations.models import Api_Configuration from application.import_observations.parsers.base_parser import ( BaseAPIParser, BaseParser, ) +from application.import_observations.types import Parser_Type STATUS_MAPPING = { "NOT_SET": "", - "EXPLOITABLE": Observation.STATUS_OPEN, - "IN_TRIAGE": Observation.STATUS_IN_REVIEW, - "RESOLVED": Observation.STATUS_RESOLVED, - "FALSE_POSITIVE": Observation.STATUS_FALSE_POSITIVE, - "NOT_AFFECTED": Observation.STATUS_NOT_AFFECTED, + "EXPLOITABLE": Status.STATUS_OPEN, + "IN_TRIAGE": Status.STATUS_IN_REVIEW, + "RESOLVED": Status.STATUS_RESOLVED, + "FALSE_POSITIVE": Status.STATUS_FALSE_POSITIVE, + "NOT_AFFECTED": Status.STATUS_NOT_AFFECTED, } @@ -30,7 +32,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_SCA + return Parser_Type.TYPE_SCA def check_connection( self, api_configuration: Api_Configuration @@ -78,7 +80,7 @@ def get_observations(self, data: list[dict]) -> list[Observation]: cvss_v3_base_score = finding.get("vulnerability", {}).get("cvssV3BaseScore") cvss_v3_vector = finding.get("vulnerability", {}).get("cvssV3Vector") severity = finding.get("vulnerability", {}).get( - "severity", Observation.SEVERITY_UNKOWN + "severity", Severity.SEVERITY_UNKOWN ) description = finding.get("vulnerability", {}).get("description") @@ -124,10 +126,10 @@ def get_severity(self, severity: str) -> str: if ( severity.capitalize(), severity.capitalize(), - ) in Observation.SEVERITY_CHOICES: + ) in Severity.SEVERITY_CHOICES: return severity.capitalize() - return Observation.SEVERITY_UNKOWN + return Severity.SEVERITY_UNKOWN def get_cwe(self, cwes: list[dict]) -> int | None: if cwes: diff --git a/backend/application/import_observations/parsers/drheader/parser.py b/backend/application/import_observations/parsers/drheader/parser.py index 41224fdbd..b2f0e37dd 100644 --- a/backend/application/import_observations/parsers/drheader/parser.py +++ b/backend/application/import_observations/parsers/drheader/parser.py @@ -2,11 +2,13 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type REFERENCES = { "Access-Control-Allow-Origin": [ @@ -93,7 +95,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_DAST + return Parser_Type.TYPE_DAST def check_format(self, file: File) -> tuple[bool, list[str], dict | list]: try: # pylint: disable=duplicate-code @@ -129,7 +131,7 @@ def get_observations(self, data: list) -> list[Observation]: for drheader_observation in data: rule = drheader_observation.get("rule") message = drheader_observation.get("message") - severity = drheader_observation.get("severity", Observation.SEVERITY_UNKOWN) + severity = drheader_observation.get("severity", Severity.SEVERITY_UNKOWN) value = drheader_observation.get("value") expected = drheader_observation.get("expected") delimiter = drheader_observation.get("delimiter") diff --git a/backend/application/import_observations/parsers/prowler/parser.py b/backend/application/import_observations/parsers/prowler/parser.py index 3fcf1bce1..c7097dfb1 100644 --- a/backend/application/import_observations/parsers/prowler/parser.py +++ b/backend/application/import_observations/parsers/prowler/parser.py @@ -2,11 +2,13 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type class ProwlerParser(BaseParser, BaseFileParser): @@ -16,7 +18,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_INFRASTRUCTURE + return Parser_Type.TYPE_INFRASTRUCTURE def check_format(self, file: File) -> tuple[bool, list[str], dict | list]: try: @@ -57,10 +59,10 @@ def get_observations(self, data: list[dict]) -> list[Observation]: "StatusExtended", "No StatusExtended found" ) severity = prowler_observation.get( - "Severity", Observation.SEVERITY_UNKOWN + "Severity", Severity.SEVERITY_UNKOWN ).capitalize() if severity == "Informational": - severity = Observation.SEVERITY_NONE + severity = Severity.SEVERITY_NONE description = self.get_description(prowler_observation) recommendation = self.get_recommendation(prowler_observation) diff --git a/backend/application/import_observations/parsers/sarif/parser.py b/backend/application/import_observations/parsers/sarif/parser.py index a694f4d82..086df734d 100644 --- a/backend/application/import_observations/parsers/sarif/parser.py +++ b/backend/application/import_observations/parsers/sarif/parser.py @@ -5,17 +5,19 @@ from django.core.files.base import File from packageurl import PackageURL -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type SEVERITIES = { - "error": Observation.SEVERITY_HIGH, - "warning": Observation.SEVERITY_MEDIUM, - "note": Observation.SEVERITY_LOW, - "none": Observation.SEVERITY_NONE, + "error": Severity.SEVERITY_HIGH, + "warning": Severity.SEVERITY_MEDIUM, + "note": Severity.SEVERITY_LOW, + "none": Severity.SEVERITY_NONE, } @@ -47,7 +49,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_SAST + return Parser_Type.TYPE_SAST def check_format(self, file: File) -> tuple[bool, list[str], dict]: try: @@ -243,17 +245,17 @@ def get_parser_severity( sarif_level = result.get("level") if sarif_level: parser_severity = SEVERITIES.get( - sarif_level.lower(), Observation.SEVERITY_UNKOWN + sarif_level.lower(), Severity.SEVERITY_UNKOWN ) elif sarif_rule.default_level: parser_severity = SEVERITIES.get( sarif_rule.default_level.lower(), - Observation.SEVERITY_UNKOWN, + Severity.SEVERITY_UNKOWN, ) elif self.get_bandit_severity(sarif_scanner, result): parser_severity = self.get_bandit_severity(sarif_scanner, result) else: - parser_severity = Observation.SEVERITY_UNKOWN + parser_severity = Severity.SEVERITY_UNKOWN return parser_severity diff --git a/backend/application/import_observations/parsers/secobserve/parser.py b/backend/application/import_observations/parsers/secobserve/parser.py index e4b9996e5..ad622d61b 100644 --- a/backend/application/import_observations/parsers/secobserve/parser.py +++ b/backend/application/import_observations/parsers/secobserve/parser.py @@ -2,11 +2,12 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type class SecObserveParser(BaseParser, BaseFileParser): @@ -16,7 +17,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_OTHER + return Parser_Type.TYPE_OTHER def check_format(self, file: File) -> tuple[bool, list[str], dict]: try: diff --git a/backend/application/import_observations/parsers/zap/parser.py b/backend/application/import_observations/parsers/zap/parser.py index 8d36bb03e..7c025b7ca 100644 --- a/backend/application/import_observations/parsers/zap/parser.py +++ b/backend/application/import_observations/parsers/zap/parser.py @@ -2,18 +2,20 @@ from django.core.files.base import File -from application.core.models import Observation, Parser +from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.base_parser import ( BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Type SEVERITIES = { - "0": Observation.SEVERITY_NONE, - "1": Observation.SEVERITY_LOW, - "2": Observation.SEVERITY_MEDIUM, - "3": Observation.SEVERITY_HIGH, - "4": Observation.SEVERITY_CRITICAL, + "0": Severity.SEVERITY_NONE, + "1": Severity.SEVERITY_LOW, + "2": Severity.SEVERITY_MEDIUM, + "3": Severity.SEVERITY_HIGH, + "4": Severity.SEVERITY_CRITICAL, } @@ -24,7 +26,7 @@ def get_name(cls) -> str: @classmethod def get_type(cls) -> str: - return Parser.TYPE_DAST + return Parser_Type.TYPE_DAST def check_format(self, file: File) -> tuple[bool, list[str], dict]: try: @@ -47,7 +49,7 @@ def get_observations(self, data: dict) -> list[Observation]: for alert in site.get("alerts"): data_title = alert.get("alert") data_severity = SEVERITIES.get( - alert.get("riskcode"), Observation.SEVERITY_UNKOWN + alert.get("riskcode"), Severity.SEVERITY_UNKOWN ) data_description = self.get_description(alert) diff --git a/backend/application/import_observations/services/import_observations.py b/backend/application/import_observations/services/import_observations.py index 7e96d7b3f..d09187f11 100644 --- a/backend/application/import_observations/services/import_observations.py +++ b/backend/application/import_observations/services/import_observations.py @@ -29,6 +29,7 @@ from application.core.services.potential_duplicates import find_potential_duplicates from application.core.services.product import set_repository_default_branch from application.core.services.security_gate import check_security_gate +from application.core.types import Status from application.epss.services.epss import epss_apply_observation from application.import_observations.models import ( Api_Configuration, @@ -236,7 +237,7 @@ def _process_data(import_parameters: ImportParameters) -> Tuple[int, int, int, s rule_engine.apply_rules_for_observation(observation_before) - if observation_before.current_status == Observation.STATUS_OPEN: + if observation_before.current_status == Status.STATUS_OPEN: observations_updated += 1 # Remove observation from list of current observations because it is still part of the check @@ -249,7 +250,7 @@ def _process_data(import_parameters: ImportParameters) -> Tuple[int, int, int, s rule_engine.apply_rules_for_observation(imported_observation) - if imported_observation.current_status == Observation.STATUS_OPEN: + if imported_observation.current_status == Status.STATUS_OPEN: observations_new += 1 # Add identity_hash to set of observations in this run to detect duplicates in this run @@ -333,8 +334,8 @@ def _process_current_observation( else: # Reopen the current observation if it is resolved, # leave the status as is otherwise. - if observation_before.parser_status == Observation.STATUS_RESOLVED: - observation_before.parser_status = Observation.STATUS_OPEN + if observation_before.parser_status == Status.STATUS_RESOLVED: + observation_before.parser_status = Status.STATUS_OPEN observation_before.current_status = get_current_status(observation_before) epss_apply_observation(observation_before) @@ -385,7 +386,7 @@ def _process_new_observation(imported_observation: Observation) -> None: imported_observation.current_severity = get_current_severity(imported_observation) if not imported_observation.parser_status: - imported_observation.parser_status = Observation.STATUS_OPEN + imported_observation.parser_status = Status.STATUS_OPEN imported_observation.current_status = get_current_status(imported_observation) # Observation has not been imported before, so it is a new one @@ -428,12 +429,12 @@ def _resolve_unimported_observations( for observation in observations_before.values(): old_status = get_current_status(observation) - observation.parser_status = Observation.STATUS_RESOLVED + observation.parser_status = Status.STATUS_RESOLVED observation.save() new_status = get_current_status(observation) if old_status != new_status: - if old_status == Observation.STATUS_OPEN: + if old_status == Status.STATUS_OPEN: observations_resolved.add(observation) observation.current_status = new_status diff --git a/backend/application/import_observations/services/parser_registry.py b/backend/application/import_observations/services/parser_registry.py index bfc42c91f..4c8d87169 100644 --- a/backend/application/import_observations/services/parser_registry.py +++ b/backend/application/import_observations/services/parser_registry.py @@ -9,6 +9,7 @@ BaseFileParser, BaseParser, ) +from application.import_observations.types import Parser_Source, Parser_Type logger = logging.getLogger("secobserve.import_observations") @@ -29,13 +30,13 @@ def register_parser(parser_class: Type[BaseParser]) -> None: SCANNERS[name] = parser_class - source = Parser.SOURCE_UNKOWN + source = Parser_Source.SOURCE_UNKOWN for base in parser_class.__bases__: if base is BaseAPIParser: - source = Parser.SOURCE_API + source = Parser_Source.SOURCE_API break if base is BaseFileParser: - source = Parser.SOURCE_FILE + source = Parser_Source.SOURCE_FILE break parser = get_parser_by_name(name) @@ -55,15 +56,17 @@ def register_parser(parser_class: Type[BaseParser]) -> None: def create_manual_parser() -> None: try: - Parser.objects.get(type=Parser.TYPE_MANUAL) + Parser.objects.get(type=Parser_Type.TYPE_MANUAL) except Parser.DoesNotExist: Parser( - name="Manual", source=Parser.SOURCE_MANUAL, type=Parser.TYPE_MANUAL + name="Manual", + source=Parser_Source.SOURCE_MANUAL, + type=Parser_Type.TYPE_MANUAL, ).save() except Parser.MultipleObjectsReturned: # Delete all manual parsers except the first one first_parser = True - for parser in Parser.objects.filter(type=Parser.TYPE_MANUAL): + for parser in Parser.objects.filter(type=Parser_Type.TYPE_MANUAL): if first_parser: first_parser = False else: diff --git a/backend/application/import_observations/types.py b/backend/application/import_observations/types.py new file mode 100644 index 000000000..ae993e584 --- /dev/null +++ b/backend/application/import_observations/types.py @@ -0,0 +1,34 @@ +class Parser_Source: + SOURCE_API = "API" + SOURCE_FILE = "File" + SOURCE_MANUAL = "Manual" + SOURCE_UNKOWN = "Unkown" + + SOURCE_CHOICES = [ + (SOURCE_API, SOURCE_API), + (SOURCE_FILE, SOURCE_FILE), + (SOURCE_MANUAL, SOURCE_MANUAL), + (SOURCE_UNKOWN, SOURCE_UNKOWN), + ] + + +class Parser_Type: + TYPE_SCA = "SCA" + TYPE_SAST = "SAST" + TYPE_DAST = "DAST" + TYPE_IAST = "IAST" + TYPE_SECRETS = "Secrets" + TYPE_INFRASTRUCTURE = "Infrastructure" + TYPE_OTHER = "Other" + TYPE_MANUAL = "Manual" + + TYPE_CHOICES = [ + (TYPE_SCA, TYPE_SCA), + (TYPE_SAST, TYPE_SAST), + (TYPE_DAST, TYPE_DAST), + (TYPE_IAST, TYPE_IAST), + (TYPE_SECRETS, TYPE_SECRETS), + (TYPE_INFRASTRUCTURE, TYPE_INFRASTRUCTURE), + (TYPE_OTHER, TYPE_OTHER), + (TYPE_MANUAL, TYPE_MANUAL), + ] diff --git a/backend/application/issue_tracker/issue_trackers/base_issue_tracker.py b/backend/application/issue_tracker/issue_trackers/base_issue_tracker.py index 7921ea65d..e2cbbd798 100644 --- a/backend/application/issue_tracker/issue_trackers/base_issue_tracker.py +++ b/backend/application/issue_tracker/issue_trackers/base_issue_tracker.py @@ -14,7 +14,7 @@ class Issue: class BaseIssueTracker: - def create_issue(self, observation: Observation) -> None: + def create_issue(self, observation: Observation) -> str: raise NotImplementedError("create_issue() must be overridden") def get_issue(self, product: Product, issue_id: str) -> Optional[Issue]: diff --git a/backend/application/issue_tracker/issue_trackers/github_issue_tracker.py b/backend/application/issue_tracker/issue_trackers/github_issue_tracker.py index d3c8cec45..ce99c867e 100644 --- a/backend/application/issue_tracker/issue_trackers/github_issue_tracker.py +++ b/backend/application/issue_tracker/issue_trackers/github_issue_tracker.py @@ -11,7 +11,7 @@ class GitHubIssueTracker(BaseIssueTracker): - def create_issue(self, observation: Observation) -> None: + def create_issue(self, observation: Observation) -> str: data: dict[str, Any] = { "title": self._get_title(observation), "body": self._get_description(observation), @@ -27,8 +27,7 @@ def create_issue(self, observation: Observation) -> None: timeout=60, ) response.raise_for_status() - observation.issue_tracker_issue_id = response.json().get("number") - observation.save() + return response.json().get("number") def get_issue(self, product: Product, issue_id: str) -> Optional[Issue]: response = requests.get( diff --git a/backend/application/issue_tracker/issue_trackers/gitlab_issue_tracker.py b/backend/application/issue_tracker/issue_trackers/gitlab_issue_tracker.py index 019971d9d..cf5fe6207 100644 --- a/backend/application/issue_tracker/issue_trackers/gitlab_issue_tracker.py +++ b/backend/application/issue_tracker/issue_trackers/gitlab_issue_tracker.py @@ -11,7 +11,7 @@ class GitLabIssueTracker(BaseIssueTracker): - def create_issue(self, observation: Observation) -> None: + def create_issue(self, observation: Observation) -> str: data = { "title": self._get_title(observation), "description": self._get_description(observation), @@ -26,8 +26,7 @@ def create_issue(self, observation: Observation) -> None: timeout=60, ) response.raise_for_status() - observation.issue_tracker_issue_id = response.json().get("iid") - observation.save() + return response.json().get("iid") def get_issue(self, product: Product, issue_id: str) -> Optional[Issue]: response = requests.get( diff --git a/backend/application/issue_tracker/issue_trackers/jira_issue_tracker.py b/backend/application/issue_tracker/issue_trackers/jira_issue_tracker.py index b9a777800..bf0d2a8f9 100644 --- a/backend/application/issue_tracker/issue_trackers/jira_issue_tracker.py +++ b/backend/application/issue_tracker/issue_trackers/jira_issue_tracker.py @@ -1,6 +1,7 @@ from typing import Optional from jira.client import JIRA +from jira.exceptions import JIRAError from jira.resources import Issue as JiraIssue from application.commons.services.functions import get_base_url_frontend @@ -22,7 +23,7 @@ def __init__(self, product: Product, with_communication: bool = True) -> None: ), ) - def create_issue(self, observation: Observation) -> None: + def create_issue(self, observation: Observation) -> str: labels = observation.product.issue_tracker_labels.split(",") jira_issue = self.jira.create_issue( @@ -33,10 +34,11 @@ def create_issue(self, observation: Observation) -> None: issuetype=observation.product.issue_tracker_issue_type, ) - observation.issue_tracker_issue_id = jira_issue.key observation.issue_tracker_jira_initial_status = str(jira_issue.fields.status) observation.save() + return jira_issue.key + def get_issue(self, product: Product, issue_id: str) -> Optional[Issue]: jira_issue = self._get_jira_issue(issue_id) if not jira_issue: @@ -125,4 +127,9 @@ def _get_description(self, observation: Observation) -> str: return description def _get_jira_issue(self, issue_id: str) -> Optional[JiraIssue]: - return self.jira.issue(issue_id, fields="summary,description,labels,status") + try: + return self.jira.issue(issue_id, fields="summary,description,labels,status") + except JIRAError as e: + if e.status_code == 404: + return None + raise diff --git a/backend/application/issue_tracker/services/issue_tracker.py b/backend/application/issue_tracker/services/issue_tracker.py index 3a79e113d..0d7d7f956 100644 --- a/backend/application/issue_tracker/services/issue_tracker.py +++ b/backend/application/issue_tracker/services/issue_tracker.py @@ -6,7 +6,11 @@ from application.commons.services.global_request import get_current_user from application.commons.services.tasks import handle_task_exception from application.core.models import Observation, Product -from application.issue_tracker.issue_trackers.base_issue_tracker import BaseIssueTracker +from application.core.types import Severity, Status +from application.issue_tracker.issue_trackers.base_issue_tracker import ( + BaseIssueTracker, + Issue, +) from application.issue_tracker.issue_trackers.github_issue_tracker import ( GitHubIssueTracker, ) @@ -14,6 +18,7 @@ GitLabIssueTracker, ) from application.issue_tracker.issue_trackers.jira_issue_tracker import JiraIssueTracker +from application.issue_tracker.types import Issue_Tracker def push_observations_to_issue_tracker( @@ -32,27 +37,38 @@ def push_observation_to_issue_tracker(observation: Observation, user: User) -> N and observation.branch == observation.product.repository_default_branch ): issue_tracker = issue_tracker_factory(observation.product) - - if observation.issue_tracker_issue_id: - issue = issue_tracker.get_issue( - observation.product, observation.issue_tracker_issue_id - ) - else: - issue = None + issue = _get_issue(observation, issue_tracker) # If the issue_tracker_issue_id is set but the issue does not exist, remove the id if observation.issue_tracker_issue_id and not issue: observation.issue_tracker_issue_id = "" observation.save() - if observation.current_status == Observation.STATUS_OPEN: - if issue: - issue_tracker.update_issue(observation, issue) + if observation.current_status == Status.STATUS_OPEN: + if observation.product.issue_tracker_minimum_severity: + numerical_minimum_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.product.issue_tracker_minimum_severity, 99 + ) else: - issue_tracker.create_issue(observation) + numerical_minimum_severity = 99 + + if observation.numerical_severity <= numerical_minimum_severity: + if issue: + issue_tracker.update_issue(observation, issue) + else: + issue_id = issue_tracker.create_issue(observation) + observation.issue_tracker_issue_id = issue_id + observation.save() + else: + if issue and not observation.issue_tracker_issue_closed: + issue_tracker.close_issue(observation, issue) + observation.issue_tracker_issue_closed = True + observation.save() else: if issue: issue_tracker.close_issue(observation, issue) + observation.issue_tracker_issue_closed = True + observation.save() except Exception as e: handle_task_exception(e, user) @@ -74,13 +90,25 @@ def push_deleted_observation_to_issue_tracker( def issue_tracker_factory( product: Product, with_communication: bool = True ) -> BaseIssueTracker: - if product.issue_tracker_type == Product.ISSUE_TRACKER_GITHUB: + if product.issue_tracker_type == Issue_Tracker.ISSUE_TRACKER_GITHUB: return GitHubIssueTracker() - if product.issue_tracker_type == Product.ISSUE_TRACKER_GITLAB: + if product.issue_tracker_type == Issue_Tracker.ISSUE_TRACKER_GITLAB: return GitLabIssueTracker() - if product.issue_tracker_type == Product.ISSUE_TRACKER_JIRA: + if product.issue_tracker_type == Issue_Tracker.ISSUE_TRACKER_JIRA: return JiraIssueTracker(product=product, with_communication=with_communication) raise ValueError(f"Unknown issue tracker type: {product.issue_tracker_type}") + + +def _get_issue( + observation: Observation, issue_tracker: BaseIssueTracker +) -> Optional[Issue]: + if observation.issue_tracker_issue_id: + issue = issue_tracker.get_issue( + observation.product, observation.issue_tracker_issue_id + ) + else: + issue = None + return issue diff --git a/backend/application/issue_tracker/types.py b/backend/application/issue_tracker/types.py new file mode 100644 index 000000000..c2fa0f0d7 --- /dev/null +++ b/backend/application/issue_tracker/types.py @@ -0,0 +1,10 @@ +class Issue_Tracker: + ISSUE_TRACKER_GITHUB = "GitHub" + ISSUE_TRACKER_GITLAB = "GitLab" + ISSUE_TRACKER_JIRA = "Jira" + + ISSUE_TRACKER_TYPE_CHOICES = [ + (ISSUE_TRACKER_GITHUB, ISSUE_TRACKER_GITHUB), + (ISSUE_TRACKER_GITLAB, ISSUE_TRACKER_GITLAB), + (ISSUE_TRACKER_JIRA, ISSUE_TRACKER_JIRA), + ] diff --git a/backend/application/metrics/api/views.py b/backend/application/metrics/api/views.py index e063b4aea..a1b9e4310 100644 --- a/backend/application/metrics/api/views.py +++ b/backend/application/metrics/api/views.py @@ -10,8 +10,8 @@ from application.access_control.services.authorization import user_has_permission_or_403 from application.access_control.services.roles_permissions import Permissions -from application.core.models import Observation from application.core.queries.product import get_product_by_id +from application.core.types import Severity from application.metrics.models import Product_Metrics_Status from application.metrics.services.export_metrics import ( export_product_metrics_csv, @@ -93,15 +93,15 @@ def get(self, request): fieldnames=[ "source_file", "Vulnerabilities_Total".lower(), - f"Vulnerabilities_{Observation.SEVERITY_CRITICAL}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_HIGH}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_LOW}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_NONE}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_UNKOWN}".lower(), - f"Vulnerabilities_{Observation.SEVERITY_HIGH}_and_above".lower(), - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}_and_above".lower(), - f"Vulnerabilities_{Observation.SEVERITY_LOW}_and_above".lower(), + f"Vulnerabilities_{Severity.SEVERITY_CRITICAL}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_HIGH}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_LOW}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_NONE}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_UNKOWN}".lower(), + f"Vulnerabilities_{Severity.SEVERITY_HIGH}_and_above".lower(), + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}_and_above".lower(), + f"Vulnerabilities_{Severity.SEVERITY_LOW}_and_above".lower(), ], ) writer.writeheader() diff --git a/backend/application/metrics/services/metrics.py b/backend/application/metrics/services/metrics.py index f0a660e17..a79330900 100644 --- a/backend/application/metrics/services/metrics.py +++ b/backend/application/metrics/services/metrics.py @@ -4,6 +4,7 @@ from django.utils import timezone from application.core.models import Observation, Product +from application.core.types import Severity, Status from application.metrics.models import Product_Metrics, Product_Metrics_Status from application.metrics.queries.product_metrics import ( get_product_metrics, @@ -84,33 +85,33 @@ def calculate_metrics_for_product( # pylint: disable=too-many-branches ).values("current_severity", "current_status") for observation in observations: - if observation.get("current_status") == Observation.STATUS_OPEN: + if observation.get("current_status") == Status.STATUS_OPEN: todays_product_metrics.open += 1 - if observation.get("current_severity") == Observation.SEVERITY_CRITICAL: + if observation.get("current_severity") == Severity.SEVERITY_CRITICAL: todays_product_metrics.open_critical += 1 - elif observation.get("current_severity") == Observation.SEVERITY_HIGH: + elif observation.get("current_severity") == Severity.SEVERITY_HIGH: todays_product_metrics.open_high += 1 - elif observation.get("current_severity") == Observation.SEVERITY_MEDIUM: + elif observation.get("current_severity") == Severity.SEVERITY_MEDIUM: todays_product_metrics.open_medium += 1 - elif observation.get("current_severity") == Observation.SEVERITY_LOW: + elif observation.get("current_severity") == Severity.SEVERITY_LOW: todays_product_metrics.open_low += 1 - elif observation.get("current_severity") == Observation.SEVERITY_NONE: + elif observation.get("current_severity") == Severity.SEVERITY_NONE: todays_product_metrics.open_none += 1 - elif observation.get("current_severity") == Observation.SEVERITY_UNKOWN: + elif observation.get("current_severity") == Severity.SEVERITY_UNKOWN: todays_product_metrics.open_unknown += 1 - elif observation.get("current_status") == Observation.STATUS_RESOLVED: + elif observation.get("current_status") == Status.STATUS_RESOLVED: todays_product_metrics.resolved += 1 - elif observation.get("current_status") == Observation.STATUS_DUPLICATE: + elif observation.get("current_status") == Status.STATUS_DUPLICATE: todays_product_metrics.duplicate += 1 - elif observation.get("current_status") == Observation.STATUS_FALSE_POSITIVE: + elif observation.get("current_status") == Status.STATUS_FALSE_POSITIVE: todays_product_metrics.false_positive += 1 - elif observation.get("current_status") == Observation.STATUS_IN_REVIEW: + elif observation.get("current_status") == Status.STATUS_IN_REVIEW: todays_product_metrics.in_review += 1 - elif observation.get("current_status") == Observation.STATUS_NOT_AFFECTED: + elif observation.get("current_status") == Status.STATUS_NOT_AFFECTED: todays_product_metrics.not_affected += 1 - elif observation.get("current_status") == Observation.STATUS_NOT_SECURITY: + elif observation.get("current_status") == Status.STATUS_NOT_SECURITY: todays_product_metrics.not_security += 1 - elif observation.get("current_status") == Observation.STATUS_RISK_ACCEPTED: + elif observation.get("current_status") == Status.STATUS_RISK_ACCEPTED: todays_product_metrics.risk_accepted += 1 todays_product_metrics.save() @@ -258,7 +259,7 @@ def get_codecharta_metrics(product: Product) -> list[dict]: observations = Observation.objects.filter( product=product, branch=product.repository_default_branch, - current_status=Observation.STATUS_OPEN, + current_status=Status.STATUS_OPEN, ) for observation in observations: if observation.origin_source_file: @@ -270,31 +271,31 @@ def get_codecharta_metrics(product: Product) -> list[dict]: file_severities_value["source_file"] = observation.origin_source_file file_severities_value["Vulnerabilities_Total".lower()] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_CRITICAL}".lower() + f"Vulnerabilities_{Severity.SEVERITY_CRITICAL}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_HIGH}".lower() + f"Vulnerabilities_{Severity.SEVERITY_HIGH}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}".lower() + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_LOW}".lower() + f"Vulnerabilities_{Severity.SEVERITY_LOW}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_NONE}".lower() + f"Vulnerabilities_{Severity.SEVERITY_NONE}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_UNKOWN}".lower() + f"Vulnerabilities_{Severity.SEVERITY_UNKOWN}".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_HIGH}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_HIGH}_and_above".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}_and_above".lower() ] = 0 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_LOW}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_LOW}_and_above".lower() ] = 0 file_severities_dict[observation.origin_source_file] = ( file_severities_value @@ -306,30 +307,30 @@ def get_codecharta_metrics(product: Product) -> list[dict]: ] += 1 if observation.current_severity in ( - Observation.SEVERITY_CRITICAL, - Observation.SEVERITY_HIGH, + Severity.SEVERITY_CRITICAL, + Severity.SEVERITY_HIGH, ): file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_HIGH}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_HIGH}_and_above".lower() ] += 1 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}_and_above".lower() ] += 1 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_LOW}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_LOW}_and_above".lower() ] += 1 - if observation.current_severity == Observation.SEVERITY_MEDIUM: + if observation.current_severity == Severity.SEVERITY_MEDIUM: file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_MEDIUM}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_MEDIUM}_and_above".lower() ] += 1 file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_LOW}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_LOW}_and_above".lower() ] += 1 - if observation.current_severity == Observation.SEVERITY_LOW: + if observation.current_severity == Severity.SEVERITY_LOW: file_severities_value[ - f"Vulnerabilities_{Observation.SEVERITY_LOW}_and_above".lower() + f"Vulnerabilities_{Severity.SEVERITY_LOW}_and_above".lower() ] += 1 return list(file_severities_dict.values()) diff --git a/backend/application/rules/models.py b/backend/application/rules/models.py index cd0711816..56956934f 100644 --- a/backend/application/rules/models.py +++ b/backend/application/rules/models.py @@ -8,7 +8,8 @@ TextField, ) -from application.core.models import Observation, Parser, Product +from application.core.models import Parser, Product +from application.core.types import Severity, Status class Rule(Model): @@ -26,11 +27,9 @@ class Rule(Model): origin_source_file = CharField(max_length=255, blank=True) origin_cloud_qualified_resource = CharField(max_length=255, blank=True) new_severity = CharField( - max_length=12, choices=Observation.SEVERITY_CHOICES, blank=True - ) - new_status = CharField( - max_length=16, choices=Observation.STATUS_CHOICES, blank=True + max_length=12, choices=Severity.SEVERITY_CHOICES, blank=True ) + new_status = CharField(max_length=16, choices=Status.STATUS_CHOICES, blank=True) enabled = BooleanField(default=True) class Meta: diff --git a/backend/poetry.lock b/backend/poetry.lock index 54dd0c706..ccf4e2238 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -205,13 +205,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -479,43 +479,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.1" +version = "42.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, - {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, - {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, - {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, - {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, - {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, - {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, + {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, + {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, + {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, + {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, + {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, + {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, ] [package.dependencies] @@ -864,13 +864,13 @@ sidecar = ["drf-spectacular-sidecar"] [[package]] name = "drf-spectacular-sidecar" -version = "2024.1.1" +version = "2024.2.1" description = "Serve self-contained distribution builds of Swagger UI and Redoc with Django" optional = false python-versions = ">=3.6" files = [ - {file = "drf-spectacular-sidecar-2024.1.1.tar.gz", hash = "sha256:099ec58b6af6a90e851a9329b12a57aa1ee7daa6cef62fb504f2ed302f10da76"}, - {file = "drf_spectacular_sidecar-2024.1.1-py3-none-any.whl", hash = "sha256:4b9e33b4dcfa43f84e3db2659d31766a018a2b98b02d8856d9cd69580a4911c9"}, + {file = "drf-spectacular-sidecar-2024.2.1.tar.gz", hash = "sha256:db95a38971c9be09986356f82041fac60183d28ebdf60c0c51eb8c1f86da3937"}, + {file = "drf_spectacular_sidecar-2024.2.1-py3-none-any.whl", hash = "sha256:dc819ef7a35448c18b2bf4273b38fe1468e14daea5fc8675afb5d0f9e6d9a0ba"}, ] [package.dependencies] @@ -1072,13 +1072,13 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < [[package]] name = "ipython" -version = "8.20.0" +version = "8.21.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"}, - {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"}, + {file = "ipython-8.21.0-py3-none-any.whl", hash = "sha256:1050a3ab8473488d7eee163796b02e511d0735cf43a04ba2a8348bd0f2eaf8a5"}, + {file = "ipython-8.21.0.tar.gz", hash = "sha256:48fbc236fbe0e138b88773fa0437751f14c3645fb483f1d4c5dee58b37e5ce73"}, ] [package.dependencies] @@ -1094,17 +1094,17 @@ stack-data = "*" traitlets = ">=5" [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath", "trio"] +test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "isort" @@ -1203,71 +1203,71 @@ referencing = ">=0.31.0" [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -1355,20 +1355,20 @@ files = [ [[package]] name = "mysqlclient" -version = "2.2.1" +version = "2.2.2" description = "Python interface to MySQL" optional = false python-versions = ">=3.8" files = [ - {file = "mysqlclient-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:c5a293baebbfcfa2905545198a54e90f1cf00f211eae6637d24930abb6432cba"}, - {file = "mysqlclient-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8f40c872f19639366e3df27bef2ff087be0e3ee0bd3453470bd29f46b54a90f6"}, - {file = "mysqlclient-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:45600f4f321096bd1ead3355bc62cfcf8d97dc78df94e4ab5db72ecb5db1bd04"}, - {file = "mysqlclient-2.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f8889cc5f0141bb307b915e981a66793df663ace92259344661084a7dd8d12a"}, - {file = "mysqlclient-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9db6305cdf2a1da350f827d2a19be7f2666eafd9eb8d4f7cbbac5df847d61b99"}, - {file = "mysqlclient-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:97eee76818774bb695e018ff4c3dafaab74b9a0b0cf32c90b02caeec3b19cd8e"}, - {file = "mysqlclient-2.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4fabe1f4b545ed6244ad0ff426e6b27054b7e5c5b1392be0de2e5f2f59be0392"}, - {file = "mysqlclient-2.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:641a7c9de443ddef186a0e89f24b4251ad44f4ddc5e7094332bf2d286d7c9e33"}, - {file = "mysqlclient-2.2.1.tar.gz", hash = "sha256:2c7ad15b87293b12fd44b47c46879ec95ec647f4567e866ccd70b8337584e9b2"}, + {file = "mysqlclient-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f2742b7791aa3c4cbdc2a972524aa63443414c0582e601bc8d948f43d7f5dd9"}, + {file = "mysqlclient-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:3d4ebe14e6a00b0abf6e467c95cf1ec5902c605e4090c0c2ebbc1dee8fc0d795"}, + {file = "mysqlclient-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:20ad697e188ce175903b3ad66cddab751c28bc132ff0f7b5cefa6173b99fa608"}, + {file = "mysqlclient-2.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:8b3771525532cdef672f2808a485fe71216625c41c38dd9dbbe96e3aa0e99bbb"}, + {file = "mysqlclient-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:45b1785afeecc4f3b998ab7ea7bf25351c03a11dabbfe34590d70290e4f4496c"}, + {file = "mysqlclient-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:675336353b608916f1bf1d4aedf0dc1172bc544135c88bddd57f3a5ccc81bd90"}, + {file = "mysqlclient-2.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e23e71ce4940949a65288ec7d189308129ba0c35d0a1b27fbeb89740e5ff2f43"}, + {file = "mysqlclient-2.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e37fd8fc0ae3a0f4dd6897449abc2e5438985e453ddfb894b5c3a7301602cc"}, + {file = "mysqlclient-2.2.2.tar.gz", hash = "sha256:6f57715fb08c80ab80d34febe0aa8ef8810e52d527ad7da2da76e8ce25e2f2e9"}, ] [[package]] @@ -1570,18 +1570,18 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pre-commit" @@ -1762,18 +1762,18 @@ files = [ [[package]] name = "pydantic" -version = "2.5.3" +version = "2.6.0" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, + {file = "pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae"}, + {file = "pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.14.6" +pydantic-core = "2.16.1" typing-extensions = ">=4.6.1" [package.extras] @@ -1781,116 +1781,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.14.6" +version = "2.16.1" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, + {file = "pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948"}, + {file = "pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798"}, + {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17"}, + {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388"}, + {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7"}, + {file = "pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4"}, + {file = "pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c"}, + {file = "pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da"}, + {file = "pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d"}, + {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864"}, + {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7"}, + {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae"}, + {file = "pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1"}, + {file = "pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c"}, + {file = "pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b"}, + {file = "pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51"}, + {file = "pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54"}, + {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e"}, + {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8"}, + {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f"}, + {file = "pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212"}, + {file = "pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f"}, + {file = "pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd"}, + {file = "pydantic_core-2.16.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706"}, + {file = "pydantic_core-2.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c"}, + {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95"}, + {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8"}, + {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca"}, + {file = "pydantic_core-2.16.1-cp38-none-win32.whl", hash = "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610"}, + {file = "pydantic_core-2.16.1-cp38-none-win_amd64.whl", hash = "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e"}, + {file = "pydantic_core-2.16.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196"}, + {file = "pydantic_core-2.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc"}, + {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e"}, + {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d"}, + {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640"}, + {file = "pydantic_core-2.16.1-cp39-none-win32.whl", hash = "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f"}, + {file = "pydantic_core-2.16.1-cp39-none-win_amd64.whl", hash = "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc"}, + {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206"}, + {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76"}, + {file = "pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34"}, ] [package.dependencies] @@ -2002,13 +1976,13 @@ pylint = ">=1.7" [[package]] name = "pytz" -version = "2023.3.post1" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -2036,7 +2010,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2073,13 +2046,13 @@ files = [ [[package]] name = "referencing" -version = "0.32.1" +version = "0.33.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, - {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] @@ -2359,13 +2332,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "types-pytz" -version = "2023.3.1.1" +version = "2024.1.0.20240203" description = "Typing stubs for pytz" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"}, - {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"}, + {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, + {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, ] [[package]] @@ -2428,17 +2401,18 @@ files = [ [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2561,4 +2535,4 @@ brotli = ["Brotli"] [metadata] lock-version = "2.0" python-versions = ">= 3.10, < 3.13" -content-hash = "674e7c4f3300d41f4c3733fdf9815aaaaba10b9291b6333339c4e53ed4d52d6a" +content-hash = "b616e116a64d1841903e69ca82252022aff41a03e0aaf0e990efd62b6005779a" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 96ab3302d..ee9c42030 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SecObserve" -version = "1.5.0" +version = "1.6.0" description = "SecObserve is an open source vulnerability management system for software development teams. " license = "BSD-3-Clause" authors = [ @@ -27,7 +27,7 @@ django-cors-headers = "4.3.1" # https://github.com/adamchainz/django-cors-header # OpenAPI 3 # ------------------------------------------------------------------------------ drf-spectacular = "0.27.1" # https://github.com/tfranzel/drf-spectacular -drf-spectacular-sidecar = "2024.1.1" # https://github.com/tfranzel/drf-spectacular-sidecar +drf-spectacular-sidecar = "2024.2.1" # https://github.com/tfranzel/drf-spectacular-sidecar # Token authentication # ------------------------------------------------------------------------------ PyJWT = "2.8.0" # https://github.com/jpadilla/pyjwt @@ -36,7 +36,7 @@ PyJWT = "2.8.0" # https://github.com/jpadilla/pyjwt requests = "2.31.0" # https://github.com/psf/requests # Database # ------------------------------------------------------------------------------ -mysqlclient = "2.2.1" +mysqlclient = "2.2.2" psycopg = { version = "3.1.17", extras = ["binary"] } # Excel and CSV # ------------------------------------------------------------------------------ diff --git a/backend/setup.cfg b/backend/setup.cfg index 00915d7c0..0b9513fbc 100644 --- a/backend/setup.cfg +++ b/backend/setup.cfg @@ -66,6 +66,9 @@ layers = ignore_imports = application.access_control.services.authorization -> application.*.models application.core.api.views -> application.rules.services.rule_engine + application.core.api.serializers -> application.import_observations.types + application.core.api.filters -> application.import_observations.types + application.core.models -> application.import_observations.types [importlinter:contract:module_layers] name = Module layers @@ -75,6 +78,7 @@ layers = (services) (queries) (models) + (types) containers = application.core application.commons diff --git a/backend/unittests/access_control/api/test_authorization_api_configurations.py b/backend/unittests/access_control/api/test_authorization_api_configurations.py index f1f941186..b09d090f8 100644 --- a/backend/unittests/access_control/api/test_authorization_api_configurations.py +++ b/backend/unittests/access_control/api/test_authorization_api_configurations.py @@ -6,14 +6,14 @@ class TestAuthorizationApiConfigurations(TestAuthorizationBase): def test_authorization_api_configurations(self): - expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_external', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]}), 'name': 'db_api_configuration_external', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 2, 'parser': 2})]})" + expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_external', 'description': '', '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_unkown': 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', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]}), 'name': 'db_api_configuration_external', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 2, 'parser': 2})]})" self._test_api( APITest( "db_admin", "get", "/api/api_configurations/", None, 200, expected_data ) ) - expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2})]})" + expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2})]})" self._test_api( APITest( "db_internal_write", @@ -25,7 +25,7 @@ def test_authorization_api_configurations(self): ) ) - expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}" + expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_api_configuration_internal', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}" self._test_api( APITest( "db_internal_write", @@ -83,7 +83,7 @@ def test_authorization_api_configurations(self): ) ) - expected_data = "{'id': 3, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'string', 'base_url': 'string', 'project_key': 'string', 'api_key': 'string', 'product': 1, 'parser': 2}" + expected_data = "{'id': 3, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'string', 'base_url': 'string', 'project_key': 'string', 'api_key': 'string', 'product': 1, 'parser': 2}" self._test_api( APITest( "db_internal_write", @@ -109,7 +109,7 @@ def test_authorization_api_configurations(self): ) ) - expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'changed', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}" + expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'changed', 'base_url': 'http://localhost:8080', 'project_key': 'secobserve', 'api_key': '__secret__', 'product': 1, 'parser': 2}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_observations.py b/backend/unittests/access_control/api/test_authorization_observations.py index 6ec532d85..579c5204f 100644 --- a/backend/unittests/access_control/api/test_authorization_observations.py +++ b/backend/unittests/access_control/api/test_authorization_observations.py @@ -6,12 +6,12 @@ class TestAuthorizationObservations(TestAuthorizationBase): def test_authorization_observations(self): - expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'product_group_name': 'db_product_group', 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'product_group_name': '', 'name': 'db_product_external', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': None, 'repository_default_branch': 3}), 'branch_name': '', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'False positive', 'parser_status': 'Open', 'rule_status': 'False positive', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.876000+01:00', 'created': '2022-12-15T17:10:35.521000+01:00', 'modified': '2022-12-16T17:13:18.283000+01:00', 'last_observation_log': '2022-12-16T17:13:18.283000+01:00', 'identity_hash': 'bc8e59b7687fe3533616b3914c636389c131eac3bdbda1b67d8d26f890a74007', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 2, 'branch': None, 'parser': 1, 'origin_service': None, 'general_rule': None, 'product_rule': 2})]})" + expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'product_group_name': 'db_product_group', 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'product_group_name': '', 'name': 'db_product_external', 'description': '', '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_unkown': 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', 'product_group': None, 'repository_default_branch': 3}), 'branch_name': '', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'False positive', 'parser_status': 'Open', 'rule_status': 'False positive', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.876000+01:00', 'created': '2022-12-15T17:10:35.521000+01:00', 'modified': '2022-12-16T17:13:18.283000+01:00', 'last_observation_log': '2022-12-16T17:13:18.283000+01:00', 'identity_hash': 'bc8e59b7687fe3533616b3914c636389c131eac3bdbda1b67d8d26f890a74007', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 2, 'branch': None, 'parser': 1, 'origin_service': None, 'general_rule': None, 'product_rule': 2})]})" self._test_api( APITest("db_admin", "get", "/api/observations/", None, 200, expected_data) ) - expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'product_group_name': 'db_product_group', 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1})]})" + expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'product_group_name': 'db_product_group', 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1})]})" self._test_api( APITest( "db_internal_write", @@ -22,7 +22,7 @@ def test_authorization_observations(self): expected_data, ) ) - expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'observation_logs': [OrderedDict({'id': 2, 'severity': '', 'status': 'Duplicate', 'comment': 'Set by product rule', 'created': '2022-12-15T17:10:35.524000+01:00', 'user': 2}), OrderedDict({'id': 1, 'severity': 'Medium', 'status': 'Open', 'comment': 'Set by parser', 'created': '2022-12-15T17:10:35.518000+01:00', 'user': 2})], 'references': [], 'evidences': [OrderedDict({'id': 1, 'name': 'db_evidence_internal'})], 'origin_source_file_url': None, 'issue_tracker_issue_url': None, 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_name_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}" + expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'branch_name': 'db_branch_internal_dev', 'parser_data': OrderedDict({'id': 1, 'name': 'db_parser_file', 'type': 'DAST', 'source': 'File'}), 'observation_logs': [OrderedDict({'id': 2, 'severity': '', 'status': 'Duplicate', 'comment': 'Set by product rule', 'created': '2022-12-15T17:10:35.524000+01:00', 'user': 2}), OrderedDict({'id': 1, 'severity': 'Medium', 'status': 'Open', 'comment': 'Set by parser', 'created': '2022-12-15T17:10:35.518000+01:00', 'user': 2})], 'references': [], 'evidences': [OrderedDict({'id': 1, 'name': 'db_evidence_internal'})], 'origin_source_file_url': None, 'issue_tracker_issue_url': None, 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_name_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_potential_duplicates.py b/backend/unittests/access_control/api/test_authorization_potential_duplicates.py index 3d54eb63f..ce18be4aa 100644 --- a/backend/unittests/access_control/api/test_authorization_potential_duplicates.py +++ b/backend/unittests/access_control/api/test_authorization_potential_duplicates.py @@ -9,7 +9,7 @@ class TestAuthorizationPotentialDuplicates(TestAuthorizationBase): def test_authorization_potential_duplicates(self): - expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'potential_duplicate_observation': OrderedDict({'id': 1, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), 'type': 'Component', 'observation': 1}), OrderedDict({'id': 2, 'potential_duplicate_observation': OrderedDict({'id': 2, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'False positive', 'parser_status': 'Open', 'rule_status': 'False positive', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.876000+01:00', 'created': '2022-12-15T17:10:35.521000+01:00', 'modified': '2022-12-16T17:13:18.283000+01:00', 'last_observation_log': '2022-12-16T17:13:18.283000+01:00', 'identity_hash': 'bc8e59b7687fe3533616b3914c636389c131eac3bdbda1b67d8d26f890a74007', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 2, 'branch': None, 'parser': 1, 'origin_service': None, 'general_rule': None, 'product_rule': 2}), 'type': 'Source', 'observation': 2})]})" + expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'potential_duplicate_observation': OrderedDict({'id': 1, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), 'type': 'Component', 'observation': 1}), OrderedDict({'id': 2, 'potential_duplicate_observation': OrderedDict({'id': 2, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'False positive', 'parser_status': 'Open', 'rule_status': 'False positive', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.876000+01:00', 'created': '2022-12-15T17:10:35.521000+01:00', 'modified': '2022-12-16T17:13:18.283000+01:00', 'last_observation_log': '2022-12-16T17:13:18.283000+01:00', 'identity_hash': 'bc8e59b7687fe3533616b3914c636389c131eac3bdbda1b67d8d26f890a74007', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 2, 'branch': None, 'parser': 1, 'origin_service': None, 'general_rule': None, 'product_rule': 2}), 'type': 'Source', 'observation': 2})]})" self._test_api( APITest( "db_admin", @@ -21,7 +21,7 @@ def test_authorization_potential_duplicates(self): ) ) - expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'potential_duplicate_observation': OrderedDict({'id': 1, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), 'type': 'Component', 'observation': 1})]})" + expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'potential_duplicate_observation': OrderedDict({'id': 1, 'scanner_name': 'db_parser', 'origin_component_name_version': '', 'title': 'db_observation_internal', 'description': '', 'recommendation': '', 'current_severity': 'Medium', 'parser_severity': 'Medium', 'rule_severity': '', 'assessment_severity': '', 'current_status': 'Duplicate', 'parser_status': 'Open', 'rule_status': 'Duplicate', 'assessment_status': '', 'scanner_observation_id': '', 'vulnerability_id': '', 'origin_component_name': '', 'origin_component_version': '', 'origin_component_purl': '', 'origin_component_cpe': '', 'origin_component_dependencies': '', 'origin_docker_image_name': '', 'origin_docker_image_tag': '', 'origin_docker_image_name_tag': '', 'origin_docker_image_name_tag_short': '', 'origin_docker_image_digest': '', 'origin_endpoint_url': '', 'origin_endpoint_scheme': '', 'origin_endpoint_hostname': '', 'origin_endpoint_port': None, 'origin_endpoint_path': '', 'origin_endpoint_params': '', 'origin_endpoint_query': '', 'origin_endpoint_fragment': '', 'origin_service_name': 'db_service_internal_backend', 'origin_source_file': '', 'origin_source_line_start': None, 'origin_source_line_end': None, 'origin_cloud_provider': '', 'origin_cloud_account_subscription_project': '', 'origin_cloud_resource': '', 'origin_cloud_resource_type': '', 'origin_cloud_qualified_resource': '', 'cvss3_score': None, 'cvss3_vector': '', 'cwe': None, 'epss_score': None, 'epss_percentile': None, 'found': None, 'scanner': 'db_parser', 'upload_filename': 'parser.json', 'api_configuration_name': '', 'import_last_seen': '2022-12-15T17:14:20.870000+01:00', 'created': '2022-12-15T17:10:35.513000+01:00', 'modified': '2022-12-16T17:13:18.282000+01:00', 'last_observation_log': '2022-12-16T17:13:18.281000+01:00', 'identity_hash': '6eef8088480aa2523aeeb64ad35f876a942cc3172cfb36752f3a052a4f88642c', 'issue_tracker_issue_id': '', 'issue_tracker_issue_closed': False, 'has_potential_duplicates': False, 'product': 1, 'branch': 1, 'parser': 1, 'origin_service': 1, 'general_rule': None, 'product_rule': 1}), 'type': 'Component', 'observation': 1})]})" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_product_rules.py b/backend/unittests/access_control/api/test_authorization_product_rules.py index 47254e6e0..7be4fed2f 100644 --- a/backend/unittests/access_control/api/test_authorization_product_rules.py +++ b/backend/unittests/access_control/api/test_authorization_product_rules.py @@ -6,12 +6,12 @@ class TestAuthorizationProductRules(TestAuthorizationBase): def test_authorization_product_rules(self): - expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_external', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]}), 'name': 'db_product_rule_external', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'False positive', 'enabled': True, 'product': 2, 'parser': 1})]})" + expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}), OrderedDict({'id': 2, 'product_data': OrderedDict({'id': 2, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_external', 'description': '', '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_unkown': 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', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]}), 'name': 'db_product_rule_external', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'False positive', 'enabled': True, 'product': 2, 'parser': 1})]})" self._test_api( APITest("db_admin", "get", "/api/product_rules/", None, 200, expected_data) ) - expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1})]})" + expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1})]})" self._test_api( APITest( "db_internal_write", @@ -23,7 +23,7 @@ def test_authorization_product_rules(self): ) ) - expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}" + expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'db_product_rule_internal', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}" self._test_api( APITest( "db_internal_write", @@ -73,7 +73,7 @@ def test_authorization_product_rules(self): ) ) - expected_data = "{'id': 4, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'string', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': '', 'enabled': True, 'product': 1, 'parser': 1}" + expected_data = "{'id': 4, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'string', 'description': '', 'scanner_prefix': '', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': '', 'enabled': True, 'product': 1, 'parser': 1}" self._test_api( APITest( "db_internal_write", @@ -100,7 +100,7 @@ def test_authorization_product_rules(self): ) ) - expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'changed', 'description': '', 'scanner_prefix': 'also_changed', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}" + expected_data = "{'id': 1, 'product_data': OrderedDict({'id': 1, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), 'name': 'changed', 'description': '', 'scanner_prefix': 'also_changed', 'title': '', 'description_observation': '', 'origin_component_name_version': '', 'origin_docker_image_name_tag': '', 'origin_endpoint_url': '', 'origin_service_name': '', 'origin_source_file': '', 'origin_cloud_qualified_resource': '', 'new_severity': '', 'new_status': 'Duplicate', 'enabled': True, 'product': 1, 'parser': 1}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_products.py b/backend/unittests/access_control/api/test_authorization_products.py index f9d8a848d..5b9f67e69 100644 --- a/backend/unittests/access_control/api/test_authorization_products.py +++ b/backend/unittests/access_control/api/test_authorization_products.py @@ -6,18 +6,18 @@ class TestAuthorizationProducts(TestAuthorizationBase): def test_authorization_products(self): - expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_external', 'name': 'db_product_external', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]})]})" + expected_data = "OrderedDict({'count': 2, 'next': None, 'previous': None, 'results': [OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}), OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_external', 'name': 'db_product_external', 'description': '', '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_unkown': 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', 'product_group': None, 'repository_default_branch': 3, 'members': [3, 4, 5]})]})" self._test_api( APITest("db_admin", "get", "/api/products/", None, 200, expected_data) ) - expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]})]})" + expected_data = "OrderedDict({'count': 1, 'next': None, 'previous': None, 'results': [OrderedDict({'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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]})]})" 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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}" + 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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': '', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}" self._test_api( APITest( "db_internal_write", "get", "/api/products/1/", None, 200, expected_data @@ -53,7 +53,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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': '', 'name': 'string', 'description': '', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': None, 'repository_default_branch': None, 'members': [2]}" + 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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': '', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': '', 'name': 'string', 'description': '', '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_unkown': 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', 'product_group': None, 'repository_default_branch': None, 'members': [2]}" self._test_api( APITest( "db_internal_write", @@ -81,7 +81,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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': 'string', '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_unkown': 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': '', 'last_observation_change': '2022-12-16T17:13:18.283000+01:00', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}" + 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_unkown_observation_count': 0, 'permissions': {, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }, 'product_group_name': 'db_product_group', 'product_group_repository_branch_housekeeping_active': None, 'product_group_security_gate_active': None, 'repository_default_branch_name': 'db_branch_internal_dev', 'name': 'db_product_internal', 'description': 'string', '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_unkown': 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', 'product_group': 3, 'repository_default_branch': 1, 'members': [2, 3]}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/base_test_case.py b/backend/unittests/base_test_case.py index eac9a77a9..22d8a17cd 100644 --- a/backend/unittests/base_test_case.py +++ b/backend/unittests/base_test_case.py @@ -10,6 +10,7 @@ Product, Product_Member, ) +from application.core.types import Severity, Status from application.import_observations.models import Api_Configuration from application.rules.models import Rule @@ -31,8 +32,8 @@ def setUp(self) -> None: self.observation_log_1 = Observation_Log( observation=self.observation_1, user=self.user_internal, - severity=Observation.SEVERITY_CRITICAL, - status=Observation.STATUS_DUPLICATE, + severity=Severity.SEVERITY_CRITICAL, + status=Status.STATUS_DUPLICATE, comment="comment", ) self.product_rule_1 = Rule(name="rule_1", product=self.product_1) diff --git a/backend/unittests/core/api/test_serializers.py b/backend/unittests/core/api/test_serializers.py index b4da8a741..f9e783d9c 100644 --- a/backend/unittests/core/api/test_serializers.py +++ b/backend/unittests/core/api/test_serializers.py @@ -2,7 +2,8 @@ from application.access_control.services.roles_permissions import Permissions, Roles from application.core.api.serializers import BranchSerializer, ProductSerializer -from application.core.models import Observation, Product_Member +from application.core.models import Product_Member +from application.core.types import Severity, Status from unittests.base_test_case import BaseTestCase @@ -25,8 +26,8 @@ def test_get_open_critical_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_CRITICAL, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -38,8 +39,8 @@ def test_get_open_high_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_HIGH, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -51,8 +52,8 @@ def test_get_open_medium_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_MEDIUM, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -64,8 +65,8 @@ def test_get_open_low_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_LOW, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -77,8 +78,8 @@ def test_get_open_none_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_NONE, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -90,8 +91,8 @@ def test_get_open_unkown_observation_count(self, mock_filter): ) mock_filter.assert_called_with( branch=self.branch_1, - current_severity=Observation.SEVERITY_UNKOWN, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, ) @@ -107,8 +108,8 @@ def test_get_open_critical_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_CRITICAL, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -121,8 +122,8 @@ def test_get_open_high_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_HIGH, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -135,8 +136,8 @@ def test_get_open_medium_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_MEDIUM, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -149,8 +150,8 @@ def test_get_open_low_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_LOW, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -163,8 +164,8 @@ def test_get_open_none_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_NONE, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -177,8 +178,8 @@ def test_get_open_unkown_observation_count(self, mock_filter): mock_filter.assert_called_with( product=self.product_1, branch=self.branch_1, - current_severity=Observation.SEVERITY_UNKOWN, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, ) @patch("application.core.api.serializers.get_current_user") diff --git a/backend/unittests/core/services/test_observation.py b/backend/unittests/core/services/test_observation.py index fed4e210c..5923f0933 100644 --- a/backend/unittests/core/services/test_observation.py +++ b/backend/unittests/core/services/test_observation.py @@ -8,6 +8,7 @@ get_identity_hash, normalize_observation_fields, ) +from application.core.types import Severity, Status from unittests.base_test_case import BaseTestCase @@ -63,123 +64,117 @@ def test_get_string_to_hash_intermediate(self): def test_get_current_severity_unkown(self): observation = Observation( title="unkown", - current_severity=Observation.SEVERITY_NONE, + current_severity=Severity.SEVERITY_NONE, ) - self.assertEqual(Observation.SEVERITY_UNKOWN, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_UNKOWN, get_current_severity(observation)) def test_get_current_severity_assessment(self): observation = Observation( title="assessment_severity", - current_severity=Observation.SEVERITY_NONE, - parser_severity=Observation.SEVERITY_LOW, - rule_severity=Observation.SEVERITY_LOW, - assessment_severity=Observation.SEVERITY_MEDIUM, + current_severity=Severity.SEVERITY_NONE, + parser_severity=Severity.SEVERITY_LOW, + rule_severity=Severity.SEVERITY_LOW, + assessment_severity=Severity.SEVERITY_MEDIUM, cvss3_score=9.5, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_MEDIUM, get_current_severity(observation)) def test_get_current_severity_rule(self): observation = Observation( title="rule_severity", - current_severity=Observation.SEVERITY_NONE, - parser_severity=Observation.SEVERITY_LOW, - rule_severity=Observation.SEVERITY_MEDIUM, + current_severity=Severity.SEVERITY_NONE, + parser_severity=Severity.SEVERITY_LOW, + rule_severity=Severity.SEVERITY_MEDIUM, cvss3_score=9.5, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_MEDIUM, get_current_severity(observation)) def test_get_current_severity_parser(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_NONE, - parser_severity=Observation.SEVERITY_LOW, + current_severity=Severity.SEVERITY_NONE, + parser_severity=Severity.SEVERITY_LOW, cvss3_score=9.5, ) - self.assertEqual(Observation.SEVERITY_LOW, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_LOW, get_current_severity(observation)) def test_get_current_severity_cvss_critical(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_NONE, + current_severity=Severity.SEVERITY_NONE, cvss3_score=9, ) - self.assertEqual( - Observation.SEVERITY_CRITICAL, get_current_severity(observation) - ) + self.assertEqual(Severity.SEVERITY_CRITICAL, get_current_severity(observation)) def test_get_current_severity_cvss_high(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_NONE, + current_severity=Severity.SEVERITY_NONE, cvss3_score=7, ) - self.assertEqual(Observation.SEVERITY_HIGH, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_HIGH, get_current_severity(observation)) def test_get_current_severity_cvss_medium(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_NONE, + current_severity=Severity.SEVERITY_NONE, cvss3_score=4, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_MEDIUM, get_current_severity(observation)) def test_get_current_severity_cvss_low(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_NONE, + current_severity=Severity.SEVERITY_NONE, cvss3_score=0.1, ) - self.assertEqual(Observation.SEVERITY_LOW, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_LOW, get_current_severity(observation)) def test_get_current_severity_cvss_none(self): observation = Observation( title="parser_severity", - current_severity=Observation.SEVERITY_MEDIUM, + current_severity=Severity.SEVERITY_MEDIUM, cvss3_score=0, ) - self.assertEqual(Observation.SEVERITY_NONE, get_current_severity(observation)) + self.assertEqual(Severity.SEVERITY_NONE, get_current_severity(observation)) # --- get_current_status --- def test_get_current_status_open(self): observation = Observation( title="open", - current_status=Observation.STATUS_RESOLVED, + current_status=Status.STATUS_RESOLVED, ) - self.assertEqual(Observation.STATUS_OPEN, get_current_status(observation)) + self.assertEqual(Status.STATUS_OPEN, get_current_status(observation)) def test_get_current_status_assessment(self): observation = Observation( title="assessment_status", - current_status=Observation.STATUS_RESOLVED, - parser_status=Observation.STATUS_NOT_AFFECTED, - rule_status=Observation.STATUS_DUPLICATE, - assessment_status=Observation.STATUS_FALSE_POSITIVE, + current_status=Status.STATUS_RESOLVED, + parser_status=Status.STATUS_NOT_AFFECTED, + rule_status=Status.STATUS_DUPLICATE, + assessment_status=Status.STATUS_FALSE_POSITIVE, cvss3_score=9.5, ) - self.assertEqual( - Observation.STATUS_FALSE_POSITIVE, get_current_status(observation) - ) + self.assertEqual(Status.STATUS_FALSE_POSITIVE, get_current_status(observation)) def test_get_current_status_rule(self): observation = Observation( title="assessment_status", - current_status=Observation.STATUS_RESOLVED, - parser_status=Observation.STATUS_NOT_AFFECTED, - rule_status=Observation.STATUS_DUPLICATE, + current_status=Status.STATUS_RESOLVED, + parser_status=Status.STATUS_NOT_AFFECTED, + rule_status=Status.STATUS_DUPLICATE, cvss3_score=9.5, ) - self.assertEqual(Observation.STATUS_DUPLICATE, get_current_status(observation)) + self.assertEqual(Status.STATUS_DUPLICATE, get_current_status(observation)) def test_get_current_status_parser(self): observation = Observation( title="parser_status", - current_status=Observation.STATUS_RESOLVED, - parser_status=Observation.STATUS_NOT_AFFECTED, - ) - self.assertEqual( - Observation.STATUS_NOT_AFFECTED, get_current_status(observation) + current_status=Status.STATUS_RESOLVED, + parser_status=Status.STATUS_NOT_AFFECTED, ) + self.assertEqual(Status.STATUS_NOT_AFFECTED, get_current_status(observation)) # --- normalize_observation_fields --- @@ -187,9 +182,9 @@ def test_normalize_observation_fields_empty(self): before_observation = Observation(title="empty") after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN normalize_observation_fields(after_observation) self.assertEqual(before_observation, after_observation) @@ -208,9 +203,9 @@ def test_normalize_observation_fields_none(self): after_observation.__dict__[key] = None value = None - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN normalize_observation_fields(after_observation) self.assertEqual(before_observation, after_observation) @@ -223,9 +218,9 @@ def test_normalize_observation_fields_selected_fields(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.description = "desc" before_observation.origin_endpoint_scheme = "https" before_observation.origin_endpoint_hostname = "www.example.com" @@ -241,9 +236,9 @@ def test_normalize_observation_fields_origin_component_name_version_1(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_component_name = "component_name" before_observation.origin_component_version = "" @@ -257,9 +252,9 @@ def test_normalize_observation_fields_origin_component_name_version_2(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_component_name = "component_name" before_observation.origin_component_version = "component_version" @@ -274,9 +269,9 @@ def test_normalize_observation_fields_origin_component_name_component_version(se ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_component_name_version = ( "component_name:component_version" ) @@ -290,9 +285,9 @@ def test_normalize_observation_fields_origin_component_name(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_component_name_version = "component_name" normalize_observation_fields(after_observation) @@ -304,9 +299,9 @@ def test_normalize_observation_fields_origin_docker_image_name_tag_1(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_docker_image_name = "docker_image_name" before_observation.origin_docker_image_tag = "" before_observation.origin_docker_image_name_tag_short = "docker_image_name" @@ -320,9 +315,9 @@ def test_normalize_observation_fields_origin_docker_image_name_tag_2(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_docker_image_name = "docker_image_name" before_observation.origin_docker_image_tag = "docker_image_tag" before_observation.origin_docker_image_name_tag_short = ( @@ -342,9 +337,9 @@ def test_normalize_observation_fields_origin_docker_image_name_docker_image_tag( ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_docker_image_name_tag = ( "docker_image_name:docker_image_tag" ) @@ -361,9 +356,9 @@ def test_normalize_observation_fields_origin_docker_image_name(self): ) after_observation = deepcopy(before_observation) - before_observation.current_severity = Observation.SEVERITY_UNKOWN + before_observation.current_severity = Severity.SEVERITY_UNKOWN before_observation.numerical_severity = 6 - before_observation.current_status = Observation.STATUS_OPEN + before_observation.current_status = Status.STATUS_OPEN before_observation.origin_docker_image_name_tag = "docker_image_name" before_observation.origin_docker_image_name_tag_short = "docker_image_name" diff --git a/backend/unittests/core/services/test_observations_bulk_actions.py b/backend/unittests/core/services/test_observations_bulk_actions.py index b9a58c0eb..339fb026d 100644 --- a/backend/unittests/core/services/test_observations_bulk_actions.py +++ b/backend/unittests/core/services/test_observations_bulk_actions.py @@ -9,6 +9,7 @@ observations_bulk_assessment, observations_bulk_delete, ) +from application.core.types import Severity, Status from unittests.base_test_case import BaseTestCase @@ -25,8 +26,8 @@ def test_observations_bulk_assessment(self, save_mock, check_mock): observations_bulk_assessment( self.product_1, - Observation.SEVERITY_CRITICAL, - Observation.STATUS_OPEN, + Severity.SEVERITY_CRITICAL, + Status.STATUS_OPEN, "comment", [1, 2], ) @@ -35,14 +36,14 @@ def test_observations_bulk_assessment(self, save_mock, check_mock): expected_calls = [ call( self.observation_1, - Observation.SEVERITY_CRITICAL, - Observation.STATUS_OPEN, + Severity.SEVERITY_CRITICAL, + Status.STATUS_OPEN, "comment", ), call( observation_2, - Observation.SEVERITY_CRITICAL, - Observation.STATUS_OPEN, + Severity.SEVERITY_CRITICAL, + Status.STATUS_OPEN, "comment", ), ] diff --git a/backend/unittests/core/services/test_potential_duplicates.py b/backend/unittests/core/services/test_potential_duplicates.py index 00ba048bb..9d0a56fef 100644 --- a/backend/unittests/core/services/test_potential_duplicates.py +++ b/backend/unittests/core/services/test_potential_duplicates.py @@ -5,13 +5,14 @@ set_potential_duplicate, set_potential_duplicate_both_ways, ) +from application.core.types import Status from unittests.base_test_case import BaseTestCase class TestSetPotentialDuplicate(BaseTestCase): def setUp(self): self.observation = Observation() - self.observation.current_status = Observation.STATUS_OPEN + self.observation.current_status = Status.STATUS_OPEN self.observation.has_potential_duplicates = True super().setUp() @@ -59,7 +60,7 @@ def test_set_potential_duplicate_with_open_duplicates(self, save_mock, filter_mo @patch("application.core.models.Observation.save") def test_set_potential_duplicate_closed_observation(self, save_mock): - self.observation.current_status = Observation.STATUS_RESOLVED + self.observation.current_status = Status.STATUS_RESOLVED set_potential_duplicate(self.observation) diff --git a/backend/unittests/core/test_models.py b/backend/unittests/core/test_models.py index 59f8bec8b..f3862b1bb 100644 --- a/backend/unittests/core/test_models.py +++ b/backend/unittests/core/test_models.py @@ -1,6 +1,7 @@ from unittest.mock import patch from application.core.models import Observation, Parser, Product +from application.core.types import Severity, Status from unittests.base_test_case import BaseTestCase @@ -17,8 +18,8 @@ def test_observation_count_critical(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_CRITICAL, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_CRITICAL, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -29,8 +30,8 @@ def test_observation_count_high(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_HIGH, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_HIGH, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -41,8 +42,8 @@ def test_observation_count_medium(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_MEDIUM, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_MEDIUM, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -53,8 +54,8 @@ def test_observation_count_low(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_LOW, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_LOW, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -65,8 +66,8 @@ def test_observation_count_none(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_NONE, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_NONE, + current_status=Status.STATUS_OPEN, ) @patch("application.core.models.Observation.objects.filter") @@ -77,8 +78,8 @@ def test_observation_count_unkown(self, mock): mock.assert_called_with( product=product, branch=None, - current_severity=Observation.SEVERITY_UNKOWN, - current_status=Observation.STATUS_OPEN, + current_severity=Severity.SEVERITY_UNKOWN, + current_status=Status.STATUS_OPEN, ) diff --git a/backend/unittests/import_observations/parsers/azure_defender/test_parser.py b/backend/unittests/import_observations/parsers/azure_defender/test_parser.py index 1e8014b2e..c369f20e9 100644 --- a/backend/unittests/import_observations/parsers/azure_defender/test_parser.py +++ b/backend/unittests/import_observations/parsers/azure_defender/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.azure_defender.parser import ( AzureDefenderParser, ) @@ -68,7 +68,7 @@ def test_defender(self): ) description = """Private links enforce secure communication, by providing private connectivity to the storage account""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "To enforce secure communications for your storage accounts, add a private endpoint as described here: https://aka.ms/connectprivatelytostorageaccount.", observation.recommendation, @@ -92,7 +92,7 @@ def test_defender(self): These accounts can be targets for attackers looking to find ways to access your data without being noticed.""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) recommendation = """Review the list of accounts that are blocked from signing in on the Accounts section. Select an account to view its role definitions and locate the source scope. If you accept the risk for specific account, use the exempt capability to exclude it from evaluation. Go to the Azure portal. diff --git a/backend/unittests/import_observations/parsers/cryptolyzer/test_parser.py b/backend/unittests/import_observations/parsers/cryptolyzer/test_parser.py index 9be61d8f2..3ecec2ff7 100644 --- a/backend/unittests/import_observations/parsers/cryptolyzer/test_parser.py +++ b/backend/unittests/import_observations/parsers/cryptolyzer/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.cryptolyzer.parser import CryptoLyzerParser @@ -54,7 +54,7 @@ def test_multiple_observations(self): "**Unrecommended cipher suites according to BSI recommendations:**\n* TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", observation.description, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://www.example.org:443", observation.origin_endpoint_url ) @@ -75,7 +75,7 @@ def test_multiple_observations(self): "**Unrecommended cipher suites according to BSI recommendations:**\n* TLS_CHACHA20_POLY1305_SHA256", observation.description, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://www.example.org:443", observation.origin_endpoint_url ) @@ -95,7 +95,7 @@ def test_multiple_observations(self): "**Unrecommended elliptic curves according to BSI recommendations:**\n* X25519\n* X448", observation.description, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://www.example.org:443", observation.origin_endpoint_url ) @@ -113,7 +113,7 @@ def test_multiple_observations(self): "**Unrecommended signature algorithms according to BSI recommendations:**\n* RSA_SHA1\n* RSA_SHA224", observation.description, ) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://www.example.org:443", observation.origin_endpoint_url ) @@ -141,7 +141,7 @@ def test_weak_tls(self): "**Weak protocols according to BSI recommendations:**\n* tls1\n* tls1_1", observation.description, ) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://tls-v1-0.badssl.com:443", observation.origin_endpoint_url ) diff --git a/backend/unittests/import_observations/parsers/cyclone_dx/test_parser.py b/backend/unittests/import_observations/parsers/cyclone_dx/test_parser.py index 047e1dc51..5d3521125 100644 --- a/backend/unittests/import_observations/parsers/cyclone_dx/test_parser.py +++ b/backend/unittests/import_observations/parsers/cyclone_dx/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.cyclone_dx.parser import CycloneDXParser @@ -45,7 +45,7 @@ def test_grype(self): description = """Directory traversal vulnerability in the (1) extract and (2) extractall functions in the tarfile module in Python allows user-assisted remote attackers to overwrite arbitrary files via a .. (dot dot) sequence in filenames in a TAR archive, a related issue to CVE-2001-1267.""" self.assertEqual(description, observation.description) self.assertEqual("CVE-2007-4559", observation.vulnerability_id) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual("python", observation.origin_component_name) self.assertEqual("3.11.3", observation.origin_component_version) self.assertEqual( @@ -56,7 +56,12 @@ def test_grype(self): observation.origin_component_cpe, ) self.assertEqual( - "example/example:dev", observation.origin_docker_image_name_tag + "example/example:dev", observation.origin_docker_image_name + ) + self.assertEqual("", observation.origin_docker_image_tag) + self.assertEqual( + "sha256:88901af20b50287be153ec4f20ed78f947eb5fa0d0a52432ced6e261b66b6cbc", + observation.origin_docker_image_digest, ) self.assertEqual( "http://mail.python.org/pipermail/python-dev/2007-August/074290.html", @@ -98,7 +103,12 @@ def test_grype(self): observation.origin_component_cpe, ) self.assertEqual( - "example/example:dev", observation.origin_docker_image_name_tag + "example/example:dev", observation.origin_docker_image_name + ) + self.assertEqual("", observation.origin_docker_image_tag) + self.assertEqual( + "sha256:88901af20b50287be153ec4f20ed78f947eb5fa0d0a52432ced6e261b66b6cbc", + observation.origin_docker_image_digest, ) self.assertEqual( "https://github.com/MariaDB/server/commit/be0a46b3d52b58956fd0d47d040b9f4514406954", @@ -133,8 +143,10 @@ def test_grype_component_version(self): self.assertEqual("CVE-2018-20225", observation.vulnerability_id) self.assertEqual("grype / 0.65.1", observation.scanner) self.assertEqual( - "example/example-backend:dev", observation.origin_docker_image_name_tag + "example/example-backend", observation.origin_docker_image_name ) + self.assertEqual("dev", observation.origin_docker_image_tag) + self.assertEqual("", observation.origin_docker_image_digest) def test_grype_tools_components(self): with open(path.dirname(__file__) + "/files/grype_3.json") as testfile: @@ -150,8 +162,10 @@ def test_grype_tools_components(self): self.assertEqual("CVE-2023-42363", observation.vulnerability_id) self.assertEqual("grype / 0.73.5", observation.scanner) self.assertEqual( - "example/example-backend:dev", observation.origin_docker_image_name_tag + "example/example-backend", observation.origin_docker_image_name ) + self.assertEqual("dev", observation.origin_docker_image_tag) + self.assertEqual("", observation.origin_docker_image_digest) def test_trivy(self): with open(path.dirname(__file__) + "/files/trivy.json") as testfile: @@ -184,8 +198,10 @@ def test_trivy(self): observation.origin_component_purl, ) self.assertEqual( - "example/example-frontend:dev", observation.origin_docker_image_name_tag + "example/example-frontend:dev", observation.origin_docker_image_name ) + self.assertEqual("", observation.origin_docker_image_tag) + self.assertEqual("", observation.origin_docker_image_digest) self.assertEqual( "https://access.redhat.com/security/cve/CVE-2023-29469", observation.unsaved_references[0], diff --git a/backend/unittests/import_observations/parsers/drheader/test_parser.py b/backend/unittests/import_observations/parsers/drheader/test_parser.py index b9e2ae27c..13d2f217d 100644 --- a/backend/unittests/import_observations/parsers/drheader/test_parser.py +++ b/backend/unittests/import_observations/parsers/drheader/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.drheader.parser import DrHEADerParser @@ -57,7 +57,7 @@ def test_drheader(self): **Expected:** no-cache""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pragma", observation.unsaved_references[0], @@ -79,7 +79,7 @@ def test_drheader(self): * strict-origin-when-cross-origin * no-referrer""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy", observation.unsaved_references[0], @@ -96,7 +96,7 @@ def test_drheader(self): **Expected:** max-age=31536000; includeSubDomains""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html", observation.unsaved_references[0], diff --git a/backend/unittests/import_observations/parsers/owasp_zap/test_parser.py b/backend/unittests/import_observations/parsers/owasp_zap/test_parser.py index 356e6facb..77da6d68c 100644 --- a/backend/unittests/import_observations/parsers/owasp_zap/test_parser.py +++ b/backend/unittests/import_observations/parsers/owasp_zap/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.zap.parser import ZAPParser @@ -37,7 +37,7 @@ def test_owasp_zap(self): self.assertEqual(5, len(observations)) observation = observations[0] - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://vulncat.fortify.com/en/detail?id=desc.config.dotnet.html5_overly_permissive_cors_policy", observation.unsaved_references[0], @@ -64,7 +64,7 @@ def test_owasp_zap(self): "

Remove all comments that return information that may help an attacker and fix any underlying problems they refer to.

", observation.recommendation, ) - self.assertEqual(Observation.SEVERITY_NONE, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_NONE, observation.parser_severity) self.assertEqual("200", observation.cwe) self.assertEqual( "https://example-backend.example.com", observation.origin_endpoint_url diff --git a/backend/unittests/import_observations/parsers/prowler/test_parser.py b/backend/unittests/import_observations/parsers/prowler/test_parser.py index bb68d8736..01ab6d23f 100644 --- a/backend/unittests/import_observations/parsers/prowler/test_parser.py +++ b/backend/unittests/import_observations/parsers/prowler/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.prowler.parser import ProwlerParser @@ -82,7 +82,7 @@ def test_aws(self): Auto Minor Version Upgrade is a feature that you can enable to have your database automatically upgraded when a new minor database engine version is available. Minor version upgrades often patch security vulnerabilities and fix bugs and therefore should be applied.""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_LOW, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_LOW, observation.parser_severity) self.assertIn( "Enable auto minor version upgrade for all databases and environments.", observation.recommendation, @@ -127,7 +127,7 @@ def test_azure(self): Turning on Microsoft Defender for App Service enables threat detection for App Service, providing threat intelligence, anomaly detection, and behavior analytics in the Microsoft Defender for Cloud.""" self.assertEqual(description, observation.description) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) recommendation = """By default, Microsoft Defender for Cloud is not enabled for your App Service instances. Enabling the Defender security service for App Service instances allows for advanced security defense using threat detection capabilities provided by Microsoft Security Response Center. * **Terraform:** https://docs.bridgecrew.io/docs/ensure-that-azure-defender-is-set-to-on-for-app-service#terraform diff --git a/backend/unittests/import_observations/parsers/sarif/test_parser.py b/backend/unittests/import_observations/parsers/sarif/test_parser.py index acd82b9b7..f97d41f86 100644 --- a/backend/unittests/import_observations/parsers/sarif/test_parser.py +++ b/backend/unittests/import_observations/parsers/sarif/test_parser.py @@ -1,7 +1,7 @@ from os import path from unittest import TestCase -from application.core.models import Observation +from application.core.types import Severity from application.import_observations.parsers.sarif.parser import SARIFParser @@ -68,7 +68,7 @@ def test_checkov(self): ) self.assertEqual(1, observation.origin_source_line_start) self.assertEqual(41, observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://docs.bridgecrew.io/docs/ensure-that-healthcheck-instructions-have-been-added-to-container-images", observation.unsaved_references[0], @@ -105,7 +105,7 @@ def test_eslint(self): ) self.assertEqual(19, observation.origin_source_line_start) self.assertEqual(19, observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://typescript-eslint.io/rules/no-unused-vars", observation.unsaved_references[0], @@ -149,7 +149,7 @@ def test_bandit(self): ) self.assertEqual(14, observation.origin_source_line_start) self.assertIsNone(observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://bandit.readthedocs.io/en/1.7.4/plugins/b104_hardcoded_bind_all_interfaces.html", observation.unsaved_references[0], @@ -183,7 +183,7 @@ def test_kics(self): ) self.assertEqual(34, observation.origin_source_line_start) self.assertIsNone(observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_HIGH, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_HIGH, observation.parser_severity) self.assertEqual( "https://docs.docker.com/compose/compose-file/#volumes", observation.unsaved_references[0], @@ -231,7 +231,7 @@ def test_trivy_config(self): ) self.assertEqual(164, observation.origin_source_line_start) self.assertEqual(176, observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://avd.aquasec.com/misconfig/avd-azu-0014", observation.unsaved_references[0], @@ -297,7 +297,7 @@ def test_semgrep(self): ) self.assertEqual(27, observation.origin_source_line_start) self.assertEqual(33, observation.origin_source_line_end) - self.assertEqual(Observation.SEVERITY_MEDIUM, observation.parser_severity) + self.assertEqual(Severity.SEVERITY_MEDIUM, observation.parser_severity) self.assertEqual( "https://semgrep.dev/r/typescript.react.portability.i18next.jsx-not-internationalized.jsx-not-internationalized", observation.unsaved_references[0], diff --git a/backend/unittests/import_observations/services/test_import_observations.py b/backend/unittests/import_observations/services/test_import_observations.py index 16e7c78c3..771bfdd28 100644 --- a/backend/unittests/import_observations/services/test_import_observations.py +++ b/backend/unittests/import_observations/services/test_import_observations.py @@ -13,6 +13,7 @@ Product, Reference, ) +from application.core.types import Severity, Status from application.import_observations.apps import _register_parser from application.import_observations.models import Vulnerability_Check from application.import_observations.services.import_observations import ( @@ -156,11 +157,9 @@ def _file_upload_observations( else: self.assertEqual(observations[0].origin_endpoint_url, "") - self.assertEqual(observations[0].current_status, Observation.STATUS_OPEN) - self.assertEqual(observations[1].current_status, Observation.STATUS_OPEN) - self.assertEqual( - observations[2].current_status, Observation.STATUS_NOT_AFFECTED - ) + self.assertEqual(observations[0].current_status, Status.STATUS_OPEN) + self.assertEqual(observations[1].current_status, Status.STATUS_OPEN) + self.assertEqual(observations[2].current_status, Status.STATUS_NOT_AFFECTED) observation_logs = Observation_Log.objects.filter( observation__product=1 @@ -172,7 +171,7 @@ def _file_upload_observations( self.assertEqual(observation_logs[2].observation, observations[2]) self.assertEqual(observation_logs[2].severity, observations[2].current_severity) - self.assertEqual(observation_logs[2].status, Observation.STATUS_OPEN) + self.assertEqual(observation_logs[2].status, Status.STATUS_OPEN) self.assertEqual(observation_logs[2].comment, "Set by parser") self.assertEqual(observation_logs[3].observation, observations[2]) @@ -230,9 +229,9 @@ def _file_upload_observations( observations = Observation.objects.filter(product=1).order_by("id") self.assertEqual(len(observations), 3) - self.assertEqual(observations[0].current_status, Observation.STATUS_RESOLVED) - self.assertEqual(observations[1].current_status, Observation.STATUS_OPEN) - self.assertEqual(observations[2].current_status, Observation.STATUS_RESOLVED) + self.assertEqual(observations[0].current_status, Status.STATUS_RESOLVED) + self.assertEqual(observations[1].current_status, Status.STATUS_OPEN) + self.assertEqual(observations[2].current_status, Status.STATUS_RESOLVED) observation_logs = Observation_Log.objects.filter( observation__product=1 @@ -240,20 +239,20 @@ def _file_upload_observations( self.assertEqual(len(observation_logs), 7) self.assertEqual(observation_logs[4].observation, observations[1]) - self.assertEqual(observation_logs[4].severity, Observation.SEVERITY_HIGH) + self.assertEqual(observation_logs[4].severity, Severity.SEVERITY_HIGH) self.assertEqual(observation_logs[4].status, "") self.assertEqual(observation_logs[4].comment, "Updated by parser") self.assertEqual(observation_logs[5].observation, observations[0]) self.assertEqual(observation_logs[5].severity, "") - self.assertEqual(observation_logs[5].status, Observation.STATUS_RESOLVED) + self.assertEqual(observation_logs[5].status, Status.STATUS_RESOLVED) self.assertEqual( observation_logs[5].comment, "Observation not found in latest scan" ) self.assertEqual(observation_logs[6].observation, observations[2]) self.assertEqual(observation_logs[6].severity, "") - self.assertEqual(observation_logs[6].status, Observation.STATUS_RESOLVED) + self.assertEqual(observation_logs[6].status, Status.STATUS_RESOLVED) self.assertEqual( observation_logs[6].comment, "Observation not found in latest scan" ) diff --git a/backend/unittests/issue_tracker/issue_trackers/test_base_issue_tracker.py b/backend/unittests/issue_tracker/issue_trackers/test_base_issue_tracker.py index 3fb3847c8..b5f8f4522 100644 --- a/backend/unittests/issue_tracker/issue_trackers/test_base_issue_tracker.py +++ b/backend/unittests/issue_tracker/issue_trackers/test_base_issue_tracker.py @@ -1,6 +1,7 @@ from unittest.mock import patch from application.core.models import Observation +from application.core.types import Severity from application.issue_tracker.issue_trackers.base_issue_tracker import BaseIssueTracker from unittests.base_test_case import BaseTestCase @@ -8,7 +9,7 @@ class TestBaseIssueTracker(BaseTestCase): def setUp(self) -> None: super().setUp() - self.observation_1.current_severity = Observation.SEVERITY_HIGH + self.observation_1.current_severity = Severity.SEVERITY_HIGH def test_get_title_no_origin(self): issue_tracker = BaseIssueTracker() diff --git a/backend/unittests/issue_tracker/issue_trackers/test_github_issue_tracker.py b/backend/unittests/issue_tracker/issue_trackers/test_github_issue_tracker.py index 859b818a3..808444a24 100644 --- a/backend/unittests/issue_tracker/issue_trackers/test_github_issue_tracker.py +++ b/backend/unittests/issue_tracker/issue_trackers/test_github_issue_tracker.py @@ -3,7 +3,7 @@ from requests import Response from requests.exceptions import HTTPError -from application.core.models import Observation +from application.core.types import Severity, Status from application.issue_tracker.issue_trackers.base_issue_tracker import Issue from application.issue_tracker.issue_trackers.github_issue_tracker import ( GitHubIssueTracker, @@ -15,17 +15,27 @@ class TestGitHubIssueTracker(BaseTestCase): def setUp(self): super().setUp() self.observation_1.pk = 1 - self.observation_1.current_severity = Observation.SEVERITY_CRITICAL + self.observation_1.current_severity = Severity.SEVERITY_CRITICAL self.observation_1.description = "description_1" self.observation_1.product.issue_tracker_project_id = "gh_project_1" self.observation_1.product.issue_tracker_labels = "label_1, label_2" self.observation_1.product.issue_tracker_api_key = "api_key_1" @patch("requests.post") - @patch("application.core.models.Observation.save") - def test_create_issue(self, save_mock, post_mock): + def test_create_issue(self, post_mock): + class MockResponse: + def raise_for_status(self): + pass + + def json(self): + return {"number": "gh_1"} + + response = MockResponse() + post_mock.return_value = response + issue_tracker = GitHubIssueTracker() - issue_tracker.create_issue(self.observation_1) + issue_id = issue_tracker.create_issue(self.observation_1) + post_mock.assert_called_once_with( url="https://api.github.com/repos/gh_project_1/issues", headers={ @@ -35,7 +45,7 @@ def test_create_issue(self, save_mock, post_mock): data='{"title": "Critical vulnerability: \\"observation_1\\"", "body": "description_1\\n\\n**Branch:** branch_1\\n\\n**SecObserve observation:** [/#/observations/1/show](/#/observations/1/show)", "labels": ["label_1", "label_2"]}', timeout=60, ) - save_mock.assert_called_once() + self.assertEqual("gh_1", issue_id) @patch("requests.post") def test_create_issue_exception(self, post_mock): @@ -209,7 +219,7 @@ def test_close_issue_exception(self, patch_mock): patch_mock.return_value = response self.observation_1.issue_tracker_issue_id = "gh_1" self.observation_1.product.issue_tracker_labels = "label_2,label_3" - self.observation_1.current_status = Observation.STATUS_RESOLVED + self.observation_1.current_status = Status.STATUS_RESOLVED with self.assertRaises(HTTPError) as e: issue_tracker = GitHubIssueTracker() issue = Issue( @@ -239,7 +249,7 @@ def test_close_issue_success(self, patch_mock): patch_mock.return_value.status_code = 200 self.observation_1.issue_tracker_issue_id = "gh_1" self.observation_1.product.issue_tracker_labels = "label_2,label_3" - self.observation_1.current_status = Observation.STATUS_RESOLVED + self.observation_1.current_status = Status.STATUS_RESOLVED issue_tracker = GitHubIssueTracker() issue = Issue( id="gh_1", diff --git a/backend/unittests/issue_tracker/issue_trackers/test_gitlab_issue_tracker.py b/backend/unittests/issue_tracker/issue_trackers/test_gitlab_issue_tracker.py index 18568a6ff..5d7f75b65 100644 --- a/backend/unittests/issue_tracker/issue_trackers/test_gitlab_issue_tracker.py +++ b/backend/unittests/issue_tracker/issue_trackers/test_gitlab_issue_tracker.py @@ -3,7 +3,7 @@ from requests import Response from requests.exceptions import HTTPError -from application.core.models import Observation +from application.core.types import Severity, Status from application.issue_tracker.issue_trackers.base_issue_tracker import Issue from application.issue_tracker.issue_trackers.gitlab_issue_tracker import ( GitLabIssueTracker, @@ -15,7 +15,7 @@ class TestGitLabIssueTracker(BaseTestCase): def setUp(self): super().setUp() self.observation_1.pk = 1 - self.observation_1.current_severity = Observation.SEVERITY_CRITICAL + self.observation_1.current_severity = Severity.SEVERITY_CRITICAL self.observation_1.description = "description_1" self.observation_1.product.issue_tracker_project_id = "gh_project_1" self.observation_1.product.issue_tracker_labels = "label_1, label_2" @@ -23,10 +23,20 @@ def setUp(self): self.observation_1.product.issue_tracker_base_url = "https://gitlab.example.com" @patch("requests.post") - @patch("application.core.models.Observation.save") - def test_create_issue(self, save_mock, post_mock): + def test_create_issue(self, post_mock): + class MockResponse: + def raise_for_status(self): + pass + + def json(self): + return {"iid": "gl_1"} + + response = MockResponse() + post_mock.return_value = response + issue_tracker = GitLabIssueTracker() - issue_tracker.create_issue(self.observation_1) + issue_id = issue_tracker.create_issue(self.observation_1) + post_mock.assert_called_once_with( url="https://gitlab.example.com/api/v4/projects/gh_project_1/issues", headers={"PRIVATE-TOKEN": "api_key_1"}, @@ -38,7 +48,7 @@ def test_create_issue(self, save_mock, post_mock): }, timeout=60, ) - save_mock.assert_called_once() + self.assertEqual("gl_1", issue_id) @patch("requests.post") def test_create_issue_exception(self, post_mock): @@ -213,7 +223,7 @@ def test_close_issue_exception(self, put_mock): put_mock.return_value = response self.observation_1.issue_tracker_issue_id = "gh_1" self.observation_1.product.issue_tracker_labels = "label_2,label_3" - self.observation_1.current_status = Observation.STATUS_RESOLVED + self.observation_1.current_status = Status.STATUS_RESOLVED with self.assertRaises(HTTPError) as e: issue_tracker = GitLabIssueTracker() issue = Issue( @@ -246,7 +256,7 @@ def test_close_issue_success(self, put_mock): put_mock.return_value.status_code = 200 self.observation_1.issue_tracker_issue_id = "gh_1" self.observation_1.product.issue_tracker_labels = "label_2,label_3" - self.observation_1.current_status = Observation.STATUS_RESOLVED + self.observation_1.current_status = Status.STATUS_RESOLVED issue_tracker = GitLabIssueTracker() issue = Issue( id="gh_1", diff --git a/backend/unittests/issue_tracker/issue_trackers/test_jira_issue_tracker.py b/backend/unittests/issue_tracker/issue_trackers/test_jira_issue_tracker.py index b9e1fa261..eb002515a 100644 --- a/backend/unittests/issue_tracker/issue_trackers/test_jira_issue_tracker.py +++ b/backend/unittests/issue_tracker/issue_trackers/test_jira_issue_tracker.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from unittest.mock import patch -from application.core.models import Observation +from application.core.types import Severity, Status from application.issue_tracker.issue_trackers.base_issue_tracker import Issue from application.issue_tracker.issue_trackers.jira_issue_tracker import JiraIssueTracker from unittests.base_test_case import BaseTestCase @@ -29,7 +29,7 @@ class TestJiraIssueTracker(BaseTestCase): def setUp(self): super().setUp() self.observation_1.pk = 1 - self.observation_1.current_severity = Observation.SEVERITY_CRITICAL + self.observation_1.current_severity = Severity.SEVERITY_CRITICAL self.observation_1.description = "description_1" self.observation_1.product.issue_tracker_project_id = "jira_project_1" self.observation_1.product.issue_tracker_base_url = "https://jira.com" @@ -63,7 +63,7 @@ def test_create_issue(self, save_mock, base_url_mock, create_issue_mock, jira_mo ) issue_tracker = JiraIssueTracker(self.observation_1.product) - issue_tracker.create_issue(self.observation_1) + issue_id = issue_tracker.create_issue(self.observation_1) create_issue_mock.assert_called_once_with( project="jira_project_1", @@ -74,7 +74,7 @@ def test_create_issue(self, save_mock, base_url_mock, create_issue_mock, jira_mo ) base_url_mock.assert_called_once() save_mock.assert_called_once() - self.assertEqual("jira_issue_1", self.observation_1.issue_tracker_issue_id) + self.assertEqual("jira_issue_1", issue_id) self.assertEqual("Open", self.observation_1.issue_tracker_jira_initial_status) @patch( @@ -327,7 +327,7 @@ def test_close_issue_success( self, transition_issue_mock, update_mock, issue_mock, jira_mock ): self.observation_1.issue_tracker_issue_id = "jira_1" - self.observation_1.current_status = Observation.STATUS_RESOLVED + self.observation_1.current_status = Status.STATUS_RESOLVED issue = Issue( id="jira_1", title="title_1", diff --git a/backend/unittests/issue_tracker/services/test_issue_tracker.py b/backend/unittests/issue_tracker/services/test_issue_tracker.py index b88affc41..d28e35d48 100644 --- a/backend/unittests/issue_tracker/services/test_issue_tracker.py +++ b/backend/unittests/issue_tracker/services/test_issue_tracker.py @@ -4,6 +4,7 @@ from application.access_control.models import User from application.core.models import Branch, Observation, Product +from application.core.types import Severity, Status from application.issue_tracker.issue_trackers.base_issue_tracker import Issue from application.issue_tracker.issue_trackers.github_issue_tracker import ( GitHubIssueTracker, @@ -17,6 +18,7 @@ push_observation_to_issue_tracker, push_observations_to_issue_tracker, ) +from application.issue_tracker.types import Issue_Tracker from unittests.base_test_case import BaseTestCase @@ -53,7 +55,7 @@ def test_push_observations_to_issue_tracker( mock_current_user.assert_called_once() mock_issue_tracker.assert_called_once_with(observation, user) - # --- push_observation_to_issue_tracker --- + # --- push_observation_to_issue_tracker / no minimum severity--- @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") def test_push_observation_to_issue_tracker_not_active(self, mock): @@ -71,28 +73,73 @@ def test_push_observation_to_issue_tracker_not_default_branch(self, mock): mock.assert_not_called() @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") - def test_push_observation_to_issue_tracker_open_no_id_no_issue(self, mock): + @patch("application.core.models.Observation.save") + def test_push_observation_to_issue_tracker_open_no_id_no_issue( + self, observation_mock, mock + ): mock.return_value.get_issue.return_value = None observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - observation.current_status = Observation.STATUS_OPEN + observation.current_status = Status.STATUS_OPEN push_observation_to_issue_tracker(observation, None) expected_calls = [call(observation.product), call().create_issue(observation)] mock.assert_has_calls(expected_calls, any_order=False) + observation_mock.assert_called_once() + + @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") + @patch("application.core.models.Observation.save") + def test_push_observation_to_issue_tracker_open_with_id_with_issue( + self, observation_mock, factory_mock + ): + issue = Issue(id=1, title="title", description="description", labels="labels") + factory_mock.return_value.get_issue.return_value = issue + + observation = Observation.objects.get(pk=1) + observation.product.issue_tracker_active = True + observation.current_status = Status.STATUS_OPEN + observation.issue_tracker_issue_id = "123" + + push_observation_to_issue_tracker(observation, None) + + observation_mock.assert_not_called() + expected_calls = [ + call(observation.product), + call().get_issue(observation.product, "123"), + call().update_issue(observation, issue), + ] + factory_mock.assert_has_calls(expected_calls, any_order=False) @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") @patch("application.core.models.Observation.save") - def test_push_observation_to_issue_tracker_open_with_id_no_issue( + def test_push_observation_to_issue_tracker_closed_no_id_no_issue( self, observation_mock, factory_mock ): factory_mock.return_value.get_issue.return_value = None observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - observation.current_status = Observation.STATUS_OPEN + observation.current_status = Status.STATUS_NOT_AFFECTED + + push_observation_to_issue_tracker(observation, None) + + expected_calls = [call(observation.product)] + factory_mock.assert_has_calls(expected_calls, any_order=False) + observation_mock.assert_not_called() + + @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") + @patch("application.core.models.Observation.save") + def test_push_observation_to_issue_tracker_closed_with_id_with_issue( + self, observation_mock, factory_mock + ): + issue = Issue(id=1, title="title", description="description", labels="labels") + factory_mock.return_value.get_issue.return_value = issue + + observation = Observation.objects.get(pk=1) + observation.product.issue_tracker_active = True + observation.current_status = Status.STATUS_FALSE_POSITIVE observation.issue_tracker_issue_id = "123" push_observation_to_issue_tracker(observation, None) @@ -100,14 +147,58 @@ def test_push_observation_to_issue_tracker_open_with_id_no_issue( expected_calls = [ call(observation.product), call().get_issue(observation.product, "123"), - call().create_issue(observation), + call().close_issue(observation, issue), ] + factory_mock.assert_has_calls(expected_calls, any_order=False) observation_mock.assert_called_once() + + @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") + @patch("application.issue_tracker.services.issue_tracker.handle_task_exception") + def test_push_observation_to_issue_tracker_exception( + self, exception_mock, factory_mock + ): + exception = Exception("error") + factory_mock.side_effect = exception + + observation = Observation.objects.get(pk=1) + observation.product.issue_tracker_active = True + push_observation_to_issue_tracker(observation, self.user_internal) + + exception_mock.assert_called_with(exception, self.user_internal) + + # --- push_observation_to_issue_tracker / higher or same than minimum severity --- + + @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") + @patch("application.core.models.Observation.save") + def test_push_observation_to_issue_tracker_with_issue_higher_than_minimum( + self, observation_mock, factory_mock + ): + issue = Issue(id=1, title="title", description="description", labels="labels") + factory_mock.return_value.get_issue.return_value = issue + + observation = Observation.objects.get(pk=1) + observation.product.issue_tracker_active = True + observation.product.issue_tracker_minimum_severity = Severity.SEVERITY_HIGH + observation.current_status = Status.STATUS_OPEN + observation.current_severity = Severity.SEVERITY_HIGH + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.current_severity, 99 + ) + observation.issue_tracker_issue_id = "123" + + push_observation_to_issue_tracker(observation, None) + + self.assertEqual(observation_mock.call_count, 2) + expected_calls = [ + call(observation.product), + call().get_issue(observation.product, "123"), + call().create_issue(observation), + ] factory_mock.assert_has_calls(expected_calls, any_order=False) @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") @patch("application.core.models.Observation.save") - def test_push_observation_to_issue_tracker_open_with_id_with_issue( + def test_push_observation_to_issue_tracker_with_issue_higher_than_minimum( self, observation_mock, factory_mock ): issue = Issue(id=1, title="title", description="description", labels="labels") @@ -115,39 +206,53 @@ def test_push_observation_to_issue_tracker_open_with_id_with_issue( observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - observation.current_status = Observation.STATUS_OPEN + observation.product.issue_tracker_minimum_severity = Severity.SEVERITY_HIGH + observation.current_status = Status.STATUS_OPEN + observation.current_severity = Severity.SEVERITY_HIGH + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.current_severity, 99 + ) observation.issue_tracker_issue_id = "123" push_observation_to_issue_tracker(observation, None) + observation_mock.assert_not_called() expected_calls = [ call(observation.product), call().get_issue(observation.product, "123"), call().update_issue(observation, issue), ] - observation_mock.assert_not_called() factory_mock.assert_has_calls(expected_calls, any_order=False) + # --- push_observation_to_issue_tracker / lower than minimum severity --- + @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") @patch("application.core.models.Observation.save") - def test_push_observation_to_issue_tracker_closed_no_id_no_issue( + def test_push_observation_to_issue_tracker_no_issue_lower_than_minimum( self, observation_mock, factory_mock ): factory_mock.return_value.get_issue.return_value = None observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - observation.current_status = Observation.STATUS_NOT_AFFECTED + observation.product.issue_tracker_minimum_severity = Severity.SEVERITY_HIGH + observation.current_status = Status.STATUS_OPEN + observation.current_severity = Severity.SEVERITY_MEDIUM + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.current_severity, 99 + ) push_observation_to_issue_tracker(observation, None) - expected_calls = [call(observation.product)] observation_mock.assert_not_called() + expected_calls = [ + call(observation.product), + ] factory_mock.assert_has_calls(expected_calls, any_order=False) @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") @patch("application.core.models.Observation.save") - def test_push_observation_to_issue_tracker_closed_with_id_with_issue( + def test_push_observation_to_issue_tracker_with_issue_lower_than_minimum_not_closed( self, observation_mock, factory_mock ): issue = Issue(id=1, title="title", description="description", labels="labels") @@ -155,32 +260,51 @@ def test_push_observation_to_issue_tracker_closed_with_id_with_issue( observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - observation.current_status = Observation.STATUS_FALSE_POSITIVE + observation.product.issue_tracker_minimum_severity = Severity.SEVERITY_HIGH + observation.current_status = Status.STATUS_OPEN + observation.current_severity = Severity.SEVERITY_MEDIUM + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.current_severity, 99 + ) observation.issue_tracker_issue_id = "123" push_observation_to_issue_tracker(observation, None) + observation_mock.assert_called_once() expected_calls = [ call(observation.product), call().get_issue(observation.product, "123"), call().close_issue(observation, issue), ] - observation_mock.assert_not_called() factory_mock.assert_has_calls(expected_calls, any_order=False) @patch("application.issue_tracker.services.issue_tracker.issue_tracker_factory") - @patch("application.issue_tracker.services.issue_tracker.handle_task_exception") - def test_push_observation_to_issue_tracker_exception( - self, exception_mock, factory_mock + @patch("application.core.models.Observation.save") + def test_push_observation_to_issue_tracker_with_issue_lower_than_minimum_already_closed( + self, observation_mock, factory_mock ): - exception = Exception("error") - factory_mock.side_effect = exception + issue = Issue(id=1, title="title", description="description", labels="labels") + factory_mock.return_value.get_issue.return_value = issue observation = Observation.objects.get(pk=1) observation.product.issue_tracker_active = True - push_observation_to_issue_tracker(observation, self.user_internal) + observation.product.issue_tracker_minimum_severity = Severity.SEVERITY_HIGH + observation.current_status = Status.STATUS_OPEN + observation.current_severity = Severity.SEVERITY_MEDIUM + observation.numerical_severity = Severity.NUMERICAL_SEVERITIES.get( + observation.current_severity, 99 + ) + observation.issue_tracker_issue_id = "123" + observation.issue_tracker_issue_closed = True - exception_mock.assert_called_with(exception, self.user_internal) + push_observation_to_issue_tracker(observation, None) + + observation_mock.assert_not_called() + expected_calls = [ + call(observation.product), + call().get_issue(observation.product, "123"), + ] + factory_mock.assert_has_calls(expected_calls, any_order=False) # --- push_deleted_observation_to_issue_tracker --- @@ -242,12 +366,12 @@ def test_push_deleted_observation_exception(self, exception_mock, factory_mock): def test_issue_tracker_factory_GitHub(self): product = Product.objects.get(pk=1) - product.issue_tracker_type = Product.ISSUE_TRACKER_GITHUB + product.issue_tracker_type = Issue_Tracker.ISSUE_TRACKER_GITHUB self.assertIsInstance(issue_tracker_factory(product), GitHubIssueTracker) def test_issue_tracker_factory_GitLab(self): product = Product.objects.get(pk=1) - product.issue_tracker_type = Product.ISSUE_TRACKER_GITLAB + product.issue_tracker_type = Issue_Tracker.ISSUE_TRACKER_GITLAB self.assertIsInstance(issue_tracker_factory(product), GitLabIssueTracker) def test_issue_tracker_exception(self): diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 236ef07ab..4070759d1 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -99,7 +99,7 @@ services: - "8025:8025" keycloak: - image: keycloak/keycloak:23.0.4 + image: keycloak/keycloak:23.0.6 environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin diff --git a/docker-compose-playwright.yml b/docker-compose-playwright.yml index 6c6a7bb4b..e07b2db31 100644 --- a/docker-compose-playwright.yml +++ b/docker-compose-playwright.yml @@ -59,7 +59,7 @@ services: playwright: - image: mcr.microsoft.com/playwright:v1.41.1 + image: mcr.microsoft.com/playwright:v1.41.2 depends_on: - frontend environment: diff --git a/docker-compose-prod-mysql.yml b/docker-compose-prod-mysql.yml index effe50757..963e70ada 100644 --- a/docker-compose-prod-mysql.yml +++ b/docker-compose-prod-mysql.yml @@ -37,7 +37,7 @@ services: - traefik frontend: - image: maibornwolff/secobserve-frontend:1.5.0 + image: maibornwolff/secobserve-frontend:1.6.0 container_name: "prod_secobserve_frontend" labels: - "traefik.enable=true" @@ -54,7 +54,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.5.0 + image: maibornwolff/secobserve-backend:1.6.0 container_name: "prod_secobserve_backend" labels: - "traefik.enable=true" diff --git a/docker-compose-prod-postgres.yml b/docker-compose-prod-postgres.yml index 77047f7b0..19695489c 100644 --- a/docker-compose-prod-postgres.yml +++ b/docker-compose-prod-postgres.yml @@ -37,7 +37,7 @@ services: - traefik frontend: - image: maibornwolff/secobserve-frontend:1.5.0 + image: maibornwolff/secobserve-frontend:1.6.0 container_name: "prod_secobserve_frontend" labels: - "traefik.enable=true" @@ -54,7 +54,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.5.0 + image: maibornwolff/secobserve-backend:1.6.0 container_name: "prod_secobserve_backend" labels: - "traefik.enable=true" diff --git a/docs/assets/images/screenshot_issue_tracker.png b/docs/assets/images/screenshot_issue_tracker.png index 55dadb0ea..9c41e7363 100644 Binary files a/docs/assets/images/screenshot_issue_tracker.png and b/docs/assets/images/screenshot_issue_tracker.png differ diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index 9be62e41d..3da1a688a 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -47,7 +47,7 @@ services: - default frontend: - image: maibornwolff/secobserve-frontend:1.5.0 + image: maibornwolff/secobserve-frontend:1.6.0 labels: - "traefik.enable=true" - "traefik.http.routers.frontend.rule=Host(`secobserve.localhost`)" @@ -63,7 +63,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.5.0 + image: maibornwolff/secobserve-backend:1.6.0 labels: - "traefik.enable=true" - "traefik.http.routers.backend.rule=Host(`secobserve-backend.localhost`)" diff --git a/docs/integrations/github_actions_and_templates.md b/docs/integrations/github_actions_and_templates.md index 45f7a82d3..69cc4bca4 100644 --- a/docs/integrations/github_actions_and_templates.md +++ b/docs/integrations/github_actions_and_templates.md @@ -382,7 +382,7 @@ trivy_filesystem_frontend: importer: SO_UPLOAD: "true" - SO_API_BASE_URL: https://secobserve.example.com + SO_API_BASE_URL: https://secobserve-backend.example.com SO_PRODUCT_NAME: SecObserve ``` diff --git a/docs/integrations/issue_trackers.md b/docs/integrations/issue_trackers.md index 401be37d4..e2ce01f35 100644 --- a/docs/integrations/issue_trackers.md +++ b/docs/integrations/issue_trackers.md @@ -17,17 +17,18 @@ The parameters for the issue tracker integration are set in the product: ![Issue tracker integration](../assets/images/screenshot_issue_tracker.png) -| | | -|-------------------|---| -| **Active** | Issues will only be pushed, if this parameter is set. | -| **Type** | Either **GitHub** or **GitLab** or **Jira** | -| **Base URL** | The base URL of the issue tracker. For **GitHub** it is `https://api.github.com`, for a self hosted **GitLab** it will be something like `https://gitlab.example.com`, for **Jira** it is `https:\\{organization_name}.atlassian.net`. | -| **API key** | An API key must be created in the issue tracker, having the permissions to create and update issues. | -| **Project id** | The path of the repository in its URL in **GitHub** or **GitLab**, e.g. `MaibornWolff/SecObserve`. For **Jira** it is the key of the project. | -| **Labels** | A comma separated list of labels, that will be set for the issue. Additional labels can be set in the issue tracker, they will be preserved when the issue is updated. | -| **Username** | *(only for Jira)* The REST API of Jira needs an authentication with username and API key. | -| **Issue type** | *(only for Jira)* The issue type to be created. | -| **Closed status** | *(only for Jira)* The status to be set when an issue is closed. | +| | | +|---------------------|---| +| **Active** | Issues will only be pushed, if this parameter is set. | +| **Type** | Either **GitHub** or **GitLab** or **Jira** | +| **Base URL** | The base URL of the issue tracker. For **GitHub** it is `https://api.github.com`, for a self hosted **GitLab** it will be something like `https://gitlab.example.com`, for **Jira** it is `https:\\{organization_name}.atlassian.net`. | +| **API key** | An API key must be created in the issue tracker, having the permissions to create and update issues. | +| **Project id** | The path of the repository in its URL in **GitHub** or **GitLab**, e.g. `MaibornWolff/SecObserve`. For **Jira** it is the key of the project. | +| **Labels** | A comma separated list of labels, that will be set for the issue. Additional labels can be set in the issue tracker, they will be preserved when the issue is updated. | +| **Minimum severity** | *(optional)* Issues will only be exported for observations with a severity that is higher or the same. | +| **Username** | *(only for Jira)* The REST API of Jira needs an authentication with username and API key. | +| **Issue type** | *(only for Jira)* The issue type to be created. | +| **Closed status** | *(only for Jira)* The status to be set when an issue is closed. | Issues are created or updated by an asynchronous background process after the import or the assessment of an observation has finished. If problems should occur during the transfer, a notification is send, see [Notifications](./notifications.md). diff --git a/end_to_end_tests/package-lock.json b/end_to_end_tests/package-lock.json index a24f5e901..886643aef 100644 --- a/end_to_end_tests/package-lock.json +++ b/end_to_end_tests/package-lock.json @@ -1,24 +1,24 @@ { "name": "end_to_end_tests", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "end_to_end_tests", - "version": "1.5.0", + "version": "1.6.0", "devDependencies": { - "@playwright/test": "1.41.1", - "@types/node": "20.11.9" + "@playwright/test": "1.41.2", + "@types/node": "20.11.16" } }, "node_modules/@playwright/test": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.1.tgz", - "integrity": "sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", "dev": true, "dependencies": { - "playwright": "1.41.1" + "playwright": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -28,9 +28,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.9.tgz", - "integrity": "sha512-CQXNuMoS/VcoAMISe5pm4JnEd1Br5jildbQEToEMQvutmv+EaQr90ry9raiudgpyDuqFiV9e4rnjSfLNq12M5w==", + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -51,12 +51,12 @@ } }, "node_modules/playwright": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.1.tgz", - "integrity": "sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", "dev": true, "dependencies": { - "playwright-core": "1.41.1" + "playwright-core": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -69,9 +69,9 @@ } }, "node_modules/playwright-core": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.1.tgz", - "integrity": "sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/end_to_end_tests/package.json b/end_to_end_tests/package.json index 89443dfd3..250a9f100 100644 --- a/end_to_end_tests/package.json +++ b/end_to_end_tests/package.json @@ -1,6 +1,6 @@ { "name": "end_to_end_tests", - "version": "1.5.0", + "version": "1.6.0", "private": true, "description": "", "main": "index.js", @@ -8,7 +8,7 @@ "keywords": [], "author": "", "devDependencies": { - "@playwright/test": "1.41.1", - "@types/node": "20.11.9" + "@playwright/test": "1.41.2", + "@types/node": "20.11.16" } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d41f6cb9b..ae7ac0540 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "secobserve", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "secobserve", - "version": "1.5.0", + "version": "1.6.0", "dependencies": { "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", @@ -14,46 +14,46 @@ "@fortawesome/free-brands-svg-icons": "6.5.1", "@fortawesome/free-solid-svg-icons": "6.5.1", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.15.6", - "@mui/material": "5.15.6", + "@mui/icons-material": "5.15.7", + "@mui/material": "5.15.7", "@textea/json-viewer": "3.3.1", "@types/inflection": "1.13.2", "@types/recharts": "1.8.29", "axios": "1.6.7", "chart.js": "4.4.1", - "markdown-to-jsx": "7.4.0", - "oidc-client-ts": "2.4.0", + "markdown-to-jsx": "7.4.1", + "oidc-client-ts": "3.0.0", "prop-types": "15.8.1", - "query-string": "8.1.0", - "ra-i18n-polyglot": "4.16.8", - "ra-input-rich-text": "4.16.8", - "ra-language-english": "4.16.8", + "query-string": "8.2.0", + "ra-i18n-polyglot": "4.16.9", + "ra-input-rich-text": "4.16.9", + "ra-language-english": "4.16.9", "react": "18.2.0", - "react-admin": "4.16.8", + "react-admin": "4.16.9", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-oidc-context": "2.3.1", - "react-router": "6.21.3", - "react-router-dom": "6.21.3", + "react-oidc-context": "3.0.0", + "react-router": "6.22.0", + "react-router-dom": "6.22.0", "runtime-env-cra": "0.2.4", - "tss-react": "4.9.3" + "tss-react": "4.9.4" }, "devDependencies": { "@microsoft/eslint-formatter-sarif": "3.0.0", "@trivago/prettier-plugin-sort-imports": "4.3.0", - "@types/jest": "29.5.11", - "@types/node": "20.11.9", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", "@types/prop-types": "15.7.11", - "@types/react": "18.2.48", + "@types/react": "18.2.52", "@types/react-dom": "18.2.18", - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/parser": "6.20.0", "@vitejs/plugin-react": "4.2.1", "eslint": "8.56.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-security": "2.1.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "rewire": "7.0.0", "typescript": "5.3.3", "vite": "5.0.12" @@ -656,262 +656,6 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", @@ -928,102 +672,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1455,14 +1103,14 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.33", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.33.tgz", - "integrity": "sha512-WcSpoJUw/UYHXpvgtl4HyMar2Ar97illUpqiS/X1gtSBp6sdDW6kB2BJ9OlVQ+Kk/RL2GDp/WHA9sbjAYV35ow==", + "version": "5.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.34.tgz", + "integrity": "sha512-e2mbTGTtReD/y5RFwnhkl1Tgl3XwgJhY040IlfkTVaU9f5LWrVhEnpRsYXu3B1CtLrwiWs4cu7aMHV9yRd4jpw==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@floating-ui/react-dom": "^2.0.6", + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.6", + "@mui/utils": "^5.15.7", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" @@ -1486,20 +1134,20 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.6.tgz", - "integrity": "sha512-0aoWS4qvk1uzm9JBs83oQmIMIQeTBUeqqu8u+3uo2tMznrB5fIKqQVCbCgq+4Tm4jG+5F7dIvnjvQ2aV7UKtdw==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.7.tgz", + "integrity": "sha512-AuF+Wo2Mp/edaO6vJnWjg+gj4tzEz5ChMZnAQpc22DXpSvM8ddgGcZvM7D7F99pIBoSv8ub+Iz0viL+yuGVmhg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.6.tgz", - "integrity": "sha512-GnkxMtlhs+8ieHLmCytg00ew0vMOiXGFCw8Ra9nxMsBjBqnrOI5gmXqUm+sGggeEU/HG8HyeqC1MX/IxOBJHzA==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.7.tgz", + "integrity": "sha512-EDAc8TVJGIA/imAvR3u4nANl2W5h3QeHieu2gK7Ypez/nIA55p08tHjf8UrMXEpxCAvfZO6piY9S9uaxETdicA==", "dependencies": { - "@babel/runtime": "^7.23.8" + "@babel/runtime": "^7.23.9" }, "engines": { "node": ">=12.0.0" @@ -1520,16 +1168,16 @@ } }, "node_modules/@mui/material": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.6.tgz", - "integrity": "sha512-rw7bDdpi2kzfmcDN78lHp8swArJ5sBCKsn+4G3IpGfu44ycyWAWX0VdlvkjcR9Yrws2KIm7c+8niXpWHUDbWoA==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/base": "5.0.0-beta.33", - "@mui/core-downloads-tracker": "^5.15.6", - "@mui/system": "^5.15.6", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.7.tgz", + "integrity": "sha512-l6+AiKZH3iOJmZCnlpel8ghYQe9Lq0BEuKP8fGj3g5xz4arO9GydqYAtLPMvuHKtArj8lJGNuT2yHYxmejincA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.34", + "@mui/core-downloads-tracker": "^5.15.7", + "@mui/system": "^5.15.7", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.6", + "@mui/utils": "^5.15.7", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "csstype": "^3.1.2", @@ -1564,12 +1212,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.6.tgz", - "integrity": "sha512-ZBX9E6VNUSscUOtU8uU462VvpvBS7eFl5VfxAzTRVQBHflzL+5KtnGrebgf6Nd6cdvxa1o0OomiaxSKoN2XDmg==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.7.tgz", + "integrity": "sha512-bcEeeXm7GyQCQvN9dwo8htGv8/6tP05p0i02Z7GXm5EoDPlBcqTNGugsjNLoGq6B0SsdyanjJGw0Jw00o1yAOA==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/utils": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.7", "prop-types": "^15.8.1" }, "engines": { @@ -1590,11 +1238,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.6.tgz", - "integrity": "sha512-KAn8P8xP/WigFKMlEYUpU9z2o7jJnv0BG28Qu1dhNQVutsLVIFdRf5Nb+0ijp2qgtcmygQ0FtfRuXv5LYetZTg==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.7.tgz", + "integrity": "sha512-ixSdslOjK1kzdGcxqj7O3d14By/LPQ7EWknsViQ8RaeT863EAQemS+zvUJDTcOpkfJh6q6gPnYMIb2TJCs9eWA==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -1621,15 +1269,15 @@ } }, "node_modules/@mui/system": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.6.tgz", - "integrity": "sha512-J01D//u8IfXvaEHMBQX5aO2l7Q+P15nt96c4NskX7yp5/+UuZP8XCQJhtBtLuj+M2LLyXHYGmCPeblsmmscP2Q==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.7.tgz", + "integrity": "sha512-9alZ4/dLxsTwUOdqakgzxiL5YW6ntqj0CfzWImgWnBMTZhgGcPsbYpBLniNkkk7/jptma4/bykWXHwju/ls/pg==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/private-theming": "^5.15.6", - "@mui/styled-engine": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.7", + "@mui/styled-engine": "^5.15.7", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.6", + "@mui/utils": "^5.15.7", "clsx": "^2.1.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -1673,11 +1321,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.6.tgz", - "integrity": "sha512-qfEhf+zfU9aQdbzo1qrSWlbPQhH1nCgeYgwhOVnj9Bn39shJQitEnXpSQpSNag8+uty5Od6PxmlNKPTnPySRKA==", + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.7.tgz", + "integrity": "sha512-8qhsxQRNV6aEOjjSk6YQIYJxkF5klhj8oG1FEEU4z6HV78TjNqRxMP08QGcdsibEbez+nihAaz6vu83b4XqbAg==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -1777,117 +1425,13 @@ } }, "node_modules/@remix-run/router": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", - "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", + "integrity": "sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ==", "engines": { "node": ">=14.0.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", @@ -1914,45 +1458,6 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2547,9 +2052,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -2563,9 +2068,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.9.tgz", - "integrity": "sha512-CQXNuMoS/VcoAMISe5pm4JnEd1Br5jildbQEToEMQvutmv+EaQr90ry9raiudgpyDuqFiV9e4rnjSfLNq12M5w==", + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2592,9 +2097,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { - "version": "18.2.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", - "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "version": "18.2.52", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.52.tgz", + "integrity": "sha512-E/YjWh3tH+qsLKaUzgpZb5AY0ChVa+ZJzF7ogehVILrFpdQk6nC/WXOv0bfFEABbXbgNxLBGU7IIZByPKb6eBw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2665,16 +2170,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", - "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", + "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/type-utils": "6.19.1", - "@typescript-eslint/utils": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/type-utils": "6.20.0", + "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2700,15 +2205,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", - "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", + "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4" }, "engines": { @@ -2728,13 +2233,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", - "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", + "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1" + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2745,13 +2250,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", - "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", + "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/utils": "6.20.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2772,9 +2277,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", - "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", + "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2785,13 +2290,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", - "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", + "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2813,17 +2318,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", - "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", + "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", "semver": "^7.5.4" }, "engines": { @@ -2838,12 +2343,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", - "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", + "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/types": "6.20.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3410,11 +2915,6 @@ "node": ">= 8" } }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, "node_modules/css-mediaquery": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", @@ -4356,20 +3856,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5573,9 +5059,12 @@ } }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/keyv": { "version": "4.5.4", @@ -5685,9 +5174,9 @@ } }, "node_modules/markdown-to-jsx": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.0.tgz", - "integrity": "sha512-zilc+MIkVVXPyTb4iIUTIz9yyqfcWjszGXnwF9K/aiBWcHXFcmdEMTkG01/oQhwSCH7SY1BnG6+ev5BzWmbPrg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.1.tgz", + "integrity": "sha512-GbrbkTnHp9u6+HqbPRFJbObi369AgJNXi/sGqq5HRsoZW063xR1XDCaConqq+whfEIAlzB1YPnOgsPc7B7bc/A==", "engines": { "node": ">= 10" }, @@ -5960,15 +5449,14 @@ "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" }, "node_modules/oidc-client-ts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", - "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.0.tgz", + "integrity": "sha512-YUcel/+C4AmXXhM/geNWc1jBsBVYzd+xhvSl1NMkxKpzZXqhLKmtj1ttSdxtDYm3P2YelnAr4zS4c96+p02iFQ==", "dependencies": { - "crypto-js": "^4.2.0", - "jwt-decode": "^3.1.2" + "jwt-decode": "^4.0.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=18" } }, "node_modules/once": { @@ -6154,9 +5642,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6409,9 +5897,9 @@ } }, "node_modules/query-string": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-8.1.0.tgz", - "integrity": "sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-8.2.0.tgz", + "integrity": "sha512-tUZIw8J0CawM5wyGBiDOAp7ObdRQh4uBor/fUR9ZjmbZVvw95OD9If4w3MQxr99rg0DJZ/9CIORcpEqU5hQG7g==", "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", @@ -6445,9 +5933,9 @@ ] }, "node_modules/ra-core": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-4.16.8.tgz", - "integrity": "sha512-QmtLZ66vdx1T8rtMWpBmCQqDPPqF87Q/8Rswjvi9vw9O2SCFBko5QUAccyDo4u819dttpJi7Gk/HPsZN3RWZWQ==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-4.16.9.tgz", + "integrity": "sha512-WXwX8MaDDkbt5R6rol4Ctnrlli5XvISAkiixFpm+iLc8zMOUBkhw+41dUlhmA9TqmTYWIqSO+kujs34kTlA8HQ==", "dependencies": { "clsx": "^1.1.1", "date-fns": "^2.19.0", @@ -6524,18 +6012,18 @@ } }, "node_modules/ra-i18n-polyglot": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-4.16.8.tgz", - "integrity": "sha512-ubJhl/XRVxcykj5kJ9yLVWCKxqw99VInk4jI6QIEab+K7qQxI/ZJhvqcltRs5xDC7UP0nD6+N+D/XT2f8UDJ/A==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-4.16.9.tgz", + "integrity": "sha512-K/kM5qM0vo3Ahm+JRqHhAJnJJ5oIkUg8RdGqKvbeuFt9tt5b4ZklSRzsA7YDpJFLR6JZpqHa4WCn5NDUQ+amPg==", "dependencies": { "node-polyglot": "^2.2.2", - "ra-core": "^4.16.8" + "ra-core": "^4.16.9" } }, "node_modules/ra-input-rich-text": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/ra-input-rich-text/-/ra-input-rich-text-4.16.8.tgz", - "integrity": "sha512-q/pw0EdrT67gWcs/6TXENJE9WTxwnedHGTWKrCbKeqVqksbwBK2/PCqFgm6UojVRh7Xw3F5+lGByeZWi8/B8aQ==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/ra-input-rich-text/-/ra-input-rich-text-4.16.9.tgz", + "integrity": "sha512-LvHePzqamn6DO9B8D0nepyFYfpBMr+N8iJajaDvjD8osUqrfnYFWwTnl9btZxOAEAAZACQLDl+QUwhchLgzk6Q==", "dependencies": { "@tiptap/core": "^2.0.3", "@tiptap/extension-color": "^2.0.3", @@ -6569,17 +6057,17 @@ } }, "node_modules/ra-language-english": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-4.16.8.tgz", - "integrity": "sha512-+z0Xc6BgW+NHYyErARhZNxb0nuNAb9/iRCACQ9o4xSmd2NJMnIjY/GP+pgb7mFNfFid+6qpr1H6JMv/S15zxtw==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-4.16.9.tgz", + "integrity": "sha512-x2EP6z67UuV07nv2phx9hkuFp63cykoswbiW1b0UQSQAYo2zCUEaOJ/D4lK9Qd6IUOUhYY12eUmTDY+js6O0eg==", "dependencies": { - "ra-core": "^4.16.8" + "ra-core": "^4.16.9" } }, "node_modules/ra-ui-materialui": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-4.16.8.tgz", - "integrity": "sha512-dEAkr1hn0Ugg2mVZeia9hZBmqJbfoFWhfJwKXlhLJMCyBpC1UwXnA12jPnXADCc1XeYCu5guDyDLfi8QKBZkVQ==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-4.16.9.tgz", + "integrity": "sha512-IXmmOwoi3VRgJ5Ip1tNxgmZPNIj4BKU9HEqyqqp2LdWWxcet8TeV2IBudP7rsHPP7hSM+H6Vo0PvMLb726Ca9g==", "dependencies": { "autosuggest-highlight": "^3.1.1", "clsx": "^1.1.1", @@ -6669,19 +6157,19 @@ } }, "node_modules/react-admin": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-4.16.8.tgz", - "integrity": "sha512-Y72v5QrEWe8t2PqTizegbm19c2OaqEKOQn+E98yEytu+0hAY1LFLg2ww4X8o9aJMIGmB+mq5Wn2ZbTPT+of+tQ==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-4.16.9.tgz", + "integrity": "sha512-Cdl0qACaocKwWEjkeEfrnfewQqw2veQKvWj1EL9PavLX2cSQ58I+1hW+xGQ5CDU/H6c6YP8K3z8JT41Rsjhr9w==", "dependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "@mui/icons-material": "^5.0.1", "@mui/material": "^5.0.2", "history": "^5.1.0", - "ra-core": "^4.16.8", - "ra-i18n-polyglot": "^4.16.8", - "ra-language-english": "^4.16.8", - "ra-ui-materialui": "^4.16.8", + "ra-core": "^4.16.9", + "ra-i18n-polyglot": "^4.16.9", + "ra-language-english": "^4.16.9", + "ra-ui-materialui": "^4.16.9", "react-hook-form": "^7.43.9", "react-router": "^6.1.0", "react-router-dom": "^6.1.0" @@ -6765,14 +6253,14 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-oidc-context": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.3.1.tgz", - "integrity": "sha512-WdhmEU6odNzMk9pvOScxUkf6/1aduiI/nQryr7+iCl2VDnYLASDTIV/zy58KuK4VXG3fBaRKukc/mRpMjF9a3Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.0.0.tgz", + "integrity": "sha512-VmSnEGWl3pTMO5zT94pGAwoK58njg6VPVFXbrepUGsLhSM0IVEKN0DtzNJvTtDSUOPA4xnJ6+jiq1fgdrWtHSQ==", "engines": { - "node": ">=12.13.0" + "node": ">=18" }, "peerDependencies": { - "oidc-client-ts": "^2.2.1", + "oidc-client-ts": "^3.0.0", "react": ">=16.8.0" } }, @@ -6811,11 +6299,11 @@ } }, "node_modules/react-router": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", - "integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz", + "integrity": "sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg==", "dependencies": { - "@remix-run/router": "1.14.2" + "@remix-run/router": "1.15.0" }, "engines": { "node": ">=14.0.0" @@ -6825,12 +6313,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz", - "integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz", + "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==", "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.3" + "@remix-run/router": "1.15.0", + "react-router": "6.22.0" }, "engines": { "node": ">=14.0.0" @@ -7447,9 +6935,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tss-react": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-4.9.3.tgz", - "integrity": "sha512-TqI0kBFmgW0f5YIOD2PMdHu6FnqSxVDUf5uJ7+gVkhemtMfwdlFpvXpddgSesktizr9PU9hY2nZ+kNnf0KQb9A==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-4.9.4.tgz", + "integrity": "sha512-4o+XFdaTcraNEIsCRxKiEX7g6xhcsdSxfHRjos3Kg9GbYIpzfK4M2MHMETTuXT54nUrldtnkipNC003v/q5KVg==", "dependencies": { "@emotion/cache": "*", "@emotion/serialize": "*", diff --git a/frontend/package.json b/frontend/package.json index 9751d4575..e986e2257 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,26 +1,26 @@ { "name": "secobserve", - "version": "1.5.0", + "version": "1.6.0", "private": true, "dependencies": { - "@mui/icons-material": "5.15.6", - "@mui/material": "5.15.6", + "@mui/icons-material": "5.15.7", + "@mui/material": "5.15.7", "@types/inflection": "1.13.2", "@types/recharts": "1.8.29", "prop-types": "15.8.1", - "query-string": "8.1.0", - "react-admin": "4.16.8", - "ra-i18n-polyglot": "4.16.8", - "ra-input-rich-text": "4.16.8", - "ra-language-english": "4.16.8", + "query-string": "8.2.0", + "react-admin": "4.16.9", + "ra-i18n-polyglot": "4.16.9", + "ra-input-rich-text": "4.16.9", + "ra-language-english": "4.16.9", "react": "18.2.0", "react-dom": "18.2.0", - "react-router": "6.21.3", - "react-router-dom": "6.21.3", - "tss-react": "4.9.3", + "react-router": "6.22.0", + "react-router-dom": "6.22.0", + "tss-react": "4.9.4", "chart.js": "4.4.1", "react-chartjs-2": "5.2.0", - "markdown-to-jsx": "7.4.0", + "markdown-to-jsx": "7.4.1", "@fortawesome/fontawesome-svg-core": "6.5.1", "@fortawesome/free-solid-svg-icons": "6.5.1", "@fortawesome/free-brands-svg-icons": "6.5.1", @@ -30,8 +30,8 @@ "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", "runtime-env-cra": "0.2.4", - "oidc-client-ts": "2.4.0", - "react-oidc-context": "2.3.1" + "oidc-client-ts": "3.0.0", + "react-oidc-context": "3.0.0" }, "scripts": { "start": "NODE_ENV=development runtime-env-cra --config-name=./public/runtime-env.js && vite", @@ -47,21 +47,21 @@ "not op_mini all" ], "devDependencies": { - "@types/jest": "29.5.11", - "@types/node": "20.11.9", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", "@types/prop-types": "15.7.11", - "@types/react": "18.2.48", + "@types/react": "18.2.52", "@types/react-dom": "18.2.18", "rewire": "7.0.0", "typescript": "5.3.3", - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/parser": "6.20.0", "eslint": "8.56.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-security": "2.1.0", "eslint-plugin-react-hooks": "4.6.0", "@microsoft/eslint-formatter-sarif": "3.0.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "@trivago/prettier-plugin-sort-imports": "4.3.0", "vite": "5.0.12", "@vitejs/plugin-react": "4.2.1" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3d93d7533..a3d96c44a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -88,7 +88,7 @@ const App = () => { {...notifications} // nosemgrep: typescript.react.best-practice.react-props-spreading.react-props-spreading // nosemgrep because the props are well defined in the import recordRepresentation={(record) => `${trim_string(record.name)}`} - />{" "} + /> ); diff --git a/frontend/src/access_control/Login.tsx b/frontend/src/access_control/Login.tsx index 2f0da6d90..b5d216b2e 100644 --- a/frontend/src/access_control/Login.tsx +++ b/frontend/src/access_control/Login.tsx @@ -3,6 +3,7 @@ import PersonIcon from "@mui/icons-material/Person"; import { Avatar, Button, Card, CardActions, CircularProgress, Stack } from "@mui/material"; import Box from "@mui/material/Box"; import PropTypes from "prop-types"; +import { Fragment } from "react"; import { useState } from "react"; import { Form, TextInput, required, useLogin, useNotify, useTheme } from "react-admin"; import { useAuth } from "react-oidc-context"; @@ -47,7 +48,7 @@ const Login = () => { }; return ( -
+ {isAuthenticated && } {!isAuthenticated && !auth.isLoading && (
@@ -117,7 +118,7 @@ const Login = () => {
)} -
+ ); }; diff --git a/frontend/src/access_control/authProvider.ts b/frontend/src/access_control/authProvider.ts index af75d8e39..d735651c9 100644 --- a/frontend/src/access_control/authProvider.ts +++ b/frontend/src/access_control/authProvider.ts @@ -49,6 +49,7 @@ const authProvider: AuthProvider = { if (oidc_signed_in()) { const user_manager = new UserManager(oidcConfig); + user_manager.removeUser(); return user_manager.signoutRedirect(); } diff --git a/frontend/src/access_control/product_api_token/ProductApiTokenCreate.tsx b/frontend/src/access_control/product_api_token/ProductApiTokenCreate.tsx index eec97ce57..5c42b1ae0 100644 --- a/frontend/src/access_control/product_api_token/ProductApiTokenCreate.tsx +++ b/frontend/src/access_control/product_api_token/ProductApiTokenCreate.tsx @@ -2,9 +2,10 @@ import AddIcon from "@mui/icons-material/Add"; import CancelIcon from "@mui/icons-material/Cancel"; import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material"; import { useState } from "react"; -import { SaveButton, SimpleForm, Toolbar, required, useNotify, useRefresh } from "react-admin"; +import { SaveButton, SimpleForm, Toolbar, useNotify, useRefresh } from "react-admin"; import CopyToClipboardButton from "../../commons/custom_fields/CopyToClipboardButton"; +import { validate_required } from "../../commons/custom_validators"; import { AutocompleteInputWide } from "../../commons/layout/themes"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; import { ROLE_CHOICES } from "../types"; @@ -47,7 +48,6 @@ const CreateProductApiToken = (props: CreateProductApiTokenProps) => { direction: "row", justifyContent: "center", alignItems: "center", - color: "#000000dd", }} variant="contained" onClick={handleRoleCancel} @@ -106,29 +106,33 @@ const CreateProductApiToken = (props: CreateProductApiTokenProps) => { Create product API token }> - + Create product API token - + -
-
Make sure to copy the token now. You won't be able to see it again!
- +
); }; -const requiredValidate = [required()]; - export default CreateProductApiToken; diff --git a/frontend/src/access_control/product_api_token/ProductApiTokenEmbeddedList.tsx b/frontend/src/access_control/product_api_token/ProductApiTokenEmbeddedList.tsx index 563689192..7faef5908 100644 --- a/frontend/src/access_control/product_api_token/ProductApiTokenEmbeddedList.tsx +++ b/frontend/src/access_control/product_api_token/ProductApiTokenEmbeddedList.tsx @@ -1,5 +1,4 @@ -import { Paper } from "@mui/material"; -import { Datagrid, ListContextProvider, Pagination, SelectField, useListController } from "react-admin"; +import { Datagrid, ListContextProvider, SelectField, useListController } from "react-admin"; import { PERMISSION_PRODUCT_API_TOKEN_REVOKE, ROLE_CHOICES } from "../../access_control/types"; import { getSettingListSize } from "../../commons/settings/functions"; @@ -30,15 +29,12 @@ const ProductApiTokenEmbeddedList = ({ product }: ProductApiTokenEmbeddedListPro return (
- - - - {product && product.permissions.includes(PERMISSION_PRODUCT_API_TOKEN_REVOKE) && ( - - )} - - - + + + {product && product.permissions.includes(PERMISSION_PRODUCT_API_TOKEN_REVOKE) && ( + + )} +
); diff --git a/frontend/src/commons/about/About.tsx b/frontend/src/commons/about/About.tsx index 293c8b60f..995776ee2 100644 --- a/frontend/src/commons/about/About.tsx +++ b/frontend/src/commons/about/About.tsx @@ -12,15 +12,15 @@ import { Stack, Typography, } from "@mui/material"; -import * as React from "react"; +import { Fragment, useState } from "react"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; const About = () => { const get_version = "version_unkown"; - const [open, setOpen] = React.useState(false); - const [backendVersion, setBackendVersion] = React.useState("..."); + const [open, setOpen] = useState(false); + const [backendVersion, setBackendVersion] = useState("..."); const getBackendVersion = async () => { httpClient(window.__RUNTIME_CONFIG__.API_BASE_URL + "/status/version", { @@ -52,13 +52,11 @@ const About = () => { direction: "row", justifyContent: "center", alignItems: "center", - color: "#000000dd", }} variant="contained" onClick={handleOk} color="inherit" > - {" "} OK ); @@ -68,13 +66,12 @@ const About = () => { } return ( - + { handleOpen(); }} > - {" "} @@ -85,7 +82,7 @@ const About = () => { SecObserve gathers results about potential security flaws from various vulnerability scanning - tools and makes them available for assessment and reporting.{" "} + tools and makes them available for assessment and reporting. @@ -134,7 +131,7 @@ const About = () => { - + ); }; diff --git a/frontend/src/commons/custom_fields/SeverityField.tsx b/frontend/src/commons/custom_fields/SeverityField.tsx index 23ecf4c15..10598a7ed 100644 --- a/frontend/src/commons/custom_fields/SeverityField.tsx +++ b/frontend/src/commons/custom_fields/SeverityField.tsx @@ -11,8 +11,13 @@ interface SeverityProps { function get_current_severity(record: any) { if (record.current_severity !== undefined) { return record.current_severity; - } else if (record.potential_duplicate_observation.current_severity !== undefined) { + } else if ( + record.potential_duplicate_observation !== undefined && + record.potential_duplicate_observation.current_severity !== undefined + ) { return record.potential_duplicate_observation.current_severity; + } else if (record.issue_tracker_minimum_severity !== undefined) { + return record.issue_tracker_minimum_severity; } else { return null; } @@ -28,6 +33,7 @@ export const SeverityField = (props: SeverityProps) => { sx={{ backgroundColor: get_severity_color(get_current_severity(record)), color: "white", + width: "fit-content", }} /> ) : null; diff --git a/frontend/src/commons/custom_fields/TextUrlField.tsx b/frontend/src/commons/custom_fields/TextUrlField.tsx index 36a5309fa..ba4f1b2b1 100644 --- a/frontend/src/commons/custom_fields/TextUrlField.tsx +++ b/frontend/src/commons/custom_fields/TextUrlField.tsx @@ -1,4 +1,5 @@ import LaunchIcon from "@mui/icons-material/Launch"; +import { Fragment } from "react"; import { useStyles } from "../../commons/layout/themes"; @@ -36,7 +37,7 @@ const TextUrlField = (props: TextUrlFieldProps) => { const { classes } = useStyles(); return ( -
+ {is_valid_url(props.url) && ( { {props.text} )} -
+ ); }; diff --git a/frontend/src/commons/custom_validators.ts b/frontend/src/commons/custom_validators.ts new file mode 100644 index 000000000..9ed75b5b7 --- /dev/null +++ b/frontend/src/commons/custom_validators.ts @@ -0,0 +1,9 @@ +import { maxLength, maxValue, minValue, required } from "react-admin"; + +export const validate_required = [required()]; +export const validate_required_255 = [required(), maxLength(255)]; +export const validate_255 = [maxLength(255)]; +export const validate_513 = [maxLength(513)]; +export const validate_2048 = [maxLength(2048)]; + +export const validate_min_0_999999 = [minValue(0), maxValue(999999)]; diff --git a/frontend/src/commons/layout/AppBar.tsx b/frontend/src/commons/layout/AppBar.tsx index f1de5c392..dd496c135 100644 --- a/frontend/src/commons/layout/AppBar.tsx +++ b/frontend/src/commons/layout/AppBar.tsx @@ -1,14 +1,14 @@ import ArticleIcon from "@mui/icons-material/Article"; import SettingsIcon from "@mui/icons-material/Settings"; import { Box, Divider, ListItemIcon, ListItemText, MenuItem, Theme, Typography, useMediaQuery } from "@mui/material"; -import * as React from "react"; +import { forwardRef } from "react"; import { AppBar, Logout, UserMenu, useUserMenu } from "react-admin"; import { Link } from "react-router-dom"; import About from "../about/About"; import Logo from "./Logo"; -const DocumentationMenu = React.forwardRef(() => { +const DocumentationMenu = forwardRef(() => { const { onClose } = useUserMenu(); return ( @@ -29,7 +29,7 @@ const DocumentationMenu = React.forwardRef(() => { ); }); -const SettingsMenu = React.forwardRef(() => { +const SettingsMenu = forwardRef(() => { const { onClose } = useUserMenu(); return ( diff --git a/frontend/src/commons/layout/Menu.tsx b/frontend/src/commons/layout/Menu.tsx index 908f33ab0..a4ae09f2b 100644 --- a/frontend/src/commons/layout/Menu.tsx +++ b/frontend/src/commons/layout/Menu.tsx @@ -1,5 +1,5 @@ import Box from "@mui/material/Box"; -import React from "react"; +import { Fragment } from "react"; import { DashboardMenuItem, MenuItemLink, MenuProps, useSidebarState, useTranslate } from "react-admin"; import observations from "../../core/observations"; @@ -14,7 +14,7 @@ const Menu = ({ dense = false }: MenuProps) => { const [open] = useSidebarState(); return ( - + { - + ); }; diff --git a/frontend/src/commons/layout/SubMenu.tsx b/frontend/src/commons/layout/SubMenu.tsx index 3d841c214..b1ce1e5bb 100644 --- a/frontend/src/commons/layout/SubMenu.tsx +++ b/frontend/src/commons/layout/SubMenu.tsx @@ -1,5 +1,6 @@ import ExpandMore from "@mui/icons-material/ExpandMore"; import { Collapse, List, ListItemIcon, MenuItem, Tooltip, Typography } from "@mui/material"; +import { Fragment } from "react"; import * as React from "react"; import { ReactElement, ReactNode } from "react"; import { useSidebarState, useTranslate } from "react-admin"; @@ -29,7 +30,7 @@ const SubMenu = (props: Props) => { ); return ( -
+ {sidebarIsOpen || isOpen ? ( header ) : ( @@ -52,7 +53,7 @@ const SubMenu = (props: Props) => { {children} -
+ ); }; diff --git a/frontend/src/commons/layout/themes.ts b/frontend/src/commons/layout/themes.ts index 1f06b3adb..ac1dfcda0 100644 --- a/frontend/src/commons/layout/themes.ts +++ b/frontend/src/commons/layout/themes.ts @@ -1,5 +1,5 @@ import { styled } from "@mui/system"; -import { AutocompleteInput, PasswordInput, SelectInput, TextInput, defaultTheme } from "react-admin"; +import { AutocompleteInput, TextInput, defaultTheme } from "react-admin"; import { makeStyles } from "tss-react/mui"; import { getSettingTheme } from "../../commons/settings/functions"; @@ -12,23 +12,15 @@ export const AutocompleteInputMedium = styled(AutocompleteInput)({ width: "15em", }); -export const SelectInputWide = styled(SelectInput)({ - width: "30em", -}); - export const TextInputWide = styled(TextInput)({ width: "30em", }); -export const PasswordInputWide = styled(PasswordInput)({ - width: "30em", -}); - export function getLinkColor() { if (getSettingTheme() == "dark") { return "#6ed2f0"; } else { - return "#00b4f0"; + return "#00B3F0"; } } @@ -57,6 +49,9 @@ export const darkTheme = { secondary: { main: "#FBBA72", }, + background: { + default: "#313131", + }, mode: "dark" as const, // Switching the dark mode on is a single property value change. }, sidebar: { @@ -102,7 +97,7 @@ export const lightTheme = { contrastText: "#fff", }, background: { - default: "#fcfcfe", + default: "#fafafb", }, mode: "light" as const, }, @@ -118,9 +113,9 @@ export const lightTheme = { styleOverrides: { root: { "& .RaReferenceField-link>*": { - color: "#00b4f0", + color: "#00B3F0", ":visited": { - color: "#00b4f0", + color: "#00B3F0", }, }, }, diff --git a/frontend/src/commons/settings/Settings.tsx b/frontend/src/commons/settings/Settings.tsx index 8824ca272..062195db2 100644 --- a/frontend/src/commons/settings/Settings.tsx +++ b/frontend/src/commons/settings/Settings.tsx @@ -1,4 +1,3 @@ -// import Card from "@mui/material/Card"; import { Box, Card, @@ -10,22 +9,32 @@ import { RadioGroup, Stack, } from "@mui/material"; +import { useState } from "react"; import { Title, useTheme } from "react-admin"; import { darkTheme, lightTheme } from "../layout/themes"; import { getSettingListSize, getSettingTheme, saveSettingListSize, saveSettingTheme } from "./functions"; const Settings = () => { + const [previousTheme, setPreviousTheme] = useState(getSettingTheme()); const [, setTheme] = useTheme(); function setLightTheme() { setTheme(lightTheme); saveSettingTheme("light"); + if (previousTheme != "light") { + window.location.reload(); + } + setPreviousTheme("light"); } function setDarkTheme() { setTheme(darkTheme); saveSettingTheme("dark"); + if (previousTheme != "dark") { + window.location.reload(); + } + setPreviousTheme("dark"); } return ( diff --git a/frontend/src/commons/settings/functions.ts b/frontend/src/commons/settings/functions.ts index 6b2250081..5167be83a 100644 --- a/frontend/src/commons/settings/functions.ts +++ b/frontend/src/commons/settings/functions.ts @@ -1,7 +1,10 @@ import { httpClient } from "../../commons/ra-data-django-rest-framework"; import { darkTheme, lightTheme } from "../layout/themes"; -export function saveSettingTheme(theme: string) { +export async function saveSettingTheme(theme: string) { + const user = JSON.parse(localStorage.getItem("user") || "{}"); + user.setting_theme = theme; + localStorage.setItem("user", JSON.stringify(user)); saveSetting({ setting_theme: theme }); } diff --git a/frontend/src/core/branches/BranchCreate.tsx b/frontend/src/core/branches/BranchCreate.tsx index b1517658c..ed7b57555 100644 --- a/frontend/src/core/branches/BranchCreate.tsx +++ b/frontend/src/core/branches/BranchCreate.tsx @@ -1,19 +1,19 @@ import AddIcon from "@mui/icons-material/Add"; import CancelIcon from "@mui/icons-material/Cancel"; import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material"; -import * as React from "react"; +import { Fragment, useState } from "react"; import { BooleanInput, CreateBase, SaveButton, SimpleForm, Toolbar, - required, useCreate, useNotify, useRefresh, } from "react-admin"; +import { validate_required_255 } from "../../commons/custom_validators"; import { TextInputWide } from "../../commons/layout/themes"; export type BranchCreateProps = { @@ -21,7 +21,7 @@ export type BranchCreateProps = { }; const BranchCreate = ({ id }: BranchCreateProps) => { - const [open, setOpen] = React.useState(false); + const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); const [create] = useCreate(); @@ -38,7 +38,6 @@ const BranchCreate = ({ id }: BranchCreateProps) => { direction: "row", justifyContent: "center", alignItems: "center", - color: "#000000dd", }} variant="contained" onClick={handleCancel} @@ -75,7 +74,7 @@ const BranchCreate = ({ id }: BranchCreateProps) => { }; return ( - + - + ); }; diff --git a/frontend/src/core/evidences/EvidenceShow.tsx b/frontend/src/core/evidences/EvidenceShow.tsx index 1228d4339..7be352f44 100644 --- a/frontend/src/core/evidences/EvidenceShow.tsx +++ b/frontend/src/core/evidences/EvidenceShow.tsx @@ -44,7 +44,7 @@ const EvidenceShow = () => { ( - + { enableClipboard={false} className={classes.displayFontSize} theme={getSettingTheme() as JsonViewerTheme} + sx={{ padding: 1 }} /> )} diff --git a/frontend/src/core/observations/ObservationAssessment.tsx b/frontend/src/core/observations/ObservationAssessment.tsx index b05a8762b..897b1f901 100644 --- a/frontend/src/core/observations/ObservationAssessment.tsx +++ b/frontend/src/core/observations/ObservationAssessment.tsx @@ -1,15 +1,16 @@ import CancelIcon from "@mui/icons-material/Cancel"; import PlaylistAddCheckIcon from "@mui/icons-material/PlaylistAddCheck"; import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material"; -import * as React from "react"; -import { SaveButton, SimpleForm, Toolbar, required, useNotify, useRefresh } from "react-admin"; +import { Fragment, useState } from "react"; +import { SaveButton, SimpleForm, Toolbar, useNotify, useRefresh } from "react-admin"; +import { validate_required, validate_required_255 } from "../../commons/custom_validators"; import { AutocompleteInputMedium, TextInputWide } from "../../commons/layout/themes"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; import { OBSERVATION_SEVERITY_CHOICES, OBSERVATION_STATUS_CHOICES } from "../types"; const ObservationAssessment = () => { - const [open, setOpen] = React.useState(false); + const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); @@ -55,7 +56,6 @@ const ObservationAssessment = () => { direction: "row", justifyContent: "center", alignItems: "center", - color: "#000000dd", }} variant="contained" onClick={handleCancel} @@ -73,7 +73,7 @@ const ObservationAssessment = () => { ); return ( - + - + Add observation }> - - - Observation - - - - - - - - + + + + + + + + + + + + + Product + + + + + + + + + + + Origins - - - - - - - + + + + + + + + + + + + + + + + + - + ); }; -const requiredValidate = [required()]; - export default ObservationCreate; diff --git a/frontend/src/core/observations/ObservationDashboardList.tsx b/frontend/src/core/observations/ObservationDashboardList.tsx index c6438f6bd..bbd9f4295 100644 --- a/frontend/src/core/observations/ObservationDashboardList.tsx +++ b/frontend/src/core/observations/ObservationDashboardList.tsx @@ -1,17 +1,10 @@ -import { Paper } from "@mui/material"; -import { - ChipField, - Datagrid, - FunctionField, - ListContextProvider, - Pagination, - TextField, - useListController, -} from "react-admin"; +import { Paper, Typography } from "@mui/material"; +import { ChipField, Datagrid, FunctionField, ListContextProvider, TextField, useListController } from "react-admin"; +import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; import { SeverityField } from "../../commons/custom_fields/SeverityField"; import { humanReadableDate } from "../../commons/functions"; -import { getElevation } from "../../metrics/functions"; +import { getSettingListSize } from "../../commons/settings/functions"; import { OBSERVATION_STATUS_OPEN } from "../types"; import { Observation } from "../types"; @@ -44,10 +37,18 @@ const ObservationDashboardList = () => { localStorage.setItem("observationdashboardlist", "true"); return ( - -
- - + + + Open observations of the last 7 days + + +
+ @@ -59,10 +60,10 @@ const ObservationDashboardList = () => { render={(record) => (record ? humanReadableDate(record.last_observation_log) : "")} /> - - -
-
+ +
+
+ ); }; diff --git a/frontend/src/core/observations/ObservationEdit.tsx b/frontend/src/core/observations/ObservationEdit.tsx index 26f7c8130..04055fa96 100644 --- a/frontend/src/core/observations/ObservationEdit.tsx +++ b/frontend/src/core/observations/ObservationEdit.tsx @@ -1,4 +1,4 @@ -import { Typography } from "@mui/material"; +import { Divider, Stack, Typography } from "@mui/material"; import { DeleteButton, Edit, @@ -6,19 +6,21 @@ import { ReferenceInput, SaveButton, SimpleForm, + TextInput, Toolbar, WithRecord, - required, useRecordContext, } from "react-admin"; import { PERMISSION_OBSERVATION_DELETE } from "../../access_control/types"; import { - AutocompleteInputMedium, - AutocompleteInputWide, - SelectInputWide, - TextInputWide, -} from "../../commons/layout/themes"; + validate_255, + validate_2048, + validate_min_0_999999, + validate_required, + validate_required_255, +} from "../../commons/custom_validators"; +import { AutocompleteInputMedium, AutocompleteInputWide, TextInputWide } from "../../commons/layout/themes"; import { OBSERVATION_SEVERITY_CHOICES, OBSERVATION_STATUS_CHOICES } from "../../core/types"; const CustomToolbar = () => { @@ -45,11 +47,17 @@ const ObservationEdit = () => { if (!data.origin_service_name) { data.origin_service_name = ""; } - if (!data.origin_component_name_version) { - data.origin_component_name_version = ""; + if (!data.origin_component_name) { + data.origin_component_name = ""; } - if (!data.origin_docker_image_name_tag) { - data.origin_docker_image_name_tag = ""; + if (!data.origin_component_version) { + data.origin_component_version = ""; + } + if (!data.origin_docker_image_name) { + data.origin_docker_image_name = ""; + } + if (!data.origin_docker_image_tag) { + data.origin_docker_image_tag = ""; } if (!data.origin_endpoint_url) { data.origin_endpoint_url = ""; @@ -57,56 +65,148 @@ const ObservationEdit = () => { if (!data.origin_source_file) { data.origin_source_file = ""; } + if (!data.origin_cloud_provider) { + data.origin_cloud_provider = ""; + } + if (!data.origin_cloud_account_subscription_project) { + data.origin_cloud_account_subscription_project = ""; + } + if (!data.origin_cloud_resource) { + data.origin_cloud_resource = ""; + } + if (!data.origin_cloud_resource_type) { + data.origin_cloud_resource_type = ""; + } + if (!data.origin_cloud_provider) { + data.origin_cloud_provider = ""; + } + data.origin_component_name_version = ""; + data.origin_docker_image_name_tag = ""; return data; }; return ( }> - - - - Observation - ( - - - - )} - /> - - - - - - Origins - - - - - - - + + Observation + + + + + + + + + + + + + + + Product + + + + + + ( + + + + )} + /> + + + + + + Origins + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; -const requiredValidate = [required()]; - export default ObservationEdit; diff --git a/frontend/src/core/observations/ObservationEmbeddedList.tsx b/frontend/src/core/observations/ObservationEmbeddedList.tsx index f7434d60c..95087dd42 100644 --- a/frontend/src/core/observations/ObservationEmbeddedList.tsx +++ b/frontend/src/core/observations/ObservationEmbeddedList.tsx @@ -1,4 +1,4 @@ -import { Paper, Stack } from "@mui/material"; +import { Stack } from "@mui/material"; import { Fragment } from "react"; import { useEffect } from "react"; import { @@ -12,7 +12,6 @@ import { ListContextProvider, NullableBooleanInput, NumberField, - Pagination, ReferenceInput, SelectColumnsButton, TextField, @@ -23,6 +22,7 @@ import { import { useNavigate } from "react-router"; import { PERMISSION_OBSERVATION_ASSESSMENT, PERMISSION_OBSERVATION_DELETE } from "../../access_control/types"; +import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; import { SeverityField } from "../../commons/custom_fields/SeverityField"; import { humanReadableDate } from "../../commons/functions"; import { AutocompleteInputMedium } from "../../commons/layout/themes"; @@ -144,41 +144,39 @@ const ObservationsEmbeddedList = ({ product }: ObservationsEmbeddedListProps) => - - - ) - } - preferenceKey="observations.embedded" - > - - - - - - - - - - - - - - label="Age" - sortBy="last_observation_log" - render={(record) => (record ? humanReadableDate(record.last_observation_log) : "")} - /> - - - - + + ) + } + preferenceKey="observations.embedded" + > + + + + + + + + + + + + + + label="Age" + sortBy="last_observation_log" + render={(record) => (record ? humanReadableDate(record.last_observation_log) : "")} + /> + + + ); diff --git a/frontend/src/core/observations/ObservationRemoveAssessment.tsx b/frontend/src/core/observations/ObservationRemoveAssessment.tsx index 3929b12c2..ba6b450d5 100644 --- a/frontend/src/core/observations/ObservationRemoveAssessment.tsx +++ b/frontend/src/core/observations/ObservationRemoveAssessment.tsx @@ -1,14 +1,15 @@ import CancelIcon from "@mui/icons-material/Cancel"; import PlaylistAddCheckIcon from "@mui/icons-material/PlaylistAddCheck"; import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material"; -import * as React from "react"; -import { SaveButton, SimpleForm, Toolbar, required, useNotify, useRefresh } from "react-admin"; +import { Fragment, useState } from "react"; +import { SaveButton, SimpleForm, Toolbar, useNotify, useRefresh } from "react-admin"; +import { validate_required_255 } from "../../commons/custom_validators"; import { TextInputWide } from "../../commons/layout/themes"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; const ObservationRemoveAssessment = () => { - const [open, setOpen] = React.useState(false); + const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); const handleOpen = () => setOpen(true); @@ -49,7 +50,6 @@ const ObservationRemoveAssessment = () => { direction: "row", justifyContent: "center", alignItems: "center", - color: "#000000dd", }} variant="contained" onClick={handleCancel} @@ -67,7 +67,7 @@ const ObservationRemoveAssessment = () => { ); return ( - +