From 5748528e8d19dbcee53d649afd6c77968ca9c4ba Mon Sep 17 00:00:00 2001 From: Atif Date: Wed, 17 Dec 2025 03:19:38 +0530 Subject: [PATCH] [admin, api] Add SensitiveFieldsAdminMixin and SensitiveFieldsSerializerMixin #448 Add reusable mixins to hide sensitive model fields from non-superusers in both admin interface and REST API. Fixes #448 --- openwisp_users/admin.py | 30 ++++++++++++++++++++++++++++++ openwisp_users/api/serializers.py | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/openwisp_users/admin.py b/openwisp_users/admin.py index b8a946bc..8f00ddf7 100644 --- a/openwisp_users/admin.py +++ b/openwisp_users/admin.py @@ -44,6 +44,36 @@ logger = logging.getLogger(__name__) +class SensitiveFieldsAdminMixin: + def get_fieldsets(self, request, obj=None): + fieldsets = super().get_fieldsets(request, obj) + if request.user.is_superuser: + return fieldsets + sensitive_fields = getattr(self.model, "sensitive_fields", []) + if not sensitive_fields: + return fieldsets + fieldsets = deepcopy(fieldsets) + for fieldset in fieldsets: + fieldset_fields = fieldset[1].get("fields", ()) + if isinstance(fieldset_fields, (list, tuple)): + fieldset[1]["fields"] = tuple( + field for field in fieldset_fields + if field not in sensitive_fields + ) + return fieldsets + + def get_readonly_fields(self, request, obj=None): + readonly = super().get_readonly_fields(request, obj) + if request.user.is_superuser: + return readonly + sensitive_fields = getattr(self.model, "sensitive_fields", []) + if not sensitive_fields: + return readonly + return list(readonly) + [ + field for field in sensitive_fields if field not in readonly + ] + + class EmailAddressInline(admin.StackedInline): model = EmailAddress extra = 0 diff --git a/openwisp_users/api/serializers.py b/openwisp_users/api/serializers.py index bc19bc1b..d17dfab8 100644 --- a/openwisp_users/api/serializers.py +++ b/openwisp_users/api/serializers.py @@ -19,6 +19,19 @@ OrganizationOwner = load_model("openwisp_users", "OrganizationOwner") +class SensitiveFieldsSerializerMixin: + def get_fields(self): + fields = super().get_fields() + request = self.context.get("request") + if not request or request.user.is_superuser: + return fields + model = self.Meta.model + sensitive_fields = getattr(model, "sensitive_fields", []) + for field_name in sensitive_fields: + fields.pop(field_name, None) + return fields + + class OrganizationSerializer(ValidatedModelSerializer): class Meta: model = Organization