自动化工具 测试平台-flask_admin+mongoEngine 实现数据权限

池小波 for 新潮测试技术 · 2020年06月09日 · 1546 次阅读

该文原创为新潮质量保障技术团队中的 “上进的中年软件测试从业者”,用于技术交流分享

前几天看到一句话感触颇多,大意是是团队管理者总是喜欢培养一个小一号的自己。说白了也就是喜欢和自己做事风格一致,但是暂时又不如自己的人。这两点都很容易解读,大多数人对自己都比较宽容,而对别人会相对苛刻一些;同时也希望自己是这个团队的权威,不希望受到挑战。

之前有跟同事聊过,我希望我周围全是 P7 或者 M3 以上的大牛。只所以这样说,是因为 14 年我出来找工作的时候,本来信心满满结果却碰了一头的包。而我自认为之前的那份工作经历,以及我的个人表现是能够非常轻松的找到一份满意的工作。自此以后终于顿悟,更有后面丰富的工作经历的磨砺,明白了两个道理、一件事:

- 不做井底之蛙

- 磨刀不误砍柴工

- 合作真的能够共赢

都是很简单的道理,有些人天生就懂,有些人就需要交点学费了。

开篇

本来都在发愁,马上两周就到了,似乎没什么素材可写了。好在有这样一件事在牵绊着,没有素材自己创造素材也要写。

最早的时候,我们有介绍如果通过 flask_admin 和 MongoEngine 来实现业务权限控制,具体请参考测试平台之权限和角色实现。有过开发和一定测试经验的小伙伴一定也接触过数据权限控制,通俗来讲就是我的数据不希望你可以看,你的数据也不要扰乱我的正常数据的使用。

背景

当测试平台逐渐投入使用后,发现过多的数据量会影响大家的工作效率,而不同项目组之前一般是不关系彼此的测试数据的。之前设计平台的时候并没有考虑数据权限方面的控制,今天就简单给大家说一下大概的设计思路。

## 调研

熟悉 flask_admin+mongoEngine 这套 MVC 框架的小伙伴,Model 层对 collection 定义的 Document, 本身具备了对调用者返回全部数据的能力,这源于 objects 属性:

# Provide a default queryset unless exists or one has been set
if 'objects' not in dir(new_class):
new_class.objects = QuerySetManager()

结合官方文档和源码的介绍,这里大概知道 objects 和 querySet 有那么千丝万缕的关系,实际上也不用过多的猜测,默认返回全部数据,源于对 objects 属性没有做任何限制。

根据上面的调研,我们发现可以对 objects 属性做限制,即可实现数据权限,但是这里就有问题了:你会发现这样并没有实现真正意义的数据权限,而是强制性的让数据固定的只返回一部分给调用者。如下面的代码所示,这里只是实现了给调用者返回的永远都是有效标识的数据,而并没有对不同的用户进行数据权限。

@queryset_manager
def objects(doc_cls, queryset):
return queryset.filter(available=True)

所以,在 Model 层做数据权限是❌的。

但是这里也给了我们很好的启示,我们可以定义不同的数据集合,方便调用者使用,如下面的代码:

`@queryset_manager
def objects(doc_cls, queryset):
"""
reference 引用时,默认返回的数据
:param queryset:
:return:
"""
tester = TesterForm.objects(testerId=current_user.id).first()
return queryset.filter(Q(creator=tester) | Q(updater=tester) | Q(projectTesters=tester)).filter(
available=True).filter(Q(readyToTestDate_lte=datetime.datetime.now().date()) & Q(
readyToReleaseDate
_gte=datetime.datetime.now().date()))

@queryset_manager
def objects_tester_rule(doc_cls, queryset):
"""
测试人员数据权限
:param queryset:
:return:
"""
tester = TesterForm.objects(testerId=current_user.id).first()
return queryset.filter(Q(creator=tester) | Q(updater=tester) | Q(projectTesters=tester)).filter(
available=True)

@queryset_manager
def objects_all(doc_cls, queryset):
"""
全部数据
:param queryset:
:return:
"""
return queryset.filter()`

PS:现在写这篇文章的时候,才有点后知后觉,Model 层确实不应该涉及数据权限,而是专心的处理数据整合和映射方面工作。

既然在 Model 层无法实现数据权限,那我们就可以在 View 层尝试实现数据权限。

解决过程

flask_admin 作为后台管理框架,有一套后台管理的脚手架,包括自动生成列表、通过各种属性控制列表能力(新增、编辑、修改)、自动生成路由接口等。这里我们就找到了一个默认查询列表数据的方法:

def get_query(self):
"""
Returns the QuerySet for this view. By default, it returns all the
objects for the current model.
"""
return self.model.objects

这里我们发现 get_query 默认返回了 Model 层对应 Document 的全部数据(如果 Model 做了限制,如只返回 available 字段为 True 的,这里同样只能获取相同的数据),找到了对应的方法,就可以有针对应的进行处理,如下面的场景,每个人只能看到自己创建的数据:

def get_query(self):
return self.model.objects(tester=current_user)

场景升级

我们现在来看解决过程确实很简单,前提是你要了解这套框架。

了解 mongoEngine 的小伙伴知道它提供了如 MySQL 数据关联同样功能的一种特殊字段类型 ReferenceField,那如何对 ReferenceField 进行数据限制呢。比如说有一个项目表、一个 bug 表,bug 表的项目字段关联到项目表,我需要在 bug 视图创建 bug 时只能选择我自己创建的项目,该如何做数据权限控制呢?

这个时候就要用到我们上面介绍到的对 Document 定义不同的数据集即可,如重定义 objects 只返回当前用户的数据(前提是我们已经知道 ReferenceField 获取的就是 objects 属性的数据,感兴趣的可以去研究源码),定义另外一个 objects_all 返回 Document 所有的数据给其他调用者使用,如项目视图可以采用重写 get_query 的方法获取所有的数据,而不是默认的 objects 属性的数据:

def get_query(self):
return self.model.objects_all()

这里有点绕,感兴趣的欢迎私聊。

思考

上面的解决方案似乎解决了所有的问题,但是作为一名测试人员,还是发现了一个场景是无法解决。也就是在上面场景下,有另外一个用例的表,项目字段同样也是关联的项目表,但是通过 ReferenceField 需要看到的项目是全部的项目(我们知道在这个场景里面 objects 已经被重写只返回当前用户的项目,并且 ReferenceField 默认获取 objects 对象的数据),但是这里,用户在写用例的时候也只能选择自己参与的项目,而不能选择所有的项目。

因为暂时还没有这样的场景,所以也就没有过多去调研了,有过这方面实战的小伙伴请不吝赐教。

结语

下次会是什么不期而至的难题呢,自动化、平台建设、CI/CD 还是?因为牵绊所以坚持,因为坚持所以牵绊,加油。

我总是习惯给自己标榜沟通能力差,希望有人帮我塑造一个沟通能力强的人设,可能真的会让我这方面有所提升。

又是一个周末的到来,祝各位小伙伴周末愉快。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册