Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tempest): add audit log events for tempest client ids #83091

17 changes: 17 additions & 0 deletions src/sentry/audit_log/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,20 @@
api_name="data-secrecy.reinstated",
)
)

default_manager.add(
AuditLogEvent(
event_id=1152,
name="TEMPEST_CLIENT_ID_ADD",
api_name="playstation-client-id.create",
template="added playstation client id {client_id}",
)
)
default_manager.add(
AuditLogEvent(
event_id=1153,
name="TEMPEST_CLIENT_ID_REMOVE",
api_name="playstation-client-id.remove",
template="removed playstation client id {client_id}",
)
)
13 changes: 11 additions & 2 deletions src/sentry/tempest/endpoints/tempest_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import features
from sentry import audit_log, features
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
Expand Down Expand Up @@ -50,9 +50,18 @@ def post(self, request: Request, project: Project) -> Response:
serializer = DRFTempestCredentialsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
serializer.save(created_by_id=request.user.id, project=project)
credentials = serializer.save(created_by_id=request.user.id, project=project)
except IntegrityError:
return Response(
{"detail": "A credential with this client ID already exists."}, status=400
)

self.create_audit_entry(
request,
organization=project.organization,
target_object=credentials.id,
event=audit_log.get_event_id("TEMPEST_CLIENT_ID_ADD"),
data=credentials.get_audit_log_data(),
)

return Response(serializer.data, status=201)
19 changes: 17 additions & 2 deletions src/sentry/tempest/endpoints/tempest_credentials_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import features
from sentry import audit_log, features
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
Expand Down Expand Up @@ -30,5 +30,20 @@ def delete(self, request: Request, project: Project, tempest_credentials_id: int
if not self.has_feature(request, project):
raise NotFound

TempestCredentials.objects.filter(project=project, id=tempest_credentials_id).delete()
try:
credentials = TempestCredentials.objects.get(project=project, id=tempest_credentials_id)
except TempestCredentials.DoesNotExist:
raise NotFound

data = credentials.get_audit_log_data()
credentials.delete()

self.create_audit_entry(
request,
organization=project.organization,
target_object=credentials.id,
event=audit_log.get_event_id("TEMPEST_CLIENT_ID_REMOVE"),
data=data,
)

return Response(status=204)
6 changes: 6 additions & 0 deletions src/sentry/tempest/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ class Meta:
name="sentry_tempestcredentials_client_project_uniq",
)
]

def get_audit_log_data(self) -> dict:
return {
"project_id": self.project.id,
"client_id": self.client_id,
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

from sentry.tempest.models import TempestCredentials
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.features import Feature
Expand Down Expand Up @@ -38,7 +40,10 @@ def test_client_secret_is_obfuscated(self):
def test_unauthenticated_user_cant_access_endpoint(self):
self.get_error_response(self.project.organization.slug, self.project.slug)

def test_create_tempest_credentials(self):
@patch(
"sentry.tempest.endpoints.tempest_credentials.TempestCredentialsEndpoint.create_audit_entry"
)
def test_create_tempest_credentials(self, create_audit_entry):
with Feature({"organizations:tempest-access": True}):
self.login_as(self.user)
response = self.get_success_response(
Expand All @@ -54,6 +59,8 @@ def test_create_tempest_credentials(self):
assert creds_obj.project == self.project
assert creds_obj.created_by_id == self.user.id

create_audit_entry.assert_called()

def test_create_tempest_credentials_without_feature_flag(self):
self.login_as(self.user)
response = self.get_error_response(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

from sentry.tempest.models import TempestCredentials
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.features import Feature
Expand Down Expand Up @@ -29,7 +31,10 @@ def test_cant_access_endpoint_if_user_is_not_authenticated(self):
)
assert response.status_code == 401

def test_delete_tempest_credentials_as_org_admin(self):
@patch(
"sentry.tempest.endpoints.tempest_credentials_details.TempestCredentialsDetailsEndpoint.create_audit_entry"
)
def test_delete_tempest_credentials_as_org_admin(self, create_audit_entry):
with Feature({"organizations:tempest-access": True}):
self.login_as(self.user)
response = self.get_response(
Expand All @@ -41,6 +46,7 @@ def test_delete_tempest_credentials_as_org_admin(self):

assert response.status_code == 204
assert not TempestCredentials.objects.filter(id=self.tempest_credentials.id).exists()
create_audit_entry.assert_called()

def test_non_admin_cant_delete_credentials(self):
non_admin_user = self.create_user()
Expand Down
Loading