2021年 Wiki 加入了很多強硬的特性,其中包括協同編輯 、頁面許可權、表情符號 等,這些功能給使用者帶來了更好的體驗。作為 Wiki 使用者兼開發者,今日來聊聊年終上線的頁面許可權,同時總結一下開發階段涉及到的技術、遇到的問題以及解決方案,關於許可權本人之前已經寫過一篇 Worktile 許可權的文章了,Worktile 許可權著重講了 RBAC(基於角色的許可權控制方案)的設計與實現,本文基於 Wiki 頁面許可權選擇的另一個主流許可權設計的方案:ACL。
本文大致分為三部分:
- ACL 介紹
- 介紹我們的許可權以及為什麼選擇它
- 設計實現
一、ACL 介紹
1. 什麼是 ACL?
ACL:Access Control List,許可權控制列表,是對檔案以及目錄的許可權控制方案。大名鼎鼎的 Linux 許可權系統,它就是 ACL 的典型案例,本人在開發過程中也受到了 Linux 許可權設計的一些啟發。
2. ACL的使用場景
使用場景也可以換個問法:為什麼要使用 ACL ?關於這個問題我們還以 Linux 作為案例:Linux 本身只提供了Owner(所有者)、Group(使用者組)、Others(其他成員),也就是說其他成員或使用者組是無法指定更細粒度的許可權。
為了更好的解釋,我們來舉個簡單的例子(場景):
有 4 個成員有 A、B、C、D,其中 A、B、C 是開發組G的成員,A成員建立了一個程式碼倉庫並把團隊開發的程式碼放置到該目錄中,其中這些程式碼主要是關於G組的,與其他成員無關,所以A把檔案目錄設定了許可權,許可權是組內可讀可寫,其他人沒有任何許可權。
現在來分析一下各個角色,A 是倉庫的 Owner,G 是 Group(含B、C),D 是與該檔案無關的成員,所以是 Others。現在入職了一個使用者E,因為E是新人,所以不想讓E去操作程式碼,只允許他檢視熟悉程式碼。
面對這種場景,試想一下如何給 E 成員設定對應許可權呢?答案是 oh no,因為 E 既不能按照 G 組許可權,也不能按照 Others 許可權,更不能是 Owner!所以面對這種雞肋的許可權,ACL 就作為了其補充,ACL 可以支援針對某一個使用者或某個使用者組做獨立的許可權,完美解決了類似場景。
二、PingCode Wiki 許可權架構
OK,瞭解了什麼是 ACL 以及使用場景,我們來聊一下 Wiki 的許可權架構,基本架構見下圖。
1. 基礎許可權 RBAC——角色對應的許可權
1.1 許可權配置
1.2 檢視許可權
2. 頁面許可權ACL——針對使用者和使用者組的許可權
頁面許可權同樣是作為 RBAC 的補充,更加細粒度的許可權劃分,即給某部分人或某部分使用者組分配對應許可權。
2.1 頁面許可權設定入口
右上角選單——「更多-許可權設定」
2.2 獨立許可權與共享許可權
獨立許可權——「許可權設定-知識庫成員」
共享許可權——「許可權設定-非知識庫成員」
從我們的需求(針對使用者和使用者組部分人群)來看是符合 ACL 解決方案的。
三、設計與實現
到這裡的同學相信已經對 Wiki 許可權功能有了大致的瞭解,下面開始詳細介紹一下開發中的設計細節,關鍵點、難點以及方案的選用取與舍。
1. 關係模型與資料庫設計
瞭解基本需求,本人根據以往的開發經驗簡單的定了一個初步方案,見下圖方案A,後來對照原型和設計圖調整了一些細節,產生了方案B,見下圖。
A方案
B 方案(最終方案)
兩種方案的區別:
A 方案是關係型資料庫的常見設計,以使用者/使用者組為主導對映模組和許可權,對應關係為:多對多 使用者/使用者組(user/group) ——> 資源(pageId)
B 方案,以模組為主導對映使用者/使用者組,一對多:資源(pageId)——> 使用者/使用者組(scopes)
考慮到許可權是全量儲存的,正是由於這種互動方式所以調整出了 B 方案,而且服務端資料庫是 mongodb,支援陣列的特性,非常的契合。
2. 對應的程式實現
資源或模組(pageId)——> 使用者/使用者組(scopes),實體對應關係
欄位 | 型別 | 描述 |
---|---|---|
scopes | Array<Schema> | 許可權設定詳細 |
permission_type | Enum | 許可權型別 |
principals | ScopeDesignation[] | 指定詳細 |
type | ScopeDesignationType | 指定型別 |
id | UIDlID | 人/使用者組/部門的唯一標識 |
value | number | 許可權值 |
許可權值計算參考了Linux,採用的是位運算:1、2、4、8...
- 擁有的許可權值累計取最大mask值:只讀1 + 編輯 2,最終資料庫存3。
判斷是否擁有某個許可權值:許可權值 & 預期的許可權值 = 存在/預期的許可權值。
3. 約定 API
還是由於儲存互動決定了新增、修改、刪除是一個 API。
- 儲存(新增/修改/移除):
{put}/api/wiki/spaces/:spaceId/pages/:pageId/permission$
獲取:
{get} /api/wiki/spaces/:spaceId/pages/:pageId/permission/:scopeType$
4. 涉及的複雜場景
許可權涉及的複雜場景非常的多,以下場景討論和決定經歷了很多波折,某些功能中途重做了兩次,迭代向後推遲了一週,兩次會議,共持續大約兩個半小時,由於文章篇幅,只列舉部分場景作為交流。
4.1 許可權繼承(複雜場景)
第一個難處理的點就是許可權繼承,確實很繞,相信很多產品都遇到過類似場景(具體是哪些這裡不贅述了),本人從技術和產品的角度都參考過 Linux 的處理,但由於系統級別和應用還是有些許不同,做了一些調整,下面羅列的是我們做過的方案。
第一版:子頁面完全繼承父頁面且不可更改- 父頁面不可見,子頁面不可見
- 父頁面只讀,子頁面編輯,最終子頁面是隻讀
- 父頁面編輯,子頁面只讀,最終子頁面是編輯
不滿足的場景和邏輯的衝突點:
- 父頁面允許檢視,預期某些子頁面允許檢視,某些子頁面不可檢視
- 父頁面允許檢視但不許編輯,預期某些子頁面開放編輯許可權
- 父頁面允許編輯,但某些子頁面不允許編輯
第二版:子頁面允許獨立許可權,預設繼承父許可權
頁面繼承邏輯:子頁面一旦設定,中斷與父頁面的繼承關係,中斷後許可權型別可以改
- 父級頁面設定許可權後,子級頁面不設定許可權,預設繼承父級許可權;
- 子級頁面設定許可權後,不再繼承父級許可權,形成自己單獨的頁面許可權
4.2 中介軟體邏輯
中介軟體涉及的場景也比較多,比如單頁面詳情檢視,拖拽移動以及跨知識庫移動,複製,刪除等等,核心的驗證邏輯如下圖。
4.3 頁面樹展示及許可權計算(難點)
4.3.1 展示邏輯
會議上,討論頁面樹的展示的可選方案如下:
- 展示全部層級頁面,根據許可權可見內容(存在標題私密性問題,層級過多後,會看到多個不可見的標題,拋棄)
- 只展示有許可權的頁面,把子頁面提出來(層級結構打亂了,拋棄)
- 落地方案:只展示有許可權的頁面,當父頁面不可見時,子頁面可見時,把父頁面展示出來,如果沒有可見的子頁面時父頁面也不可見(方案二)---父頁面不可見的標題用灰色展示(點選效果保留),點選後右側無許可權頁面
- 直接隱藏父與子(已實現方案一,僅能透過通知檢視,入口少,改變繼承關係後,不太適用,拋棄)
最終確定的是一個折中方案,如果子頁面有許可權,那麼會把父頁面也展示出來,保證樹結構的完整性,若當前頁面和子頁面都沒有許可權,那麼就過濾掉。如下圖
4.3.2 許可權計算(技術難點)
難點:
- 頁面樹涉及到繼承
- 全量的計算(弄不好需要遞迴處理),所以程式計算複雜度要考慮
- 過濾邏輯(子有檢視許可權,父一定展示)
解決手段
- 資料庫儲存 parent 和 parent_ids 欄位,parent_ids 按照樹的層級拍平排序,比如樹的層級是 A->B->C,parent_ids 儲存的是 [A,B,C]。
- 把列表資料分割成兩組,一組是所有的頁面,另一組是所有作為了父頁面的,具體怎麼查詢只需要驗證parent_id 是否存在即可,屬於資料庫操作。
- 把 List 資料結構轉換成 Map 或 Set,減少迭代次數來降低複雜度。
具體處理邏輯
準備工作:
- 從資料庫中取出當前知識庫的頁面列表資料和頁面對應的 ACL 許可權列表
- 將頁面列表拍平轉換為Map(為了下面減少複雜度)
- 將許可權列表拍平轉換為 Map(目的同上)
- 取出 RBAC 的知識庫許可權
- 用來處理子頁面可見,父頁面不可見,而需要展示的父頁面集合 Set(在下面會詳細講解具體用處)
- 最終要 頁面列表(含許可權值)
如下圖(為了方便後續的講解,特標註序號)
處理流程,如下圖:
- 迭代頁面列表
- 取出一個頁面,找對應許可權
- 如果找到了設定的對應許可權,那麼合併 RBAC 許可權放入最終的結果集,沒有找到執行下面邏輯a. 透過當前頁面的 parent_ids 按倒序迭代(上面解決手段已講解 parent_ids 的排列順序)
b. 如果沒有父級或所有父級均沒有許可權,則繼續最外層迭代,反之取出最近有許可權的父級頁面,並且將父級頁面與當前頁面之間的頁面放入 ⑥ (Set)中,這塊大家可能有疑問,舉個例子:假設有3個頁面,A、B、C,C 的父級是 B,B 的父級是 A,當前迭代的是 C,C 沒有許可權,B也沒有許可權,A有許可權,那麼最終將 A、B 推入 Set 中,C 由於繼承邏輯 也擁有許可權。 - 將 Set 中的父級頁面補充到最終的結果集
- 繼續迭代,直至完畢
最後發現 ⑥ 是為了補充缺失的一部分資料:子頁面可見,父頁面不可見,但要展示的父頁面。
大家可能從上面環節還有一個疑問就是為什麼要合併 RBAC 知識庫許可權,請接著看。
4.4 許可權相關特殊處理
上面談到了已經驗證出頁面許可權,但還是合併了知識庫許可權,是因為產品邏輯中有以下規定:
- 知識庫沒有編輯許可權,頁面許可權有,那麼最終有編輯許可權,所以要覆蓋知識庫的編輯許可權。
- 擁有隻讀許可權的成員不能執行的操作:
- 不能建立子頁面
- 不能刪除當前頁面
- 不能移動當前頁面
- 不能移動至只讀的頁面下(對目標頁面來說實際上是新增頁面)
- 不能複製頁面
- 不能複製至只讀頁面下
- 不能釋出頁面
- 不能重新命名頁面
- 不能進入編輯頁面
注意的是切勿漏掉場景。
4.4.1 個人對後續的設想
針對於這些特殊處理,個人感覺終究不是解決方案,而且後續新加或移除許可權,都要兼顧多處,技術角度維護起來困難,也容易漏掉,所以設想頁面也加入更多許可權點,讓使用者來主動勾選所需要和禁止的許可權,技術上就可以統一一套邏輯。
五、寫在最後
上面只是列舉了一些通用和些許複雜的場景,實際開發中還有很多的細節,所以開發期間很忙,經歷了頭腦風暴,也有走誤區的及時調整和反思,這些都是寶貴的經驗。
最後談談開發至今的一些感悟:
- 團隊之間及時溝通,充分理解需求,建立統一認知,避免把煙囪做成水井的糗事,也減少互相甩鍋的情況,對團隊有良好的作用,值得一提的是Wiki團隊秉承著這一優良習慣,與 Wiki 這款產品的價值觀相契合 。
- 技術人員要積極考慮需求,優先站在使用者和產品角度思考,其次再考慮技術,切勿過於執著技術,卻忽略了使用者和產品的初衷。
- 技術人員對每一行程式碼負責,別把技術債留給自己或別人,程式碼終將會回饋團隊和你自身。
- 要做好細節,作為還是技術都要做好細節,“剛”到可能是細節決定的成敗。
到這裡結束了,非常感謝每個看到這裡的同學 ,有任何疑問歡迎討論交流。
歡迎關注 Wiki,關注 PingCode 研發中心
最後,推薦我們的智慧化研發管理工具 PingCode 給大家。
關於PingCode
PingCode是由國內老牌SaaS廠商Worktile 打造的智慧化研發管理工具,圍繞企業研發管理需求推出了Agile(敏捷開發)、Testhub(測試管理)、Wiki(知識庫)、Plan(專案集)、Goals(目標管理)、Flow(自動化管理)、Access (目錄管理)七大子產品以及應用市場,實現了對專案、任務、需求、缺陷、迭代規劃、測試、目標管理等研發管理全流程的覆蓋以及程式碼託管工具、CI/CD流水線、自動化測試等眾多主流開發工具的打通。
自正式釋出以來,以酷狗音樂、商湯科技、電銀資訊、51社保、萬國資料、金鷹卡通、用友、國汽智控、智齒客服、易快報等知名企業為代表,已經有超過13個行業的眾多企業選擇PingCode落地研發管理。