Comment puis-je éviter l'escalade de l'autorisation dans l'admin Django lors de l'octroi autorisation « changement d'utilisateur »?
-
21-09-2019 - |
Question
J'ai un site django avec une grande base de clients. Je voudrais donner à notre service à la clientèle la possibilité de modifier les comptes d'utilisateurs normaux, faire des choses comme les mots de passe changeants, adresses e-mail, etc. Cependant, si je vous accordez à quelqu'un intégré dans l'autorisation de auth | user | Can change user
, ils acquièrent la possibilité de définir le drapeau is_superuser
sur un compte, y compris le leur. (!!!)
Quelle est la meilleure façon de supprimer cette option pour le personnel non-super-utilisateur? Je suis sûr que cela implique django.contrib.auth.forms.UserChangeForm
de sous-classement et accrocher dans mon objet UserAdmin
déjà sur mesure ... en quelque sorte. Mais je ne peux pas trouver de la documentation sur la façon de faire, et je ne comprends pas encore assez bien le fonctionnement interne.
La solution
ils gagnent la possibilité de définir le drapeau is_superuser sur un compte, y compris le leur. (!!!)
Non seulement cela, ils gagnent également la possibilité de se donner toutes les autorisations un par un, même effet ...
Je suis sûr que cela implique subclassing django.contrib.auth.forms.UserChangeForm
Eh bien, pas nécessairement. La forme que vous voyez dans la page de changement de admin django est créé dynamiquement par l'application d'administration, et basée sur UserChangeForm
, mais cette classe ajoute à peine la validation regex au champ username
.
et l'accrocher dans mon objet UserAdmin déjà sur mesure ...
Un UserAdmin
personnalisé est le chemin à parcourir ici. En gros, vous voulez changer la propriété fieldsets
à quelque chose comme ça:
class MyUserAdmin(UserAdmin):
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
# Removing the permission part
# (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
# Keeping the group parts? Ok, but they shouldn't be able to define
# their own groups, up to you...
(_('Groups'), {'fields': ('groups',)}),
)
Mais le problème est que cette restriction s'appliquera à tous les utilisateurs. Si ce n'est pas ce que vous voulez, vous pouvez par exemple passer outre change_view
à se comporter différemment selon l'autorisation des utilisateurs. Extrait de code:
class MyUserAdmin(UserAdmin):
staff_fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
# No permissions
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
def change_view(self, request, *args, **kwargs):
# for non-superuser
if not request.user.is_superuser:
try:
self.fieldsets = self.staff_fieldsets
response = super(MyUserAdmin, self).change_view(request, *args, **kwargs)
finally:
# Reset fieldsets to its original value
self.fieldsets = UserAdmin.fieldsets
return response
else:
return super(MyUserAdmin, self).change_view(request, *args, **kwargs)
Autres conseils
La partie ci-dessous de la réponse acceptée est une condition de course où si deux utilisateurs du personnel tentent d'accéder au formulaire d'administration en même temps, l'un d'entre eux peuvent obtenir le formulaire de super-utilisateur.
try: self.readonly_fields = self.staff_self_readonly_fields response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs) finally: # Reset fieldsets to its original value self.fieldsets = UserAdmin.fieldsets
Pour éviter cette condition de course (et à mon avis, d'améliorer la qualité globale de la solution), nous pouvons remplacer les méthodes de get_fieldsets()
et get_readonly_fields()
directement:
class UserAdmin(BaseUserAdmin):
staff_fieldsets = (
(None, {'fields': ('username')}),
('Personal info', {'fields': ('first_name', 'last_name', 'email')}),
# No permissions
('Important dates', {'fields': ('last_login', 'date_joined')}),
)
staff_readonly_fields = ('username', 'first_name', 'last_name', 'email', 'last_login', 'date_joined')
def get_fieldsets(self, request, obj=None):
if not request.user.is_superuser:
return self.staff_fieldsets
else:
return super(UserAdmin, self).get_fieldsets(request, obj)
def get_readonly_fields(self, request, obj=None):
if not request.user.is_superuser:
return self.staff_readonly_fields
else:
return super(UserAdmin, self).get_readonly_fields(request, obj)
Un grand merci à Clément. Ce que je suis venu avec quand faire la même chose pour mon site est que je besoin en plus de faire tous les champs readonly pour les utilisateurs que vous autres que moi. Ainsi, se basant sur la réponse de Clément I addeed champs et readonly champ de mot de passe caché lors de la visualisation pas auto
class MyUserAdmin(UserAdmin):
model = User
staff_self_fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
# No permissions
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
staff_other_fieldsets = (
(None, {'fields': ('username', )}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
# No permissions
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
staff_self_readonly_fields = ('last_login', 'date_joined')
def change_view(self, request, object_id, form_url='', extra_context=None, *args, **kwargs):
# for non-superuser
if not request.user.is_superuser:
try:
if int(object_id) != request.user.id:
self.readonly_fields = User._meta.get_all_field_names()
self.fieldsets = self.staff_other_fieldsets
else:
self.readonly_fields = self.staff_self_readonly_fields
self.fieldsets = self.staff_self_fieldsets
response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
except:
logger.error('Admin change view error. Returned all readonly fields')
self.fieldsets = self.staff_other_fieldsets
self.readonly_fields = ('first_name', 'last_name', 'email', 'username', 'password', 'last_login', 'date_joined')
response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
finally:
# Reset fieldsets to its original value
self.fieldsets = UserAdmin.fieldsets
self.readonly_fields = UserAdmin.readonly_fields
return response
else:
return super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
Code complet pour django 1.1 (limité aux informations des utilisateurs de base pour le personnel (non superutilisateurs))
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
class MyUserAdmin(UserAdmin):
my_fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
)
def change_view(self, request, object_id, extra_context=None):
# for non-superuser
print 'test'
if not request.user.is_superuser:
self.fieldsets = self.my_fieldsets
response = UserAdmin.change_view(self, request, object_id,
extra_context=None)
return response
else:
return UserAdmin.change_view(self, request, object_id,
extra_context=None)
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
Cette approche a été mis en place de plusieurs conseils utiles sur le web. Dans ce cas, nous modifions UserAdmin de sorte que, pour le personnel non-utilisateur avec superuser ajouter / autorisation de modification, les seules autorisations et les groupes qu'ils peuvent accorder un autre utilisateur sont ceux que le membre du personnel a déjà.
(pour Django 1,11)
from django.contrib.auth.admin import UserAdmin, User
from django.contrib import admin
class RestrictedUserAdmin(UserAdmin):
model = User
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(RestrictedUserAdmin, self).formfield_for_dbfield(db_field, **kwargs)
user = kwargs['request'].user
if not user.is_superuser:
if db_field.name == 'groups':
field.queryset = field.queryset.filter(id__in=[i.id for i in user.groups.all()])
if db_field.name == 'user_permissions':
field.queryset = field.queryset.filter(id__in=[i.id for i in user.user_permissions.all()])
if db_field.name == 'is_superuser':
field.widget.attrs['disabled'] = True
return field
admin.site.unregister(User)
admin.site.register(User, RestrictedUserAdmin)
Cela devrait également être fait pour GroupAdmin si un utilisateur reçoit l'autorisation de changer de groupe.