Come posso evitare il permesso escalation di Django di amministrazione nel concedere l'autorizzazione “Cambia utente”?
-
21-09-2019 - |
Domanda
Ho un sito Django con una vasta clientela. Vorrei dare il nostro servizio clienti la capacità di alterare le normali account utente, fare le cose come cambiare le password, indirizzi e-mail, ecc, tuttavia, se mi concedo qualcuno il built-nel permesso auth | user | Can change user
, guadagnano la possibilità di impostare il flag is_superuser
in nessun caso, compreso il proprio. (!!!)
Qual è il modo migliore per rimuovere questa opzione per il personale non-superuser? Sono sicuro che si tratta di sottoclassi django.contrib.auth.forms.UserChangeForm
e agganciandolo nella mia oggetto UserAdmin
già personalizzato ... in qualche modo. Ma non riesco a trovare alcuna documentazione su come fare questo, e non ho ancora a capire i meccanismi interni abbastanza bene.
Soluzione
guadagnano la possibilità di impostare il flag is_superuser su qualsiasi account, compreso il proprio. (!!!)
Non solo questo, ma anche acquisire la capacità di darsi le autorizzazioni uno per uno, stesso effetto ...
Sono sicuro che si tratta di creazione di sottoclassi django.contrib.auth.forms.UserChangeForm
Beh, non necessariamente. Il modulo che vedete nella pagina di cambio di amministrazione di Django viene dinamicamente creata dall'applicazione di amministrazione, e sulla base di UserChangeForm
, ma questa classe aggiunge a malapena convalida regex al campo username
.
e agganciandolo nella mia oggetto UserAdmin già personalizzato ...
Un UserAdmin
personalizzato è il modo di andare qui. In sostanza, si vuole modificare la proprietà fieldsets
a qualcosa di simile:
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',)}),
)
Ma il problema qui è che questa limitazione si applicherà a tutti gli utenti. Se questo non è quello che si vuole è, si potrebbe ad esempio ignorare change_view
a comportarsi in modo diverso a seconda del consenso degli utenti. Frammento di codice:
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)
Altri suggerimenti
La parte di sotto della risposta accettata è una condizione di competizione in cui se due utenti del personale cercano di accedere al modulo di amministrazione, allo stesso tempo, una di esse può ottenere la forma superutente.
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
Per evitare questa condizione di gara (e, a mio parere migliorare la qualità complessiva della soluzione), siamo in grado di sostituire direttamente i metodi get_fieldsets()
e get_readonly_fields()
:
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)
Gran grazie a Clément. Quello che mi è venuta quando si fa lo stesso per il mio sito è che avevo bisogno inoltre di rendere tutti i campi di sola lettura per gli utenti di altri che di sé. Quindi, basandosi su risposta di Clemente I addeed campi di sola lettura e campo password nascondersi durante la visualizzazione non 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)
codice completo per Django 1.1 (limitato alle informazioni degli utenti di base per il personale (non superuser))
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)
Questo approccio è stato messo insieme da diversi suggerimenti utili sul web. In questo caso stiamo modificando UserAdmin in modo che, per il personale non superuser con l'utente aggiungere il permesso / cambio, gli unici permessi e gruppi possono concedere un altro utente sono quelli del membro del personale ha già.
(per 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)
Questo dovrebbe altresì essere fatto per GroupAdmin se un utente è dato il permesso di modificare i gruppi.