added permission classes for sharing + tests
This commit is contained in:
@ -72,6 +72,23 @@ def is_object_owner(user, obj):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_object_shared(user, obj):
|
||||||
|
"""
|
||||||
|
Tests if a given user is shared for a given object
|
||||||
|
test performed by checking user against the objects shared table
|
||||||
|
superusers bypass all checks, unauthenticated users cannot own anything
|
||||||
|
:param user django auth user object
|
||||||
|
:param obj any object that should be tested
|
||||||
|
:return: true if user is shared for object, false otherwise
|
||||||
|
"""
|
||||||
|
# TODO this could be improved/cleaned up by adding share checks for relevant objects
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return False
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
return user in obj.shared.all()
|
||||||
|
|
||||||
|
|
||||||
def share_link_valid(recipe, share):
|
def share_link_valid(recipe, share):
|
||||||
"""
|
"""
|
||||||
Verifies the validity of a share uuid
|
Verifies the validity of a share uuid
|
||||||
@ -147,6 +164,21 @@ class CustomIsOwner(permissions.BasePermission):
|
|||||||
return is_object_owner(request.user, obj)
|
return is_object_owner(request.user, obj)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomIsShared(permissions.BasePermission): # TODO function duplicate name
|
||||||
|
"""
|
||||||
|
Custom permission class for django rest framework views
|
||||||
|
verifies user is shared for the object he is trying to access
|
||||||
|
"""
|
||||||
|
message = _('You cannot interact with this object as its not owned by you!')
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return request.user.is_authenticated
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
print("called is shared")
|
||||||
|
return is_object_shared(request.user, obj)
|
||||||
|
|
||||||
|
|
||||||
class CustomIsGuest(permissions.BasePermission):
|
class CustomIsGuest(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
Custom permission class for django rest framework views
|
Custom permission class for django rest framework views
|
||||||
|
27
cookbook/tests/api/test_api_shopping.py
Normal file
27
cookbook/tests/api/test_api_shopping.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.contrib import auth
|
||||||
|
from django.db.models import ProtectedError
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from cookbook.models import Storage, Sync, Keyword, ShoppingList
|
||||||
|
from cookbook.tests.views.test_views import TestViews
|
||||||
|
|
||||||
|
|
||||||
|
class TestApiShopping(TestViews):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestApiShopping, self).setUp()
|
||||||
|
self.list_1 = ShoppingList.objects.create(created_by=auth.get_user(self.user_client_1))
|
||||||
|
self.list_2 = ShoppingList.objects.create(created_by=auth.get_user(self.user_client_2))
|
||||||
|
|
||||||
|
def test_shopping_view_permissions(self):
|
||||||
|
self.batch_requests([(self.anonymous_client, 403), (self.guest_client_1, 404), (self.user_client_1, 200), (self.user_client_2, 404), (self.admin_client_1, 404), (self.superuser_client, 200)],
|
||||||
|
reverse('api:shoppinglist-detail', args={self.list_1.id}))
|
||||||
|
|
||||||
|
self.list_1.shared.add(auth.get_user(self.user_client_2))
|
||||||
|
|
||||||
|
self.batch_requests([(self.anonymous_client, 403), (self.guest_client_1, 404), (self.user_client_1, 200), (self.user_client_2, 200), (self.admin_client_1, 404), (self.superuser_client, 200)],
|
||||||
|
reverse('api:shoppinglist-detail', args={self.list_1.id}))
|
||||||
|
|
||||||
|
# TODO add tests for editing
|
@ -26,7 +26,7 @@ from rest_framework.parsers import JSONParser, FileUploadParser, MultiPartParser
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ViewSetMixin
|
from rest_framework.viewsets import ViewSetMixin
|
||||||
|
|
||||||
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest, CustomIsShare
|
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest, CustomIsShare, CustomIsShared
|
||||||
from cookbook.helper.recipe_url_import import get_from_html
|
from cookbook.helper.recipe_url_import import get_from_html
|
||||||
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog, ShoppingListRecipe, ShoppingList, ShoppingListEntry
|
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog, ShoppingListRecipe, ShoppingList, ShoppingListEntry
|
||||||
from cookbook.provider.dropbox import Dropbox
|
from cookbook.provider.dropbox import Dropbox
|
||||||
@ -155,7 +155,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = MealPlan.objects.all()
|
queryset = MealPlan.objects.all()
|
||||||
serializer_class = MealPlanSerializer
|
serializer_class = MealPlanSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated] # TODO fix permissions
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = MealPlan.objects.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).distinct().all()
|
queryset = MealPlan.objects.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).distinct().all()
|
||||||
@ -244,7 +244,7 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
||||||
queryset = ShoppingListRecipe.objects.all()
|
queryset = ShoppingListRecipe.objects.all()
|
||||||
serializer_class = ShoppingListRecipeSerializer
|
serializer_class = ShoppingListRecipeSerializer
|
||||||
permission_classes = [CustomIsUser] # TODO add custom validation
|
permission_classes = [CustomIsUser, ] # TODO add custom validation
|
||||||
|
|
||||||
# TODO custom get qs
|
# TODO custom get qs
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
|||||||
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||||
queryset = ShoppingListEntry.objects.all()
|
queryset = ShoppingListEntry.objects.all()
|
||||||
serializer_class = ShoppingListEntrySerializer
|
serializer_class = ShoppingListEntrySerializer
|
||||||
permission_classes = [CustomIsOwner] # TODO add custom validation
|
permission_classes = [CustomIsOwner, ] # TODO add custom validation
|
||||||
|
|
||||||
# TODO custom get qs
|
# TODO custom get qs
|
||||||
|
|
||||||
@ -260,11 +260,12 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
|||||||
class ShoppingListViewSet(viewsets.ModelViewSet):
|
class ShoppingListViewSet(viewsets.ModelViewSet):
|
||||||
queryset = ShoppingList.objects.all()
|
queryset = ShoppingList.objects.all()
|
||||||
serializer_class = ShoppingListSerializer
|
serializer_class = ShoppingListSerializer
|
||||||
permission_classes = [CustomIsOwner]
|
permission_classes = [CustomIsOwner | CustomIsShared]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.queryset.filter(created_by=self.request.user).all()
|
if self.request.user.is_superuser:
|
||||||
return queryset
|
return self.queryset
|
||||||
|
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).all()
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
autosync = self.request.query_params.get('autosync', None)
|
autosync = self.request.query_params.get('autosync', None)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.db.models import Q
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
@ -49,7 +50,7 @@ def food(request):
|
|||||||
|
|
||||||
@group_required('user')
|
@group_required('user')
|
||||||
def shopping_list(request):
|
def shopping_list(request):
|
||||||
f = ShoppingListFilter(request.GET, queryset=ShoppingList.objects.filter(created_by=request.user).all().order_by('finished', 'created_at'))
|
f = ShoppingListFilter(request.GET, queryset=ShoppingList.objects.filter(Q(created_by=request.user) | Q(shared=request.user)).all().order_by('finished', 'created_at'))
|
||||||
|
|
||||||
table = ShoppingListTable(f.qs)
|
table = ShoppingListTable(f.qs)
|
||||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||||
|
Reference in New Issue
Block a user