Django 钩子拒绝用户基于站点访问管理员

Django hook to deny user access to admin based on site

我们有一个包含多个站点的 Django 项目(使用站点框架的派生),其中每个站点都是我们域的子域。我需要能够根据用户是否具有站点外键来拒绝某些员工用户(具有站点外键的自定义用户模型的实例)访问管理站点。

自定义用户模型基本上是这样的:

class SiteTenantUser(AbstractUser):
    site = models.ForeignKey(Site, null=True)

您的第一个想法可能是建议我们使用自定义 SiteAuthBackend 身份验证后端的 authenticate 方法阻止身份验证,以便仅让用户登录到他们自己的站点。这可以防止身份验证,但诀窍在于我们拥有具有跨站点访问权限的超级用户,因此我们希望使用通配符会话 cookie (.ourdomain.com) 让超级用户从一个站点转到另一个站点而无需每次登录。但是,通过这样做,除了 authenticate 之外,我们现在还需要一些东西来防止没有该权限的用户进行跨站点访问。否则他们可以针对自己的站点进行身份验证,然后切换到另一个站点的管理员 url 并进行合法会话并进入大门。

总而言之,虽然我们的自定义身份验证后端阻止非超级用户员工用户(仅限于单个站点)登录到另一个站点的管理员,但它不会阻止他们登录到自己的管理员然后切换到另一个站点的管理员。是否有一个简单的钩子来防止这种情况,类似于 Django 如何拒绝来自任何管理页面的没有 is_staff 的用户,无论他们是否通过身份验证?

PS 我们有一个全局 get_current_site 方法,它使用来自中间件的缓存值并在任何地方工作,不需要请求。

您可以创建自定义中间件来拒绝用户访问。在 AuthenticationMiddleware.

之后添加到 MIDDLEWARE_CLASSES
from django.http import HttpResponseForbidden 

class CheckUserSiteMiddleware(object):

    def process_request(self, request):
        if (request.path.startswith('/admin/') and
                request.user.is_authenticated() and
                user.site != get_current_site()):
            return HttpResponseForbidden()

我最终创建了一个自定义 AdminSite,如下所示:

from django.contrib.admin import AdminSite

class TenantSitesAdminSite(AdminSite):
    def has_permission(self, request):
        user = request.user
        return (
            user.is_active and
            user.is_staff and
            (user.is_superuser or user.site == get_current_site()))

site = TenantSitesAdminSite()

为了使我的自定义 site 生效,我必须将 contrib.admin.site 的所有用法切换为使用自定义 site,以及取消注册任何第 3 方或贡献来自 contrib.admin.site 的管理员并使用自定义 site 或(邪恶的)猴子补丁 contrib.admin.site 重新注册它们以指向自定义 site。不会承认我做了什么。 :)

通过使用 CustomAdminSite 并重新定义 has_permission 逻辑,在特定站点上没有权限的员工用户在尝试访问管理员时获得与非员工用户相同的体验——这是被带到管理员登录页面。 @catvaran 的建议奏效了,但用户只会收到 403 错误页面,如果不返回他们获得授权的站点并注销,甚至无法进入管理员登录页面,我更希望体验与至于非员工用户(即任何未经授权的管理页面请求都会将他们带到管理员登录页面)。