diff --git a/api/integrations/slack/permissions.py b/api/integrations/slack/permissions.py index 10dfc4b4ce2f..85f614ae698b 100644 --- a/api/integrations/slack/permissions.py +++ b/api/integrations/slack/permissions.py @@ -1,3 +1,4 @@ +from django.shortcuts import get_object_or_404 from rest_framework.permissions import IsAuthenticated from environments.models import Environment @@ -12,3 +13,15 @@ def has_permission(self, request, view): api_key=view.kwargs.get("environment_api_key") ) return request.user.is_environment_admin(environment) + + +class SlackGetChannelPermissions(IsAuthenticated): + def has_permission(self, request, view): + if not super().has_permission(request, view): # pragma: no cover + return False + + environment = get_object_or_404( + Environment.objects.select_related("project"), + api_key=view.kwargs.get("environment_api_key"), + ) + return request.user.is_project_admin(environment.project) diff --git a/api/integrations/slack/views.py b/api/integrations/slack/views.py index 825ac02e0958..c05bc66b5fef 100644 --- a/api/integrations/slack/views.py +++ b/api/integrations/slack/views.py @@ -28,7 +28,7 @@ InvalidStateError, SlackConfigurationDoesNotExist, ) -from .permissions import OauthInitPermission +from .permissions import OauthInitPermission, SlackGetChannelPermissions signer = TimestampSigner() @@ -36,6 +36,7 @@ class SlackGetChannelsViewSet(GenericViewSet): serializer_class = SlackChannelListSerializer pagination_class = None # set here to ensure documentation is correct + permission_classes = [SlackGetChannelPermissions] def get_api_token(self) -> str: environment = Environment.objects.get( diff --git a/api/sales_dashboard/urls.py b/api/sales_dashboard/urls.py index 024edb4fb3f0..42395408270b 100644 --- a/api/sales_dashboard/urls.py +++ b/api/sales_dashboard/urls.py @@ -1,4 +1,3 @@ -from django.contrib.admin.views.decorators import staff_member_required from django.urls import path from . import views @@ -7,7 +6,7 @@ urlpatterns = [ - path("", staff_member_required(views.OrganisationList.as_view()), name="index"), + path("", views.OrganisationList.as_view(), name="index"), path( "organisations/", views.organisation_info, @@ -40,7 +39,7 @@ ), path( "email-usage/", - staff_member_required(views.EmailUsage.as_view()), + views.EmailUsage.as_view(), name="email-usage", ), path( diff --git a/api/sales_dashboard/views.py b/api/sales_dashboard/views.py index 53513ab87d8b..cba108f53041 100644 --- a/api/sales_dashboard/views.py +++ b/api/sales_dashboard/views.py @@ -21,6 +21,7 @@ from django.template import loader from django.urls import reverse, reverse_lazy from django.utils import timezone +from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe from django.views.generic import ListView from django.views.generic.edit import FormView @@ -54,6 +55,10 @@ email_regex = re.compile(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$") +@method_decorator( + name="get", + decorator=staff_member_required(), +) class OrganisationList(ListView): model = Organisation paginate_by = OBJECTS_PER_PAGE @@ -261,6 +266,14 @@ def migrate_identities_to_edge(request, project_id): return HttpResponseRedirect(reverse("sales_dashboard:index")) +@method_decorator( + name="get", + decorator=staff_member_required(), +) +@method_decorator( + name="post", + decorator=staff_member_required(), +) class EmailUsage(FormView): form_class = EmailUsageForm template_name = "sales_dashboard/email-usage.html" diff --git a/api/tests/integration/slack/test_slack_get_channels.py b/api/tests/integration/slack/test_slack_get_channels.py index 7c00f04bac21..51a66037e0be 100644 --- a/api/tests/integration/slack/test_slack_get_channels.py +++ b/api/tests/integration/slack/test_slack_get_channels.py @@ -2,6 +2,25 @@ from django.urls import reverse from rest_framework import status +from rest_framework.test import APIClient + +from environments.models import Environment + + +def test_get_channels_fails_if_user_has_no_permission( + staff_client: APIClient, environment: Environment, environment_api_key: str +) -> None: + # Given + url = reverse( + "api-v1:environments:integrations-slack-channels-list", + args=[environment_api_key], + ) + + # When + response = staff_client.get(url) + + # Then + assert response.status_code == status.HTTP_403_FORBIDDEN def test_get_channels_returns_400_when_slack_project_config_does_not_exist( diff --git a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py index e19c450936e3..c42e8ac8ca6e 100644 --- a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py +++ b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py @@ -145,3 +145,57 @@ def test_list_organisations_filter_plan( # Then assert response.status_code == 200 assert list(response.context_data["organisation_list"]) == [organisation] + + +def test_list_organisations_fails_if_not_staff( + organisation: Organisation, + client: Client, +) -> None: + # Given + user = FFAdminUser.objects.create(email="notastaffuser@example.com") + client.force_login(user) + + url = reverse("sales_dashboard:index") + + # When + response = client.get(url) + + # Then + assert response.status_code == 302 + assert response.url == "/admin/login/?next=/sales-dashboard/" + + +def test_get_email_usage_fails_if_not_staff( + organisation: Organisation, + client: Client, +) -> None: + # Given + user = FFAdminUser.objects.create(email="notastaffuser@example.com") + client.force_login(user) + + url = reverse("sales_dashboard:email-usage") + + # When + response = client.get(url) + + # Then + assert response.status_code == 302 + assert response.url == "/admin/login/?next=/sales-dashboard/email-usage/" + + +def test_post_email_usage_fails_if_not_staff( + organisation: Organisation, + client: Client, +) -> None: + # Given + user = FFAdminUser.objects.create(email="notastaffuser@example.com") + client.force_login(user) + + url = reverse("sales_dashboard:email-usage") + + # When + response = client.post(url) + + # Then + assert response.status_code == 302 + assert response.url == "/admin/login/?next=/sales-dashboard/email-usage/"