From 358c46434956f2f2fe01463f21b9ee508607be4f Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 8 Aug 2026 16:17:50 +1200 Subject: [PATCH] Barcode API bugs (#12565) * Add explicit permission checks for barcode scanning - Previous role_required attribute did not do anything * Additional unit tests --- .../InvenTree/plugin/base/barcodes/api.py | 25 +++++-- .../plugin/base/barcodes/test_barcode.py | 74 +++++++++++++++++++ .../suppliers/test_supplier_barcodes.py | 17 ++++- 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/backend/InvenTree/plugin/base/barcodes/api.py b/src/backend/InvenTree/plugin/base/barcodes/api.py index af84a5b4e8..9e32fd3113 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/api.py +++ b/src/backend/InvenTree/plugin/base/barcodes/api.py @@ -389,8 +389,6 @@ class BarcodePOAllocate(BarcodeView): - A SupplierPart object """ - role_required = ['purchase_order.add'] - serializer_class = barcode_serializers.BarcodePOAllocateSerializer def get_supplier_part( @@ -444,6 +442,13 @@ class BarcodePOAllocate(BarcodeView): def handle_barcode(self, barcode: str, request, **kwargs): """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 purchase_order = kwargs.get('purchase_order') @@ -493,12 +498,17 @@ class BarcodePOReceive(BarcodeView): - location: The destination location for the received item (optional) """ - role_required = ['purchase_order.add'] - serializer_class = barcode_serializers.BarcodePOReceiveSerializer def handle_barcode(self, barcode: str, request, **kwargs): """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) # Extract optional fields from the dataset @@ -667,8 +677,6 @@ class BarcodeSOAllocate(BarcodeView): - Quantity """ - role_required = ['sales_order.add'] - serializer_class = barcode_serializers.BarcodeSOAllocateSerializer def get_line_item(self, stock_item, **kwargs): @@ -736,6 +744,11 @@ class BarcodeSOAllocate(BarcodeView): line: SalesOrderLineItem 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) response = self.scan_barcode(barcode, request, **kwargs) diff --git a/src/backend/InvenTree/plugin/base/barcodes/test_barcode.py b/src/backend/InvenTree/plugin/base/barcodes/test_barcode.py index 667ef8d4ed..71ab47135a 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/test_barcode.py +++ b/src/backend/InvenTree/plugin/base/barcodes/test_barcode.py @@ -256,6 +256,50 @@ class BarcodeAPITest(InvenTreeAPITestCase): 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): """Unit tests for the barcode endpoint for allocating items to a sales order.""" @@ -309,6 +353,36 @@ class SOAllocateTest(InvenTreeAPITestCase): 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): """Test when no data is provided.""" result = self.postBarcode('', expected_code=400) diff --git a/src/backend/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py b/src/backend/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py index b8dea2cd17..10909ff6aa 100644 --- a/src/backend/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py +++ b/src/backend/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py @@ -183,7 +183,7 @@ class SupplierBarcodeTests(InvenTreeAPITestCase): class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): """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): """Create supplier part and purchase_order.""" @@ -257,6 +257,21 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): mouser_plugin = registry.get_plugin('mouserplugin') 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): """Test receiving an item from a barcode.""" url = reverse('api-barcode-po-receive')