diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index b9fb0987..50cf67cb 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -73,6 +73,7 @@ from cvat.apps.iam.permissions import (CloudStoragePermission, class ServerViewSet(viewsets.ViewSet): serializer_class = None + iam_organization_field = None # To get nice documentation about ServerViewSet actions it is necessary # to implement the method. By default, ViewSet doesn't provide it. @@ -245,6 +246,7 @@ class ProjectViewSet(viewsets.ModelViewSet): ordering_fields = ("id", "name", "owner", "status", "assignee") ordering = ("-id",) http_method_names = ('get', 'post', 'head', 'patch', 'delete') + iam_organization_field = 'organization' def get_serializer_class(self): if self.request.path.endswith('tasks'): @@ -557,6 +559,7 @@ class TaskViewSet(UploadMixin, viewsets.ModelViewSet): search_fields = ("name", "owner__username", "mode", "status") filterset_class = TaskFilter ordering_fields = ("id", "name", "owner", "status", "assignee", "subset") + iam_organization_field = 'organization' def get_queryset(self): queryset = super().get_queryset() @@ -589,6 +592,7 @@ class TaskViewSet(UploadMixin, viewsets.ModelViewSet): if instance.project: db_project = instance.project db_project.save() + assert instance.organization == db_project.organization def perform_destroy(self, instance): task_dirname = instance.get_task_dirname() @@ -882,6 +886,7 @@ class TaskViewSet(UploadMixin, viewsets.ModelViewSet): class JobViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin): queryset = Job.objects.all().order_by('id') + iam_organization_field = 'segment__task__organization' def get_queryset(self): queryset = super().get_queryset() @@ -986,6 +991,7 @@ class JobViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, class IssueViewSet(viewsets.ModelViewSet): queryset = Issue.objects.all().order_by('-id') http_method_names = ['get', 'post', 'patch', 'delete', 'options'] + iam_organization_field = 'job__segment__task__organization' def get_queryset(self): queryset = super().get_queryset() @@ -1020,6 +1026,7 @@ class IssueViewSet(viewsets.ModelViewSet): class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all().order_by('-id') http_method_names = ['get', 'post', 'patch', 'delete', 'options'] + iam_organization_field = 'issue__job__segment__task__organization' def get_queryset(self): queryset = super().get_queryset() @@ -1061,6 +1068,7 @@ class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, http_method_names = ['get', 'post', 'head', 'patch', 'delete'] search_fields = ('username', 'first_name', 'last_name') filterset_class = UserFilter + iam_organization_field = 'memberships__organization' def get_queryset(self): queryset = super().get_queryset() @@ -1154,6 +1162,7 @@ class CloudStorageViewSet(viewsets.ModelViewSet): queryset = CloudStorageModel.objects.all().prefetch_related('data').order_by('-id') search_fields = ('provider_type', 'display_name', 'resource', 'credentials_type', 'owner__username', 'description') filterset_class = CloudStorageFilter + iam_organization_field = 'organization' def get_serializer_class(self): if self.request.method in ("POST", "PATCH"): diff --git a/cvat/apps/iam/filters.py b/cvat/apps/iam/filters.py index 7af7c56d..3a0539d5 100644 --- a/cvat/apps/iam/filters.py +++ b/cvat/apps/iam/filters.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: MIT import coreapi -from django.core.exceptions import FieldError from rest_framework.filters import BaseFilterBackend class OrganizationFilterBackend(BaseFilterBackend): @@ -20,11 +19,9 @@ class OrganizationFilterBackend(BaseFilterBackend): # filter isn't necessary but it is an extra check that we show only # objects inside an organization if the request in context of the # organization. - try: - organization = request.iam_context['organization'] - if organization: - return queryset.filter(organization=organization) - else: - return queryset - except FieldError: + visibility = request.iam_context['visibility'] + if visibility and view.iam_organization_field: + visibility[view.iam_organization_field] = visibility.pop('organization') + return queryset.filter(**visibility).distinct() + else: return queryset diff --git a/cvat/apps/iam/views.py b/cvat/apps/iam/views.py index 752843e4..dd9b5974 100644 --- a/cvat/apps/iam/views.py +++ b/cvat/apps/iam/views.py @@ -32,22 +32,27 @@ def get_context(request): org_id = request.GET.get('org_id') org_header = request.headers.get('X-Organization') - if org_id and (org_slug or org_header): + if org_id != None and (org_slug != None or org_header != None): raise BadRequest('You cannot specify "org_id" query parameter with ' + '"org" query parameter or "X-Organization" HTTP header at the same time.') - if org_slug and org_header and org_slug != org_header: + if org_slug != None and org_header != None and org_slug != org_header: raise BadRequest('You cannot specify "org" query parameter and ' + '"X-Organization" HTTP header with different values.') - org_slug = org_slug or org_header + org_slug = org_slug if org_slug != None else org_header + org_filter = None if org_slug: organization = Organization.objects.get(slug=org_slug) membership = Membership.objects.filter(organization=organization, user=request.user).first() + org_filter = { 'organization': organization.id } elif org_id: - organization = Organization.objects.get(id=int(org_id)) - membership = Membership.objects.filter(organization=organization, - user=request.user).first() + organization = Organization.objects.get(id=int(org_id)) + membership = Membership.objects.filter(organization=organization, + user=request.user).first() + org_filter = { 'organization': organization.id } + elif org_slug is not None: + org_filter = { 'organization': None } except Organization.DoesNotExist: raise BadRequest(f'{org_slug or org_id} organization does not exist.') @@ -58,6 +63,7 @@ def get_context(request): "privilege": groups[0] if groups else None, "membership": membership, "organization": organization, + "visibility": org_filter, } return context diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index a0f40842..85bd0af4 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -555,6 +555,7 @@ def return_response(success_code=status.HTTP_200_OK): class FunctionViewSet(viewsets.ViewSet): lookup_value_regex = '[a-zA-Z0-9_.-]+' lookup_field = 'func_id' + iam_organization_field = None @return_response() def list(self, request): @@ -585,6 +586,8 @@ class FunctionViewSet(viewsets.ViewSet): return lambda_func.invoke(db_task, request.data) class RequestViewSet(viewsets.ViewSet): + iam_organization_field = None + @return_response() def list(self, request): queue = LambdaQueue() diff --git a/cvat/apps/organizations/views.py b/cvat/apps/organizations/views.py index 1ba702f4..ebd12053 100644 --- a/cvat/apps/organizations/views.py +++ b/cvat/apps/organizations/views.py @@ -21,6 +21,7 @@ class OrganizationViewSet(viewsets.ModelViewSet): ordering = ['-id'] http_method_names = ['get', 'post', 'patch', 'delete', 'head', 'options'] pagination_class = None + iam_organization_field = None def get_queryset(self): queryset = super().get_queryset() @@ -52,6 +53,7 @@ class MembershipViewSet(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, ordering = ['-id'] http_method_names = ['get', 'patch', 'delete', 'head', 'options'] filterset_class = MembershipFilter + iam_organization_field = 'organization' def get_serializer_class(self): if self.request.method in SAFE_METHODS: @@ -68,6 +70,7 @@ class InvitationViewSet(viewsets.ModelViewSet): queryset = Invitation.objects.all() ordering = ['-created_date'] http_method_names = ['get', 'post', 'patch', 'delete', 'head', 'options'] + iam_organization_field = 'membership__organization' def get_serializer_class(self): if self.request.method in SAFE_METHODS: diff --git a/cvat/apps/restrictions/views.py b/cvat/apps/restrictions/views.py index 46af4917..8b23e2ff 100644 --- a/cvat/apps/restrictions/views.py +++ b/cvat/apps/restrictions/views.py @@ -16,6 +16,7 @@ class RestrictionsViewSet(viewsets.ViewSet): serializer_class = None permission_classes = [AllowAny] authentication_classes = [] + iam_organization_field = None # To get nice documentation about ServerViewSet actions it is necessary # to implement the method. By default, ViewSet doesn't provide it.