Skip to content

Commit b1a8dc9

Browse files
committed
[change] Hide sensitive fields for non-superuser on shared objects
1 parent 65c6b17 commit b1a8dc9

File tree

6 files changed

+96
-1
lines changed

6 files changed

+96
-1
lines changed

openwisp_users/multitenancy.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class MultitenantAdminMixin(object):
2020

2121
multitenant_shared_relations = None
2222
multitenant_parent = None
23+
sensitive_fields = []
24+
25+
def get_sensitive_fields(self, request, obj=None):
26+
return self.sensitive_fields
2327

2428
def __init__(self, *args, **kwargs):
2529
super().__init__(*args, **kwargs)
@@ -37,6 +41,21 @@ def get_repr(self, obj):
3741

3842
get_repr.short_description = _("name")
3943

44+
def get_fields(self, request, obj=None):
45+
"""
46+
Return the list of fields to be displayed in the admin.
47+
48+
If the user is not a superuser, it will remove sensitive fields.
49+
"""
50+
fields = super().get_fields(request, obj)
51+
if obj and not request.user.is_superuser:
52+
if self.multitenant_parent:
53+
obj = getattr(obj, self.multitenant_parent)
54+
if getattr(obj, "organization_id", None) is None:
55+
sensitive_fields = self.get_sensitive_fields(request, obj)
56+
return [f for f in fields if f not in sensitive_fields]
57+
return fields
58+
4059
def get_queryset(self, request):
4160
"""
4261
If current user is not superuser, show only the

openwisp_users/tests/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,40 @@ def _test_org_admin_view_shareable_object(
290290
)
291291
self.assertContains(response, expected_element, html=True)
292292

293+
def _test_sensitive_fields_visibility_on_shared_and_org_objects(
294+
self,
295+
sensitive_fields,
296+
shared_obj_path,
297+
org_obj_path,
298+
organization,
299+
org_admin=None,
300+
super_user=None,
301+
):
302+
org_admin = org_admin or self._create_administrator(
303+
organizations=[organization]
304+
)
305+
super_user = super_user or self._get_admin()
306+
307+
self.client.force_login(org_admin)
308+
with self.subTest("Org admin should not see sensitive fields in shared object"):
309+
response = self.client.get(shared_obj_path)
310+
self.assertEqual(response.status_code, 200)
311+
for field in sensitive_fields:
312+
self.assertNotContains(response, field)
313+
314+
with self.subTest("Org admin should see sensitive fields in org object"):
315+
response = self.client.get(org_obj_path)
316+
self.assertEqual(response.status_code, 200)
317+
for field in sensitive_fields:
318+
self.assertContains(response, field)
319+
320+
self.client.force_login(super_user)
321+
with self.subTest("Superuser should see sensitive fields in shared object"):
322+
response = self.client.get(shared_obj_path)
323+
self.assertEqual(response.status_code, 200)
324+
for field in sensitive_fields:
325+
self.assertContains(response, field)
326+
293327
def _test_object_organization_fk_autocomplete_view(
294328
self,
295329
model,

tests/testapp/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def change_view(self, request, object_id, form_url="", extra_context=None):
6161

6262

6363
class TemplateAdmin(BaseAdmin):
64-
pass
64+
sensitive_fields = ["secrets"]
6565

6666

6767
class TagAdmin(BaseAdmin):
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.1 on 2025-07-04 13:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("testapp", "0006_alter_book_shelf"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="template",
15+
name="secrets",
16+
field=models.TextField(blank=True, null=True),
17+
),
18+
]

tests/testapp/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
class Template(ShareableOrgMixin):
1010
name = models.CharField(max_length=16)
11+
secrets = models.TextField(
12+
blank=True,
13+
null=True,
14+
)
1115

1216
def __str__(self):
1317
return self.name

tests/testapp/tests/test_admin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,23 @@ def test_superuser_create_shareable_template(self):
9191
)
9292
self.assertEqual(response.status_code, 200)
9393
self.assertEqual(Template.objects.count(), 1)
94+
95+
def test_hide_sensitive_fields_for_shared_object(self):
96+
"""
97+
Test that sensitive fields are hidden for shared objects for non-superusers.
98+
"""
99+
org = self._get_org()
100+
shared_template = self._create_template(
101+
organization=None, secrets="shared-secret"
102+
)
103+
org_template = self._create_template(organization=org, secrets="org-secret")
104+
self._test_sensitive_fields_visibility_on_shared_and_org_objects(
105+
sensitive_fields=["secrets"],
106+
shared_obj_path=reverse(
107+
"admin:testapp_template_change", args=(shared_template.id,)
108+
),
109+
org_obj_path=reverse(
110+
"admin:testapp_template_change", args=(org_template.id,)
111+
),
112+
organization=org,
113+
)

0 commit comments

Comments
 (0)