Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/administration/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ Device.objects.filter(
### Creating and Modifying Objects

The same sort of logic is in play when a user attempts to create or modify an object in NetBox, with a twist. Once validation has completed, NetBox starts an atomic database transaction to facilitate the change, and the object is created or saved normally. Next, still within the transaction, NetBox issues a second query to retrieve the newly created/updated object, filtering the restricted queryset with the object's primary key. If this query fails to return the object, NetBox knows that the new revision does not match the constraints imposed by the permission. The transaction is then rolled back, leaving the database in its original state prior to the change, and the user is informed of the violation.

#### Preserving Restricted Related Values

When editing an object that references related objects the user is not permitted to view (for example a tag, tenant, or custom field value constrained away by a permission), those current values are shown read-only on the edit form and preserved when the form is saved. Single-value fields are disabled entirely; multi-value fields (such as tags) remain editable for the values the user can see, while the restricted current values are rendered as disabled options. This prevents a user from unintentionally clearing related objects they cannot see, without exposing any other restricted objects or allowing the restricted values to be replaced.
8 changes: 8 additions & 0 deletions netbox/circuits/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ class CircuitTerminationForm(NetBoxModelForm):
FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')),
)

restricted_related_selectors = {
'termination': {'path': 'termination', 'lock_fields': ('termination_type',)},
}

class Meta:
model = CircuitTermination
fields = [
Expand Down Expand Up @@ -307,6 +311,10 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
FieldSet('group', 'member_type', 'member', 'priority', 'tags', name=_('Group Assignment')),
)

restricted_related_selectors = {
'member': {'path': 'member', 'lock_fields': ('member_type',)},
}

class Meta:
model = CircuitGroupAssignment
fields = [
Expand Down
4 changes: 4 additions & 0 deletions netbox/dcim/forms/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class ScopedForm(forms.Form):
selector=True
)

restricted_related_selectors = {
'scope': {'path': 'scope', 'lock_fields': ('scope_type',)},
}

def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {})
Expand Down
6 changes: 6 additions & 0 deletions netbox/dcim/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,12 @@ class MACAddressForm(PrimaryModelForm):
),
)

restricted_related_selectors = {
# The selectors are stored as the assigned_object GenericForeignKey; the model picks the matching one.
'interface': {'path': 'assigned_object', 'model': Interface},
'vminterface': {'path': 'assigned_object', 'model': VMInterface},
}

class Meta:
model = MACAddress
fields = [
Expand Down
5 changes: 5 additions & 0 deletions netbox/extras/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,11 @@ class EventRuleForm(OwnerMixin, NetBoxModelForm):
FieldSet('action_type', 'action_choice', 'action_data', name=_('Action')),
)

# action_object_type/action_object_id are recomputed in clean() from these two fields.
restricted_related_selectors = {
'action_choice': {'path': 'action_object', 'lock_fields': ('action_type',)},
}

class Meta:
model = EventRule
fields = (
Expand Down
15 changes: 15 additions & 0 deletions netbox/ipam/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
FieldSet('nat_inside', name=_('NAT IP (Inside)')),
)

restricted_related_selectors = {
# The selectors are stored as the assigned_object GenericForeignKey; the model picks the matching one.
'interface': {'path': 'assigned_object', 'model': Interface},
'vminterface': {'path': 'assigned_object', 'model': VMInterface},
'fhrpgroup': {'path': 'assigned_object', 'model': FHRPGroup},
}

class Meta:
model = IPAddress
fields = [
Expand Down Expand Up @@ -651,6 +658,10 @@ class Meta:
'tags',
]

restricted_related_selectors = {
'scope': {'path': 'scope', 'lock_fields': ('scope_type',)},
}

def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {})
Expand Down Expand Up @@ -813,6 +824,10 @@ class ServiceForm(PrimaryModelForm):
),
)

restricted_related_selectors = {
'parent': {'path': 'parent', 'lock_fields': ('parent_object_type',)},
}

class Meta:
model = Service
fields = [
Expand Down
Loading