Skip to content

Commit

Permalink
Fix OPTIONS request to REST API methods (#4402)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Manovich authored Mar 1, 2022
1 parent 6fad176 commit cc98ff0
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 6 deletions.
6 changes: 3 additions & 3 deletions cvat/apps/engine/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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'

Expand Down Expand Up @@ -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',
Expand Down
14 changes: 12 additions & 2 deletions cvat/apps/iam/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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.'

Expand Down
28 changes: 27 additions & 1 deletion tests/rest_api/test_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions tests/rest_api/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down

0 comments on commit cc98ff0

Please sign in to comment.