Skip to content

Commit

Permalink
Filtering for sandbox (#4216)
Browse files Browse the repository at this point in the history
* Implemented visibility for org= as { 'organization': None }

Now we have 3 types of visibility (aka org_filter)
1. org=slug - see objects only for the organization
2. org=     - see objects only for sandbox (organzation is None)
3. None     - see all objects which you can access
  • Loading branch information
Nikita Manovich authored Jan 24, 2022
1 parent f831142 commit 5ccc596
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 14 deletions.
9 changes: 9 additions & 0 deletions cvat/apps/engine/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

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.
Expand Down Expand Up @@ -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'):
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -589,6 +592,7 @@ def perform_create(self, serializer):
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()
Expand Down Expand Up @@ -882,6 +886,7 @@ def dataset_export(self, request, pk):
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()
Expand Down Expand Up @@ -986,6 +991,7 @@ def data(self, request, pk):
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()
Expand Down Expand Up @@ -1020,6 +1026,7 @@ def comments(self, request, pk):
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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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"):
Expand Down
13 changes: 5 additions & 8 deletions cvat/apps/iam/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -20,11 +19,9 @@ def filter_queryset(self, request, queryset, view):
# 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
18 changes: 12 additions & 6 deletions cvat/apps/iam/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.')

Expand All @@ -58,6 +63,7 @@ def get_context(request):
"privilege": groups[0] if groups else None,
"membership": membership,
"organization": organization,
"visibility": org_filter,
}

return context
Expand Down
3 changes: 3 additions & 0 deletions cvat/apps/lambda_manager/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ def func_wrapper(*args, **kwargs):
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):
Expand Down Expand Up @@ -585,6 +586,8 @@ def call(self, request, func_id):
return lambda_func.invoke(db_task, request.data)

class RequestViewSet(viewsets.ViewSet):
iam_organization_field = None

@return_response()
def list(self, request):
queue = LambdaQueue()
Expand Down
3 changes: 3 additions & 0 deletions cvat/apps/organizations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions cvat/apps/restrictions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 5ccc596

Please sign in to comment.