測試平臺-flask_admin+mongoEngine 實現資料許可權

池小波發表於2020-06-09

該文原創為新潮質量保障技術團隊中的 “上進的中年軟體測試從業者”,用於技術交流分享

前幾天看到一句話感觸頗多,大意是是團隊管理者總是喜歡培養一個小一號的自己。說白了也就是喜歡和自己做事風格一致,但是暫時又不如自己的人。這兩點都很容易解讀,大多數人對自己都比較寬容,而對別人會相對苛刻一些;同時也希望自己是這個團隊的權威,不希望受到挑戰。

之前有跟同事聊過,我希望我周圍全是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還是?因為牽絆所以堅持,因為堅持所以牽絆,加油。

我總是習慣給自己標榜溝通能力差,希望有人幫我塑造一個溝通能力強的人設,可能真的會讓我這方面有所提升。

又是一個週末的到來,祝各位小夥伴週末愉快。

相關文章