Просмотр исходного кода

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

Jeremy Stretch 2 лет назад
Родитель
Сommit
43e6308d90
2 измененных файлов с 44 добавлено и 3 удалено
  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.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
 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
 
 __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.
 

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

@@ -1,9 +1,13 @@
+import time
+
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 from .widgets import APISelect, APISelectMultiple, ClearableFileInput
 
 __all__ = (
     'BootstrapMixin',
+    'CheckLastUpdatedMixin',
 )
 
 
@@ -11,7 +15,6 @@ class BootstrapMixin:
     """
     Add the base Bootstrap CSS classes to form elements.
     """
-
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
@@ -60,3 +63,41 @@ class BootstrapMixin:
                     field.widget.attrs['class'] = f'{css} is-invalid'
 
         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."
+            ))