Skip to content

Commit

Permalink
fix: prevent unauthorised remove-users access (#3791)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewelwell authored Apr 18, 2024
1 parent 37fec5b commit 05353a5
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 1 deletion.
13 changes: 12 additions & 1 deletion api/organisations/permissions/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Model
from django.views import View
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.request import Request

Expand Down Expand Up @@ -88,6 +88,17 @@ class OrganisationPermission(BasePermission):
def has_permission(self, request, view):
if view.action == "create" and settings.RESTRICT_ORG_CREATE_TO_SUPERUSERS:
return request.user.is_superuser

organisation_id = view.kwargs.get("pk")
if organisation_id and not organisation_id.isnumeric():
raise ValidationError("Invalid organisation ID")

if view.action == "remove_users":
return request.user.is_organisation_admin(int(organisation_id))

if organisation_id:
return request.user.belongs_to(int(organisation_id))

return True

def has_object_permission(self, request, view, obj):
Expand Down
63 changes: 63 additions & 0 deletions api/tests/unit/organisations/test_unit_organisations_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1797,3 +1797,66 @@ def test_doesnt_retrieve_stale_api_usage_notifications(
# Then
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 0


def test_non_organisation_user_cannot_remove_user_from_organisation(
staff_user: FFAdminUser, organisation: Organisation, api_client: APIClient
) -> None:
# Given
another_organisation = Organisation.objects.create(name="another organisation")
another_user = FFAdminUser.objects.create(email="[email protected]")
another_user.add_organisation(another_organisation)
api_client.force_authenticate(another_user)

url = reverse(
"api-v1:organisations:organisation-remove-users", args=[organisation.id]
)

data = [{"id": staff_user.id}]

# When
response = api_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_403_FORBIDDEN


def test_non_admin_user_cannot_remove_user_from_organisation(
staff_user: FFAdminUser,
organisation: Organisation,
staff_client: APIClient,
admin_user: FFAdminUser,
) -> None:
# Given
url = reverse(
"api-v1:organisations:organisation-remove-users", args=[organisation.id]
)

data = [{"id": admin_user.id}]

# When
response = staff_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_403_FORBIDDEN


def test_validation_error_if_non_numeric_organisation_id(
staff_client: APIClient,
) -> None:
# Given
url = reverse("api-v1:organisations:organisation-remove-users", args=["foo"])

data = []

# When
response = staff_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST

0 comments on commit 05353a5

Please sign in to comment.