fixed scoping and permissions for tokens

This commit is contained in:
vabene1111
2022-08-04 18:33:45 +02:00
parent 3f77b73a61
commit 3ec4afb02f
5 changed files with 90 additions and 53 deletions

View File

@@ -12,6 +12,7 @@ from zipfile import ZipFile
import requests
import validators
from PIL import UnidentifiedImageError
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from django.contrib import messages
@@ -25,16 +26,15 @@ from django.db.models.functions import Coalesce, Lower
from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
from PIL import UnidentifiedImageError
from oauth2_provider.models import AccessToken
from recipe_scrapers import scrape_html, scrape_me
from recipe_scrapers import scrape_me
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
from requests.exceptions import MissingSchema
from rest_framework import decorators, status, viewsets
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.decorators import api_view, permission_classes
from rest_framework.exceptions import APIException, PermissionDenied
@@ -51,10 +51,10 @@ from cookbook.helper import recipe_url_import as helper
from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner,
CustomIsOwnerReadOnly, CustomIsShare, CustomIsShared,
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner,
CustomIsOwnerReadOnly, CustomIsShared,
CustomIsSpaceOwner, CustomIsUser, group_required,
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission, CustomUserPermission)
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission, CustomUserPermission, CustomTokenHasReadWriteScope)
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup
from cookbook.helper.scrapers.scrapers import text_scraper
@@ -364,7 +364,7 @@ class UserViewSet(viewsets.ModelViewSet):
"""
queryset = User.objects
serializer_class = UserSerializer
permission_classes = [CustomUserPermission]
permission_classes = [CustomUserPermission & CustomTokenHasReadWriteScope]
http_method_names = ['get', 'patch']
def get_queryset(self):
@@ -382,14 +382,14 @@ class UserViewSet(viewsets.ModelViewSet):
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [CustomIsAdmin]
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
http_method_names = ['get', ]
class SpaceViewSet(viewsets.ModelViewSet):
queryset = Space.objects
serializer_class = SpaceSerializer
permission_classes = [CustomIsOwner & CustomIsAdmin]
permission_classes = [CustomIsOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
http_method_names = ['get', 'patch']
def get_queryset(self):
@@ -399,7 +399,7 @@ class SpaceViewSet(viewsets.ModelViewSet):
class UserSpaceViewSet(viewsets.ModelViewSet):
queryset = UserSpace.objects
serializer_class = UserSpaceSerializer
permission_classes = [CustomIsSpaceOwner | CustomIsOwnerReadOnly]
permission_classes = [(CustomIsSpaceOwner | CustomIsOwnerReadOnly) & CustomTokenHasReadWriteScope]
http_method_names = ['get', 'patch', 'delete']
def destroy(self, request, *args, **kwargs):
@@ -417,7 +417,7 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
class UserPreferenceViewSet(viewsets.ModelViewSet):
queryset = UserPreference.objects
serializer_class = UserPreferenceSerializer
permission_classes = [CustomIsOwner, ]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
http_method_names = ['get', 'patch', ]
def get_queryset(self):
@@ -429,7 +429,7 @@ class StorageViewSet(viewsets.ModelViewSet):
# TODO handle delete protect error and adjust test
queryset = Storage.objects
serializer_class = StorageSerializer
permission_classes = [CustomIsAdmin, ]
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
@@ -438,7 +438,7 @@ class StorageViewSet(viewsets.ModelViewSet):
class SyncViewSet(viewsets.ModelViewSet):
queryset = Sync.objects
serializer_class = SyncSerializer
permission_classes = [CustomIsAdmin, ]
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
@@ -447,7 +447,7 @@ class SyncViewSet(viewsets.ModelViewSet):
class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
queryset = SyncLog.objects
serializer_class = SyncLogSerializer
permission_classes = [CustomIsAdmin, ]
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -457,7 +457,7 @@ class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
class SupermarketViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Supermarket.objects
serializer_class = SupermarketSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
@@ -468,7 +468,7 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
queryset = SupermarketCategory.objects
model = SupermarketCategory
serializer_class = SupermarketCategorySerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
@@ -478,7 +478,7 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = SupermarketCategoryRelation.objects
serializer_class = SupermarketCategoryRelationSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -490,7 +490,7 @@ class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
queryset = Keyword.objects
model = Keyword
serializer_class = KeywordSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
@@ -498,14 +498,14 @@ class UnitViewSet(viewsets.ModelViewSet, MergeMixin, FuzzyFilterMixin):
queryset = Unit.objects
model = Unit
serializer_class = UnitSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
queryset = FoodInheritField.objects
serializer_class = FoodInheritFieldSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
# exclude fields not yet implemented
@@ -517,7 +517,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
queryset = Food.objects
model = Food
serializer_class = FoodSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -565,7 +565,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = RecipeBook.objects
serializer_class = RecipeBookSerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
@@ -584,7 +584,7 @@ class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
"""
queryset = RecipeBookEntry.objects
serializer_class = RecipeBookEntrySerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
queryset = self.queryset.filter(
@@ -612,7 +612,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
"""
queryset = MealPlan.objects
serializer_class = MealPlanSerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
queryset = self.queryset.filter(
@@ -637,7 +637,7 @@ class MealTypeViewSet(viewsets.ModelViewSet):
"""
queryset = MealType.objects
serializer_class = MealTypeSerializer
permission_classes = [CustomIsOwner]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
def get_queryset(self):
queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(
@@ -648,7 +648,7 @@ class MealTypeViewSet(viewsets.ModelViewSet):
class IngredientViewSet(viewsets.ModelViewSet):
queryset = Ingredient.objects
serializer_class = IngredientSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_serializer_class(self):
@@ -672,7 +672,7 @@ class IngredientViewSet(viewsets.ModelViewSet):
class StepViewSet(viewsets.ModelViewSet):
queryset = Step.objects
serializer_class = StepSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
query_params = [
QueryParam(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'),
@@ -716,7 +716,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
queryset = Recipe.objects
serializer_class = RecipeSerializer
# TODO split read and write permission for meal plan guest
permission_classes = [CustomRecipePermission]
permission_classes = [CustomRecipePermission & CustomTokenHasReadWriteScope]
pagination_class = RecipePagination
query_params = [
@@ -917,7 +917,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
queryset = ShoppingListRecipe.objects
serializer_class = ShoppingListRecipeSerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(
@@ -933,7 +933,7 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
queryset = ShoppingListEntry.objects
serializer_class = ShoppingListEntrySerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
query_params = [
QueryParam(name='id',
description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'),
@@ -972,7 +972,7 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
class ShoppingListViewSet(viewsets.ModelViewSet):
queryset = ShoppingList.objects
serializer_class = ShoppingListSerializer
permission_classes = [CustomIsOwner | CustomIsShared]
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(
@@ -994,7 +994,7 @@ class ShoppingListViewSet(viewsets.ModelViewSet):
class ViewLogViewSet(viewsets.ModelViewSet):
queryset = ViewLog.objects
serializer_class = ViewLogSerializer
permission_classes = [CustomIsOwner]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -1005,7 +1005,7 @@ class ViewLogViewSet(viewsets.ModelViewSet):
class CookLogViewSet(viewsets.ModelViewSet):
queryset = CookLog.objects
serializer_class = CookLogSerializer
permission_classes = [CustomIsOwner]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -1015,7 +1015,7 @@ class CookLogViewSet(viewsets.ModelViewSet):
class ImportLogViewSet(viewsets.ModelViewSet):
queryset = ImportLog.objects
serializer_class = ImportLogSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -1025,7 +1025,7 @@ class ImportLogViewSet(viewsets.ModelViewSet):
class ExportLogViewSet(viewsets.ModelViewSet):
queryset = ExportLog.objects
serializer_class = ExportLogSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
@@ -1035,7 +1035,7 @@ class ExportLogViewSet(viewsets.ModelViewSet):
class BookmarkletImportViewSet(viewsets.ModelViewSet):
queryset = BookmarkletImport.objects
serializer_class = BookmarkletImportSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_serializer_class(self):
if self.action == 'list':
@@ -1049,7 +1049,7 @@ class BookmarkletImportViewSet(viewsets.ModelViewSet):
class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = UserFile.objects
serializer_class = UserFileSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
parser_classes = [MultiPartParser]
def get_queryset(self):
@@ -1060,7 +1060,7 @@ class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Automation.objects
serializer_class = AutomationSerializer
permission_classes = [CustomIsUser]
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space).all()
@@ -1070,7 +1070,7 @@ class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = InviteLink.objects
serializer_class = InviteLinkSerializer
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin]
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
if is_space_owner(self.request.user, self.request.space):
@@ -1083,7 +1083,7 @@ class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class CustomFilterViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = CustomFilter.objects
serializer_class = CustomFilterSerializer
permission_classes = [CustomIsOwner]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
def get_queryset(self):
self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
@@ -1094,7 +1094,7 @@ class CustomFilterViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class AccessTokenViewSet(viewsets.ModelViewSet):
queryset = AccessToken.objects
serializer_class = AccessTokenSerializer
permission_classes = [CustomIsOwner]
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(user=self.request.user)
@@ -1114,16 +1114,22 @@ class CustomAuthToken(ObtainAuthToken):
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
if token := AccessToken.objects.filter(scope__contains='read').filter(scope__contains='write').first():
access_token = token
else:
access_token = AccessToken.objects.create(user=request.user, token=f'tda_{str(uuid.uuid4()).replace("-","_")}', expires=(timezone.now() + timezone.timedelta(days=365*5)), scope='read write app')
return Response({
'token': token.key,
'id': access_token.id,
'token': access_token.token,
'scope': access_token.scope,
'expires': access_token.expires,
'user_id': user.pk,
})
@api_view(['POST'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsUser])
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
# TODO add rate limiting
def recipe_from_source(request):
"""
@@ -1211,7 +1217,7 @@ def recipe_from_source(request):
@api_view(['GET'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsAdmin])
@permission_classes([CustomIsAdmin & CustomTokenHasReadWriteScope])
# TODO add rate limiting
def reset_food_inheritance(request):
"""
@@ -1227,7 +1233,7 @@ def reset_food_inheritance(request):
@api_view(['GET'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsAdmin])
@permission_classes([CustomIsAdmin & CustomTokenHasReadWriteScope])
# TODO add rate limiting
def switch_active_space(request, space_id):
"""
@@ -1247,7 +1253,7 @@ def switch_active_space(request, space_id):
@api_view(['GET'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsUser])
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
def download_file(request, file_id):
"""
function to download a user file securely (wrapping as zip to prevent any context based XSS problems)
@@ -1272,7 +1278,7 @@ def download_file(request, file_id):
@api_view(['POST'])
# @schema(AutoSchema()) #TODO add proper schema
@permission_classes([CustomIsUser])
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
def import_files(request):
"""
function to handle files passed by application importer