Quellcode durchsuchen

Closes #11732: Protect against errant overwriting of data via web UI forms

Jeremy Stretch vor 2 Jahren
Ursprung
Commit
43e6308d90
2 geänderte Dateien mit 44 neuen und 3 gelöschten Zeilen
  1. 2 2
      netbox/netbox/forms/base.py
  2. 42 1
      netbox/utilities/forms/mixins.py

+ 2 - 2
netbox/netbox/forms/base.py

@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
 from extras.models import CustomField, Tag
 from extras.models import CustomField, Tag
-from utilities.forms import BootstrapMixin, CSVModelForm
+from utilities.forms import BootstrapMixin, CSVModelForm, CheckLastUpdatedMixin
 from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
 from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
 
 
 __all__ = (
 __all__ = (
@@ -17,7 +17,7 @@ __all__ = (
 )
 )
 
 
 
 
-class NetBoxModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
+class NetBoxModelForm(BootstrapMixin, CheckLastUpdatedMixin, CustomFieldsMixin, forms.ModelForm):
     """
     """
     Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
     Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
 
 

+ 42 - 1
netbox/utilities/forms/mixins.py

@@ -1,9 +1,13 @@
+import time
+
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 from .widgets import APISelect, APISelectMultiple, ClearableFileInput
 from .widgets import APISelect, APISelectMultiple, ClearableFileInput
 
 
 __all__ = (
 __all__ = (
     'BootstrapMixin',
     'BootstrapMixin',
+    'CheckLastUpdatedMixin',
 )
 )
 
 
 
 
@@ -11,7 +15,6 @@ class BootstrapMixin:
     """
     """
     Add the base Bootstrap CSS classes to form elements.
     Add the base Bootstrap CSS classes to form elements.
     """
     """
-
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
 
 
@@ -60,3 +63,41 @@ class BootstrapMixin:
                     field.widget.attrs['class'] = f'{css} is-invalid'
                     field.widget.attrs['class'] = f'{css} is-invalid'
 
 
         return is_valid
         return is_valid
+
+
+class CheckLastUpdatedMixin(forms.Form):
+    """
+    Checks whether the object being saved has been updated since the form was initialized. If so, validation fails.
+    This prevents a user from inadvertently overwriting any changes made to the object between when the form was
+    initialized and when it was submitted.
+
+    This validation does not apply to newly created objects, or if the `_init_time` field is not present in the form
+    data.
+    """
+    _init_time = forms.DecimalField(
+        initial=time.time,
+        required=False,
+        widget=forms.HiddenInput()
+    )
+
+    def clean(self):
+        super().clean()
+
+        # Skip for absent or newly created instances
+        if not self.instance or not self.instance.pk:
+            return
+
+        # Skip if a form init time has not been specified
+        if not (form_init_time := self.cleaned_data.get('_init_time')):
+            return
+
+        # Skip if the object does not have a last_updated value
+        if not (last_updated := getattr(self.instance, 'last_updated', None)):
+            return
+
+        # Check that the submitted initialization time is not earlier than the object's modification time
+        if form_init_time < last_updated.timestamp():
+            raise forms.ValidationError(_(
+                "This object has been modified since the form was rendered. Please consult the object's change "
+                "log for details."
+            ))