mirror of
https://github.com/inventree/InvenTree.git
synced 2026-10-14 14:11:55 +00:00
Check user permissions before locating stock (#13000)
* Check user permissions before locating stock * Update unit test
This commit is contained in:
1 parent
c3917c343b
commit
445fa3e125
3 files changed
+65
-5
No files matched your search
@@ -1,7 +1,12 @@
|
||||
"""API for location plugins."""
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import NotFound, ParseError, ValidationError
|
||||
from rest_framework.exceptions import (
|
||||
NotFound,
|
||||
ParseError,
|
||||
PermissionDenied,
|
||||
ValidationError,
|
||||
)
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
@@ -11,6 +16,7 @@ from InvenTree.tasks import offload_task
|
||||
from plugin import PluginMixinEnum, registry
|
||||
from plugin.registry import call_plugin_function
|
||||
from stock.models import StockItem, StockLocation
|
||||
from users.permissions import check_user_permission
|
||||
|
||||
|
||||
class LocatePluginSerializer(serializers.Serializer):
|
||||
@@ -58,6 +64,12 @@ class LocatePluginView(GenericAPIView):
|
||||
|
||||
# StockItem takes priority
|
||||
if item_pk:
|
||||
# Check permissions *before* lookup, to prevent existence leaks
|
||||
if not check_user_permission(request.user, StockItem, 'view'):
|
||||
raise PermissionDenied(
|
||||
'User does not have permission to view stock items'
|
||||
)
|
||||
|
||||
try:
|
||||
StockItem.objects.get(pk=item_pk)
|
||||
|
||||
@@ -76,10 +88,16 @@ class LocatePluginView(GenericAPIView):
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
raise NotFound(f"StockItem matching PK '{item_pk}' not found")
|
||||
except Exception:
|
||||
log_error('locate_stock_item', plugin=plugin.slug)
|
||||
return ValidationError('Error locating stock item')
|
||||
log_error('locate_stock_item', plugin=plugin)
|
||||
raise ValidationError('Error locating stock item')
|
||||
|
||||
elif location_pk:
|
||||
# Check permissions *before* lookup, to prevent existence leaks
|
||||
if not check_user_permission(request.user, StockLocation, 'view'):
|
||||
raise PermissionDenied(
|
||||
'User does not have permission to view stock locations'
|
||||
)
|
||||
|
||||
try:
|
||||
StockLocation.objects.get(pk=location_pk)
|
||||
|
||||
@@ -98,7 +116,7 @@ class LocatePluginView(GenericAPIView):
|
||||
except (ValueError, StockLocation.DoesNotExist):
|
||||
raise NotFound(f"StockLocation matching PK '{location_pk}' not found")
|
||||
except Exception:
|
||||
log_error('locate_stock_location', plugin=plugin.slug)
|
||||
return ValidationError('Error locating stock location')
|
||||
log_error('locate_stock_location', plugin=plugin)
|
||||
raise ValidationError('Error locating stock location')
|
||||
else:
|
||||
raise ParseError("Must supply either 'item' or 'location' parameter")
|
||||
@@ -20,6 +20,8 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
|
||||
fixtures = ['category', 'part', 'location', 'stock']
|
||||
|
||||
roles = ['stock.view', 'stock_location.view']
|
||||
|
||||
def test_installed(self):
|
||||
"""Test that a locate plugin is actually installed."""
|
||||
plugins = registry.with_mixin(PluginMixinEnum.LOCATE)
|
||||
@@ -69,6 +71,39 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
f"StockLocation matching PK '{pk}' not found", str(response.data)
|
||||
)
|
||||
|
||||
def test_locate_permissions(self):
|
||||
"""Test that the locate endpoint enforces role permissions.
|
||||
|
||||
A user without the required view permission must be denied,
|
||||
for both existing and non-existent targets (no existence oracle).
|
||||
"""
|
||||
url = reverse('api-locate-plugin')
|
||||
|
||||
item = StockItem.objects.get(pk=1)
|
||||
item.set_metadata('located', False)
|
||||
|
||||
self.clearRoles()
|
||||
|
||||
for pk in [1, 99999]:
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': pk}, expected_code=403)
|
||||
self.post(
|
||||
url, {'plugin': 'samplelocate', 'location': pk}, expected_code=403
|
||||
)
|
||||
|
||||
# The plugin must not have been triggered
|
||||
item.refresh_from_db()
|
||||
self.assertFalse(item.metadata['located'])
|
||||
|
||||
# Item permission does not grant location access (and vice versa)
|
||||
self.assignRole('stock.view')
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': 1}, expected_code=200)
|
||||
self.post(url, {'plugin': 'samplelocate', 'location': 1}, expected_code=403)
|
||||
|
||||
self.clearRoles()
|
||||
self.assignRole('stock_location.view')
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': 1}, expected_code=403)
|
||||
self.post(url, {'plugin': 'samplelocate', 'location': 1}, expected_code=200)
|
||||
|
||||
def test_locate_item(self):
|
||||
"""Test that the plugin correctly 'locates' a StockItem.
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@ class SampleLocatePluginTests(InvenTreeAPITestCase):
|
||||
# Right plugin - no search item
|
||||
self.post(url, {'plugin': 'samplelocate'}, expected_code=400)
|
||||
|
||||
# No permissions
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': 1}, expected_code=403)
|
||||
|
||||
self.assignRole('stock.view')
|
||||
|
||||
# Right plugin - wrong reference
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': 999}, expected_code=404)
|
||||
|
||||
@@ -39,6 +44,8 @@ class SampleLocatePluginTests(InvenTreeAPITestCase):
|
||||
self.post(url, {'plugin': 'samplelocate', 'item': 1}, expected_code=200)
|
||||
|
||||
# Right plugin - wrong reference
|
||||
self.post(url, {'plugin': 'samplelocate', 'location': 999}, expected_code=403)
|
||||
self.assignRole('stock_location.view')
|
||||
self.post(url, {'plugin': 'samplelocate', 'location': 999}, expected_code=404)
|
||||
|
||||
# Right plugin - right reference
|
||||
|
||||
Reference in new issue
Block a user