授予“用户更改”权限时,如何防止 Django 管理中的权限升级允许?

发布于 2024-08-22 00:52:57 字数 317 浏览 8 评论 0原文

我有一个拥有大量客户群的 Django 网站。我想让我们的客户服务部门能够更改普通用户帐户,执行诸如更改密码、电子邮件地址等操作。但是,如果我授予某人内置的 auth |用户 |可以更改用户权限,他们能够在任何帐户(包括他们自己的帐户)上设置is_superuser标志。 (!!!)

对于非超级用户员工删除此选项的最佳方法是什么?我确信它涉及子类化 django.contrib.auth.forms.UserChangeForm 并将其挂接到我已经自定义的 UserAdmin 对象中......以某种方式。但我找不到任何关于如何执行此操作的文档,而且我还不太了解内部结构。

I have a django site with a large customer base. I would like to give our customer service department the ability to alter normal user accounts, doing things like changing passwords, email addresses, etc. However, if I grant someone the built-in auth | user | Can change user permission, they gain the ability to set the is_superuser flag on any account, including their own. (!!!)

What's the best way to remove this option for non-superuser staff? I'm sure it involves subclassing django.contrib.auth.forms.UserChangeForm and hooking it into my already-custom UserAdmin object... somehow. But I can't find any documentation on how to do this, and I don't yet understand the internals well enough.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

勿忘初心 2024-08-29 00:52:57

他们能够在任何帐户(包括他们自己的帐户)上设置 is_superuser 标志。 (!!!)

不仅如此,他们还获得了将任何权限一一赋予自己的能力,同样的效果……

我确定它涉及子类化 django.contrib.auth.forms.UserChangeForm

好吧,不一定。您在 django 管理的更改页面中看到的表单是由管理应用程序动态创建的,并且基于 UserChangeForm,但此类几乎没有向 username 字段添加正则表达式验证。

并将其挂接到我已经自定义的 UserAdmin 对象中...

自定义 UserAdmin 是此处的方法。基本上,您希望将 fieldsets 属性更改为类似的内容:

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',)}),
    )

但这里的问题是此限制将适用于所有用户。如果这不是您想要的,您可以覆盖 change_view 以根据用户的权限进行不同的行为。代码片段:

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)

they gain the ability to set the is_superuser flag on any account, including their own. (!!!)

Not only this, they also gain the ability to give themselves any permissions one-by-one, same effect...

I'm sure it involves subclassing django.contrib.auth.forms.UserChangeForm

Well, not necessarily. The form you see in the change page of django's admin is dynamically created by the admin application, and based on UserChangeForm, but this class barely adds regex validation to the username field.

and hooking it into my already-custom UserAdmin object...

A custom UserAdmin is the way to go here. Basically, you want to change the fieldsets property to something like that :

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',)}),
    )

But the problem here is that this restriction will apply to all users. If this is not what you want, you could for example override change_view to behave differently depending on the permission of the users. Code snippet :

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)
辞取 2024-08-29 00:52:57

接受的答案的下面部分有一个竞争条件,如果两个员工用户尝试同时访问管理表单,其中一个可能会获得超级用户表单。

<前><代码>尝试:
self.readonly_fields = self.staff_self_readonly_fields
响应 = super(MyUserAdmin, self).change_view(请求, object_id, form_url, extra_context, *args, **kwargs)
最后:
# 将字段集重置为其原始值
self.fieldsets = UserAdmin.fieldsets

为了避免这种竞争条件(并且在我看来提高解决方案的整体质量),我们可以直接重写 get_fieldsets()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)

The below part of the accepted answer has a race condition where if two staff users try to access the admin form at the same time, one of them may get the superuser form.

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

To avoid this race condition (and in my opinion improve the overall quality of the solution), we can override the get_fieldsets() and get_readonly_fields() methods directly:

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)
沉鱼一梦 2024-08-29 00:52:57

非常感谢克莱门特。当我对我的网站执行相同操作时,我想到的是,我还需要将所有字段设置为除您自己以外的用户只读。因此,根据克莱门特的回答,我添加了只读字段和密码字段,在查看非自身时隐藏

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)

Great thanks to Clément. What I came up with when doing the same for my site is that I needed additionally to make all fields readonly for users you other than self. So basing on Clément's answer I addeed readonly fields and password field hiding when viewing not self

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)
好多鱼好多余 2024-08-29 00:52:57

这种方法是根据网络上的一些有用提示组合而成的。在本例中,我们正在修改 UserAdmin,以便对于具有用户添加/更改权限的非超级用户员工,他们可以授予其他用户的唯一权限和组是该员工已经拥有的权限和组。

(对于 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)

如果用户被授予更改组的权限,则同样应该对 GroupAdmin 执行此操作。

This approach was put together from several helpful tips on the web. In this case we are modifying UserAdmin so that, for non-superuser staff with user add/change permission, the only permissions and groups they can grant another user are the ones the staff member already has.

(for 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)

This should likewise be done for GroupAdmin if a user is given permission to change groups.

心头的小情儿 2024-08-29 00:52:57

django 1.1的完整代码(仅限工作人员(非超级用户)的基本用户信息)

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)

Full code for django 1.1 (limited to basic user information for staff (not superusers))

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文