Skip to content

Commit

Permalink
Merge pull request #1043 from MaibornWolff/dev
Browse files Browse the repository at this point in the history
chore: merge to main for release 1.6.0
  • Loading branch information
StefanFl authored Feb 4, 2024
2 parents 3cee44e + 7788c1e commit ddca849
Show file tree
Hide file tree
Showing 145 changed files with 3,440 additions and 3,125 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- main
- chore/doc-about
- fix/doc_api_base_url

permissions:
contents: write
Expand Down
19 changes: 19 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion backend/application/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.5.0"
__version__ = "1.6.0"
21 changes: 21 additions & 0 deletions backend/application/commons/api/permissions.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions backend/application/commons/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"]
Expand Down
16 changes: 16 additions & 0 deletions backend/application/commons/services/notification.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
8 changes: 5 additions & 3 deletions backend/application/core/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
32 changes: 22 additions & 10 deletions backend/application/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 (
Expand All @@ -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"
Expand All @@ -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(
Expand Down Expand Up @@ -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()
Expand All @@ -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)

Expand Down Expand Up @@ -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",
Expand All @@ -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"):
Expand Down Expand Up @@ -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",
Expand All @@ -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)


Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions backend/application/core/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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,
),
),
]
Loading

0 comments on commit ddca849

Please sign in to comment.