Metabase二次开发教程-05-行级权限
-
行级权限
- metabase运行的middleware中包含4个enterprise edition的方法,其中 middleware.row_level_restrictions/apply-row-level-permissions 处理行级权限
- 开启行级权限后,一次图表查询可能会触发多次DP流程,因为组装行级权限过程中会回调 query-processor/process-query
- 注意,本文中的代码框引用的都是返回的参数示例,而不是源码,如需查看源码请移步gitHub
apply-row-level-permissions
- 调用 query->table-id->gtap 方法,从元数据中查询 table-id->gtap;
;; 示例 table-id->gtap {2#metabase.models.group_table_access_policy.GroupTableAccessPolicyInstance{ :id 1, :group_id 4, :table_id 2, :card_id nil, :attribute_remappings {user_id [:dimension [:field-id 3]]} }}
- 如果 table-id->gtap 为空,说明该表单不受权限控制,直接返回 query;否则,调用 gtapped-query 方法对 query 进行改写
- gtappend-query 方法本质上做两件事情:调用 apply-gtags 改写query,以及更新query中的 :gtap-perms字段
apply-gtaps
- apply-gtaps 方法的核心是调用 mbql.util/replace方法,该方法可以替换掉一个map中符合其筛选条件的子map
- 第2个参数 (_ :guard (every-pred …)) 用于从 m 中筛选出符合条件的子map并存到 &match 中
;;示例 m {:database 1, :query {:source-table 2, :filter [:= [:field-id 2] 3 6 7]}, :type :query, :middleware {:js-int-to-string? true, :add-default-userland-constraints? true}, :info {:executed-by 2, :context :ad-hoc, :nested? false, :query-hash #object[[B 0x2f2dcce0 [B@2f2dcce0]}, :constraints {:max-results 10000, :max-results-bare-rows 2000}}
;;示例 &match {:source-table 2, :filter [:= [:field-id 2] 3 6 7]}
- 第3个参数是用于替换 &match 的新值,本例中通过3步计算
- 调用 apply-gtab 方法,计算 updated 值;
;; 示例 updated { :filter [:= [:field-id 2] 3 6 7], :source-query { :source-table 2, :filter [:= [:field-id 3] [:value 1 {:base_type :type/Integer, :special_type :type/FK, :database_type INTEGER, :name USER_ID}]], :fields [[:field-id 9] [:field-id 3] [:field-id 5] [:field-id 6] [:field-id 8] [:field-id 7] [:field-id 1] [:datetime-field [:field-id 4] :default] [:field-id 2]] }, :source-metadata [ {:name ID, :id 9, :table_id 2, :display_name ID, :base_type :type/BigInteger, :special_type :type/PK, :fingerprint nil, :settings nil} {:name USER_ID, :id 3, :table_id 2, :display_name USER_ID, :base_type :type/Integer, :special_type :type/FK, :fingerprint {:global {:distinct-count 929, :nil% 0.0}}, :settings nil} {:name PRODUCT_ID, :id 5, :table_id 2, :display_name PRODUCT_ID, :base_type :type/Integer, :special_type :type/FK, :fingerprint {:global {:distinct-count 200, :nil% 0.0}}, :settings nil} {:name SUBTOTAL, :id 6, :table_id 2, :display_name SUBTOTAL, :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 340, :nil% 0.0}, :type {:type/Number {:min 15.691943673970439, :q1 49.74894519060184, :q3 105.42965746993103, :max 148.22900526552291, :sd 32.53705013056317, :avg 77.01295465356547}}}, :settings nil} {:name TAX, :id 8, :table_id 2, :display_name TAX, :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 797, :nil% 0.0}, :type {:type/Number {:min 0.0, :q1 2.273340386603857, :q3 5.337275338216307, :max 11.12, :sd 2.3206651358900316, :avg 3.8722100000000004}}}, :settings nil} {:name TOTAL, :id 7, :table_id 2, :display_name TOTAL, :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 10000, :nil% 0.0}, :type {:type/Number {:min 12.061602936923117, :q1 52.006147617878135, :q3 109.55803018499738, :max 238.32732001721533, :sd 38.35967664847571, :avg 82.96014815230805}}}, :settings nil} {:name DISCOUNT, :id 1, :table_id 2, :display_name DISCOUNT, :base_type :type/Float, :special_type :type/Discount, :fingerprint {:global {:distinct-count 701, :nil% 0.898}, :type {:type/Number {:min 0.17088996672584322, :q1 2.9786226681458743, :q3 7.338187788658235, :max 61.69684269960571, :sd 3.053663125001991, :avg 5.161255547580326}}}, :settings nil} {:table_id 2, :special_type :type/CreationTimestamp, :unit :default, :name CREATED_AT, :settings nil, :id 4, :display_name CREATED_AT, :fingerprint {:global {:distinct-count 9998, :nil% 0.0}, :type {:type/DateTime {:earliest 2016-04-30T18:56:13.352Z, :latest 2020-04-19T14:07:15.657Z}}}, :base_type :type/DateTime} {:name QUANTITY, :id 2, :table_id 2, :display_name QUANTITY, :base_type :type/Integer, :special_type :type/Quantity, :fingerprint {:global {:distinct-count 62, :nil% 0.0}, :type {:type/Number {:min 0.0, :q1 1.755882607764982, :q3 4.882654507928044, :max 100.0, :sd 4.214258386403798, :avg 3.7015}}}, :settings nil} ] }
- 在 updated 基础上,递归调用 apply-gtabs 计算 recursively-updated 值;
- 在 recursively-updated 基础上,调用 mbql.util/replace 方法,为 :source-table 所在的map加上一个同级关键字 :gtap? true,即标记为已处理;
apply-gtap
- apply-gtap 是增加行级权限的核心方法,会结合gtap生成新的子map
;;入参 m {:source-table 2, :filter [:= [:field-id 2] 3 6 7]}
;;出参 { :filter [:= [:field-id 2] 3 6 7], :source-query { :source-table 2, :filter [:= [:field-id 3] [:value 1 {:base_type :type/Integer, :special_type :type/FK, :database_type "INTEGER", :name "USER_ID"}]], :fields [[:field-id 9] [:field-id 3] [:field-id 5] [:field-id 6] [:field-id 8] [:field-id 7] [:field-id 1] [:datetime-field [:field-id 4] :default] [:field-id 2]] }, :source-metadata [ {:name "ID", :id 9, :table_id 2, :display_name "ID", :base_type :type/BigInteger, :special_type :type/PK, :fingerprint nil, :settings nil} {:name "USER_ID", :id 3, :table_id 2, :display_name "USER_ID", :base_type :type/Integer, :special_type :type/FK, :fingerprint {:global {:distinct-count 929, :nil% 0.0}}, :settings nil} {:name "PRODUCT_ID", :id 5, :table_id 2, :display_name "PRODUCT_ID", :base_type :type/Integer, :special_type :type/FK, :fingerprint {:global {:distinct-count 200, :nil% 0.0}}, :settings nil} {:name "SUBTOTAL", :id 6, :table_id 2, :display_name "SUBTOTAL", :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 340, :nil% 0.0}, :type {:type/Number {:min 15.691943673970439, :q1 49.74894519060184, :q3 105.42965746993103, :max 148.22900526552291, :sd 32.53705013056317, :avg 77.01295465356547}}}, :settings nil} {:name "TAX", :id 8, :table_id 2, :display_name "TAX", :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 797, :nil% 0.0}, :type {:type/Number {:min 0.0, :q1 2.273340386603857, :q3 5.337275338216307, :max 11.12, :sd 2.3206651358900316, :avg 3.8722100000000004}}}, :settings nil} {:name "TOTAL", :id 7, :table_id 2, :display_name "TOTAL", :base_type :type/Float, :special_type nil, :fingerprint {:global {:distinct-count 10000, :nil% 0.0}, :type {:type/Number {:min 12.061602936923117, :q1 52.006147617878135, :q3 109.55803018499738, :max 238.32732001721533, :sd 38.35967664847571, :avg 82.96014815230805}}}, :settings nil} {:name "DISCOUNT", :id 1, :table_id 2, :display_name "DISCOUNT", :base_type :type/Float, :special_type :type/Discount, :fingerprint {:global {:distinct-count 701, :nil% 0.898}, :type {:type/Number {:min 0.17088996672584322, :q1 2.9786226681458743, :q3 7.338187788658235, :max 61.69684269960571, :sd 3.053663125001991, :avg 5.161255547580326}}}, :settings nil} {:table_id 2, :special_type :type/CreationTimestamp, :unit :default, :name "CREATED_AT", :settings nil, :id 4, :display_name "CREATED_AT", :fingerprint {:global {:distinct-count 9998, :nil% 0.0}, :type {:type/DateTime {:earliest "2016-04-30T18:56:13.352Z", :latest "2020-04-19T14:07:15.657Z"}}}, :base_type :type/DateTime} {:name "QUANTITY", :id 2, :table_id 2, :display_name "QUANTITY", :base_type :type/Integer, :special_type :type/Quantity, :fingerprint {:global {:distinct-count 62, :nil% 0.0}, :type {:type/Number {:min 0.0, :q1 1.755882607764982, :q3 4.882654507928044, :max 100.0, :sd 4.214258386403798, :avg 3.7015}}}, :settings nil} ] }
- 其内部调用了metabase.util/prog1 方法,该方法本质上是一个装饰器:运行主函数(第一个参数)并返回其结果(保存在 <> 中),返回前运行其它函数(比如记录日志)
- 主函数是一个merge,即将m中的 :source-table 和 :source-query 先排除,然后插入 gtap->source 方法的返回结果
gtap->source
- 该函数通过调用 preprocess-source-query ,生成包含 :source-query 和 :source-metadata 的map,示例参照上面 apply-gtap 的出参;
- preprocess-source-query中执行顺序如下:
- 调用 card-gtap->source 或 table-gtap->source 生成初版source-query,放到 query 中;
;; 示例:table-gtap->source生成的query { :database 1, :type :query, :query { :source-query { :source-table 2, :parameters [{:type :category, :target [:dimension [:field-id 3]], :value 1}] }}}
- 将 query 传入 metabase.query-processor/query->preprocessed 生成 preprocessed;
;; 示例: preprocessed { :database 1, :type :query, :query { :source-metadata [...], :fields [...], :limit 1048576, :source-query { :source-table 2, :filter [:= [:field-id 3] [:value 1 {:base_type :type/Integer, :special_type :type/FK, :database_type INTEGER, :name USER_ID}]], :fields [[:field-id 9] [:field-id 3] [:field-id 5] [:field-id 6] [:field-id 8] [:field-id 7] [:field-id 1] [:datetime-field [:field-id 4] :default] [:field-id 2]] }}}
- 从 preprocessed 中取出 :query 字段中的 :source-query 和 :source-metadata两个字段信息,返回给调用方
- 调用 card-gtap->source 或 table-gtap->source 生成初版source-query,放到 query 中;
gtap->parameters
- 在gtap->source 中,根据是否包含card-id来选择调用 card-gtap->source 或 table-gtap->source,而这两个函数都会调用 gtap->parameters 来获取权限数据
- 工作流程:
- 从 gtap 中获取表定义的权限设置,保存在 attribute-remappings 中;
- 从 current-user 中获取用户的权限设置,保存在 login_attributes 中;
- 调用 attr-remapping->parameter
;; 示例 attribute-remappings {user_id [:dimension [:field-id 3]]}
;; 示例 login_attributes {user_id 1, quantity 2}
转载自:https://blog.csdn.net/weixin_43821438/article/details/111594677