diff --git a/horilla/models.py b/horilla/models.py index e840b8157..82fadc302 100644 --- a/horilla/models.py +++ b/horilla/models.py @@ -8,9 +8,12 @@ the application, such as tracking creation and modification timestamps and user information, audit logging, and active/inactive status management. """ +import re + from auditlog.models import AuditlogHistoryField from auditlog.registry import auditlog from django.contrib.auth.models import User +from django.core.exceptions import ValidationError from django.db import models from django.db.models.fields.files import FieldFile from django.urls import reverse @@ -34,6 +37,14 @@ def url(self: FieldFile): setattr(FieldFile, "url", url) +def has_xss(value): + """Basic check for common XSS patterns.""" + if not isinstance(value, str): + return False + xss_pattern = re.compile(r"<.*?script.*?>|javascript:|on\w+=", re.IGNORECASE) + return bool(xss_pattern.search(value)) + + class HorillaModel(models.Model): """ An abstract base model that includes common fields and functionalities @@ -80,6 +91,8 @@ class HorillaModel(models.Model): Override the save method to automatically set the created_by and modified_by fields based on the current request user. """ + self.full_clean() + request = getattr(_thread_locals, "request", None) if request: @@ -99,6 +112,26 @@ class HorillaModel(models.Model): super(HorillaModel, self).save(*args, **kwargs) + def clean_fields(self, exclude=None): + errors = {} + + # Get the list of fields to exclude from validation + total_exclude = set(exclude or []).union(getattr(self, "xss_exempt_fields", [])) + + for field in self._meta.get_fields(): + if ( + isinstance(field, (models.CharField, models.TextField)) + and field.name not in total_exclude + ): + value = getattr(self, field.name, None) + if value and has_xss(value): + errors[field.name] = ValidationError( + "Potential XSS content detected." + ) + + if errors: + raise ValidationError(errors) + def get_verbose_name(self): return self._meta.verbose_name diff --git a/horilla_automations/models.py b/horilla_automations/models.py index e8b845a4f..35596541b 100644 --- a/horilla_automations/models.py +++ b/horilla_automations/models.py @@ -75,6 +75,12 @@ class MailAutomation(HorillaModel): condition = models.TextField() + xss_exempt_fields = [ + "condition_html", + "condition", + "condition_querystring", + ] + def save(self, *args, **kwargs): if not self.pk: self.method_title = self.title.replace(" ", "_").lower()