Django内置权限扩展案例

当Django的内置权限无法满足需求的时候就自己扩展吧~

背景介绍

overmind项目使用了Django内置的权限系统,Django内置权限系统基于model层做控制,新的model创建后会默认新建三个权限,分别为:add、change、delete,如果给用户或组赋予delete的权限,那么用户将可以删除这个model下的所有数据。

原本overmind只管理了我们自己部门的数据库,权限设置只针对具体的功能不针对细粒度的数据库实例,例如用户A 有审核的权限,那么用户A 可以审核所有的DB,此时使用内置的权限系统就可以满足需求了,但随着系统的不断完善要接入其他部门的数据库管理,这就要求针对不同用户开放不同DB的权限了,例如A部门的用户只能操作A部门的DB,Django内置基于model的权限无法满足需求了。

实现过程

先来确定下需求:

  1. 保持原本的基于功能的权限控制不变,例如用户A有查询权限,B有审核权限
  2. 增加针对DB实例的权限控制,例如用户A只能查询特定的DB,B只能审核特定的DB

对于上边需求1用内置的权限系统已经可以实现,这里不赘述,重点看下需求2,DB信息都存放在同一个表里,不同用户能操作不同的DB,也就是需要把每一条DB信息与有权限操作的用户进行关联,为了方便操作,我们考虑把DB跟用户组关联,在用户组里的用户都有权限,而操作类型经过分析主要有两类读和写,那么需要给每个MySQL实例添加两个字段分别记录对此实例有读和写权限的用户组

如下代码在原来的model基础上添加read_groupswrite_groups字段,DB实例跟用户组应是ManyToManyField多对多关系,一个实例可以关联多个用户组,一个用户组也可以属于多个实例

class Mysql(models.Model):Env = ((1, 'Dev'),(2, 'Qa'),(3, 'Prod'),)create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')project_id = models.IntegerField(verbose_name='项目')project_tmp = models.CharField(max_length=128, default='')environment = models.IntegerField(choices=Env, verbose_name='环境')master_host = models.GenericIPAddressField(verbose_name='master主机')master_port = models.IntegerField(default=3306, verbose_name='master端口')slave_host = models.GenericIPAddressField(null=True, verbose_name='slave主机')slave_port = models.IntegerField(null=True, default=3306, verbose_name='slave端口')database = models.CharField(max_length=64, verbose_name='数据库')read_groups = models.ManyToManyField(Group, related_name='read', verbose_name='读权限')write_groups = models.ManyToManyField(Group, related_name='write', verbose_name='写权限')description = models.TextField(null=True, verbose_name='备注')

model确定了,接下来我们分三部分详细介绍下权限验证的具体实现

列表页权限控制

8fqkJd2s8HJjQAAAAASUVORK5CYII=

如上图列表页,每个用户进入系统后只能查看自己有读权限的MySQL实例列表,管理员能查看所有,代码如下:

def mysql(request):if request.method == 'GET':if request.user.is_superuser:_lists = Mysql.objects.all().order_by('id')else:# 获取登录用户的所有组_user_groups = request.user.groups.all()# 构造一个空的QuerySet然后合并_lists = Mysql.objects.none()for group in _user_groups:_lists = _lists | group.read.all()return render(request, 'overmind/mysql.index.html', {'request': request, 'lPage': _lists})

实现的思路是:获取登录用户的所有组,然后循环查询每个组有读取权限的数据库实例,最后把每个组有权限读的数据库实例进行合并返回

获取登录用户的所有组用到了ManyToMany的查询方法:request.user.groups.all()

最终返回的一个结果是QuerySet,所以我们需要先构造一个空的Queryset:Mysql.objects.none()

QuerySet合并不能用简单的相加,应为:QuerySet-1 | QuerySet-2

查询接口权限控制

A9upb9Dm1JoCAAAAAElFTkSuQmCC

如上图系统中有很多功能是需要根据项目、环境查询对应的DB信息的,对于此类接口也需要控制用户只能查询自己有权限读的DB实例,管理员能查看所有,代码如下:

def get_project_database(request, project, environment):if request.method == 'GET':_jsondata = {}if request.user.is_superuser:# 返回所有项目和环境匹配的DB_lists = Mysql.objects.filter(project_id=int(project),environment=int(environment))_jsondata = {i.id: i.database for i in _lists}else:# 只返回用户有权限查询的DB_user_groups = request.user.groups.all()for group in _user_groups:# 循环mysql表中有read_groups权限的所有组for mysql in group.read.all():if mysql.project_id == int(project) and mysql.environment == int(environment):_jsondata[mysql.id] = mysql.databasereturn JsonResponse(_jsondata)

实现思路与上边类似,只是多了一步根据项目和环境再进行判断

需要根据group去反查都有哪些DB实例包含了该组,这里用到了M2M的related_name属性:group.read.all()

更多关于Django ORM查询的内容可以看这篇文章Django model select的各种用法详解有详细的总结

执行操作权限控制

除了上边的两个场景之外我们还需要在执行具体的操作之前去判断是否有权限,例如执行审核操作前判断用户是否对此DB有写权限

有很多地方都需要做这个判断,所以把这个权限判断单独写个方法来处理,代码如下:

def check_permission(perm, mysql, user):# 如果用户是超级管理员则有权限if user.is_superuser:return True# 取出用户所属的所有组_user_groups = user.groups.all()# 取出Mysql对应权限的所有组if perm == 'read':_mysql_groups = mysql.read_groups.all()if perm == 'write':_mysql_groups = mysql.write_groups.all()# 用户组和DB权限组取交集,有则表示有权限,否则没有权限group_list = list(set(_user_groups).intersection(set(_mysql_groups)))return False if len(group_list) == 0 else True

实现思路是:根据传入的第三个用户参数,来获取到用户所有的组,然后根据传入的第一个参数类型读取或写入和第二个参数DB实例来获取到有权限的所有组,然后对两个组取交集,交集不为空则表示有权限,为空则没有

M2M的.all()取出来的结果是个list,两个list取交集的方法为:list(set(list-A).intersection(set(list-B)))

view中使用就很简单了,如下:

def query(request):if request.method == 'POST':postdata = request.body.decode('utf-8')_host = get_object_or_404(Mysql, id=int(postdata.get('database')))# 检查用户是否有DB的查询权限if check_permission('read', _host, request.user) == False:return JsonResponse({'state': 0, 'message': '当前用户没有查询此DB的权限'})

写在最后

  1. Django有第三方的基于object的权限管理模块Django-guardian,本项目没有使用主要是因为一来权限需求并不复杂,自己实现也很方便,二来个人在非必要的情况下并不喜欢引用过多第三方的包,后续升级维护都是负担
  2. 方案和代码不尽完美,各位有更好的方案建议或更优雅的代码写法欢迎与我交流

o_scan.qrcode.png

相关文章推荐阅读:

  • Django+JWT实现Token认证
  • 我们自研的那些Devops工具

转载于:https://www.cnblogs.com/37Y37/p/10514575.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/449674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter Mac iOS 环境配置

官方文档:flutter.io/docs/get-st… 1.需要的命令行工具 bash curl git 2.x mkdir rm unzip which 2.SDK下载地址 flutter_macos_v1.0.0-stable.zip storage.googleapis.com/flutter_inf… 3.解压Flutter SDK cd ~/Flutter/SDK $ unzip ~/Downloads/flutter_macos_v…

IntelliJ IDEA中新建JAVA WEB项目、maven项目

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。 在IntelliJ IDEA 中新建一个Web应用项目。 1、 在主界面顶部菜单栏依次“File”-"New"-"Project..." 2、在对话框中…

S/4HANA业务角色概览之订单到收款篇

2019独角兽企业重金招聘Python工程师标准>>> 大家好我叫Sean Zhang,中文名张正永。目前在S/4HANA产品研发部门任职产品经理,而这一阶段要从2017年算起,而在那之前接触更多还是技术类的,比如做过iOS、HANA、ABAP、UI5等…

ItelliJ IDEA开发工具使用—创建一个web项目

转自:https://blog.csdn.net/wangyang1354/article/details/50452806概念需要明确一下IDEA中的项目(project)与eclipse中的项目(project)是不同的概念,IDEA的project 相当于之前eclipse的workspace,IDEA的M…

极客无极限 一行HTML5代码引发的创意大爆炸

摘要:一行HTML5代码能做什么?国外开发者Jose Jesus Perez Aguinaga写了一行HTML5代码的文本编辑器。这件事在分享到Code Wall、Hacker News之后,引起了众多开发者的注意,纷纷发表了自己的创意。 这是最初的HTML5代码,它…

DOCKER windows 7 详细安装教程

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。 DOCKER windows安装 DOCKER windows安装 1.下载程序包2. 设置环境变量3. 启动DOCKERT4. 分析start.sh5. 利用SSH工具管理6. 下载镜像 6.1…

芝麻信用免押金成趋势 报告称租赁经济有望突破10万亿元

中新网1月16日电 “很多物品都是租来的,但生活不是。”如今,越来越多的年轻人选择了“租”生活,从房子到车子,从服饰到电脑,甚至玩具、婴儿车,全都可以租用,租赁已成为当下年轻人追求品质生活的…

开发者成功学:扔掉你那些很sexy的想法

摘要:在开发者的世界里,开发iPhone应用并不像表面那么光鲜,收支不成正比是常有之事,劳心劳力开发的应用无人问津更是屡见不鲜。走出了开发的一小步却难以迈出销售推广上的一大步,究竟如何才能将应用卖出去并获取利润&a…

html-body相关标签

一 字体标签 字体标签包含&#xff1a;h1~h6、<font>、<u>、<b>、<strong><em>、<sup>、<sub> 标题 标题使用<h1>至<h6>标签进行定义。<h1>定义最大的标题&#xff0c;<h6>定义最小的标题。具有align属性&a…

CentOS上安装Docker (图解)

更简单的办法&#xff1a;三分钟装好 Docker ( 图解&#xff09; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 // 用上面那个办法吧&#xff0c;简单多了&#xff0c;下面这个方法看看…

Uber提出有创造力的POET:自行开发更困难环境和解决方案

近日&#xff0c;Uber 发文介绍了一种开放式方法 POET&#xff08;Paired Open-Ended Trailblazer&#xff09;&#xff0c;可自行开发难度递增的环境及其解决方案&#xff0c;还可以实现不同环境中的智能体迁移&#xff0c;促进进化。Uber AI 实验室注重开放性&#xff08;ope…

C语言编译过程总结详解

源文&#xff1a;http://bbs.dzsc.com/space/viewspacepost.aspx?postid76976 C语言的编译链接过程要把我们编写的一个c程序&#xff08;源代码&#xff09;转换成可以在硬件上运行的程序&#xff08;可执行代码&#xff09;&#xff0c;需要进行编译和链接。编译就是把文本形…

promise之我见

在我们平时的方法中有很多方法是promise封装的&#xff0c; 有些函数后边跟的then和catch 就是promise的方法&#xff0c;先看一下pormise的特点 &#xff08;1&#xff09;对象的状态不受外界影响。Promise对象代表一个异步操作&#xff0c;有三种状态&#xff1a;pending&…

CPP虚析构函数

#include<iostream> using namespace std;class base {public:base(){};virtual ~base(){}; };// 在类声明中声明纯虚析构函数 //base::~base() {}class father: public base {public:~father(){cout << "father" << endl;} };int main() {base* a…

idea 新建springboot 的 web 项目

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 IDAE中新建web项目见&#xff1a;IntelliJ IDEA中新建JAVA WEB项目、maven项目 默认已有一个 maven 的 web 项目&#xff1a;gentle --…

从全栈式解决方案到情感化,揭秘问众智能切入车载语音市场的最佳姿势...

*问众智能CEO张亚 如果说语音交互是车载场景的最佳方式&#xff0c;未来谁掌握车内语音交互“话语权”&#xff0c;谁就将主宰车辆智能网联的新时代。 经过多年的渐进式发展&#xff0c;语音交互的价值正逐步走出单纯“控制方式”的狭隘理解&#xff0c;向业内人眼中的“智能…

阿里云日志添加要查询字段

第一步&#xff1a;在API基控制器&#xff08;base文件下&#xff09;下面 $arr 就是我要接受的所有参数值&#xff0c;而 msg_id就是我以后要在阿里云日志中查询的字段&#xff0c;以此字段统计某些数据 $arr 是前台API接口传过来的数据 &#xff0c;这里我需要使用 $arr[id] …

总理整节从事量化交易员所做工作与代码

数据获取&#xff08;期权数据&#xff09; 本人从事领域为量化期权领域&#xff08;皇冠上的明珠&#xff0c;真好听&#xff0c;可是做起来&#xff0c;难度真是&#xff08;滴-------------&#xff09;&#xff09;。从最开始的手动从三大所复制粘贴期权数据&#xff0c;到…

Docker 上安装、启动 MySQL (图解)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 在docker仓库中搜索mysql的镜像&#xff1a; docker search mysql 下载镜像&#xff1a; docker pull mysql 2. 查看本地镜…

关于 std::set/std::map 的几个为什么

2013-01-20 std::set/std::map &#xff08;以下用 std::map 代表&#xff09; 是常用的关联式容器&#xff0c;也是 ADT&#xff08;抽象数据类型&#xff09;。也就是说&#xff0c;其接口&#xff08;不是 OO 意义下的 interface&#xff09;不仅规定了操作的功能&#xff…