From cd8c6fa81aaaf166b47c7d66decc755c5c75a7d2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 26 Feb 2021 13:53:23 +1100 Subject: [PATCH] Add RolePermission for API endpoints --- InvenTree/InvenTree/api.py | 81 ++++++++++++++++++++++++++++++++++++++ InvenTree/part/api.py | 9 ++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index c204c0befb..0180ad22b5 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -20,6 +20,8 @@ from rest_framework.views import APIView from .views import AjaxView from .version import inventreeVersion, inventreeInstanceName +from users.models import check_user_role, RuleSet + from plugins import plugins as inventree_plugins @@ -70,6 +72,85 @@ class AttachmentMixin: attachment.save() +class RolePermission(permissions.BasePermission): + """ + Role mixin for API endpoints, allowing us to specify the user "role" + which is required for certain operations. + + Each endpoint can have one or more of the following actions: + - GET + - POST + - PUT + - PATCH + - DELETE + + Specify the required "role" using the role_required attribute. + + e.g. + + role_required = "part" + + The RoleMixin class will then determine if the user has the required permission + to perform the specified action. + + For example, a DELETE action will be rejected unless the user has the "part.remove" permission + + """ + + def has_permission(self, request, view): + """ + Determine if the current user has the specified permissions + """ + + # First, check that the user is authenticated! + auth = permissions.IsAuthenticated() + + if not auth.has_permission(request, view): + return False + + user = request.user + + # Superuser can do it all + if False and user.is_superuser: + return True + + # Map the request method to a permission type + rolemap = { + 'GET': 'view', + 'OPTIONS': 'view', + 'POST': 'add', + 'PUT': 'change', + 'PATCH': 'change', + 'DELETE': 'delete', + } + + permission = rolemap[request.method] + + role = getattr(view, 'role_required', None) + + if not role: + raise AttributeError(f"'role_required' not specified for view {type(view).__name__}") + + roles = [] + + if type(role) is str: + roles = [role] + elif type(role) in [list, tuple]: + roles = role + else: + raise TypeError(f"'role_required' is of incorrect type ({type(role)}) for view {type(view).__name__}") + + for role in roles: + + if role not in RuleSet.RULESET_NAMES: + raise ValueError(f"Role '{role}' is not a valid role") + + if not check_user_role(user, role, permission): + return False + + # All checks passed + return True + class ActionPluginView(APIView): """ Endpoint for running custom action plugins. diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 88d691797e..ded9a64cbb 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -29,7 +29,8 @@ from . import serializers as part_serializers from InvenTree.views import TreeSerializer from InvenTree.helpers import str2bool, isNull -from InvenTree.api import AttachmentMixin +from InvenTree.api import AttachmentMixin, RolePermission + from InvenTree.status_codes import BuildStatus @@ -105,6 +106,12 @@ class CategoryList(generics.ListCreateAPIView): 'description', ] + role_required = 'part' + + permission_classes = [ + RolePermission, + ] + class CategoryDetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of a single PartCategory object """