Skip to content

Commit ba57526

Browse files
authored
OBS-1202 fix permissions (#115)
1 parent f047b46 commit ba57526

File tree

12 files changed

+189
-62
lines changed

12 files changed

+189
-62
lines changed

docker/requirements-diode-netbox-plugin.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ coverage==7.6.0
44
grpcio==1.62.1
55
protobuf==5.28.1
66
pytest==8.0.2
7-
netboxlabs-netbox-branching
7+
netboxlabs-netbox-branching==0.5.7

netbox_diode_plugin/api/applier.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def _pre_apply(model_class: models.Model, change: Change, created: dict):
101101
# resolve foreign key references to new objects
102102
for ref_field in change.new_refs:
103103
v = _get_path(data, ref_field)
104-
if isinstance(v, (list, tuple)):
104+
if isinstance(v, list | tuple):
105105
ref_list = []
106106
for ref in v:
107107
if isinstance(ref, str):

netbox_diode_plugin/api/common.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections import defaultdict
1010
from dataclasses import dataclass, field
1111
from enum import Enum
12+
from zoneinfo import ZoneInfo
1213

1314
import netaddr
1415
from django.apps import apps
@@ -20,7 +21,6 @@
2021
from extras.models import CustomField
2122
from netaddr.eui import EUI
2223
from rest_framework import status
23-
from zoneinfo import ZoneInfo
2424

2525
logger = logging.getLogger("netbox.diode_data")
2626

@@ -166,7 +166,7 @@ def _validate_relations(self, change_data: dict, model: models.Model) -> tuple[l
166166
excluded_relation_fields = []
167167
rel_errors = defaultdict(list)
168168
for f in model._meta.get_fields():
169-
if isinstance(f, (GenericRelation, GenericForeignKey)):
169+
if isinstance(f, GenericRelation | GenericForeignKey):
170170
excluded_relation_fields.append(f.name)
171171
continue
172172
if not f.is_relation:
@@ -251,7 +251,7 @@ def error_from_validation_error(e, object_name):
251251
if e.detail:
252252
if isinstance(e.detail, dict):
253253
errors[object_name] = e.detail
254-
elif isinstance(e.detail, (list, tuple)):
254+
elif isinstance(e.detail, list | tuple):
255255
errors[object_name] = {
256256
NON_FIELD_ERRORS: e.detail
257257
}

netbox_diode_plugin/api/differ.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def prechange_data_from_instance(instance) -> dict: # noqa: C901
8484
custom_field_values = instance.get_custom_fields()
8585
cfmap = {}
8686
for cf, value in custom_field_values.items():
87-
if isinstance(value, (datetime.datetime, datetime.date)):
87+
if isinstance(value, datetime.datetime | datetime.date):
8888
cfmap[cf.name] = value
8989
else:
9090
cfmap[cf.name] = cf.serialize(value)

netbox_diode_plugin/api/matcher.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import logging
66
from dataclasses import dataclass
77
from functools import cache, lru_cache
8-
from typing import Type
98

109
import netaddr
1110
from django.contrib.contenttypes.fields import ContentType
@@ -221,7 +220,7 @@ class ObjectMatchCriteria:
221220
fields: tuple[str] | None = None
222221
expressions: tuple | None = None
223222
condition: Q | None = None
224-
model_class: Type[models.Model] | None = None
223+
model_class: type[models.Model] | None = None
225224
name: str | None = None
226225

227226
def __hash__(self):
@@ -365,7 +364,7 @@ def _build_expressions_queryset(self, data) -> models.QuerySet:
365364
"""Builds a queryset for the constraint with the given data."""
366365
data = self._prepare_data(data)
367366
replacements = {
368-
F(field): Value(value) if isinstance(value, (str, int, float, bool)) else value
367+
F(field): Value(value) if isinstance(value, str | int | float | bool) else value
369368
for field, value in data.items()
370369
}
371370

@@ -413,7 +412,7 @@ class CustomFieldMatcher:
413412

414413
name: str
415414
custom_field: str
416-
model_class: Type[models.Model]
415+
model_class: type[models.Model]
417416

418417
def fingerprint(self, data: dict) -> str|None:
419418
"""Fingerprint the custom field value."""
@@ -448,7 +447,7 @@ class GlobalIPNetworkIPMatcher:
448447

449448
ip_fields: tuple[str]
450449
vrf_field: str
451-
model_class: Type[models.Model]
450+
model_class: type[models.Model]
452451
name: str
453452

454453
def _check_condition(self, data: dict) -> bool:
@@ -508,7 +507,7 @@ class VRFIPNetworkIPMatcher:
508507

509508
ip_fields: tuple[str]
510509
vrf_field: str
511-
model_class: Type[models.Model]
510+
model_class: type[models.Model]
512511
name: str
513512

514513
def _check_condition(self, data: dict) -> bool:
@@ -583,7 +582,7 @@ class AutoSlugMatcher:
583582

584583
name: str
585584
slug_field: str
586-
model_class: Type[models.Model]
585+
model_class: type[models.Model]
587586

588587
def fingerprint(self, data: dict) -> str|None:
589588
"""Fingerprint the custom field value."""
@@ -750,7 +749,7 @@ def _fingerprint_all(data: dict, object_type: str|None = None) -> str:
750749
if k.startswith("_"):
751750
continue
752751
values.append(k)
753-
if isinstance(v, (list, tuple)):
752+
if isinstance(v, list | tuple):
754753
values.extend(sorted(v))
755754
elif isinstance(v, dict):
756755
values.append(_fingerprint_all(v))

netbox_diode_plugin/api/supported_models.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import logging
77
import time
88
from functools import lru_cache
9-
from typing import List, Type
109

1110
from django.apps import apps
1211
from django.db import models
@@ -82,9 +81,9 @@ def extract_supported_models() -> dict[str, dict]:
8281
return extracted_models
8382

8483

85-
def get_prerequisites(model_class, fields) -> List[dict[str, str]]:
84+
def get_prerequisites(model_class, fields) -> list[dict[str, str]]:
8685
"""Get the prerequisite models for the model."""
87-
prerequisites: List[dict[str, str]] = []
86+
prerequisites: list[dict[str, str]] = []
8887
prerequisite_models = getattr(model_class, "prerequisite_models", [])
8988

9089
for prereq in prerequisite_models:
@@ -252,7 +251,7 @@ def get_serializer_for_model(model, prefix=""):
252251
return netbox_get_serializer_for_model(model, prefix)
253252

254253

255-
def discover_models(root_packages: List[str]) -> list[Type[models.Model]]:
254+
def discover_models(root_packages: list[str]) -> list[type[models.Model]]:
256255
"""Discovers all model classes in specified root packages."""
257256
discovered_models = []
258257

netbox_diode_plugin/api/transformer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
import copy
66
import datetime
7+
import graphlib
78
import json
89
import logging
910
import re
1011
from collections import defaultdict
1112
from functools import lru_cache
1213
from uuid import uuid4
1314

14-
import graphlib
1515
from django.utils.text import slugify
1616
from extras.models.customfields import CustomField
1717
from rest_framework import serializers
@@ -474,7 +474,7 @@ def _update_dict_refs(data, new_refs):
474474
for k, v in data.items():
475475
if isinstance(v, UnresolvedReference) and v.uuid in new_refs:
476476
v.uuid = new_refs[v.uuid]
477-
elif isinstance(v, (list, tuple)):
477+
elif isinstance(v, list | tuple):
478478
for item in v:
479479
if isinstance(item, UnresolvedReference) and item.uuid in new_refs:
480480
item.uuid = new_refs[item.uuid]
@@ -517,7 +517,7 @@ def _update_resolved_refs(data, new_refs):
517517
for k, v in list(data.items()):
518518
if isinstance(v, UnresolvedReference) and v.uuid in new_refs:
519519
data[k] = new_refs[v.uuid]
520-
elif isinstance(v, (list, tuple)):
520+
elif isinstance(v, list | tuple):
521521
new_items = []
522522
has_refs = False
523523
for item in v:
@@ -539,7 +539,7 @@ def cleanup_unresolved_references(data: dict) -> list[str]:
539539
if k != 'id':
540540
unresolved.add(k)
541541
data[k] = str(v)
542-
elif isinstance(v, (list, tuple)):
542+
elif isinstance(v, list | tuple):
543543
items = []
544544
for item in v:
545545
if isinstance(item, UnresolvedReference):

netbox_diode_plugin/navigation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
PluginMenuItem(
1010
link="plugins:netbox_diode_plugin:settings",
1111
link_text=_("Settings"),
12-
staff_only= True,
12+
permissions=("netbox_diode_plugin.view_setting",),
1313
),
1414
PluginMenuItem(
1515
link="plugins:netbox_diode_plugin:client_credential_list",
1616
link_text=_("Client Credentials"),
17-
staff_only= True,
17+
permissions=("netbox_diode_plugin.view_clientcredentials",),
1818
),
1919
)
2020

netbox_diode_plugin/tests/test_updates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _harmonize_formats(data):
2626
return _tuples_to_lists(data)
2727

2828
def _tuples_to_lists(data):
29-
if isinstance(data, (tuple, list)):
29+
if isinstance(data, tuple | list):
3030
return [_tuples_to_lists(d) for d in data]
3131
if isinstance(data, dict):
3232
return {k: _tuples_to_lists(v) for k, v in data.items()}
@@ -165,7 +165,7 @@ def _check_set_by(self, obj, path, value):
165165
path = path[:-1]
166166
cur = self._follow_path(obj, path)
167167

168-
if isinstance(value, (list, tuple)):
168+
if isinstance(value, list | tuple):
169169
vals = set(value)
170170
else:
171171
vals = {value}

netbox_diode_plugin/tests/test_views.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,32 @@
88
from django.contrib.messages.middleware import MessageMiddleware
99
from django.contrib.messages.storage.fallback import FallbackStorage
1010
from django.contrib.sessions.middleware import SessionMiddleware
11-
from django.test import RequestFactory, TestCase
11+
from django.test import RequestFactory
12+
from django.test import TestCase as _TestCase
1213
from django.urls import reverse
1314
from rest_framework import status
15+
from users.models import ObjectPermission
16+
from utilities.permissions import resolve_permission_type
1417

1518
from netbox_diode_plugin.models import Setting
1619
from netbox_diode_plugin.views import SettingsEditView, SettingsView
1720

1821
User = get_user_model()
1922

2023

24+
class TestCase(_TestCase):
25+
"""Base test case class for NetBox Diode plugin tests."""
26+
27+
def add_permissions(self, user, *names):
28+
"""Assign a set of permissions to the test user. Accepts permission names in the form <app>.<action>_<model>."""
29+
for name in names:
30+
object_type, action = resolve_permission_type(name)
31+
obj_perm = ObjectPermission(name=name, actions=[action])
32+
obj_perm.save()
33+
obj_perm.users.add(user)
34+
obj_perm.object_types.add(object_type)
35+
36+
2137
class SettingsViewTestCase(TestCase):
2238
"""Test case for the SettingsView."""
2339

@@ -31,7 +47,7 @@ def setUp(self):
3147
def test_returns_200_for_authenticated(self):
3248
"""Test that the view returns 200 for an authenticated user."""
3349
self.request.user = User.objects.create_user("foo", password="pass")
34-
self.request.user.is_staff = True
50+
self.add_permissions(self.request.user, "netbox_diode_plugin.view_setting")
3551

3652
response = self.view.get(self.request)
3753
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -49,7 +65,7 @@ def test_redirects_to_login_page_for_unauthenticated_user(self):
4965
def test_settings_created_if_not_found(self):
5066
"""Test that the settings are created with placeholder data if not found."""
5167
self.request.user = User.objects.create_user("foo", password="pass")
52-
self.request.user.is_staff = True
68+
self.add_permissions(self.request.user, "netbox_diode_plugin.view_setting")
5369

5470
with mock.patch("netbox_diode_plugin.models.Setting.objects.get") as mock_get:
5571
mock_get.side_effect = Setting.DoesNotExist
@@ -71,8 +87,8 @@ def setUp(self):
7187
def test_returns_200_for_authenticated(self):
7288
"""Test that the view returns 200 for an authenticated user."""
7389
request = self.request_factory.get(self.path)
74-
request.user = User.objects.create_user("foo", password="pass")
75-
request.user.is_staff = True
90+
request.user = User.objects.create_user("foo", password="pass", is_staff=True)
91+
self.add_permissions(request.user, "netbox_diode_plugin.view_setting", "netbox_diode_plugin.change_setting")
7692
request.htmx = None
7793
self.view.setup(request)
7894

@@ -91,8 +107,8 @@ def test_redirects_to_login_page_for_unauthenticated_user(self):
91107

92108
def test_settings_updated(self):
93109
"""Test that the settings are updated."""
94-
user = User.objects.create_user("foo", password="pass")
95-
user.is_staff = True
110+
user = User.objects.create_user("foo", password="pass", is_staff=True)
111+
self.add_permissions(user, "netbox_diode_plugin.view_setting", "netbox_diode_plugin.change_setting")
96112

97113
request = self.request_factory.get(self.path)
98114
request.user = user
@@ -149,8 +165,13 @@ def test_settings_update_disallowed_on_get_method(self):
149165
) as mock_get_plugin_config:
150166
mock_get_plugin_config.return_value = "grpc://localhost:8080/diode"
151167

152-
user = User.objects.create_user("foo", password="pass")
153-
user.is_staff = True
168+
user = User.objects.create_user("foo", password="pass", is_staff=True)
169+
self.add_permissions(
170+
user,
171+
"netbox_diode_plugin.view_setting",
172+
"netbox_diode_plugin.add_setting",
173+
"netbox_diode_plugin.change_setting",
174+
)
154175

155176
request = self.request_factory.post(self.path)
156177
request.user = user
@@ -188,8 +209,13 @@ def test_settings_update_disallowed_on_post_method(self):
188209
) as mock_get_plugin_config:
189210
mock_get_plugin_config.return_value = "grpc://localhost:8080/diode"
190211

191-
user = User.objects.create_user("foo", password="pass")
192-
user.is_staff = True
212+
user = User.objects.create_user("foo", password="pass", is_staff=True)
213+
self.add_permissions(
214+
user,
215+
"netbox_diode_plugin.view_setting",
216+
"netbox_diode_plugin.add_setting",
217+
"netbox_diode_plugin.change_setting",
218+
)
193219

194220
request = self.request_factory.post(self.path)
195221
request.user = user

0 commit comments

Comments
 (0)