mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #11732: Protect against errant overwriting of data via web UI forms
This commit is contained in:
@ -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.
|
||||
|
||||
|
@ -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."
|
||||
))
|
||||
|
Reference in New Issue
Block a user