added permission classes for sharing + tests
This commit is contained in:
@ -72,6 +72,23 @@ def is_object_owner(user, obj):
|
||||
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):
|
||||
"""
|
||||
Verifies the validity of a share uuid
|
||||
@ -147,6 +164,21 @@ class CustomIsOwner(permissions.BasePermission):
|
||||
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):
|
||||
"""
|
||||
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.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.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
|
||||
@ -155,7 +155,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
queryset = MealPlan.objects.all()
|
||||
serializer_class = MealPlanSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
permission_classes = [permissions.IsAuthenticated] # TODO fix permissions
|
||||
|
||||
def get_queryset(self):
|
||||
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):
|
||||
queryset = ShoppingListRecipe.objects.all()
|
||||
serializer_class = ShoppingListRecipeSerializer
|
||||
permission_classes = [CustomIsUser] # TODO add custom validation
|
||||
permission_classes = [CustomIsUser, ] # TODO add custom validation
|
||||
|
||||
# TODO custom get qs
|
||||
|
||||
@ -252,7 +252,7 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
||||
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||
queryset = ShoppingListEntry.objects.all()
|
||||
serializer_class = ShoppingListEntrySerializer
|
||||
permission_classes = [CustomIsOwner] # TODO add custom validation
|
||||
permission_classes = [CustomIsOwner, ] # TODO add custom validation
|
||||
|
||||
# TODO custom get qs
|
||||
|
||||
@ -260,11 +260,12 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||
class ShoppingListViewSet(viewsets.ModelViewSet):
|
||||
queryset = ShoppingList.objects.all()
|
||||
serializer_class = ShoppingListSerializer
|
||||
permission_classes = [CustomIsOwner]
|
||||
permission_classes = [CustomIsOwner | CustomIsShared]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.queryset.filter(created_by=self.request.user).all()
|
||||
return queryset
|
||||
if self.request.user.is_superuser:
|
||||
return self.queryset
|
||||
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).all()
|
||||
|
||||
def get_serializer_class(self):
|
||||
autosync = self.request.query_params.get('autosync', None)
|
||||
|
@ -1,6 +1,7 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.db.models.functions import Lower
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext as _
|
||||
@ -49,7 +50,7 @@ def food(request):
|
||||
|
||||
@group_required('user')
|
||||
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)
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
Reference in New Issue
Block a user