Skip to content

Commit 4745623

Browse files
committed
Merge branch 'dev' of https://github.com/MaibornWolff/SecObserve into stackable
2 parents 5331b73 + ac98b89 commit 4745623

86 files changed

Lines changed: 3949 additions & 1448 deletions

File tree

Some content is hidden

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

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ jobs:
6767

6868
# Upload the results to GitHub's code scanning dashboard.
6969
- name: "Upload to code-scanning"
70-
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
70+
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
7171
with:
7272
sarif_file: results.sarif

backend/application/core/api/filters.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ChoiceFilter,
1010
FilterSet,
1111
ModelChoiceFilter,
12+
MultipleChoiceFilter,
1213
NumberFilter,
1314
OrderingFilter,
1415
)
@@ -27,7 +28,7 @@
2728
Product_Member,
2829
Service,
2930
)
30-
from application.core.types import Status
31+
from application.core.types import Severity, Status
3132
from application.licenses.models import License_Component
3233

3334

@@ -217,6 +218,10 @@ class Meta:
217218

218219
class ObservationFilter(FilterSet):
219220
title = CharFilter(field_name="title", lookup_expr="icontains")
221+
current_severity = MultipleChoiceFilter(field_name="current_severity", choices=Severity.SEVERITY_CHOICES)
222+
current_status = MultipleChoiceFilter(field_name="current_status", choices=Status.STATUS_CHOICES)
223+
branch_name = CharFilter(field_name="branch__name", lookup_expr="icontains")
224+
origin_service_name = CharFilter(field_name="origin_service__name", lookup_expr="icontains")
220225
origin_component_name_version = CharFilter(field_name="origin_component_name_version", lookup_expr="icontains")
221226
origin_docker_image_name_tag_short = CharFilter(
222227
field_name="origin_docker_image_name_tag_short", lookup_expr="icontains"
@@ -248,6 +253,7 @@ class ObservationFilter(FilterSet):
248253
("title", "title"),
249254
(("numerical_severity", "id"), "current_severity"),
250255
("current_status", "current_status"),
256+
("current_priority", "current_priority"),
251257
("origin_component_name_version", "origin_component_name_version"),
252258
(
253259
"origin_docker_image_name_tag_short",
@@ -345,6 +351,7 @@ class ObservationLogFilter(FilterSet):
345351
)
346352
branch_name = CharFilter(field_name="observation__branch__name", lookup_expr="icontains")
347353
branch = ModelChoiceFilter(field_name="observation__branch", queryset=Branch.objects.all())
354+
origin_service_name = CharFilter(field_name="observation__origin_service__name", lookup_expr="icontains")
348355
origin_service = ModelChoiceFilter(field_name="observation__origin_service", queryset=Service.objects.all())
349356
origin_component_name_version = CharFilter(
350357
field_name="observation__origin_component_name_version", lookup_expr="icontains"
@@ -399,6 +406,7 @@ class ObservationLogFilter(FilterSet):
399406
),
400407
("severity", "severity"),
401408
("status", "status"),
409+
("priority", "priority"),
402410
("comment", "comment"),
403411
("created", "created"),
404412
("assessment_status", "assessment_status"),
@@ -514,6 +522,8 @@ class ComponentFilter(FilterSet):
514522
field_name="product__product_group",
515523
queryset=Product.objects.filter(is_product_group=True),
516524
)
525+
branch_name = CharFilter(field_name="branch__name", lookup_expr="icontains")
526+
origin_service_name = CharFilter(field_name="origin_service__name", lookup_expr="icontains")
517527

518528
ordering = ExtendedOrderingFilter(
519529
# tuple-mapping retains order

backend/application/core/api/serializers_product.py

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ def validate(self, attrs: dict) -> dict:
103103

104104

105105
class ProductGroupSerializer(ProductCoreSerializer):
106-
open_critical_observation_count = IntegerField(read_only=True)
107-
open_high_observation_count = IntegerField(read_only=True)
108-
open_medium_observation_count = IntegerField(read_only=True)
109-
open_low_observation_count = IntegerField(read_only=True)
110-
open_none_observation_count = IntegerField(read_only=True)
111-
open_unknown_observation_count = IntegerField(read_only=True)
106+
active_critical_observation_count = IntegerField(read_only=True)
107+
active_high_observation_count = IntegerField(read_only=True)
108+
active_medium_observation_count = IntegerField(read_only=True)
109+
active_low_observation_count = IntegerField(read_only=True)
110+
active_none_observation_count = IntegerField(read_only=True)
111+
active_unknown_observation_count = IntegerField(read_only=True)
112112
forbidden_licenses_count = IntegerField(read_only=True)
113113
review_required_licenses_count = IntegerField(read_only=True)
114114
unknown_licenses_count = IntegerField(read_only=True)
@@ -135,12 +135,12 @@ class Meta:
135135
"description",
136136
"products_count",
137137
"permissions",
138-
"open_critical_observation_count",
139-
"open_high_observation_count",
140-
"open_medium_observation_count",
141-
"open_low_observation_count",
142-
"open_none_observation_count",
143-
"open_unknown_observation_count",
138+
"active_critical_observation_count",
139+
"active_high_observation_count",
140+
"active_medium_observation_count",
141+
"active_low_observation_count",
142+
"active_none_observation_count",
143+
"active_unknown_observation_count",
144144
"repository_branch_housekeeping_active",
145145
"repository_branch_housekeeping_keep_inactive_days",
146146
"repository_branch_housekeeping_exempt_branches",
@@ -189,12 +189,12 @@ class Meta:
189189

190190

191191
class ProductListSerializer(ProductCoreSerializer):
192-
open_critical_observation_count = IntegerField(read_only=True)
193-
open_high_observation_count = IntegerField(read_only=True)
194-
open_medium_observation_count = IntegerField(read_only=True)
195-
open_low_observation_count = IntegerField(read_only=True)
196-
open_none_observation_count = IntegerField(read_only=True)
197-
open_unknown_observation_count = IntegerField(read_only=True)
192+
active_critical_observation_count = IntegerField(read_only=True)
193+
active_high_observation_count = IntegerField(read_only=True)
194+
active_medium_observation_count = IntegerField(read_only=True)
195+
active_low_observation_count = IntegerField(read_only=True)
196+
active_none_observation_count = IntegerField(read_only=True)
197+
active_unknown_observation_count = IntegerField(read_only=True)
198198
forbidden_licenses_count = IntegerField(read_only=True)
199199
review_required_licenses_count = IntegerField(read_only=True)
200200
unknown_licenses_count = IntegerField(read_only=True)
@@ -533,12 +533,12 @@ class ProductApiTokenSerializer(Serializer):
533533

534534
class BranchSerializer(ModelSerializer):
535535
name_with_product = SerializerMethodField()
536-
open_critical_observation_count = IntegerField(read_only=True)
537-
open_high_observation_count = IntegerField(read_only=True)
538-
open_medium_observation_count = IntegerField(read_only=True)
539-
open_low_observation_count = IntegerField(read_only=True)
540-
open_none_observation_count = IntegerField(read_only=True)
541-
open_unknown_observation_count = IntegerField(read_only=True)
536+
active_critical_observation_count = IntegerField(read_only=True)
537+
active_high_observation_count = IntegerField(read_only=True)
538+
active_medium_observation_count = IntegerField(read_only=True)
539+
active_low_observation_count = IntegerField(read_only=True)
540+
active_none_observation_count = IntegerField(read_only=True)
541+
active_unknown_observation_count = IntegerField(read_only=True)
542542
forbidden_licenses_count = IntegerField(read_only=True)
543543
review_required_licenses_count = IntegerField(read_only=True)
544544
unknown_licenses_count = IntegerField(read_only=True)
@@ -578,12 +578,12 @@ def get_name_with_product(self, obj: Branch) -> str:
578578

579579
class ServiceSerializer(ModelSerializer):
580580
name_with_product = SerializerMethodField()
581-
open_critical_observation_count = IntegerField(read_only=True)
582-
open_high_observation_count = IntegerField(read_only=True)
583-
open_medium_observation_count = IntegerField(read_only=True)
584-
open_low_observation_count = IntegerField(read_only=True)
585-
open_none_observation_count = IntegerField(read_only=True)
586-
open_unknown_observation_count = IntegerField(read_only=True)
581+
active_critical_observation_count = IntegerField(read_only=True)
582+
active_high_observation_count = IntegerField(read_only=True)
583+
active_medium_observation_count = IntegerField(read_only=True)
584+
active_low_observation_count = IntegerField(read_only=True)
585+
active_none_observation_count = IntegerField(read_only=True)
586+
active_unknown_observation_count = IntegerField(read_only=True)
587587
forbidden_licenses_count = IntegerField(read_only=True)
588588
review_required_licenses_count = IntegerField(read_only=True)
589589
unknown_licenses_count = IntegerField(read_only=True)

backend/application/core/api/views.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,12 @@ def get_serializer_class(self) -> type[BaseSerializer[Any]]:
225225
def export_observations_excel(self, request: Request, pk: int) -> HttpResponse:
226226
product = self.__get_product(pk)
227227

228-
status = self.request.query_params.get("status")
229-
if status and (status, status) not in Status.STATUS_CHOICES:
230-
raise ValidationError(f"Status {status} is not a valid choice")
228+
statuses = self.request.query_params.getlist("status")
229+
for status in statuses:
230+
if status and (status, status) not in Status.STATUS_CHOICES:
231+
raise ValidationError(f"Status {status} is not a valid choice")
231232

232-
workbook = export_observations_excel(product, status)
233+
workbook = export_observations_excel(product, statuses)
233234

234235
with NamedTemporaryFile() as tmp:
235236
workbook.save(tmp.name) # nosemgrep: python.lang.correctness.tempfile.flush.tempfile-without-flush
@@ -256,14 +257,15 @@ def export_observations_excel(self, request: Request, pk: int) -> HttpResponse:
256257
def export_observations_csv(self, request: Request, pk: int) -> HttpResponse:
257258
product = self.__get_product(pk)
258259

259-
status = self.request.query_params.get("status")
260-
if status and (status, status) not in Status.STATUS_CHOICES:
261-
raise ValidationError(f"Status {status} is not a valid choice")
260+
statuses = self.request.query_params.getlist("status")
261+
for status in statuses:
262+
if status and (status, status) not in Status.STATUS_CHOICES:
263+
raise ValidationError(f"Status {status} is not a valid choice")
262264

263265
response = HttpResponse(content_type="text/csv")
264266
response["Content-Disposition"] = "attachment; filename=observations.csv"
265267

266-
export_observations_csv(response, product, status)
268+
export_observations_csv(response, product, statuses)
267269

268270
return response
269271

@@ -545,6 +547,7 @@ def get_queryset(self) -> QuerySet[Observation]:
545547
.select_related("product__product_group")
546548
.select_related("branch")
547549
.select_related("parser")
550+
.select_related("origin_service")
548551
)
549552

550553
def filter_queryset(self, queryset: QuerySet) -> QuerySet:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from django.db import migrations
2+
3+
# The component view has to be created after all other migrations. Otherwise some alterations of
4+
# observation lead to errors, due to https://www.sqlite.org/lang_altertable.html#caution.
5+
# It will be created before the first query runs.
6+
7+
DROP_SQL = "DROP VIEW IF EXISTS core_component;"
8+
9+
10+
class Migration(migrations.Migration):
11+
dependencies = [
12+
("core", "0076_alter_product_notification_ms_teams_webhook_and_more"),
13+
]
14+
15+
operations = [
16+
migrations.RunSQL(
17+
sql=DROP_SQL,
18+
reverse_sql=DROP_SQL,
19+
),
20+
]
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Generated by Django 5.2.11 on 2026-02-11 18:54
2+
3+
import django.core.validators
4+
import django.db.models.deletion
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("core", "0077_drop_component_view"),
12+
("import_observations", "0016_api_configuration_migrate_names"),
13+
("rules", "0018_rule_rego_module_rule_type"),
14+
("vex", "0010_cyclonedx_cyclonedx_branch_cyclonedx_vulnerability"),
15+
]
16+
17+
operations = [
18+
migrations.AddField(
19+
model_name="observation",
20+
name="assessment_priority",
21+
field=models.IntegerField(
22+
null=True,
23+
validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)],
24+
),
25+
),
26+
migrations.AddField(
27+
model_name="observation",
28+
name="current_priority",
29+
field=models.IntegerField(
30+
null=True,
31+
validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)],
32+
),
33+
),
34+
migrations.AddField(
35+
model_name="observation",
36+
name="general_rule_rego",
37+
field=models.ForeignKey(
38+
blank=True,
39+
null=True,
40+
on_delete=django.db.models.deletion.PROTECT,
41+
related_name="general_rules_rego",
42+
to="rules.rule",
43+
),
44+
),
45+
migrations.AddField(
46+
model_name="observation",
47+
name="product_rule_rego",
48+
field=models.ForeignKey(
49+
blank=True,
50+
null=True,
51+
on_delete=django.db.models.deletion.PROTECT,
52+
related_name="product_rules_rego",
53+
to="rules.rule",
54+
),
55+
),
56+
migrations.AddField(
57+
model_name="observation",
58+
name="rule_priority",
59+
field=models.IntegerField(
60+
null=True,
61+
validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)],
62+
),
63+
),
64+
migrations.AddField(
65+
model_name="observation",
66+
name="rule_rego_priority",
67+
field=models.IntegerField(
68+
null=True,
69+
validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)],
70+
),
71+
),
72+
migrations.AddField(
73+
model_name="observation",
74+
name="rule_rego_severity",
75+
field=models.CharField(
76+
blank=True,
77+
choices=[
78+
("Unknown", "Unknown"),
79+
("None", "None"),
80+
("Low", "Low"),
81+
("Medium", "Medium"),
82+
("High", "High"),
83+
("Critical", "Critical"),
84+
],
85+
max_length=12,
86+
),
87+
),
88+
migrations.AddField(
89+
model_name="observation",
90+
name="rule_rego_status",
91+
field=models.CharField(
92+
blank=True,
93+
choices=[
94+
("Open", "Open"),
95+
("Resolved", "Resolved"),
96+
("Duplicate", "Duplicate"),
97+
("False positive", "False positive"),
98+
("In review", "In review"),
99+
("Not affected", "Not affected"),
100+
("Not security", "Not security"),
101+
("Risk accepted", "Risk accepted"),
102+
],
103+
max_length=16,
104+
),
105+
),
106+
migrations.AddField(
107+
model_name="observation",
108+
name="rule_rego_vex_justification",
109+
field=models.CharField(
110+
blank=True,
111+
choices=[
112+
("component_not_present", "component_not_present"),
113+
("vulnerable_code_not_present", "vulnerable_code_not_present"),
114+
(
115+
"vulnerable_code_cannot_be_controlled_by_adversary",
116+
"vulnerable_code_cannot_be_controlled_by_adversary",
117+
),
118+
("vulnerable_code_not_in_execute_path", "vulnerable_code_not_in_execute_path"),
119+
("inline_mitigations_already_exist", "inline_mitigations_already_exist"),
120+
("code_not_present", "code_not_present"),
121+
("code_not_reachable", "code_not_reachable"),
122+
("requires_configuration", "requires_configuration"),
123+
("requires_dependency", "requires_dependency"),
124+
("requires_environment", "requires_environment"),
125+
("protected_by_compiler", "protected_by_compiler"),
126+
("protected_at_runtime", "protected_at_runtime"),
127+
("protected_at_perimeter", "protected_at_perimeter"),
128+
("protected_by_mitigating_control", "protected_by_mitigating_control"),
129+
],
130+
max_length=64,
131+
),
132+
),
133+
migrations.AddField(
134+
model_name="observation_log",
135+
name="priority",
136+
field=models.IntegerField(
137+
null=True,
138+
validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)],
139+
),
140+
),
141+
migrations.AddIndex(
142+
model_name="observation",
143+
index=models.Index(fields=["current_priority"], name="core_observ_current_ba21e4_idx"),
144+
),
145+
]

0 commit comments

Comments
 (0)