Skip to content

Commit ecd0beb

Browse files
committed
Add CS views for creating and deleting service accounts
1 parent dd1db0a commit ecd0beb

File tree

4 files changed

+333
-0
lines changed

4 files changed

+333
-0
lines changed

django/thunderstore/api/cyberstorm/tests/test_team.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework.test import APIClient
66

77
from thunderstore.account.factories import ServiceAccountFactory
8+
from thunderstore.account.models.service_account import ServiceAccount
89
from thunderstore.core.types import UserType
910
from thunderstore.repository.factories import TeamFactory, TeamMemberFactory
1011
from thunderstore.repository.models.team import Team, TeamMember
@@ -533,6 +534,239 @@ def test_team_service_account_list_api_view__for_member__sorts_results(
533534
assert result[2]["name"] == charlie.first_name
534535

535536

537+
@pytest.mark.django_db
538+
def test_team_service_account_create__when_creating_a_service_account__succeeds(
539+
api_client: APIClient,
540+
team_owner: TeamMember,
541+
):
542+
api_client.force_authenticate(team_owner.user)
543+
544+
response = api_client.post(
545+
f"/api/cyberstorm/team/{team_owner.team.name}/service-account/create/",
546+
json.dumps(
547+
{
548+
"nickname": "CoolestTeamServiceAccountName",
549+
}
550+
),
551+
content_type="application/json",
552+
)
553+
554+
assert response.status_code == 200
555+
response_json = response.json()
556+
assert response_json["nickname"] == "CoolestTeamServiceAccountName"
557+
assert response_json["team_name"] == team_owner.team.name
558+
assert response_json["api_token"][:4] == "tss_"
559+
assert (
560+
ServiceAccount.objects.filter(
561+
owner__name=team_owner.team.name,
562+
user__first_name="CoolestTeamServiceAccountName",
563+
).count()
564+
== 1
565+
)
566+
567+
568+
@pytest.mark.django_db
569+
def test_team_service_account_create__when_creating_a_service_account__fails_because_nickname_too_long(
570+
api_client: APIClient,
571+
team_owner: TeamMember,
572+
):
573+
api_client.force_authenticate(team_owner.user)
574+
575+
response = api_client.post(
576+
f"/api/cyberstorm/team/{team_owner.team.name}/service-account/create/",
577+
json.dumps(
578+
{
579+
"nickname": "LongestCoolestTeamServiceAccountNameEver",
580+
}
581+
),
582+
content_type="application/json",
583+
)
584+
585+
assert response.status_code == 400
586+
response_json = response.json()
587+
assert response_json["nickname"] == [
588+
"Ensure this value has at most 32 characters (it has 40)."
589+
]
590+
assert (
591+
ServiceAccount.objects.filter(
592+
owner__name=team_owner.team.name,
593+
user__first_name="LongestCoolestTeamServiceAccountNameEver",
594+
).count()
595+
== 0
596+
)
597+
598+
599+
@pytest.mark.django_db
600+
def test_team_service_account_create__when_creating_a_service_account__fails_because_user_is_not_authenticated(
601+
api_client: APIClient,
602+
team: Team,
603+
):
604+
response = api_client.post(
605+
f"/api/cyberstorm/team/{team.name}/service-account/create/",
606+
json.dumps(
607+
{
608+
"nickname": "CoolestTeamServiceAccountName",
609+
}
610+
),
611+
content_type="application/json",
612+
)
613+
614+
assert response.status_code == 401
615+
response_json = response.json()
616+
assert response_json["detail"] == "Authentication credentials were not provided."
617+
assert (
618+
ServiceAccount.objects.filter(
619+
owner__name=team.name, user__first_name="CoolestTeamServiceAccountName"
620+
).count()
621+
== 0
622+
)
623+
624+
625+
@pytest.mark.django_db
626+
def test_team_service_account_create__when_creating_a_service_account__fails_because_user_is_not_team_member(
627+
api_client: APIClient,
628+
team: Team,
629+
):
630+
non_team_user = User.objects.create()
631+
api_client.force_authenticate(non_team_user)
632+
633+
response = api_client.post(
634+
f"/api/cyberstorm/team/{team.name}/service-account/create/",
635+
json.dumps(
636+
{
637+
"nickname": "CoolestTeamServiceAccountName",
638+
}
639+
),
640+
content_type="application/json",
641+
)
642+
643+
assert response.status_code == 400
644+
response_json = response.json()
645+
assert response_json["team"] == [
646+
"Select a valid choice. That choice is not one of the available choices."
647+
]
648+
assert (
649+
ServiceAccount.objects.filter(
650+
owner__name=team.name, user__first_name="CoolestTeamServiceAccountName"
651+
).count()
652+
== 0
653+
)
654+
655+
656+
@pytest.mark.django_db
657+
def test_team_service_account_create__when_creating_a_service_account__fails_because_user_is_not_team_owner(
658+
api_client: APIClient,
659+
team: Team,
660+
team_member: TeamMember,
661+
):
662+
663+
api_client.force_authenticate(team_member.user)
664+
665+
response = api_client.post(
666+
f"/api/cyberstorm/team/{team.name}/service-account/create/",
667+
json.dumps(
668+
{
669+
"nickname": "CoolestTeamServiceAccountName",
670+
}
671+
),
672+
content_type="application/json",
673+
)
674+
675+
assert response.status_code == 400
676+
response_json = response.json()
677+
assert response_json["team"] == ["Must be an owner to create a service account"]
678+
assert (
679+
ServiceAccount.objects.filter(
680+
owner__name=team.name, user__first_name="CoolestTeamServiceAccountName"
681+
).count()
682+
== 0
683+
)
684+
685+
686+
@pytest.mark.django_db
687+
def test_team_service_account_delete__when_deleting_a_service_account__succeeds(
688+
api_client: APIClient,
689+
team_owner: TeamMember,
690+
service_account: ServiceAccount,
691+
):
692+
api_client.force_authenticate(team_owner.user)
693+
694+
response = api_client.post(
695+
f"/api/cyberstorm/team/{service_account.owner.name}/service-account/delete/",
696+
json.dumps({"service_account_uuid": str(service_account.uuid)}),
697+
content_type="application/json",
698+
)
699+
700+
assert response.status_code == 200
701+
response_json = response.json()
702+
assert response_json["detail"] == "Service account deleted"
703+
assert ServiceAccount.objects.filter(uuid=service_account.uuid).count() == 0
704+
705+
706+
@pytest.mark.django_db
707+
def test_team_service_account_delete__when_deleting_a_service_account__fails_because_user_is_not_authenticated(
708+
api_client: APIClient,
709+
team: Team,
710+
service_account: ServiceAccount,
711+
):
712+
response = api_client.post(
713+
f"/api/cyberstorm/team/{team.name}/service-account/delete/",
714+
json.dumps({"service_account_uuid": str(service_account.uuid)}),
715+
content_type="application/json",
716+
)
717+
718+
assert response.status_code == 401
719+
response_json = response.json()
720+
assert response_json["detail"] == "Authentication credentials were not provided."
721+
assert ServiceAccount.objects.filter(uuid=service_account.uuid).count() == 1
722+
723+
724+
@pytest.mark.django_db
725+
def test_team_service_account_delete__when_deleting_a_service_account__fails_because_user_is_not_team_member(
726+
api_client: APIClient,
727+
team: Team,
728+
service_account: ServiceAccount,
729+
):
730+
non_team_user = User.objects.create()
731+
api_client.force_authenticate(non_team_user)
732+
733+
response = api_client.post(
734+
f"/api/cyberstorm/team/{team.name}/service-account/delete/",
735+
json.dumps({"service_account_uuid": str(service_account.uuid)}),
736+
content_type="application/json",
737+
)
738+
739+
assert response.status_code == 400
740+
response_json = response.json()
741+
assert response_json["service_account"] == [
742+
"Select a valid choice. That choice is not one of the available choices."
743+
]
744+
assert ServiceAccount.objects.filter(uuid=service_account.uuid).count() == 1
745+
746+
747+
@pytest.mark.django_db
748+
def test_team_service_account_delete__when_deleting_a_service_account__fails_because_user_is_not_team_owner(
749+
api_client: APIClient,
750+
team_member: TeamMember,
751+
team: Team,
752+
service_account: ServiceAccount,
753+
):
754+
api_client.force_authenticate(team_member.user)
755+
756+
response = api_client.post(
757+
f"/api/cyberstorm/team/{team.name}/service-account/delete/",
758+
json.dumps({"service_account_uuid": str(service_account.uuid)}),
759+
content_type="application/json",
760+
)
761+
762+
assert response.status_code == 400
763+
response_json = response.json()
764+
assert response_json["service_account"] == [
765+
"Must be an owner to delete a service account"
766+
]
767+
assert ServiceAccount.objects.filter(uuid=service_account.uuid).count() == 1
768+
769+
536770
@pytest.mark.django_db
537771
def test_team_member_add_api_view__when_adding_a_member__succeeds(
538772
api_client: APIClient,

django/thunderstore/api/cyberstorm/views/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
RemoveTeamMemberAPIView,
1919
TeamAPIView,
2020
TeamCreateAPIView,
21+
TeamCreateServiceAccountAPIView,
22+
TeamDeleteServiceAccountAPIView,
2123
TeamMemberAddAPIView,
2224
TeamMemberListAPIView,
2325
TeamServiceAccountListAPIView,
@@ -38,6 +40,8 @@
3840
"TeamMemberAddAPIView",
3941
"TeamMemberListAPIView",
4042
"TeamServiceAccountListAPIView",
43+
"TeamCreateServiceAccountAPIView",
44+
"TeamDeleteServiceAccountAPIView",
4145
"PackageRatingRateAPIView",
4246
"PackageDeprecateAPIView",
4347
"PackageListingEditCategoriesAPIView",

django/thunderstore/api/cyberstorm/views/team.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from rest_framework.response import Response
99
from rest_framework.views import APIView
1010

11+
from thunderstore.account.forms import (
12+
CreateServiceAccountForm,
13+
DeleteServiceAccountForm,
14+
)
1115
from thunderstore.account.models.service_account import ServiceAccount
1216
from thunderstore.api.cyberstorm.serializers import (
1317
CyberstormServiceAccountSerializer,
@@ -318,3 +322,82 @@ def get_queryset(self) -> QuerySet[ServiceAccount]:
318322
return ServiceAccount.objects.exclude(
319323
~Q(owner__name__iexact=self.kwargs["team_id"]),
320324
).select_related("user")
325+
326+
327+
class TeamCreateServiceAccountRequestSerialiazer(serializers.Serializer):
328+
nickname = serializers.CharField()
329+
330+
331+
class TeamCreateServiceAccountResponseSerialiazer(serializers.Serializer):
332+
nickname = serializers.CharField()
333+
team_name = serializers.CharField()
334+
api_token = serializers.CharField()
335+
336+
337+
class TeamCreateServiceAccountAPIView(APIView):
338+
@conditional_swagger_auto_schema(
339+
request_body=TeamCreateServiceAccountRequestSerialiazer,
340+
responses={200: TeamCreateServiceAccountResponseSerialiazer},
341+
operation_id="cyberstorm.team.service-account.create",
342+
tags=["cyberstorm"],
343+
)
344+
def post(self, request: HttpRequest, team_name: str):
345+
serializer = TeamCreateServiceAccountRequestSerialiazer(data=request.data)
346+
serializer.is_valid(raise_exception=True)
347+
team = get_object_or_404(Team, name__iexact=team_name)
348+
form = CreateServiceAccountForm(
349+
user=request.user,
350+
data={
351+
**serializer.validated_data,
352+
"team": team,
353+
},
354+
)
355+
356+
if form.is_valid():
357+
service_account = form.save()
358+
return Response(
359+
TeamCreateServiceAccountResponseSerialiazer(
360+
{
361+
"nickname": service_account.nickname,
362+
"team_name": service_account.owner.name,
363+
"api_token": form.api_token,
364+
}
365+
).data
366+
)
367+
else:
368+
raise ValidationError(form.errors)
369+
370+
371+
class TeamDeleteServiceAccountRequestSerialiazer(serializers.Serializer):
372+
service_account_uuid = serializers.CharField()
373+
374+
375+
class TeamDeleteServiceAccountResponseSerialiazer(serializers.Serializer):
376+
detail = serializers.CharField()
377+
378+
379+
class TeamDeleteServiceAccountAPIView(APIView):
380+
@conditional_swagger_auto_schema(
381+
request_body=TeamDeleteServiceAccountRequestSerialiazer,
382+
responses={200: TeamDeleteServiceAccountResponseSerialiazer},
383+
operation_id="cyberstorm.team.service-account.delete",
384+
tags=["cyberstorm"],
385+
)
386+
def post(self, request: HttpRequest, team_name: str):
387+
serializer = TeamDeleteServiceAccountRequestSerialiazer(data=request.data)
388+
serializer.is_valid(raise_exception=True)
389+
service_account = get_object_or_404(
390+
ServiceAccount,
391+
owner__name__iexact=team_name,
392+
uuid=serializer.validated_data["service_account_uuid"],
393+
)
394+
form = DeleteServiceAccountForm(
395+
user=request.user,
396+
data={"service_account": service_account},
397+
)
398+
399+
if form.is_valid():
400+
form.save()
401+
return Response({"detail": "Service account deleted"})
402+
else:
403+
raise ValidationError(form.errors)

django/thunderstore/api/urls.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
RemoveTeamMemberAPIView,
2121
TeamAPIView,
2222
TeamCreateAPIView,
23+
TeamCreateServiceAccountAPIView,
24+
TeamDeleteServiceAccountAPIView,
2325
TeamMemberAddAPIView,
2426
TeamMemberListAPIView,
2527
TeamServiceAccountListAPIView,
@@ -140,6 +142,16 @@
140142
TeamServiceAccountListAPIView.as_view(),
141143
name="cyberstorm.team.service-account",
142144
),
145+
path(
146+
"team/<str:team_name>/service-account/create/",
147+
TeamCreateServiceAccountAPIView.as_view(),
148+
name="cyberstorm.team.service-account.create",
149+
),
150+
path(
151+
"team/<str:team_name>/service-account/delete/",
152+
TeamDeleteServiceAccountAPIView.as_view(),
153+
name="cyberstorm.team.service-account.delete",
154+
),
143155
path(
144156
"current-user/delete/",
145157
UserDeleteAPIView.as_view(),

0 commit comments

Comments
 (0)