Skip to content

Commit ec968f1

Browse files
committed
Merge branch 'dev' of https://github.com/MaibornWolff/SecObserve into stackable
2 parents 669b5ee + c473bcd commit ec968f1

File tree

36 files changed

+632
-146
lines changed

36 files changed

+632
-146
lines changed

.github/workflows/build_push_dev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
-
3838
name: Build and push backend
3939
id: build-and-push-backend
40-
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
40+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
4141
with:
4242
context: .
4343
file: ./docker/backend/prod/django/Dockerfile
@@ -78,7 +78,7 @@ jobs:
7878
-
7979
name: Build and push frontend
8080
id: build-and-push-frontend
81-
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
81+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
8282
with:
8383
context: .
8484
file: ./docker/frontend/prod/Dockerfile

.github/workflows/build_push_release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
run: echo "CREATED=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
3737
-
3838
name: Build and push backend
39-
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
39+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
4040
with:
4141
context: .
4242
file: ./docker/backend/prod/django/Dockerfile
@@ -74,7 +74,7 @@ jobs:
7474
run: echo "CREATED=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
7575
-
7676
name: Build and push frontend
77-
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
77+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
7878
with:
7979
context: .
8080
file: ./docker/frontend/prod/Dockerfile

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
persist-credentials: false
3838

3939
- name: "Run analysis"
40-
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
40+
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
4141
with:
4242
results_file: results.sarif
4343
results_format: sarif

backend/application/access_control/api/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ def validate(self, attrs: dict) -> dict:
232232
raise ValidationError("Authorization group and user cannot be changed")
233233

234234
if self.instance is None:
235+
if data_authorization_group is None:
236+
raise ValidationError("Authorization group is required")
237+
if data_user is None:
238+
raise ValidationError("User is required")
239+
235240
authorization_group_member = get_authorization_group_member(data_authorization_group, data_user)
236241
if authorization_group_member:
237242
raise ValidationError(

backend/application/access_control/api/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,9 @@ def password_rules(self, request: Request) -> Response:
211211
class PasswordRules:
212212
password_rules: str
213213

214-
password_rules_text = password_validators_help_texts(self._get_password_validators())
215-
password_rules = PasswordRules("- " + "\n- ".join(password_rules_text))
214+
password_rules_list = password_validators_help_texts(self._get_password_validators())
215+
password_rules_list = list(map(lambda s: s.replace("Your password", "The password"), password_rules_list))
216+
password_rules = PasswordRules("- " + "\n- ".join(password_rules_list))
216217
response_serializer = UserPasswortRulesSerializer(password_rules)
217218
return Response(response_serializer.data, status=status.HTTP_200_OK)
218219

backend/application/core/api/serializers_observation.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ObservationSerializer(ModelSerializer):
100100

101101
class Meta:
102102
model = Observation
103-
exclude = ["numerical_severity", "issue_tracker_jira_initial_status"]
103+
exclude = ["numerical_severity", "issue_tracker_jira_initial_status", "origin_source_file_link"]
104104

105105
def to_representation(self, instance: Observation) -> dict:
106106
response = super().to_representation(instance)
@@ -174,6 +174,7 @@ class Meta:
174174
"numerical_severity",
175175
"issue_tracker_jira_initial_status",
176176
"origin_component_dependencies",
177+
"origin_source_file_link",
177178
]
178179

179180
def get_branch_name(self, observation: Observation) -> str:
@@ -208,6 +209,9 @@ def get_cve_found_in(self, observation: Observation) -> list[dict[str, str]]:
208209
def _get_origin_source_file_url(observation: Observation) -> Optional[str]:
209210
origin_source_file_url = None
210211

212+
if observation.origin_source_file_link:
213+
return observation.origin_source_file_link
214+
211215
if observation.product.repository_prefix and observation.origin_source_file:
212216
if not validators.url(observation.product.repository_prefix):
213217
return None
@@ -567,7 +571,7 @@ class NestedObservationSerializer(ModelSerializer):
567571

568572
class Meta:
569573
model = Observation
570-
exclude = ["numerical_severity", "issue_tracker_jira_initial_status"]
574+
exclude = ["numerical_severity", "issue_tracker_jira_initial_status", "origin_source_file_link"]
571575

572576
def get_scanner_name(self, observation: Observation) -> str:
573577
return get_scanner_name(observation)

backend/application/core/api/serializers_product.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -460,21 +460,26 @@ def validate(self, attrs: dict) -> dict:
460460
data_product: Optional[Product] = attrs.get("product")
461461
data_user = attrs.get("user")
462462

463-
if self.instance is not None and (
464-
(data_product and data_product != self.instance.product) or (data_user and data_user != self.instance.user)
465-
):
466-
raise ValidationError("Product and user cannot be changed")
463+
current_user = get_current_user()
467464

468465
if self.instance is None:
466+
if data_product is None:
467+
raise ValidationError("Product must be set")
468+
if data_user is None:
469+
raise ValidationError("User must be set")
470+
469471
product_member = get_product_member(data_product, data_user)
470472
if product_member:
471473
raise ValidationError(f"Product member {data_product} / {data_user} already exists")
472474

473-
current_user = get_current_user()
474-
if self.instance is not None:
475-
highest_user_role = get_highest_user_role(self.instance.product, current_user)
476-
else:
477475
highest_user_role = get_highest_user_role(data_product, current_user)
476+
else:
477+
if (data_product and data_product != self.instance.product) or (
478+
data_user and data_user != self.instance.user
479+
):
480+
raise ValidationError("Product and user cannot be changed")
481+
482+
highest_user_role = get_highest_user_role(self.instance.product, current_user)
478483

479484
if highest_user_role != Roles.Owner and not (current_user and current_user.is_superuser):
480485
if attrs.get("role") == Roles.Owner:
@@ -498,26 +503,28 @@ def validate(self, attrs: dict) -> dict:
498503
data_product: Optional[Product] = attrs.get("product")
499504
data_authorization_group = attrs.get("authorization_group")
500505

501-
if self.instance is not None and (
502-
(data_product and data_product != self.instance.product)
503-
or (data_authorization_group and data_authorization_group != self.instance.authorization_group)
504-
):
505-
raise ValidationError("Product and authorization group cannot be changed")
506+
current_user = get_current_user()
506507

507508
if self.instance is None:
509+
if data_product is None:
510+
raise ValidationError("Product must be set")
511+
if data_authorization_group is None:
512+
raise ValidationError("Authorization group must be set")
513+
508514
product_authorization_group_member = get_product_authorization_group_member(
509515
data_product, data_authorization_group
510516
)
511517
if product_authorization_group_member:
512518
raise ValidationError(
513519
f"Product authorization group member {data_product} / {data_authorization_group} already exists"
514520
)
515-
516-
current_user = get_current_user()
517-
if self.instance is not None:
518-
highest_user_role = get_highest_user_role(self.instance.product, current_user)
519-
else:
520521
highest_user_role = get_highest_user_role(data_product, current_user)
522+
else:
523+
if (data_product and data_product != self.instance.product) or (
524+
data_authorization_group and data_authorization_group != self.instance.authorization_group
525+
):
526+
raise ValidationError("Product and authorization group cannot be changed")
527+
highest_user_role = get_highest_user_role(self.instance.product, current_user)
521528

522529
if highest_user_role != Roles.Owner and not (current_user and current_user.is_superuser):
523530
if attrs.get("role") == Roles.Owner:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.1 on 2025-05-26 20:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("core", "0062_alter_branch_osv_linux_distribution_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="observation",
15+
name="origin_source_file_link",
16+
field=models.CharField(blank=True, max_length=2048),
17+
),
18+
]

backend/application/core/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ class Observation(Model):
461461
origin_source_line_start = IntegerField(null=True, validators=[MinValueValidator(0), MaxValueValidator(999999)])
462462
origin_source_line_end = IntegerField(null=True, validators=[MinValueValidator(0), MaxValueValidator(999999)])
463463

464+
origin_source_file_link = CharField(max_length=2048, blank=True)
465+
464466
origin_cloud_provider = CharField(max_length=255, blank=True)
465467
origin_cloud_account_subscription_project = CharField(max_length=255, blank=True)
466468
origin_cloud_resource = CharField(max_length=255, blank=True)

backend/application/core/services/observation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def _get_string_to_hash(observation: Observation) -> str: # pylint: disable=too
4242
hash_string += str(observation.origin_source_line_start)
4343
if observation.origin_source_line_end:
4444
hash_string += str(observation.origin_source_line_end)
45+
if observation.origin_source_file_link:
46+
hash_string += observation.origin_source_file_link
4547

4648
if observation.origin_cloud_provider:
4749
hash_string += observation.origin_cloud_provider
@@ -178,6 +180,8 @@ def normalize_observation_fields(observation: Observation) -> None:
178180
observation.origin_service_name = ""
179181
if observation.origin_source_file is None:
180182
observation.origin_source_file = ""
183+
if observation.origin_source_file_link is None:
184+
observation.origin_source_file_link = ""
181185
if observation.scanner is None:
182186
observation.scanner = ""
183187
if observation.api_configuration_name is None:

backend/application/import_observations/parsers/gitleaks/__init__.py

Whitespace-only changes.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from json import dumps
2+
from typing import Any, Optional
3+
4+
from application.core.models import Branch, Observation, Product
5+
from application.core.types import Severity
6+
from application.import_observations.parsers.base_parser import (
7+
BaseFileParser,
8+
BaseParser,
9+
)
10+
from application.import_observations.types import Parser_Filetype, Parser_Type
11+
12+
13+
class GitleaksParser(BaseParser, BaseFileParser):
14+
@classmethod
15+
def get_name(cls) -> str:
16+
return "Gitleaks"
17+
18+
@classmethod
19+
def get_filetype(cls) -> str:
20+
return Parser_Filetype.FILETYPE_JSON
21+
22+
@classmethod
23+
def get_type(cls) -> str:
24+
return Parser_Type.TYPE_SECRETS
25+
26+
def check_format(self, data: Any) -> bool:
27+
if (
28+
isinstance(data, list) # pylint: disable=too-many-boolean-expressions
29+
and len(data) >= 1
30+
and isinstance(data[0], dict)
31+
and data[0].get("RuleID")
32+
and data[0].get("Match")
33+
and data[0].get("Secret")
34+
):
35+
return True
36+
return False
37+
38+
def get_observations(self, data: list, product: Product, branch: Optional[Branch]) -> tuple[list[Observation], str]:
39+
observations = []
40+
41+
for entry in data:
42+
rule_id = entry.get("RuleID")
43+
description = entry.get("Description")
44+
start_line = entry.get("StartLine")
45+
end_line = entry.get("EndLine")
46+
match = entry.get("Match")
47+
secret = entry.get("Secret")
48+
file = entry.get("File")
49+
link = entry.get("Link")
50+
commit = entry.get("Commit")
51+
date = entry.get("Date")
52+
message = entry.get("Message")
53+
54+
if match:
55+
if secret:
56+
match = match.replace(secret, "REDACTED")
57+
description += f"\n\n**Match:** `{match}`"
58+
59+
if commit:
60+
description += f"\n\n**Commit hash:** {commit}"
61+
if date:
62+
description += f"\n\n**Commit date:** {date}"
63+
if message:
64+
if message.find("\n") >= 0:
65+
message = message.split("\n")[0] + " ..."
66+
description += f"\n\n**Commit message:** {message}"
67+
68+
observation = Observation(
69+
title=rule_id,
70+
parser_severity=Severity.SEVERITY_MEDIUM,
71+
description=description,
72+
origin_source_file=file,
73+
origin_source_line_start=self.get_int_or_none(start_line),
74+
origin_source_line_end=self.get_int_or_none(end_line),
75+
origin_source_file_link=link,
76+
)
77+
78+
evidence = []
79+
evidence.append("Entry")
80+
evidence_string = dumps(entry)
81+
if secret:
82+
evidence_string = evidence_string.replace(secret, "REDACTED")
83+
evidence.append(evidence_string)
84+
85+
observation.unsaved_evidences.append(evidence)
86+
87+
observations.append(observation)
88+
89+
return observations, self.get_name()

backend/application/licenses/api/serializers.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ def validate(self, attrs: dict) -> dict:
274274
raise ValidationError("License group and authorization group cannot be changed")
275275

276276
if self.instance is None:
277+
if data_license_group is None:
278+
raise ValidationError("License group is required")
279+
if data_authorization_group is None:
280+
raise ValidationError("Authorization group is required")
281+
277282
license_group_authorization_group_member = get_license_group_authorization_group_member(
278283
data_license_group, data_authorization_group
279284
)
@@ -309,6 +314,11 @@ def validate(self, attrs: dict) -> dict:
309314
raise ValidationError("License group and user cannot be changed")
310315

311316
if self.instance is None:
317+
if data_license_group is None:
318+
raise ValidationError("License group is required")
319+
if data_user is None:
320+
raise ValidationError("User is required")
321+
312322
license_group_member = get_license_group_member(data_license_group, data_user)
313323
if license_group_member:
314324
raise ValidationError(f"License group member {data_license_group} / {data_user} already exists")
@@ -516,6 +526,11 @@ def validate(self, attrs: dict) -> dict:
516526
raise ValidationError("License policy and user cannot be changed")
517527

518528
if self.instance is None:
529+
if data_license_policy is None:
530+
raise ValidationError("License policy is required")
531+
if data_user is None:
532+
raise ValidationError("User is required")
533+
519534
license_group_member = get_license_policy_member(data_license_policy, data_user)
520535
if license_group_member:
521536
raise ValidationError(f"License policy member {data_license_policy} / {data_user} already exists")
@@ -546,6 +561,11 @@ def validate(self, attrs: dict) -> dict:
546561
raise ValidationError("License policy and authorization group cannot be changed")
547562

548563
if self.instance is None:
564+
if data_license_policy is None:
565+
raise ValidationError("License policy is required")
566+
if data_authorization_group is None:
567+
raise ValidationError("Authorization group is required")
568+
549569
license_policy_authorization_group_member = get_license_policy_authorization_group_member(
550570
data_license_policy, data_authorization_group
551571
)

0 commit comments

Comments
 (0)