Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix OPTIONS request to REST API methods #4402

Merged
merged 1 commit into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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