Enhance search functionality with case-insensitive title search and security improvements
- Add case-insensitive search across all fields (inhalt, titel, geltungsbereich) - Include Vorgabe.titel field in search scope for better coverage - Implement comprehensive input validation against SQL injection and XSS - Add German error messages for validation failures - Escape search terms in templates to prevent XSS attacks - Add input length limits and character validation - Preserve user input on validation errors for better UX
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mb-4">Suche</h1>
|
<h1 class="mb-4">Suche</h1>
|
||||||
|
|
||||||
|
{% if error_message %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<strong>Fehler:</strong> {{ error_message }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Search form -->
|
<!-- Search form -->
|
||||||
<form action="." method="post">
|
<form action="." method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -13,7 +19,9 @@
|
|||||||
id="query"
|
id="query"
|
||||||
name="q"
|
name="q"
|
||||||
placeholder="Suchbegriff eingeben …"
|
placeholder="Suchbegriff eingeben …"
|
||||||
required>
|
value="{{ search_term|default:'' }}"
|
||||||
|
required
|
||||||
|
maxlength="200">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Suchen</button>
|
<button type="submit" class="btn btn-primary">Suchen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.html import escape
|
||||||
|
import re
|
||||||
from abschnitte.utils import render_textabschnitte
|
from abschnitte.utils import render_textabschnitte
|
||||||
from dokumente.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
|
from dokumente.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich, Vorgabe
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
import datetime
|
import datetime
|
||||||
import pprint
|
import pprint
|
||||||
@@ -9,23 +12,60 @@ def startseite(request):
|
|||||||
standards=list(Dokument.objects.filter(aktiv=True))
|
standards=list(Dokument.objects.filter(aktiv=True))
|
||||||
return render(request, 'startseite.html', {"dokumente":standards,})
|
return render(request, 'startseite.html', {"dokumente":standards,})
|
||||||
|
|
||||||
|
def validate_search_input(search_term):
|
||||||
|
"""
|
||||||
|
Validate search input to prevent SQL injection and XSS
|
||||||
|
"""
|
||||||
|
if not search_term:
|
||||||
|
raise ValidationError("Suchbegriff darf nicht leer sein")
|
||||||
|
|
||||||
|
# Remove any HTML tags to prevent XSS
|
||||||
|
search_term = re.sub(r'<[^>]*>', '', search_term)
|
||||||
|
|
||||||
|
# Allow only alphanumeric characters, spaces, and basic punctuation
|
||||||
|
# This prevents SQL injection and other malicious input
|
||||||
|
if not re.match(r'^[a-zA-Z0-9äöüÄÖÜß\s\-\.\,\:\;\!\?\(\)\[\]\{\}\"\']+$', search_term):
|
||||||
|
raise ValidationError("Ungültige Zeichen im Suchbegriff")
|
||||||
|
|
||||||
|
# Limit length to prevent DoS attacks
|
||||||
|
if len(search_term) > 200:
|
||||||
|
raise ValidationError("Suchbegriff ist zu lang")
|
||||||
|
|
||||||
|
return search_term.strip()
|
||||||
|
|
||||||
def search(request):
|
def search(request):
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render(request, 'search.html')
|
return render(request, 'search.html')
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
suchbegriff=request.POST.get("q")
|
raw_search_term = request.POST.get("q", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
suchbegriff = validate_search_input(raw_search_term)
|
||||||
|
except ValidationError as e:
|
||||||
|
return render(request, 'search.html', {
|
||||||
|
'error_message': str(e),
|
||||||
|
'search_term': escape(raw_search_term)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Escape the search term for display in templates
|
||||||
|
safe_search_term = escape(suchbegriff)
|
||||||
result= {"all": {}}
|
result= {"all": {}}
|
||||||
qs = VorgabeKurztext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
qs = VorgabeKurztext.objects.filter(inhalt__icontains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
||||||
result["kurztext"] = {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
result["kurztext"] = {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
||||||
qs = VorgabeLangtext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
qs = VorgabeLangtext.objects.filter(inhalt__icontains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
|
||||||
result['langtext']= {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
result['langtext']= {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
|
||||||
|
qs = Vorgabe.objects.filter(titel__icontains=suchbegriff).exclude(gueltigkeit_bis__lt=datetime.date.today())
|
||||||
|
result['titel']= {k: list(g) for k, g in groupby(qs, key=lambda o: o.dokument)}
|
||||||
for r in result.keys():
|
for r in result.keys():
|
||||||
for s in result[r].keys():
|
for s in result[r].keys():
|
||||||
result["all"][s] = set(result[r][s])
|
if r == 'titel':
|
||||||
|
result["all"][s] = set(result["all"].get(s, set()) | set(result[r][s]))
|
||||||
|
else:
|
||||||
|
result["all"][s] = set(result["all"].get(s, set()) | set(result[r][s]))
|
||||||
result["geltungsbereich"]={}
|
result["geltungsbereich"]={}
|
||||||
geltungsbereich=set(list([x.geltungsbereich for x in Geltungsbereich.objects.filter(inhalt__contains=suchbegriff)]))
|
geltungsbereich=set(list([x.geltungsbereich for x in Geltungsbereich.objects.filter(inhalt__icontains=suchbegriff)]))
|
||||||
for s in geltungsbereich:
|
for s in geltungsbereich:
|
||||||
result["geltungsbereich"][s]=render_textabschnitte(s.geltungsbereich_set.order_by("order"))
|
result["geltungsbereich"][s]=render_textabschnitte(s.geltungsbereich_set.order_by("order"))
|
||||||
pprint.pp (result)
|
pprint.pp (result)
|
||||||
return render(request,"results.html",{"suchbegriff":suchbegriff,"resultat":result})
|
return render(request,"results.html",{"suchbegriff":safe_search_term,"resultat":result})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user