阶段三-07权限控制和优化

项目目前的权限类需求

需要在test_ptl admin中注册,配置后发现只能管理到model级别

# 权限注册 Permission model页面,ContentType类似页面中的方法
admin.site.regeister(Permission) 
admin.site.regeister(ContentType)

确定组(角色)和用户的对应关系

我们决定在项目管理中使用组来充当角色(开发、测试等)

# models.PJmember   role 注释或删掉
groups = models.ManyToManyField(Group, related_name='member_belong_to', verbose_name='角色(组)')

migrate之后会生成一张关联表:test_plt_pjmember_groups

# PjMemberAdmin 中关于role的需要调整
# 多选样式组件,样式同权限管理页面
filter_horizontal = ['groups']

@admin.display(description='组(角色)')
def roles(self, obj: PjMember):
    return [g.name for g in obj.groups.all()]

此时会存在一个问题:在项目页面中选择了某个用户为开发角色,进入权限管理页面发现该用户并没有添加为开发。赋权是割裂开的

现在有一个需求:项目里面的内容只有加入了项目的成员才能展示!!!

从一般的设计原则来看,我们应该自己设计一套RBAC(Role Based Access Control)的权限控制系统,但是可以基于django提供的auth app来定制; 常规的RBAC也不考虑诸如Project的问题。这个问题在于要提供对象粒度的权限控制,而auth提供的是Mode1粒度的。
变通方案有:
1) 使得auth的用户和组关系,要随着项目成员和组关系的变化而变化(同步)这样至少保证在django (auth) 眼里,用户加入的组都是 (在项目中) 客观存在的

2)用户只能选择自己加入过的项目;之后才能看到此项目的接口、用例等

以上方案存在的漏洞:在不同项目中具有不同角色时,会在所有加入的项目中获得所持有的组的权限的合集
解决方案:可以在ModelAdmin的has xxx permission中进行更细粒度的控制(判断当前用户在选中的项目中是否有此角色)

django的信号-监听功能

m2m_changed 当一个model实例上的 ManyToManyField 被改变时发出。用它来监听pjmember这个model对应的test_plt_pjmember_groups(中间表),发生变化时触发我们想要执行的方法(把test_plt_pjmember_groups表关系单项同步到auth_user_groups)

@receiver(m2m_changed, sender=PjMember.groups.through) 
def member_groups_changed(sender, instance, **kwargs):
    pks = kwargs.get('pk_set')  # 当前被添加/删除的Group的PKs
    user = instance.user  # 当前被变更的User

    if kwargs.get('action') == 'post_add':  # 项目成员中添加组的情况
        curr_gs = [g.pk for g in user.groups.all()]  # auth用户当前所在组
        for pk in pks:
            if pk not in curr_gs:
                user.groups.add(Group.objects.get(pk=pk))

    if kwargs.get('action') == 'post_remove':  # 项目成员中删除组的况
        for member in user.project_members.all():  # 在其他项月中作为成员
            in_prj_gs = [g.pk for g in member.groups.all()]
            for pk in pks:
                if pk not in in_prj_gs:  # 已经从当前项目除,且不企其他项目,则丽除
                    user.groups.remove(Group.objects.get(pk=pk))

仅展示用户加入的项目

    def get_queryset(self, request):
        qs: QuerySet = super().get_queryset(request)
        return qs if request.user.is_superuser else qs.filter(members__id=request.user.id)

自定义中间件,用来控制用户选择默认项目

在test_plt下创建一个 中间件.py来定义我们的方法。方法中要用到django的session机制

from django.http import HttpRequest
from django.shortcuts import redirect
from django.contrib import messages
from django.utils.http import urlencode


class EnsureProjectIdMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request: HttpRequest):
        # 中间件被调用时自动执行
        pi = request.path_info  # 获取uri路径
        prj_id = request.session.get('default_project_id', default=None)
        while True:
            if not request.user.is_authenticated:  # 未登录
                break
            if prj_id:  # 已选择默认项目
                break
            if '/admin/test_plt/' not in pi and '/admin/django_celery_results/' not in pi and '/admin/django_celery_beat/' not in pi:
                break
            if '/admin/test_plt/pj/' in pi:
                break

            messages.warning(request, '请您指定一个默认项目!')
            request.session['test_plt_redirect_url'] = request.path
            return redirect('/admin/test_plt/pj/')

        response = self.get_response(request)

        return response

选择默认项目的方法也需要修改下

# PjAdmin
def select_pj(self, request, queryset):
    proj = queryset.first()  # queryset.first取到的就是 Model的对象
    logger = logging.getLogger('test_plt')
    # 缺省项目存到django的 request.session里,用字典格式维护
    request.session['default_project_id'] = proj.id
    # pj默认调用__str__
    self.message_user(request, f'默认项目为【{proj}】,后续操作基于此项目。您可以随时重新切换', level=messages.WARNING)
    redirect_ulr = request.session.get('test_plt_redirect_url', default=None)
    if redirect_ulr:
        del request.session['test_plt_redirect_url']
        return redirect(redirect_ulr)
select_pj.short_description = '设为默认项目'

自定义项目加入权限控制

举个例子,我想要这个自定义的方法加入权限控制。需要在它对应的model中加入permissions = [() ]

    class Meta:
        verbose_name = '接口定义'
        verbose_name_plural = verbose_name
        permissions = [
            ('run_api', '运行所选的接口')
        ]

完成后记得makemigrations migrate

想要检测用户是否有权限的话,在自定义的actions中开头加入:

has_perm = request.user.has_perm('test_plt.run_api') ;用has_perm判断是否有权限展示 或 跳到其他页面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值