added permission classes for sharing + tests

This commit is contained in:
vabene1111
2020-10-15 23:41:38 +02:00
parent 5c1cecb7e7
commit cd46203d55
4 changed files with 69 additions and 8 deletions

View File

@ -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

View 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

View File

@ -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)

View File

@ -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)