It should be possible to express an OR case in a permissions constraint #3938

Closed
opened 2025-12-29 18:32:10 +01:00 by adam · 4 comments
Owner

Originally created by @cpmills1975 on GitHub (Aug 4, 2020).

Originally assigned to: @jeremystretch on GitHub.

Environment

  • Python version: 3.6.8
  • NetBox version: 2.9-beta1

Proposed Functionality

Some method of expressing an OR clause in a permissions constraint should be possible.

Use Case

In a dev instance I've created a couple of devices. 'Device 001' has a tenant 'Tenant A' assigned to it, 'Device Two' does not. I have a group called Group A and a set of permissions that grant this group access to devices assigned to Tenant A using the following constraint: { "tenant__slug": "tenant-a" }. I have granted permissions to view the device interfaces with a constraint as follows: { "device__tenant__slug": "tenant-a" }. I have two cables between 'Device One' and 'Device Two' and by virtue of this permission, when viewing Device One, I can see that they are both connected. One connection was created from Device One, the other from Device Two so both have different 'A' ends.

I've been able to grant permission for the member of Group A to view the details of one of the connections using a constraint on dcim > cables as follows: { "_termination_a_device__tenant__slug": "tenant-a" } (incidentally, should I be able to do this? the preceding underscore makes it feel like this relationship should be private in some way). Despite being able to see the connection from the device page for 'Device 001', one of the cables cannot be traced as the A end of the cable is 'Device 002' which the member does not have access to and it does not match the above constraint. The cables view shows only one of the two cables.

I could resolve this, I guess, by adding a constraint similar to the above but for _termination_b_device__tenant__slug, but needing a whole new permission rule for this feels clunky.

Supporting OR clauses in the permissions model would resolve this.

To incite discussion, how about the constraint clause requiring something like the following?

OR Clause:
{ "ANY": [ "_termination_a_device__tenant__slug": "tenant-a", "_termination_b_device__tenant__slug": "tenant-a" ] }

AND Clause:
{ "ALL": [ "_termination_a_device__tenant__slug": "tenant-a", "_termination_a_device__device_role__slug: "management" ] }

Even better would be some way of making this recursive such that nested clauses would work.

Database Changes

Not sure whether any changes would be required to the permissions model to support this.

External Dependencies

None

Originally created by @cpmills1975 on GitHub (Aug 4, 2020). Originally assigned to: @jeremystretch on GitHub. <!-- NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED. This form is only for proposing specific new features or enhancements. If you have a general idea or question, please post to our mailing list instead of opening an issue: https://groups.google.com/forum/#!forum/netbox-discuss NOTE: Due to an excessive backlog of feature requests, we are not currently accepting any proposals which significantly extend NetBox's feature scope. Please describe the environment in which you are running NetBox. Be sure that you are running an unmodified instance of the latest stable release before submitting a bug report. --> ### Environment * Python version: 3.6.8 * NetBox version: 2.9-beta1 <!-- Describe in detail the new functionality you are proposing. Include any specific changes to work flows, data models, or the user interface. --> ### Proposed Functionality Some method of expressing an OR clause in a permissions constraint should be possible. <!-- Convey an exa mple use case for your proposed feature. Write from the perspective of a NetBox user who would benefit from the proposed functionality and describe how. ---> ### Use Case In a dev instance I've created a couple of devices. 'Device 001' has a tenant 'Tenant A' assigned to it, 'Device Two' does not. I have a group called Group A and a set of permissions that grant this group access to devices assigned to Tenant A using the following constraint: { "tenant__slug": "tenant-a" }. I have granted permissions to view the device interfaces with a constraint as follows: { "device__tenant__slug": "tenant-a" }. I have two cables between 'Device One' and 'Device Two' and by virtue of this permission, when viewing Device One, I can see that they are both connected. One connection was created from Device One, the other from Device Two so both have different 'A' ends. I've been able to grant permission for the member of Group A to view the details of one of the connections using a constraint on dcim > cables as follows: { "_termination_a_device__tenant__slug": "tenant-a" } (incidentally, should I be able to do this? the preceding underscore makes it feel like this relationship should be private in some way). Despite being able to see the connection from the device page for 'Device 001', one of the cables cannot be traced as the A end of the cable is 'Device 002' which the member does not have access to and it does not match the above constraint. The cables view shows only one of the two cables. I could resolve this, I guess, by adding a constraint similar to the above but for _termination_b_device__tenant__slug, but needing a whole new permission rule for this feels clunky. Supporting OR clauses in the permissions model would resolve this. To incite discussion, how about the constraint clause requiring something like the following? OR Clause: { "ANY": [ "_termination_a_device__tenant__slug": "tenant-a", "_termination_b_device__tenant__slug": "tenant-a" ] } AND Clause: { "ALL": [ "_termination_a_device__tenant__slug": "tenant-a", "_termination_a_device__device_role__slug: "management" ] } Even better would be some way of making this recursive such that nested clauses would work. <!-- Note any changes to the database schema necessary to support the new feature. For example, does the proposal require adding a new model or field? (Not all new features require database changes.) ---> ### Database Changes Not sure whether any changes would be required to the permissions model to support this. <!-- List any new dependencies on external libraries or services that this new feature would introduce. For example, does the proposal require the installation of a new Python package? (Not all new features introduce new dependencies.) --> ### External Dependencies None
adam added the status: acceptedtype: featurebeta labels 2025-12-29 18:32:10 +01:00
adam closed this issue 2025-12-29 18:32:10 +01:00
Author
Owner

@jeremystretch commented on GitHub (Aug 4, 2020):

The docs currently say:

To achieve a logical OR with a different set of constraints, simply create another permission assignment for the same model and user/group.

IMO this is the optimal approach, because whereas ANDing multiple attributes results in finer scope, ORing them tends to greatly increase the scope of granted permissions, and it's more manageable and more efficient to document each one as a separate entity.

As for your particular use case, it's probably preferable to introduce a more robust filtering mechanism for the cable model. (As you've noted, _termination_a_device and _termination_b_device aren't intended to be referenced directly.) Something like termination__device=device, which would match either end of the cable. #3592 may be relevant here.

@jeremystretch commented on GitHub (Aug 4, 2020): The docs currently say: > To achieve a logical OR with a different set of constraints, simply create another permission assignment for the same model and user/group. IMO this is the optimal approach, because whereas ANDing multiple attributes results in _finer_ scope, ORing them tends to greatly increase the scope of granted permissions, and it's more manageable and more efficient to document each one as a separate entity. As for your particular use case, it's probably preferable to introduce a more robust filtering mechanism for the cable model. (As you've noted, `_termination_a_device` and `_termination_b_device` aren't intended to be referenced directly.) Something like `termination__device=device`, which would match either end of the cable. #3592 may be relevant here.
Author
Owner

@cpmills1975 commented on GitHub (Aug 4, 2020):

A generic termination__device filter would be perfect and avoid the need for two separate rules for the A end and the B end. How does the device page obtain its list of connections? One assumes not by running the same filters as I defined above as presumably one connection would have remained invisible? Would I be better filtering my cables some other way?

@cpmills1975 commented on GitHub (Aug 4, 2020): A generic termination__device filter would be perfect and avoid the need for two separate rules for the A end and the B end. How does the device page obtain its list of connections? One assumes not by running the same filters as I defined above as presumably one connection would have remained invisible? Would I be better filtering my cables some other way?
Author
Owner

@jeremystretch commented on GitHub (Aug 6, 2020):

Looking at this further, I don't think my suggestion above is going to be feasible with the current cabling model. However, we could extend ObjectPermission to accept a list of JSON objects, rather than a single object, to be ORed using Django's Q objects. Using your case as an example, you'd end up with something like this for the constraints definition:

[
    {"_termination_a_device__tenant__slug": "tenant-a"},
    {"_termination_b_device__tenant__slug": "tenant-a"}
]

This would result in the queryset being constructed as:

Cable.objects.filter(
    Q(_termination_a_device__tenant__slug='tenant-a') |
    Q(_termination_b_device__tenant__slug='tenant-b')
)

(I'll also note that the AND condition is already supported by declaring a single JSON object specifying both parameters.)

@jeremystretch commented on GitHub (Aug 6, 2020): Looking at this further, I don't think my suggestion above is going to be feasible with the current cabling model. However, we could extend ObjectPermission to accept a list of JSON objects, rather than a single object, to be ORed using Django's [Q objects](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#q-objects). Using your case as an example, you'd end up with something like this for the constraints definition: ```json [ {"_termination_a_device__tenant__slug": "tenant-a"}, {"_termination_b_device__tenant__slug": "tenant-a"} ] ``` This would result in the queryset being constructed as: ``` Cable.objects.filter( Q(_termination_a_device__tenant__slug='tenant-a') | Q(_termination_b_device__tenant__slug='tenant-b') ) ``` (I'll also note that the AND condition is already supported by declaring a single JSON object specifying both parameters.)
Author
Owner

@cpmills1975 commented on GitHub (Aug 6, 2020):

Perfect!

On 6 Aug 2020, at 19:03, Jeremy Stretch notifications@github.com wrote:


Looking at this further, I don't think my suggestion above is going to be feasible with the current cabling model. However, we could extend ObjectPermission to accept a list of JSON objects, rather than a single object, to be ORed using Django's Q objects. Using your case as an example, you'd end up with something like this for the constraints definition:

[
{"_termination_a_device__tenant__slug": "tenant-a"},
{"_termination_b_device__tenant__slug": "tenant-a"}
]
This would result in the queryset being constructed as:

Cable.objects.filter(
Q(_termination_a_device__tenant__slug='tenant-a') |
Q(_termination_b_device__tenant__slug='tenant-b')
)
(I'll also note that the AND condition is already supported by declaring a single JSON object specifying both parameters.)


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

@cpmills1975 commented on GitHub (Aug 6, 2020): Perfect! > On 6 Aug 2020, at 19:03, Jeremy Stretch <notifications@github.com> wrote: > >  > Looking at this further, I don't think my suggestion above is going to be feasible with the current cabling model. However, we could extend ObjectPermission to accept a list of JSON objects, rather than a single object, to be ORed using Django's Q objects. Using your case as an example, you'd end up with something like this for the constraints definition: > > [ > {"_termination_a_device__tenant__slug": "tenant-a"}, > {"_termination_b_device__tenant__slug": "tenant-a"} > ] > This would result in the queryset being constructed as: > > Cable.objects.filter( > Q(_termination_a_device__tenant__slug='tenant-a') | > Q(_termination_b_device__tenant__slug='tenant-b') > ) > (I'll also note that the AND condition is already supported by declaring a single JSON object specifying both parameters.) > > — > You are receiving this because you authored the thread. > Reply to this email directly, view it on GitHub, or unsubscribe.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#3938