Skip to content

Commit 772ae53

Browse files
authored
Merge pull request #670 from jbernal0019/master
Fix #500.
2 parents fd0d3fd + d595fb1 commit 772ae53

File tree

3 files changed

+92
-55
lines changed

3 files changed

+92
-55
lines changed

chris_backend/plugininstances/permissions.py

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22
from rest_framework import permissions
33

44

5-
class IsOwnerOrChrisOrAuthenticatedReadOnlyOrPublicReadOnly(permissions.BasePermission):
5+
class IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly(
6+
permissions.BasePermission):
67
"""
7-
Custom permission to only allow owners of an object or superuser
8-
'chris' to modify/edit it. Read only is allowed to authenticated users and to
9-
unauthenticated users if the related feed is public.
8+
Custom permission to only allow owners of an object or superuser 'chris' to
9+
modify/edit it (e.g. a plugin instance).
10+
Read-only access is allowed to users that have been granted the object's feed
11+
access permission or to any user if the object's feed is public.
1012
"""
1113

1214
def has_object_permission(self, request, view, obj):
1315
user = request.user
16+
17+
if hasattr(obj, 'plugin_inst'):
18+
obj = obj.plugin_inst
19+
1420
if user.username == 'chris' or user == obj.owner:
1521
return True
1622

17-
return request.method in permissions.SAFE_METHODS and (user.is_authenticated or
18-
obj.feed.public)
23+
return request.method in permissions.SAFE_METHODS and (
24+
obj.feed.public or obj.feed.has_user_permission(user))
1925

2026

2127
class IsNotDeleteFSPluginInstance(permissions.BasePermission):
@@ -26,28 +32,3 @@ class IsNotDeleteFSPluginInstance(permissions.BasePermission):
2632

2733
def has_object_permission(self, request, view, obj):
2834
return request.method != 'DELETE' or obj.plugin.meta.type != 'fs'
29-
30-
31-
class IsAuthenticatedReadOnlyOrPublicReadOnly(permissions.BasePermission):
32-
"""
33-
Custom permission to allow read only access to authenticated users and to
34-
unauthenticated users if the related feed is public.
35-
"""
36-
37-
def has_object_permission(self, request, view, obj):
38-
return request.method in permissions.SAFE_METHODS and (
39-
request.user.is_authenticated or obj.feed.public)
40-
41-
42-
class IsOwnerOrReadOnly(permissions.BasePermission):
43-
"""
44-
Custom permission to only allow owners of an object modify/edit it.
45-
Read only is allowed to other users.
46-
"""
47-
48-
def has_object_permission(self, request, view, obj):
49-
if request.method in permissions.SAFE_METHODS:
50-
return True
51-
52-
# Write permissions are only allowed to the owner and superuser 'chris'.
53-
return request.user == obj.owner

chris_backend/plugininstances/tests/test_views.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -860,27 +860,36 @@ def test_plugin_instance_delete_failure_fs_plugin(self):
860860

861861
class PluginInstanceListQuerySearchViewTests(ViewTests):
862862
"""
863-
Test the plugininstance-list-query-search view.
863+
Test the allplugininstance-list-query-search view.
864864
"""
865865

866866
def setUp(self):
867867
super(PluginInstanceListQuerySearchViewTests, self).setUp()
868868

869869
user = User.objects.get(username=self.username)
870870

871-
# create two plugin instances
871+
# create three plugin instances
872872
plugin = Plugin.objects.get(meta__name="pacspull")
873+
PluginInstance.objects.get_or_create(
874+
plugin=plugin, owner=user, compute_resource=plugin.compute_resources.all()[0])
875+
873876
(inst, tf) = PluginInstance.objects.get_or_create(
874877
plugin=plugin, owner=user, compute_resource=plugin.compute_resources.all()[0])
875878

879+
inst.status = 'finishedSuccessfully' # set second instance's status
880+
inst.save()
881+
876882
plugin = Plugin.objects.get(meta__name="mri_convert")
877883
(inst, tf) = PluginInstance.objects.get_or_create(
878884
plugin=plugin, owner=user, previous=inst,
879885
compute_resource=plugin.compute_resources.all()[0])
880-
# set second instance's status
881-
inst.status = 'finishedSuccessfully'
886+
887+
inst.status = 'finishedSuccessfully' # set third instance's status
882888
inst.save()
883889

890+
inst.feed.public = True # second and third instances now belongs to a public feed
891+
inst.feed.save()
892+
884893
self.list_url = reverse("allplugininstance-list-query-search") + '?status=created'
885894

886895
def test_plugin_instance_query_search_list_success(self):
@@ -890,9 +899,17 @@ def test_plugin_instance_query_search_list_success(self):
890899
self.assertContains(response, 'created')
891900
self.assertNotContains(response,'finishedSuccessfully')
892901

893-
def test_plugin_instance_query_search_list_failure_unauthenticated(self):
902+
def test_plugin_instance_query_search_list_success_but_empty_unauthenticated(self):
894903
response = self.client.get(self.list_url)
895-
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
904+
self.assertEqual(response.status_code, status.HTTP_200_OK)
905+
self.assertEqual(response.data['results'], [])
906+
907+
def test_plugin_instance_query_search_list_success_unauthenticated(self):
908+
list_url = reverse("allplugininstance-list-query-search") + '?status=finishedSuccessfully'
909+
response = self.client.get(list_url)
910+
# response should only contain the instances that match the query
911+
self.assertContains(response, 'finishedSuccessfully')
912+
self.assertNotContains(response, 'created')
896913

897914

898915
class PluginInstanceDescendantListViewTests(ViewTests):

chris_backend/plugininstances/views.py

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
from django.db.models import Q
23
from rest_framework import generics
34
from rest_framework import permissions
45
from rest_framework.reverse import reverse
@@ -14,8 +15,7 @@
1415
from .serializers import PARAMETER_SERIALIZERS
1516
from .serializers import GenericParameterSerializer, PluginInstanceSplitSerializer
1617
from .serializers import PluginInstanceSerializer
17-
from .permissions import (IsOwnerOrChrisOrAuthenticatedReadOnlyOrPublicReadOnly,
18-
IsOwnerOrReadOnly, IsAuthenticatedReadOnlyOrPublicReadOnly,
18+
from .permissions import (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,
1919
IsNotDeleteFSPluginInstance)
2020
from .tasks import run_plugin_instance, cancel_plugin_instance
2121
from .utils import run_if_ready
@@ -92,13 +92,15 @@ def list(self, request, *args, **kwargs):
9292
queryset = self.get_plugin_instances_queryset()
9393
response = services.get_list_response(self, queryset)
9494
plugin = self.get_object()
95+
9596
# append document-level link relations
9697
links = {'plugin': reverse('plugin-detail', request=request,
9798
kwargs={"pk": plugin.id}),
9899
'compute_resources': reverse('plugin-computeresource-list',
99100
request=request, kwargs={"pk": plugin.id})
100101
}
101102
response = services.append_collection_links(response, links)
103+
102104
# append write template
103105
param_names = plugin.get_plugin_parameter_names()
104106
template_data = {'title': '', 'compute_resource_name': '', 'previous_id': '',
@@ -112,8 +114,9 @@ def get_plugin_instances_queryset(self):
112114
"""
113115
Custom method to get the actual plugin instances' queryset.
114116
"""
117+
user = self.request.user
115118
plugin = self.get_object()
116-
return self.filter_queryset(plugin.instances.all())
119+
return plugin.instances.filter(owner=user)
117120

118121

119122
@extend_schema_view(
@@ -125,8 +128,6 @@ class AllPluginInstanceList(generics.ListAPIView):
125128
"""
126129
http_method_names = ['get']
127130
serializer_class = PluginInstanceSerializer
128-
queryset = PluginInstance.objects.all()
129-
permission_classes = (permissions.IsAuthenticated,)
130131

131132
def list(self, request, *args, **kwargs):
132133
"""
@@ -140,17 +141,55 @@ def list(self, request, *args, **kwargs):
140141
links = {'plugins': reverse('plugin-list', request=request)}
141142
return services.append_collection_links(response, links)
142143

144+
def get_queryset(self):
145+
"""
146+
Overriden to return a custom queryset that is only comprised by the plugin
147+
instances in public feeds if the user is not authenticated. For the superuser
148+
'chris' all plugin instances are retuned. For other users all the plugin instances
149+
in public feeds or owned by the user or belonging to feeds that have been shared
150+
with the user are returned.
151+
"""
152+
user = self.request.user
153+
154+
if not user.is_authenticated:
155+
return PluginInstance.objects.filter(feed__public=True)
156+
157+
if user.username == 'chris':
158+
return PluginInstance.objects.all()
159+
160+
lookup = Q(feed__public=True) | Q(feed__owner=user) | Q(
161+
feed__shared_users=user) | Q(feed__shared_groups__in=user.groups.all())
162+
return PluginInstance.objects.filter(lookup)
163+
143164

144165
class AllPluginInstanceListQuerySearch(generics.ListAPIView):
145166
"""
146167
A view for the collection of plugin instances resulting from a query search.
147168
"""
148169
http_method_names = ['get']
149170
serializer_class = PluginInstanceSerializer
150-
queryset = PluginInstance.objects.all()
151-
permission_classes = (permissions.IsAuthenticated,)
152171
filterset_class = PluginInstanceFilter
153172

173+
def get_queryset(self):
174+
"""
175+
Overriden to return a custom queryset that is only comprised by the plugin
176+
instances in public feeds if the user is not authenticated. For the superuser
177+
'chris' all plugin instances are retuned. For other users all the plugin instances
178+
in public feeds or owned by the user or belonging to feeds that have been shared
179+
with the user are returned.
180+
"""
181+
user = self.request.user
182+
183+
if not user.is_authenticated:
184+
return PluginInstance.objects.filter(feed__public=True)
185+
186+
if user.username == 'chris':
187+
return PluginInstance.objects.all()
188+
189+
lookup = Q(feed__public=True) | Q(feed__owner=user) | Q(
190+
feed__shared_users=user) | Q(feed__shared_groups__in=user.groups.all())
191+
return PluginInstance.objects.filter(lookup)
192+
154193

155194
class PluginInstanceDetail(generics.RetrieveUpdateDestroyAPIView):
156195
"""
@@ -159,7 +198,7 @@ class PluginInstanceDetail(generics.RetrieveUpdateDestroyAPIView):
159198
http_method_names = ['get', 'put', 'delete']
160199
serializer_class = PluginInstanceSerializer
161200
queryset = PluginInstance.objects.all()
162-
permission_classes = (IsOwnerOrChrisOrAuthenticatedReadOnlyOrPublicReadOnly,
201+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,
163202
IsNotDeleteFSPluginInstance,)
164203

165204
def retrieve(self, request, *args, **kwargs):
@@ -218,7 +257,7 @@ class PluginInstanceDescendantList(generics.ListAPIView):
218257
http_method_names = ['get']
219258
serializer_class = PluginInstanceSerializer
220259
queryset = PluginInstance.objects.all()
221-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
260+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
222261

223262
def list(self, request, *args, **kwargs):
224263
"""
@@ -242,7 +281,7 @@ class PluginInstanceSplitList(generics.ListCreateAPIView):
242281
http_method_names = ['get', 'post']
243282
serializer_class = PluginInstanceSplitSerializer
244283
queryset = PluginInstance.objects.all()
245-
permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly,)
284+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
246285

247286
def perform_create(self, serializer):
248287
"""
@@ -317,7 +356,7 @@ class PluginInstanceSplitDetail(generics.RetrieveAPIView):
317356
http_method_names = ['get']
318357
queryset = PluginInstanceSplit.objects.all()
319358
serializer_class = PluginInstanceSplitSerializer
320-
permission_classes = (permissions.IsAuthenticated,)
359+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
321360

322361

323362
class PluginInstanceParameterList(generics.ListAPIView):
@@ -327,7 +366,7 @@ class PluginInstanceParameterList(generics.ListAPIView):
327366
http_method_names = ['get']
328367
serializer_class = GenericParameterSerializer
329368
queryset = PluginInstance.objects.all()
330-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
369+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
331370

332371
def list(self, request, *args, **kwargs):
333372
"""
@@ -352,7 +391,7 @@ class StrParameterDetail(generics.RetrieveAPIView):
352391
http_method_names = ['get']
353392
serializer_class = PARAMETER_SERIALIZERS['string']
354393
queryset = StrParameter.objects.all()
355-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
394+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
356395

357396

358397
class IntParameterDetail(generics.RetrieveAPIView):
@@ -362,7 +401,7 @@ class IntParameterDetail(generics.RetrieveAPIView):
362401
http_method_names = ['get']
363402
serializer_class = PARAMETER_SERIALIZERS['integer']
364403
queryset = IntParameter.objects.all()
365-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
404+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
366405

367406

368407
class FloatParameterDetail(generics.RetrieveAPIView):
@@ -372,7 +411,7 @@ class FloatParameterDetail(generics.RetrieveAPIView):
372411
http_method_names = ['get']
373412
serializer_class = PARAMETER_SERIALIZERS['float']
374413
queryset = FloatParameter.objects.all()
375-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
414+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
376415

377416

378417
class BoolParameterDetail(generics.RetrieveAPIView):
@@ -382,7 +421,7 @@ class BoolParameterDetail(generics.RetrieveAPIView):
382421
http_method_names = ['get']
383422
serializer_class = PARAMETER_SERIALIZERS['boolean']
384423
queryset = BoolParameter.objects.all()
385-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
424+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
386425

387426

388427
class PathParameterDetail(generics.RetrieveAPIView):
@@ -392,7 +431,7 @@ class PathParameterDetail(generics.RetrieveAPIView):
392431
http_method_names = ['get']
393432
serializer_class = PARAMETER_SERIALIZERS['path']
394433
queryset = PathParameter.objects.all()
395-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
434+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)
396435

397436

398437
class UnextpathParameterDetail(generics.RetrieveAPIView):
@@ -402,4 +441,4 @@ class UnextpathParameterDetail(generics.RetrieveAPIView):
402441
http_method_names = ['get']
403442
serializer_class = PARAMETER_SERIALIZERS['unextpath']
404443
queryset = UnextpathParameter.objects.all()
405-
permission_classes = (IsAuthenticatedReadOnlyOrPublicReadOnly,)
444+
permission_classes = (IsOwnerOrChrisOrHasFeedPermissionReadOnlyOrPublicFeedReadOnly,)

0 commit comments

Comments
 (0)