Skip to content

Commit

Permalink
feat: manage settings in SecObserve UI (#1413)
Browse files Browse the repository at this point in the history
* feat: manage settings in SecObserve UI

* chore: unittests and code quality

* chore: code quality
  • Loading branch information
StefanFl authored Apr 20, 2024
1 parent 5c67c17 commit f8404d9
Show file tree
Hide file tree
Showing 75 changed files with 1,542 additions and 423 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@
from typing import Optional

import jwt
from constance import config
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import AuthenticationFailed

from application.access_control.models import User
from application.access_control.queries.user import get_user_by_username
from application.access_control.services.jwt_secret import get_secret
from application.commons.models import Settings

ALGORITHM = "HS256"
JWT_PREFIX = "JWT"


def create_jwt(user: User) -> str:
settings = Settings.load()
if user.is_superuser:
jwt_validity_duration = config.JWT_VALIDITY_DURATION_SUPERUSER
jwt_validity_duration = settings.jwt_validity_duration_superuser
else:
jwt_validity_duration = config.JWT_VALIDITY_DURATION_USER
jwt_validity_duration = settings.jwt_validity_duration_user

payload = {
"exp": datetime.now(tz=timezone.utc) + timedelta(hours=jwt_validity_duration),
Expand Down
20 changes: 20 additions & 0 deletions backend/application/commons/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import TYPE_CHECKING

from django.utils.functional import LazyObject


class LazyConfig(LazyObject):
def _setup(self):
from application.commons.models import ( # pylint: disable=import-outside-toplevel
Settings,
)

# Can't be imported because module wouldn't be ready

self._wrapped = Settings.load()


if TYPE_CHECKING:
from application.commons.models import Settings
settings_static: "Settings" = LazyConfig() # type: ignore [assignment]
# LazyObject wraps the Settings model and returns the instance of the model when accessed.
8 changes: 8 additions & 0 deletions backend/application/commons/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ def has_object_permission(self, request, view, obj):
return True

return False


class UserHasSuperuserPermission(BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.is_superuser

def has_permission(self, request, view):
return request.user.is_superuser
17 changes: 15 additions & 2 deletions backend/application/commons/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,31 @@
SerializerMethodField,
)

from application.commons.models import Notification
from application.commons.models import Notification, Settings
from application.commons.services.global_request import get_current_user


class VersionSerializer(Serializer):
version = CharField(max_length=200)


class SettingsSerializer(Serializer):
class StatusSettingsSerializer(Serializer):
features = ListField(child=CharField(), min_length=0, max_length=200, required=True)


class SettingsSerializer(ModelSerializer):
id = SerializerMethodField()

class Meta:
model = Settings
fields = "__all__"

def get_id(self, obj: Settings): # pylint: disable=unused-argument
# obj is needed for the signature but we don't need it
# The id is hardcoded to 1 because there is only one instance of the Settings model
return 1


class NotificationSerializer(ModelSerializer):
message = SerializerMethodField()
product_name = SerializerMethodField()
Expand Down
41 changes: 35 additions & 6 deletions backend/application/commons/api/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from constance import config
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
Expand All @@ -12,14 +11,18 @@
from rest_framework.viewsets import GenericViewSet

from application.commons.api.filters import NotificationFilter
from application.commons.api.permissions import UserHasNotificationPermission
from application.commons.api.permissions import (
UserHasNotificationPermission,
UserHasSuperuserPermission,
)
from application.commons.api.serializers import (
NotificationBulkSerializer,
NotificationSerializer,
SettingsSerializer,
StatusSettingsSerializer,
VersionSerializer,
)
from application.commons.models import Notification
from application.commons.models import Notification, Settings
from application.commons.queries.notification import get_notifications
from application.commons.services.notification import bulk_delete

Expand Down Expand Up @@ -48,18 +51,44 @@ def get(self, request):
return response


class SettingsView(APIView):
serializer_class = SettingsSerializer
class StatusSettingsView(APIView):
serializer_class = StatusSettingsSerializer

@action(detail=True, methods=["get"], url_name="settings")
def get(self, request):
features = []
if config.FEATURE_VEX:
settings = Settings.load()
if settings.feature_vex:
features.append("feature_vex")
content = {"features": features}
return Response(content)


class SettingsView(APIView):
serializer_class = SettingsSerializer
permission_classes = (IsAuthenticated, UserHasSuperuserPermission)

@action(detail=True, methods=["get"], url_name="settings")
def get(self, request, pk=None): # pylint: disable=unused-argument
# pk is needed for the API signature but we don't need it
settings = Settings.load()
response_serializer = SettingsSerializer(settings)
return Response(response_serializer.data)

@action(detail=True, methods=["patch"], url_name="settings")
def patch(self, request, pk=None): # pylint: disable=unused-argument
# pk is needed for the API signature but we don't need it
request_serializer = SettingsSerializer(data=request.data)
if not request_serializer.is_valid():
raise ValidationError(request_serializer.errors)

settings = request_serializer.create(request_serializer.validated_data)
settings.save()

response_serializer = SettingsSerializer(settings)
return Response(response_serializer.data)


class NotificationViewSet(
GenericViewSet, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
):
Expand Down
Loading

0 comments on commit f8404d9

Please sign in to comment.