Barcode API bugs (#12565)

* Add explicit permission checks for barcode scanning

- Previous role_required attribute did not do anything

* Additional unit tests
This commit is contained in:
Oliver
2026-08-08 14:17:50 +10:00
committed by GitHub
parent ad818889df
commit 358c464349
3 changed files with 109 additions and 7 deletions
@@ -389,8 +389,6 @@ class BarcodePOAllocate(BarcodeView):
- A SupplierPart object - A SupplierPart object
""" """
role_required = ['purchase_order.add']
serializer_class = barcode_serializers.BarcodePOAllocateSerializer serializer_class = barcode_serializers.BarcodePOAllocateSerializer
def get_supplier_part( def get_supplier_part(
@@ -444,6 +442,13 @@ class BarcodePOAllocate(BarcodeView):
def handle_barcode(self, barcode: str, request, **kwargs): def handle_barcode(self, barcode: str, request, **kwargs):
"""Scan the provided barcode data.""" """Scan the provided barcode data."""
if not check_user_permission(request.user, order.models.PurchaseOrder, 'add'):
raise PermissionDenied({
'error': _(
'You do not have the required permissions for purchase orders'
)
})
# The purchase order is provided as part of the request # The purchase order is provided as part of the request
purchase_order = kwargs.get('purchase_order') purchase_order = kwargs.get('purchase_order')
@@ -493,12 +498,17 @@ class BarcodePOReceive(BarcodeView):
- location: The destination location for the received item (optional) - location: The destination location for the received item (optional)
""" """
role_required = ['purchase_order.add']
serializer_class = barcode_serializers.BarcodePOReceiveSerializer serializer_class = barcode_serializers.BarcodePOReceiveSerializer
def handle_barcode(self, barcode: str, request, **kwargs): def handle_barcode(self, barcode: str, request, **kwargs):
"""Handle a barcode scan for a purchase order item.""" """Handle a barcode scan for a purchase order item."""
if not check_user_permission(request.user, order.models.PurchaseOrder, 'add'):
raise PermissionDenied({
'error': _(
'You do not have the required permissions for purchase orders'
)
})
logger.debug("BarcodePOReceive: scanned barcode - '%s'", barcode) logger.debug("BarcodePOReceive: scanned barcode - '%s'", barcode)
# Extract optional fields from the dataset # Extract optional fields from the dataset
@@ -667,8 +677,6 @@ class BarcodeSOAllocate(BarcodeView):
- Quantity - Quantity
""" """
role_required = ['sales_order.add']
serializer_class = barcode_serializers.BarcodeSOAllocateSerializer serializer_class = barcode_serializers.BarcodeSOAllocateSerializer
def get_line_item(self, stock_item, **kwargs): def get_line_item(self, stock_item, **kwargs):
@@ -736,6 +744,11 @@ class BarcodeSOAllocate(BarcodeView):
line: SalesOrderLineItem ID value (optional) line: SalesOrderLineItem ID value (optional)
shipment: SalesOrderShipment ID value (optional) shipment: SalesOrderShipment ID value (optional)
""" """
if not check_user_permission(request.user, order.models.SalesOrder, 'add'):
raise PermissionDenied({
'error': _('You do not have the required permissions for sales orders')
})
logger.debug("BarcodeSOAllocate: scanned barcode - '%s'", barcode) logger.debug("BarcodeSOAllocate: scanned barcode - '%s'", barcode)
response = self.scan_barcode(barcode, request, **kwargs) response = self.scan_barcode(barcode, request, **kwargs)
@@ -256,6 +256,50 @@ class BarcodeAPITest(InvenTreeAPITestCase):
self.assertIn('object does not exist', str(response.data[k])) self.assertIn('object does not exist', str(response.data[k]))
class POAllocateTest(InvenTreeAPITestCase):
"""Unit tests for the barcode endpoint for allocating items to a purchase order."""
fixtures = ['category', 'company', 'part', 'location', 'stock']
roles = ['part.view']
@classmethod
def setUpTestData(cls):
"""Setup for all tests."""
super().setUpTestData()
cls.supplier = company.models.Company.objects.filter(is_supplier=True).first()
cls.purchase_order = order.models.PurchaseOrder.objects.create(
supplier=cls.supplier
)
def postBarcode(self, barcode, expected_code=None, **kwargs):
"""Post barcode and return results."""
data = {'barcode': barcode, **kwargs}
response = self.post(
reverse('api-barcode-po-allocate'), data=data, expected_code=expected_code
)
return response.data
def test_permission_denied(self):
"""A user without the 'purchase_order.add' role cannot allocate against a PO via barcode scan."""
response = self.postBarcode(
'abcde', purchase_order=self.purchase_order.pk, expected_code=403
)
self.assertIn('do not have the required permissions', str(response['error']))
# Once the role is granted, the request proceeds past the permission check
# (still fails with 400, as 'abcde' does not match any barcode plugin -
# but that is a *different* error to the permission check above)
self.assignRole('purchase_order.add')
self.postBarcode(
'abcde', purchase_order=self.purchase_order.pk, expected_code=400
)
class SOAllocateTest(InvenTreeAPITestCase): class SOAllocateTest(InvenTreeAPITestCase):
"""Unit tests for the barcode endpoint for allocating items to a sales order.""" """Unit tests for the barcode endpoint for allocating items to a sales order."""
@@ -309,6 +353,36 @@ class SOAllocateTest(InvenTreeAPITestCase):
return response.data return response.data
def test_permission_denied(self):
"""A user without the 'sales_order.add' role cannot allocate stock via barcode scan."""
self.clearRoles()
response = self.post(
reverse('api-barcode-so-allocate'),
data={
'barcode': self.stock_item.format_barcode(),
'sales_order': self.sales_order.pk,
},
expected_code=403,
)
self.assertIn(
'do not have the required permissions', str(response.data['error'])
)
# No allocation should have been created
self.assertEqual(self.line_item.allocated_quantity(), 0)
# Restore the required roles - the request should now succeed
self.assignRole('sales_order.add')
self.assignRole('stock.view')
self.postBarcode(
self.stock_item.format_barcode(),
sales_order=self.sales_order.pk,
expected_code=200,
)
def test_no_data(self): def test_no_data(self):
"""Test when no data is provided.""" """Test when no data is provided."""
result = self.postBarcode('', expected_code=400) result = self.postBarcode('', expected_code=400)
@@ -183,7 +183,7 @@ class SupplierBarcodeTests(InvenTreeAPITestCase):
class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase):
"""Tests barcode scanning to receive a purchase order item.""" """Tests barcode scanning to receive a purchase order item."""
roles = ['stock.view', 'stock_location.view'] roles = ['stock.view', 'stock_location.view', 'purchase_order.add']
def setUp(self): def setUp(self):
"""Create supplier part and purchase_order.""" """Create supplier part and purchase_order."""
@@ -257,6 +257,21 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase):
mouser_plugin = registry.get_plugin('mouserplugin') mouser_plugin = registry.get_plugin('mouserplugin')
mouser_plugin.set_setting('SUPPLIER_ID', mouser.pk) mouser_plugin.set_setting('SUPPLIER_ID', mouser.pk)
def test_permission_denied(self):
"""A user without the 'purchase_order.add' role cannot receive stock via barcode scan."""
url = reverse('api-barcode-po-receive')
self.clearRoles()
response = self.post(url, data={'barcode': DIGIKEY_BARCODE}, expected_code=403)
self.assertIn(
'do not have the required permissions', str(response.data['error'])
)
# No stock should have been received
self.assertFalse(StockItem.objects.filter(part__name='Test Part').exists())
def test_receive(self): def test_receive(self):
"""Test receiving an item from a barcode.""" """Test receiving an item from a barcode."""
url = reverse('api-barcode-po-receive') url = reverse('api-barcode-po-receive')