Skip to content

Commit 2ec1c2c

Browse files
authored
feat(tempest): Endpoint for deleting tempest credentials (#82225)
Endpoint for deletion of tempest credentials, only allowed to org admins. Audit log entries will be done as a part of a separate ticket to get to the PoC as soon as possible Part of getsentry/team-gdx#52 --------- Signed-off-by: Vjeran Grozdanic <[email protected]>
1 parent 62e12db commit 2ec1c2c

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

src/sentry/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@
314314
SentryInternalAppTokensEndpoint,
315315
)
316316
from sentry.tempest.endpoints.tempest_credentials import TempestCredentialsEndpoint
317+
from sentry.tempest.endpoints.tempest_credentials_details import TempestCredentialsDetailsEndpoint
317318
from sentry.uptime.endpoints.project_uptime_alert_details import ProjectUptimeAlertDetailsEndpoint
318319
from sentry.uptime.endpoints.project_uptime_alert_index import ProjectUptimeAlertIndexEndpoint
319320
from sentry.users.api.endpoints.authenticator_index import AuthenticatorIndexEndpoint
@@ -2794,6 +2795,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
27942795
TempestCredentialsEndpoint.as_view(),
27952796
name="sentry-api-0-project-tempest-credentials",
27962797
),
2798+
re_path(
2799+
r"^(?P<organization_id_or_slug>[^\/]+)/(?P<project_id_or_slug>[^\/]+)/tempest-credentials/(?P<tempest_credentials_id>\d+)/$",
2800+
TempestCredentialsDetailsEndpoint.as_view(),
2801+
name="sentry-api-0-project-tempest-credentials-details",
2802+
),
27972803
*workflow_urls.urlpatterns,
27982804
]
27992805

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from rest_framework.exceptions import NotFound
2+
from rest_framework.request import Request
3+
from rest_framework.response import Response
4+
5+
from sentry import features
6+
from sentry.api.api_owners import ApiOwner
7+
from sentry.api.api_publish_status import ApiPublishStatus
8+
from sentry.api.base import region_silo_endpoint
9+
from sentry.api.bases import ProjectEndpoint
10+
from sentry.models.project import Project
11+
from sentry.tempest.models import TempestCredentials
12+
from sentry.tempest.permissions import TempestCredentialsPermission
13+
14+
15+
@region_silo_endpoint
16+
class TempestCredentialsDetailsEndpoint(ProjectEndpoint):
17+
publish_status = {
18+
"DELETE": ApiPublishStatus.PRIVATE,
19+
}
20+
owner = ApiOwner.GDX
21+
22+
permission_classes = (TempestCredentialsPermission,)
23+
24+
def has_feature(self, request: Request, project: Project) -> bool:
25+
return features.has(
26+
"organizations:tempest-access", project.organization, actor=request.user
27+
)
28+
29+
def delete(self, request: Request, project: Project, tempest_credentials_id: int) -> Response:
30+
if not self.has_feature(request, project):
31+
raise NotFound
32+
33+
TempestCredentials.objects.filter(project=project, id=tempest_credentials_id).delete()
34+
return Response(status=204)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from sentry.tempest.models import TempestCredentials
2+
from sentry.testutils.cases import APITestCase
3+
from sentry.testutils.helpers.features import Feature
4+
5+
6+
class TestTempestCredentialsDetails(APITestCase):
7+
endpoint = "sentry-api-0-project-tempest-credentials-details"
8+
9+
def setUp(self):
10+
super().setUp()
11+
self.tempest_credentials = self.create_tempest_credentials(self.project)
12+
13+
def test_cant_access_endpoint_if_feature_flag_is_disabled(self):
14+
self.login_as(self.user)
15+
response = self.get_response(
16+
self.project.organization.slug,
17+
self.project.slug,
18+
self.tempest_credentials.id,
19+
method="DELETE",
20+
)
21+
assert response.status_code == 404
22+
23+
def test_cant_access_endpoint_if_user_is_not_authenticated(self):
24+
response = self.get_response(
25+
self.project.organization.slug,
26+
self.project.slug,
27+
self.tempest_credentials.id,
28+
method="DELETE",
29+
)
30+
assert response.status_code == 401
31+
32+
def test_delete_tempest_credentials_as_org_admin(self):
33+
with Feature({"organizations:tempest-access": True}):
34+
self.login_as(self.user)
35+
response = self.get_response(
36+
self.project.organization.slug,
37+
self.project.slug,
38+
self.tempest_credentials.id,
39+
method="DELETE",
40+
)
41+
42+
assert response.status_code == 204
43+
assert not TempestCredentials.objects.filter(id=self.tempest_credentials.id).exists()
44+
45+
def test_non_admin_cant_delete_credentials(self):
46+
non_admin_user = self.create_user()
47+
self.create_member(
48+
user=non_admin_user, organization=self.project.organization, role="member"
49+
)
50+
with Feature({"organizations:tempest-access": True}):
51+
self.login_as(non_admin_user)
52+
response = self.get_response(
53+
self.project.organization.slug,
54+
self.project.slug,
55+
self.tempest_credentials.id,
56+
method="DELETE",
57+
)
58+
59+
assert response.status_code == 403
60+
assert TempestCredentials.objects.filter(id=self.tempest_credentials.id).exists()

0 commit comments

Comments
 (0)