Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/audit/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@
"Phased rollout created for feature: %s by release pipeline: %s (stage: %s)"
)
PHASED_ROLLOUT_STATE_UPDATED_MESSAGE = "Phased rollout split changed from '%s%%' to '%s%%' for feature '%s' by release pipeline '%s' (stage: '%s')"
PROJECT_CREATED_MESSAGE = "New Project created: %s"
PROJECT_DELETED_MESSAGE = "Project deleted: %s"
1 change: 1 addition & 0 deletions api/audit/related_object_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ class RelatedObjectType(enum.Enum):
WAREHOUSE_CONNECTION = "Warehouse connection"
EXPERIMENT = "Experiment"
METRIC = "Metric"
PROJECT = "project"
34 changes: 34 additions & 0 deletions api/projects/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import typing

from common.projects.permissions import (
TAG_SUPPORTED_PERMISSIONS,
VIEW_PROJECT,
)
from django.conf import settings
from django.db import transaction
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema
from rest_framework import status, viewsets
Expand All @@ -16,6 +19,9 @@
from rest_framework.request import Request
from rest_framework.response import Response

from audit.constants import PROJECT_CREATED_MESSAGE, PROJECT_DELETED_MESSAGE
from audit.models import AuditLog
from audit.related_object_type import RelatedObjectType
from environments.dynamodb.migrator import IdentityMigrator
from environments.identities.models import Identity
from environments.serializers import EnvironmentSerializerLight
Expand Down Expand Up @@ -106,10 +112,38 @@ def get_queryset(self): # type: ignore[no-untyped-def]

def perform_create(self, serializer): # type: ignore[no-untyped-def]
project = serializer.save()
is_master_api_key_user = getattr(
self.request.user, "is_master_api_key_user", False
)
if getattr(self.request.user, "is_master_api_key_user", False) is False:
UserProjectPermission.objects.create( # type: ignore[misc]
user=self.request.user, project=project, admin=True
)
AuditLog.objects.create(
project=project,
author=None if is_master_api_key_user else self.request.user,
master_api_key=self.request.user.key if is_master_api_key_user else None, # type: ignore[union-attr]
related_object_id=project.id,
related_object_type=RelatedObjectType.PROJECT.name,
log=PROJECT_CREATED_MESSAGE % project.name,
)

def perform_destroy(self, instance: typing.Any) -> None:
with transaction.atomic():
is_master_api_key_user = getattr(
self.request.user, "is_master_api_key_user", False
)
AuditLog.objects.create(
project=None,
author=None if is_master_api_key_user else self.request.user,
master_api_key=self.request.user.key
if is_master_api_key_user
else None, # type: ignore[union-attr]
related_object_id=instance.id,
related_object_type=RelatedObjectType.PROJECT.name,
log=PROJECT_DELETED_MESSAGE % instance.name,
)
instance.delete()

@action(
detail=False,
Expand Down
48 changes: 48 additions & 0 deletions api/tests/unit/projects/test_unit_projects_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from rest_framework.test import APIClient
from task_processor.task_run_method import TaskRunMethod

from audit.constants import PROJECT_CREATED_MESSAGE, PROJECT_DELETED_MESSAGE
from audit.models import AuditLog
from audit.related_object_type import RelatedObjectType
from environments.dynamodb.types import ProjectIdentityMigrationStatus
from environments.identities.models import Identity
from features.models import Feature, FeatureSegment
Expand Down Expand Up @@ -1069,3 +1072,48 @@ def test_list_projects__default_enforce_feature_owners__returns_false(
assert len(response.json()) > 0
assert "enforce_feature_owners" in response.json()[0]
assert response.json()[0]["enforce_feature_owners"] is False


def test_create_project__valid_request__creates_audit_log(
admin_client, organisation
) -> None:
# Given
url = reverse("api-v1:projects:project-list")
project_name = "New Audit Log Project"
data = {"name": project_name, "organisation": organisation.id}
initial_audit_log_count = AuditLog.objects.count()

# When
response = admin_client.post(url, data=data)

# Then
assert response.status_code == status.HTTP_201_CREATED
assert AuditLog.objects.count() == initial_audit_log_count + 1

# Verify the audit log details
audit_log = AuditLog.objects.order_by("-created_date").first()
assert audit_log.related_object_type == RelatedObjectType.PROJECT.name
assert audit_log.log == PROJECT_CREATED_MESSAGE % project_name
assert audit_log.project_id == response.data["id"]


def test_delete_project__valid_request__creates_audit_log(
admin_client, project, organisation
) -> None:
# Given
url = reverse("api-v1:projects:project-detail", args=[project.id])
project_name = project.name
initial_audit_log_count = AuditLog.objects.count()

# When
response = admin_client.delete(url)

# Then
assert response.status_code == status.HTTP_204_NO_CONTENT
assert AuditLog.objects.count() == initial_audit_log_count + 1

# Verify the audit log details
audit_log = AuditLog.objects.order_by("-created_date").first()
assert audit_log.related_object_type == RelatedObjectType.PROJECT.name
assert audit_log.log == PROJECT_DELETED_MESSAGE % project_name
Comment thread
srijantrpth marked this conversation as resolved.
assert audit_log.related_object_id == project.id
Loading