From b362c6a9678d93e73070703cb292f41e918d7065 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 21 Apr 2020 13:41:38 -0400 Subject: [PATCH] Fixes #2994: Prevent modifying termination points of existing cable to ensure end-to-end path integrity --- docs/release-notes/version-2.8.md | 1 + netbox/dcim/models/__init__.py | 32 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index 25a992d95..e29ee1aa1 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -8,6 +8,7 @@ ### Bug Fixes +* [#2994](https://github.com/netbox-community/netbox/issues/2994) - Prevent modifying termination points of existing cable to ensure end-to-end path integrity * [#4361](https://github.com/netbox-community/netbox/issues/4361) - Fix Type of `connection_state` in Swagger schema * [#4388](https://github.com/netbox-community/netbox/issues/4388) - Fix detection of connected endpoints when connecting rear ports * [#4489](https://github.com/netbox-community/netbox/issues/4489) - Fix display of parent/child role on device type view diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 2c1940296..e0953839a 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -2070,6 +2070,20 @@ class Cable(ChangeLoggedModel): # A copy of the PK to be used by __str__ in case the object is deleted self._pk = self.pk + @classmethod + def from_db(cls, db, field_names, values): + """ + Cache the original A and B terminations of existing Cable instances for later reference inside clean(). + """ + instance = super().from_db(db, field_names, values) + + instance._orig_termination_a_type = instance.termination_a_type + instance._orig_termination_a_id = instance.termination_a_id + instance._orig_termination_b_type = instance.termination_b_type + instance._orig_termination_b_id = instance.termination_b_id + + return instance + def __str__(self): return self.label or '#{}'.format(self._pk) @@ -2098,6 +2112,24 @@ class Cable(ChangeLoggedModel): 'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type) }) + # If editing an existing Cable instance, check that neither termination has been modified. + if self.pk: + err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.' + if ( + self.termination_a_type != self._orig_termination_a_type or + self.termination_a_id != self._orig_termination_a_id + ): + raise ValidationError({ + 'termination_a': err_msg + }) + if ( + self.termination_b_type != self._orig_termination_b_type or + self.termination_b_id != self._orig_termination_b_id + ): + raise ValidationError({ + 'termination_b': err_msg + }) + type_a = self.termination_a_type.model type_b = self.termination_b_type.model