diff --git a/InvenTree/InvenTree/admin.py b/InvenTree/InvenTree/admin.py new file mode 100644 index 0000000000..2d5798a9d1 --- /dev/null +++ b/InvenTree/InvenTree/admin.py @@ -0,0 +1,33 @@ +"""Admin classes""" + +from import_export.resources import ModelResource + + +class InvenTreeResource(ModelResource): + """Custom subclass of the ModelResource class provided by django-import-export" + + Ensures that exported data are escaped to prevent malicious formula injection. + Ref: https://owasp.org/www-community/attacks/CSV_Injection + """ + + def export_resource(self, obj): + """Custom function to override default row export behaviour. + + Specifically, strip illegal leading characters to prevent formula injection + """ + row = super().export_resource(obj) + + illegal_start_vals = ['@', '=', '+', '-', '@', '\t', '\r', '\n'] + + for idx, val in enumerate(row): + if type(val) is str: + val = val.strip() + + # If the value starts with certain 'suspicious' values, remove it! + while len(val) > 0 and val[0] in illegal_start_vals: + # Remove the first character + val = val[1:] + + row[idx] = val + + return row diff --git a/InvenTree/build/admin.py b/InvenTree/build/admin.py index eec7376ede..6f203d071b 100644 --- a/InvenTree/build/admin.py +++ b/InvenTree/build/admin.py @@ -4,15 +4,14 @@ from django.contrib import admin from import_export.admin import ImportExportModelAdmin from import_export.fields import Field -from import_export.resources import ModelResource import import_export.widgets as widgets from build.models import Build, BuildItem - +from InvenTree.admin import InvenTreeResource import part.models -class BuildResource(ModelResource): +class BuildResource(InvenTreeResource): """Class for managing import/export of Build data.""" # For some reason, we need to specify the fields individually for this ModelResource, # but we don't for other ones. diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index d279c9227d..11e9a2720e 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -5,8 +5,8 @@ from django.contrib import admin import import_export.widgets as widgets from import_export.admin import ImportExportModelAdmin from import_export.fields import Field -from import_export.resources import ModelResource +from InvenTree.admin import InvenTreeResource from part.models import Part from .models import (Company, ManufacturerPart, ManufacturerPartAttachment, @@ -14,7 +14,7 @@ from .models import (Company, ManufacturerPart, ManufacturerPartAttachment, SupplierPriceBreak) -class CompanyResource(ModelResource): +class CompanyResource(InvenTreeResource): """Class for managing Company data import/export.""" class Meta: @@ -38,7 +38,7 @@ class CompanyAdmin(ImportExportModelAdmin): ] -class SupplierPartResource(ModelResource): +class SupplierPartResource(InvenTreeResource): """Class for managing SupplierPart data import/export.""" part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) @@ -74,7 +74,7 @@ class SupplierPartAdmin(ImportExportModelAdmin): autocomplete_fields = ('part', 'supplier', 'manufacturer_part',) -class ManufacturerPartResource(ModelResource): +class ManufacturerPartResource(InvenTreeResource): """Class for managing ManufacturerPart data import/export.""" part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) @@ -117,7 +117,7 @@ class ManufacturerPartAttachmentAdmin(ImportExportModelAdmin): autocomplete_fields = ('manufacturer_part',) -class ManufacturerPartParameterResource(ModelResource): +class ManufacturerPartParameterResource(InvenTreeResource): """Class for managing ManufacturerPartParameter data import/export.""" class Meta: @@ -144,7 +144,7 @@ class ManufacturerPartParameterAdmin(ImportExportModelAdmin): autocomplete_fields = ('manufacturer_part',) -class SupplierPriceBreakResource(ModelResource): +class SupplierPriceBreakResource(InvenTreeResource): """Class for managing SupplierPriceBreak data import/export.""" part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart)) diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index be953de701..aa24c095f6 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -5,7 +5,8 @@ from django.contrib import admin import import_export.widgets as widgets from import_export.admin import ImportExportModelAdmin from import_export.fields import Field -from import_export.resources import ModelResource + +from InvenTree.admin import InvenTreeResource from .models import (PurchaseOrder, PurchaseOrderExtraLine, PurchaseOrderLineItem, SalesOrder, SalesOrderAllocation, @@ -97,7 +98,7 @@ class SalesOrderAdmin(ImportExportModelAdmin): autocomplete_fields = ('customer',) -class PurchaseOrderResource(ModelResource): +class PurchaseOrderResource(InvenTreeResource): """Class for managing import / export of PurchaseOrder data.""" # Add number of line items @@ -116,7 +117,7 @@ class PurchaseOrderResource(ModelResource): ] -class PurchaseOrderLineItemResource(ModelResource): +class PurchaseOrderLineItemResource(InvenTreeResource): """Class for managing import / export of PurchaseOrderLineItem data.""" part_name = Field(attribute='part__part__name', readonly=True) @@ -135,7 +136,7 @@ class PurchaseOrderLineItemResource(ModelResource): clean_model_instances = True -class PurchaseOrderExtraLineResource(ModelResource): +class PurchaseOrderExtraLineResource(InvenTreeResource): """Class for managing import / export of PurchaseOrderExtraLine data.""" class Meta(GeneralExtraLineMeta): @@ -144,7 +145,7 @@ class PurchaseOrderExtraLineResource(ModelResource): model = PurchaseOrderExtraLine -class SalesOrderResource(ModelResource): +class SalesOrderResource(InvenTreeResource): """Class for managing import / export of SalesOrder data.""" # Add number of line items @@ -163,7 +164,7 @@ class SalesOrderResource(ModelResource): ] -class SalesOrderLineItemResource(ModelResource): +class SalesOrderLineItemResource(InvenTreeResource): """Class for managing import / export of SalesOrderLineItem data.""" part_name = Field(attribute='part__name', readonly=True) @@ -192,7 +193,7 @@ class SalesOrderLineItemResource(ModelResource): clean_model_instances = True -class SalesOrderExtraLineResource(ModelResource): +class SalesOrderExtraLineResource(InvenTreeResource): """Class for managing import / export of SalesOrderExtraLine data.""" class Meta(GeneralExtraLineMeta): diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 9c1648e616..bf4ae571f5 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -5,14 +5,14 @@ from django.contrib import admin import import_export.widgets as widgets from import_export.admin import ImportExportModelAdmin from import_export.fields import Field -from import_export.resources import ModelResource import part.models as models from company.models import SupplierPart +from InvenTree.admin import InvenTreeResource from stock.models import StockLocation -class PartResource(ModelResource): +class PartResource(InvenTreeResource): """Class for managing Part data import/export.""" # ForeignKey fields @@ -92,7 +92,7 @@ class PartAdmin(ImportExportModelAdmin): ] -class PartCategoryResource(ModelResource): +class PartCategoryResource(InvenTreeResource): """Class for managing PartCategory data import/export.""" parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(models.PartCategory)) @@ -157,7 +157,7 @@ class PartTestTemplateAdmin(admin.ModelAdmin): autocomplete_fields = ('part',) -class BomItemResource(ModelResource): +class BomItemResource(InvenTreeResource): """Class for managing BomItem data import/export.""" level = Field(attribute='level', readonly=True) @@ -266,7 +266,7 @@ class ParameterTemplateAdmin(ImportExportModelAdmin): search_fields = ('name', 'units') -class ParameterResource(ModelResource): +class ParameterResource(InvenTreeResource): """Class for managing PartParameter data import/export.""" part = Field(attribute='part', widget=widgets.ForeignKeyWidget(models.Part)) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index f3c56553c5..270281ae3e 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -5,10 +5,10 @@ from django.contrib import admin import import_export.widgets as widgets from import_export.admin import ImportExportModelAdmin from import_export.fields import Field -from import_export.resources import ModelResource from build.models import Build from company.models import Company, SupplierPart +from InvenTree.admin import InvenTreeResource from order.models import PurchaseOrder, SalesOrder from part.models import Part @@ -16,7 +16,7 @@ from .models import (StockItem, StockItemAttachment, StockItemTestResult, StockItemTracking, StockLocation) -class LocationResource(ModelResource): +class LocationResource(InvenTreeResource): """Class for managing StockLocation data import/export.""" parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(StockLocation)) @@ -68,7 +68,7 @@ class LocationAdmin(ImportExportModelAdmin): ] -class StockItemResource(ModelResource): +class StockItemResource(InvenTreeResource): """Class for managing StockItem data import/export.""" # Custom managers for ForeignKey fields