improved permission handlin
This commit is contained in:
parent
2904d5938d
commit
df8170fa55
@ -4,7 +4,7 @@ Source: https://djangosnippets.org/snippets/1703/
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
@ -13,7 +13,15 @@ from rest_framework import permissions
|
||||
from cookbook.models import ShareLink
|
||||
|
||||
|
||||
# Helper Functions
|
||||
|
||||
def get_allowed_groups(groups_required):
|
||||
"""
|
||||
Builds a list of all groups equal or higher to the provided groups
|
||||
This means checking for guest will also allow admins to access
|
||||
:param groups_required: list or tuple of groups
|
||||
:return: tuple of groups
|
||||
"""
|
||||
groups_allowed = tuple(groups_required)
|
||||
if 'guest' in groups_required:
|
||||
groups_allowed = groups_allowed + ('user', 'admin')
|
||||
@ -22,15 +30,70 @@ def get_allowed_groups(groups_required):
|
||||
return groups_allowed
|
||||
|
||||
|
||||
def has_group_permission(user, groups):
|
||||
"""
|
||||
Tests if a given user is member of a certain group (or any higher group)
|
||||
Superusers always bypass permission checks. Unauthenticated users cant be member of any
|
||||
group thus always return false.
|
||||
:param user: django auth user object
|
||||
:param groups: list or tuple of groups the user should be checked for
|
||||
:return: True if user is in allowed groups, false otherwise
|
||||
"""
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
groups_allowed = get_allowed_groups(groups)
|
||||
if user.is_authenticated:
|
||||
if user.is_superuser | bool(user.groups.filter(name__in=groups_allowed)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_object_owner(user, obj):
|
||||
"""
|
||||
Tests if a given user is the owner of a given object
|
||||
test performed by checking user against the objects user and create_by field (if exists)
|
||||
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 owner of object, false otherwise
|
||||
"""
|
||||
# TODO this could be improved/cleaned up by adding get_owner methods to all models that allow owner checks
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if owner := getattr(obj, 'created_by', None):
|
||||
return owner == user
|
||||
if owner := getattr(obj, 'user', None):
|
||||
return owner == user
|
||||
return False
|
||||
|
||||
|
||||
def share_link_valid(recipe, share):
|
||||
"""
|
||||
Verifies the validity of a share uuid
|
||||
:param recipe: recipe object
|
||||
:param share: share uuid
|
||||
:return: true if a share link with the given recipe and uuid exists, false otherwise
|
||||
"""
|
||||
try:
|
||||
return True if ShareLink.objects.filter(recipe=recipe, uuid=share).exists() else False
|
||||
except ValidationError:
|
||||
return False
|
||||
|
||||
|
||||
# Django Views
|
||||
|
||||
def group_required(*groups_required):
|
||||
"""Requires user membership in at least one of the groups passed in."""
|
||||
"""
|
||||
Decorator that tests the requesting user to be member of at least one of the provided groups
|
||||
or higher level groups
|
||||
:param groups_required: list of required groups
|
||||
:return: true if member of group, false otherwise
|
||||
"""
|
||||
|
||||
def in_groups(u):
|
||||
groups_allowed = get_allowed_groups(groups_required)
|
||||
if u.is_authenticated:
|
||||
if u.is_superuser | bool(u.groups.filter(name__in=groups_allowed)):
|
||||
return True
|
||||
return False
|
||||
return has_group_permission(u, groups_required)
|
||||
|
||||
return user_passes_test(in_groups, login_url='index')
|
||||
|
||||
@ -43,18 +106,10 @@ class GroupRequiredMixin(object):
|
||||
groups_required = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('login'))
|
||||
else:
|
||||
if not request.user.is_superuser:
|
||||
group_allowed = get_allowed_groups(self.groups_required)
|
||||
user_groups = []
|
||||
for group in request.user.groups.values_list('name', flat=True):
|
||||
user_groups.append(group)
|
||||
if len(set(user_groups).intersection(group_allowed)) <= 0:
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('index'))
|
||||
if not has_group_permission(request.user, self.groups_required):
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('index'))
|
||||
|
||||
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
@ -65,38 +120,55 @@ class OwnerRequiredMixin(object):
|
||||
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('login'))
|
||||
else:
|
||||
obj = self.get_object()
|
||||
if not (obj.created_by == request.user or request.user.is_superuser):
|
||||
if not is_object_owner(request.user, self.get_object()):
|
||||
messages.add_message(request, messages.ERROR, _('You cannot interact with this object as its not owned by you!'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
def share_link_valid(recipe, share):
|
||||
"""
|
||||
Verifies if a share uuid is valid for a given recipe
|
||||
"""
|
||||
try:
|
||||
return True if ShareLink.objects.filter(recipe=recipe, uuid=share).exists() else False
|
||||
except ValidationError:
|
||||
return False
|
||||
# Django Rest Framework Permission classes
|
||||
|
||||
|
||||
class DRFOwnerPermissions(permissions.BasePermission):
|
||||
class CustomIsOwner(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies user has ownership over object
|
||||
(either user or created_by or user is request user)
|
||||
"""
|
||||
message = _('You cannot interact with this object as its not owned by you!')
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
#if request.user.is_superuser:
|
||||
# return True
|
||||
if owner := getattr(obj, 'created_by', None):
|
||||
return owner == request.user
|
||||
if owner := getattr(obj, 'user', None):
|
||||
return owner == request.user
|
||||
return False
|
||||
return is_object_owner(request.user, obj)
|
||||
|
||||
|
||||
class CustomIsGuest(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: guest
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['guest'])
|
||||
|
||||
|
||||
class CustomIsUser(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: user
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['guest'])
|
||||
|
||||
|
||||
class CustomIsAdmin(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: admin
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['guest'])
|
||||
|
@ -15,7 +15,7 @@ from rest_framework import viewsets, permissions
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListModelMixin
|
||||
|
||||
from cookbook.helper.permission_helper import group_required, DRFOwnerPermissions
|
||||
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin
|
||||
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@ -47,12 +47,9 @@ class UserNameViewSet(viewsets.ModelViewSet):
|
||||
|
||||
|
||||
class UserPreferenceViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
Update user preference settings
|
||||
"""
|
||||
queryset = UserPreference.objects.all()
|
||||
serializer_class = UserPreferenceSerializer
|
||||
permission_classes = [DRFOwnerPermissions, ]
|
||||
permission_classes = [CustomIsOwner, ]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if UserPreference.objects.filter(user=self.request.user).exists():
|
||||
@ -60,23 +57,20 @@ class UserPreferenceViewSet(viewsets.ModelViewSet):
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
def get_queryset(self):
|
||||
# if self.request.user.is_superuser:
|
||||
# return UserPreference.objects.all()
|
||||
return UserPreference.objects.filter(user=self.request.user).all()
|
||||
if self.request.user.is_superuser:
|
||||
return self.queryset
|
||||
return self.queryset.filter(user=self.request.user)
|
||||
|
||||
|
||||
class RecipeBookViewSet(RetrieveModelMixin, UpdateModelMixin, ListModelMixin, viewsets.GenericViewSet):
|
||||
"""
|
||||
Update user preference settings
|
||||
"""
|
||||
queryset = RecipeBook.objects.all()
|
||||
serializer_class = RecipeBookSerializer
|
||||
permission_classes = [DRFOwnerPermissions, ]
|
||||
permission_classes = [CustomIsOwner, CustomIsAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_superuser:
|
||||
return RecipeBook.objects.all()
|
||||
return RecipeBook.objects.filter(created_by=self.request.user).all()
|
||||
return self.queryset
|
||||
return self.queryset.filter(created_by=self.request.user)
|
||||
|
||||
|
||||
class MealPlanViewSet(viewsets.ModelViewSet):
|
||||
|
Loading…
Reference in New Issue
Block a user