diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 1e8e0004fabc..a971a985986d 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -260,7 +260,7 @@ class ProjectViewSet(viewsets.ModelViewSet): ordering_fields = filter_fields ordering = "-id" lookup_fields = {'owner': 'owner__username', 'assignee': 'assignee__username'} - http_method_names = ('get', 'post', 'head', 'patch', 'delete') + http_method_names = ('get', 'post', 'head', 'patch', 'delete', 'options') iam_organization_field = 'organization' def get_serializer_class(self): @@ -1243,7 +1243,7 @@ def perform_create(self, serializer): class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin): queryset = User.objects.prefetch_related('groups').all() - http_method_names = ['get', 'post', 'head', 'patch', 'delete'] + http_method_names = ['get', 'post', 'head', 'patch', 'delete', 'options'] search_fields = ('username', 'first_name', 'last_name') iam_organization_field = 'memberships__organization' @@ -1313,7 +1313,7 @@ def self(self, request): '201': CloudStorageWriteSerializer, }, tags=['cloud storages'], versions=['2.0'])) class CloudStorageViewSet(viewsets.ModelViewSet): - http_method_names = ['get', 'post', 'patch', 'delete'] + http_method_names = ['get', 'post', 'patch', 'delete', 'options'] queryset = CloudStorageModel.objects.all().prefetch_related('data') search_fields = ('provider_type', 'display_name', 'resource', diff --git a/cvat/apps/iam/permissions.py b/cvat/apps/iam/permissions.py index 3d632850846c..750bd9584555 100644 --- a/cvat/apps/iam/permissions.py +++ b/cvat/apps/iam/permissions.py @@ -1015,8 +1015,14 @@ class PolicyEnforcer(BasePermission): # pylint: disable=no-self-use def check_permission(self, request, view, obj): permissions = [] - for perm in OpenPolicyAgentPermission.__subclasses__(): - permissions.extend(perm.create(request, view, obj)) + # DRF can send OPTIONS request. Internally it will try to get + # information about serializers for PUT and POST requests (clone + # request and replace the http method). To avoid handling + # ('POST', 'metadata') and ('PUT', 'metadata') in every request, + # the condition below is enough. + if not self.is_metadata_request(request, view): + for perm in OpenPolicyAgentPermission.__subclasses__(): + permissions.extend(perm.create(request, view, obj)) return all(permissions) @@ -1029,6 +1035,10 @@ def has_permission(self, request, view): def has_object_permission(self, request, view, obj): return self.check_permission(request, view, obj) + @staticmethod + def is_metadata_request(request, view): + return request.method == 'OPTIONS' or view.action == 'metadata' + class IsMemberInOrganization(BasePermission): message = 'You should be an active member in the organization.' diff --git a/tests/rest_api/test_organizations.py b/tests/rest_api/test_organizations.py index 8e5a9ac1cf37..617464eea089 100644 --- a/tests/rest_api/test_organizations.py +++ b/tests/rest_api/test_organizations.py @@ -4,9 +4,35 @@ from http import HTTPStatus import pytest -from .utils.config import get_method, patch_method, delete_method +from .utils.config import get_method, options_method, patch_method, delete_method from deepdiff import DeepDiff +class TestMetadataOrganizations: + _ORG = 2 + + @pytest.mark.parametrize('privilege, role, is_member', [ + ('admin', None, None), + ('user', None, False), + ('business', None, False), + ('worker', None, False), + (None, 'owner', True), + (None, 'maintainer', True), + (None, 'worker', True), + (None, 'supervisor', True), + ]) + def test_can_send_options_request(self, privilege, role, is_member, + find_users, organizations): + exclude_org = None if is_member else self._ORG + org = self._ORG if is_member else None + user = find_users(privilege=privilege, role=role, org=org, + exclude_org=exclude_org)[0]['username'] + + response = options_method(user, f'organizations') + assert response.status_code == HTTPStatus.OK + + response = options_method(user, f'organizations/{self._ORG}') + assert response.status_code == HTTPStatus.OK + class TestGetOrganizations: _ORG = 2 diff --git a/tests/rest_api/utils/config.py b/tests/rest_api/utils/config.py index 29f863050ba7..2d4360fd758a 100644 --- a/tests/rest_api/utils/config.py +++ b/tests/rest_api/utils/config.py @@ -24,6 +24,9 @@ def get_api_url(endpoint, **kwargs): def get_method(username, endpoint, **kwargs): return requests.get(get_api_url(endpoint, **kwargs), auth=(username, USER_PASS)) +def options_method(username, endpoint, **kwargs): + return requests.options(get_api_url(endpoint, **kwargs), auth=(username, USER_PASS)) + def delete_method(username, endpoint, **kwargs): return requests.delete(get_api_url(endpoint, **kwargs), auth=(username, USER_PASS))