252 lines
8.4 KiB
Python
252 lines
8.4 KiB
Python
"""
|
|
Source: https://djangosnippets.org/snippets/1703/
|
|
"""
|
|
from cookbook.models import ShareLink
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import user_passes_test
|
|
from django.core.exceptions import ValidationError
|
|
from django.http import HttpResponseRedirect
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.utils.translation import gettext as _
|
|
from rest_framework import permissions
|
|
from rest_framework.permissions import SAFE_METHODS
|
|
|
|
|
|
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')
|
|
if 'user' in groups_required:
|
|
groups_allowed = groups_allowed + ('admin',)
|
|
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
|
|
if getattr(obj, 'get_owner', None):
|
|
return obj.get_owner() == user
|
|
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
|
|
:param recipe: recipe object
|
|
:param share: share uuid
|
|
:return: true if a share link with the given recipe and uuid exists
|
|
"""
|
|
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):
|
|
"""
|
|
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):
|
|
return has_group_permission(u, groups_required)
|
|
|
|
return user_passes_test(in_groups, login_url='view_no_group')
|
|
|
|
|
|
class GroupRequiredMixin(object):
|
|
"""
|
|
groups_required - list of strings, required param
|
|
"""
|
|
|
|
groups_required = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
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!') # noqa: E501
|
|
)
|
|
return HttpResponseRedirect(reverse_lazy('index'))
|
|
|
|
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class OwnerRequiredMixin(object):
|
|
|
|
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('account_login') + '?next=' + request.path
|
|
)
|
|
else:
|
|
if not is_object_owner(request.user, self.get_object()):
|
|
messages.add_message(
|
|
request,
|
|
messages.ERROR,
|
|
_('You cannot interact with this object as it is not owned by you!') # noqa: E501
|
|
)
|
|
return HttpResponseRedirect(reverse('index'))
|
|
|
|
return super(OwnerRequiredMixin, self) \
|
|
.dispatch(request, *args, **kwargs)
|
|
|
|
|
|
# Django Rest Framework Permission classes
|
|
|
|
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 it is not owned by you!') # noqa: E501
|
|
|
|
def has_permission(self, request, view):
|
|
return request.user.is_authenticated
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
return is_object_owner(request.user, obj)
|
|
|
|
|
|
# TODO function duplicate/too similar name
|
|
class CustomIsShared(permissions.BasePermission):
|
|
"""
|
|
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 it is not owned by you!') # noqa: E501
|
|
|
|
def has_permission(self, request, view):
|
|
return request.user.is_authenticated
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
return is_object_shared(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):
|
|
return has_group_permission(request.user, ['guest'])
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
return 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):
|
|
return has_group_permission(request.user, ['user'])
|
|
|
|
|
|
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):
|
|
return has_group_permission(request.user, ['admin'])
|
|
|
|
|
|
class CustomIsShare(permissions.BasePermission):
|
|
"""
|
|
Custom permission class for django rest framework views
|
|
verifies the requesting user provided a valid share link
|
|
"""
|
|
message = _('You do not have the required permissions to view this page!')
|
|
|
|
def has_permission(self, request, view):
|
|
return request.method in SAFE_METHODS and 'pk' in view.kwargs
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
share = request.query_params.get('share', None)
|
|
if share:
|
|
return share_link_valid(obj, share)
|
|
return False
|