mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Sanitize search text in bootstrap table (#3609)
* Sanitize search text in bootstrap table * Clean search query on the server side before rendering search page template - Refactor existing sanitizing code into functions * Make ASCII and Unicode cleaning optional
This commit is contained in:
parent
e8621a97bc
commit
8fa67b8671
@ -20,7 +20,9 @@ from django.http import StreamingHttpResponse
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
import regex
|
||||||
import requests
|
import requests
|
||||||
|
from bleach import clean
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@ -856,6 +858,55 @@ def clean_decimal(number):
|
|||||||
return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()
|
return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()
|
||||||
|
|
||||||
|
|
||||||
|
def strip_html_tags(value: str, raise_error=True, field_name=None):
|
||||||
|
"""Strip HTML tags from an input string using the bleach library.
|
||||||
|
|
||||||
|
If raise_error is True, a ValidationError will be thrown if HTML tags are detected
|
||||||
|
"""
|
||||||
|
|
||||||
|
cleaned = clean(
|
||||||
|
value,
|
||||||
|
strip=True,
|
||||||
|
tags=[],
|
||||||
|
attributes=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add escaped characters back in
|
||||||
|
replacements = {
|
||||||
|
'>': '>',
|
||||||
|
'<': '<',
|
||||||
|
'&': '&',
|
||||||
|
}
|
||||||
|
|
||||||
|
for o, r in replacements.items():
|
||||||
|
cleaned = cleaned.replace(o, r)
|
||||||
|
|
||||||
|
# If the length changed, it means that HTML tags were removed!
|
||||||
|
if len(cleaned) != len(value) and raise_error:
|
||||||
|
|
||||||
|
field = field_name or 'non_field_errors'
|
||||||
|
|
||||||
|
raise ValidationError({
|
||||||
|
field: [_("Remove HTML tags from this value")]
|
||||||
|
})
|
||||||
|
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
def remove_non_printable_characters(value: str, remove_ascii=True, remove_unicode=True):
|
||||||
|
"""Remove non-printable / control characters from the provided string"""
|
||||||
|
|
||||||
|
if remove_ascii:
|
||||||
|
# Remove ASCII control characters
|
||||||
|
cleaned = regex.sub(u'[\x01-\x1F]+', '', value)
|
||||||
|
|
||||||
|
if remove_unicode:
|
||||||
|
# Remove Unicode control characters
|
||||||
|
cleaned = regex.sub(u'[^\P{C}]+', '', value)
|
||||||
|
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = 'object_id'):
|
def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = 'object_id'):
|
||||||
"""Lookup method for the GenericForeignKey fields.
|
"""Lookup method for the GenericForeignKey fields.
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
"""Mixins for (API) views in the whole project."""
|
"""Mixins for (API) views in the whole project."""
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
import regex
|
|
||||||
from bleach import clean
|
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status
|
||||||
from rest_framework.exceptions import ValidationError
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from InvenTree.helpers import remove_non_printable_characters, strip_html_tags
|
||||||
|
|
||||||
|
|
||||||
class CleanMixin():
|
class CleanMixin():
|
||||||
"""Model mixin class which cleans inputs using the Mozilla bleach tools."""
|
"""Model mixin class which cleans inputs using the Mozilla bleach tools."""
|
||||||
@ -49,34 +46,8 @@ class CleanMixin():
|
|||||||
Ref: https://github.com/mozilla/bleach/issues/192
|
Ref: https://github.com/mozilla/bleach/issues/192
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cleaned = clean(
|
cleaned = strip_html_tags(data, field_name=field)
|
||||||
data,
|
cleaned = remove_non_printable_characters(cleaned)
|
||||||
strip=True,
|
|
||||||
tags=[],
|
|
||||||
attributes=[],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add escaped characters back in
|
|
||||||
replacements = {
|
|
||||||
'>': '>',
|
|
||||||
'<': '<',
|
|
||||||
'&': '&',
|
|
||||||
}
|
|
||||||
|
|
||||||
for o, r in replacements.items():
|
|
||||||
cleaned = cleaned.replace(o, r)
|
|
||||||
|
|
||||||
# If the length changed, it means that HTML tags were removed!
|
|
||||||
if len(cleaned) != len(data):
|
|
||||||
raise ValidationError({
|
|
||||||
field: [_("Remove HTML tags from this value")]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Remove ASCII control characters
|
|
||||||
cleaned = regex.sub(u'[\x01-\x1F]+', '', cleaned)
|
|
||||||
|
|
||||||
# Remove Unicode control characters
|
|
||||||
cleaned = regex.sub(u'[^\P{C}]+', '', cleaned)
|
|
||||||
|
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ from part.models import PartCategory
|
|||||||
from users.models import RuleSet, check_user_role
|
from users.models import RuleSet, check_user_role
|
||||||
|
|
||||||
from .forms import EditUserForm, SetPasswordForm
|
from .forms import EditUserForm, SetPasswordForm
|
||||||
|
from .helpers import remove_non_printable_characters, strip_html_tags
|
||||||
|
|
||||||
|
|
||||||
def auth_request(request):
|
def auth_request(request):
|
||||||
@ -600,6 +601,9 @@ class SearchView(TemplateView):
|
|||||||
|
|
||||||
query = request.POST.get('search', '')
|
query = request.POST.get('search', '')
|
||||||
|
|
||||||
|
query = strip_html_tags(query, raise_error=False)
|
||||||
|
query = remove_non_printable_characters(query)
|
||||||
|
|
||||||
context['query'] = query
|
context['query'] = query
|
||||||
|
|
||||||
return super(TemplateView, self).render_to_response(context)
|
return super(TemplateView, self).render_to_response(context)
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var search_text = sanitizeInputString("{{ query|escapejs }}");
|
||||||
|
|
||||||
function addItem(label, title, icon, options) {
|
function addItem(label, title, icon, options) {
|
||||||
|
|
||||||
// Construct a "badge" to add to the sidebar item
|
// Construct a "badge" to add to the sidebar item
|
||||||
@ -85,7 +87,7 @@
|
|||||||
"{% url 'api-part-list' %}",
|
"{% url 'api-part-list' %}",
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
},
|
},
|
||||||
checkbox: false,
|
checkbox: false,
|
||||||
disableFilters: true,
|
disableFilters: true,
|
||||||
@ -96,7 +98,7 @@
|
|||||||
|
|
||||||
loadPartCategoryTable($("#table-category"), {
|
loadPartCategoryTable($("#table-category"), {
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -107,7 +109,7 @@
|
|||||||
"{% url 'api-manufacturer-part-list' %}",
|
"{% url 'api-manufacturer-part-list' %}",
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
part_detail: true,
|
part_detail: true,
|
||||||
supplier_detail: true,
|
supplier_detail: true,
|
||||||
manufacturer_detail: true
|
manufacturer_detail: true
|
||||||
@ -122,7 +124,7 @@
|
|||||||
"{% url 'api-supplier-part-list' %}",
|
"{% url 'api-supplier-part-list' %}",
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
part_detail: true,
|
part_detail: true,
|
||||||
supplier_detail: true,
|
supplier_detail: true,
|
||||||
manufacturer_detail: true
|
manufacturer_detail: true
|
||||||
@ -141,7 +143,7 @@
|
|||||||
loadBuildTable('#table-build-order', {
|
loadBuildTable('#table-build-order', {
|
||||||
locale: '{{ request.LANGUAGE_CODE }}',
|
locale: '{{ request.LANGUAGE_CODE }}',
|
||||||
params: {
|
params: {
|
||||||
original_search: '{{ query }}',
|
original_search: search_text,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,7 +158,7 @@
|
|||||||
filterKey: 'stocksearch',
|
filterKey: 'stocksearch',
|
||||||
url: "{% url 'api-stock-list' %}",
|
url: "{% url 'api-stock-list' %}",
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
part_detail: true,
|
part_detail: true,
|
||||||
location_detail: true
|
location_detail: true
|
||||||
}
|
}
|
||||||
@ -167,7 +169,7 @@
|
|||||||
loadStockLocationTable($("#table-location"), {
|
loadStockLocationTable($("#table-location"), {
|
||||||
filterKey: 'locationsearch',
|
filterKey: 'locationsearch',
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -180,8 +182,8 @@
|
|||||||
|
|
||||||
loadCompanyTable('#table-manufacturer', "{% url 'api-company-list' %}", {
|
loadCompanyTable('#table-manufacturer', "{% url 'api-company-list' %}", {
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
is_manufacturer: "true",
|
is_manufacturer: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,8 +192,8 @@
|
|||||||
|
|
||||||
loadCompanyTable('#table-supplier', "{% url 'api-company-list' %}", {
|
loadCompanyTable('#table-supplier', "{% url 'api-company-list' %}", {
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
is_supplier: "true",
|
is_supplier: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -199,7 +201,7 @@
|
|||||||
|
|
||||||
loadPurchaseOrderTable('#table-purchase-order', {
|
loadPurchaseOrderTable('#table-purchase-order', {
|
||||||
params: {
|
params: {
|
||||||
original_search: '{{ query }}',
|
original_search: search_text,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -210,8 +212,8 @@
|
|||||||
|
|
||||||
loadCompanyTable('#table-customer', "{% url 'api-company-list' %}", {
|
loadCompanyTable('#table-customer', "{% url 'api-company-list' %}", {
|
||||||
params: {
|
params: {
|
||||||
original_search: "{{ query }}",
|
original_search: search_text,
|
||||||
is_customer: "true",
|
is_customer: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -219,7 +221,7 @@
|
|||||||
|
|
||||||
loadSalesOrderTable('#table-sales-orders', {
|
loadSalesOrderTable('#table-sales-orders', {
|
||||||
params: {
|
params: {
|
||||||
original_search: '{{ query }}',
|
original_search: search_text,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -230,7 +232,7 @@
|
|||||||
enableSidebar(
|
enableSidebar(
|
||||||
'search',
|
'search',
|
||||||
{
|
{
|
||||||
hide_toggle: 'true',
|
hide_toggle: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -346,7 +346,9 @@ function convertQueryParameters(params, filters) {
|
|||||||
if ('original_search' in params) {
|
if ('original_search' in params) {
|
||||||
var search = params['search'] || '';
|
var search = params['search'] || '';
|
||||||
|
|
||||||
params['search'] = search + ' ' + params['original_search'];
|
var clean_search = sanitizeInputString(search + ' ' + params['original_search']);
|
||||||
|
|
||||||
|
params['search'] = clean_search;
|
||||||
|
|
||||||
delete params['original_search'];
|
delete params['original_search'];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user