【metabase二次开发 • 五】行级权限运作解析

本文详细解析了Metabase企业版中的行级权限处理机制,包括apply-row-level-permissions、apply-gtaps、apply-gtap及gtap->source等核心方法。这些方法在中间件中对查询进行权限过滤和改写,确保用户只能访问到其被授权的数据。通过实例展示了权限控制如何影响查询流程,以及如何结合用户权限设置生成新的查询结构。
摘要由CSDN通过智能技术生成

行级权限

  • metabase运行的middleware中包含4个enterprise edition的方法,其中 middleware.row_level_restrictions/apply-row-level-permissions 处理行级权限

  • 开启行级权限后,一次图表查询可能会触发多次DP流程,因为组装行级权限过程中会回调 query-processor/process-query

  • 注意,本文中的代码框引用的都是返回的参数示例,而不是源码,如需查看源码请移步gitHub


apply-row-level-permissions

  1. 调用 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]]}
    }}
    
  2. 如果 table-id->gtap 为空,说明该表单不受权限控制,直接返回 query;否则,调用 gtapped-query 方法对 query 进行改写
  3. gtappend-query 方法本质上做两件事情:调用 apply-gtags 改写query,以及更新query中的 :gtap-perms字段

apply-gtaps

  1. apply-gtaps 方法的核心是调用 mbql.util/replace方法,该方法可以替换掉一个map中符合其筛选条件的子map
  2. 第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. 第3个参数是用于替换 &match 的新值,本例中通过3步计算
    a. 调用 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}
      ]
    }
    
    b. 在 updated 基础上,递归调用 apply-gtabs 计算 recursively-updated 值;
    c. 在 recursively-updated 基础上,调用 mbql.util/replace 方法,为 :source-table 所在的map加上一个同级关键字 :gtap? true,即标记为已处理;

apply-gtap

  1. 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}
      ]
    }
    
  2. 其内部调用了metabase.util/prog1 方法,该方法本质上是一个装饰器:运行主函数(第一个参数)并返回其结果(保存在 <> 中),返回前运行其它函数(比如记录日志)
  3. 主函数是一个merge,即将m中的 :source-table:source-query 先排除,然后插入 gtap->source 方法的返回结果

gtap->source

  1. 该函数通过调用 preprocess-source-query ,生成包含 :source-query:source-metadata 的map,示例参照上面 apply-gtap 的出参;
  2. preprocess-source-query中执行顺序如下:
    a. 调用 card-gtap->sourcetable-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}]
    }}}
    
    b. 将 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]]
    }}}
    
    c. 从 preprocessed 中取出 :query 字段中的 :source-query:source-metadata两个字段信息,返回给调用方

gtap->parameters

  1. gtap->source 中,根据是否包含card-id来选择调用 card-gtap->sourcetable-gtap->source,而这两个函数都会调用 gtap->parameters 来获取权限数据
  2. 工作流程:
    a. 从 gtap 中获取表定义的权限设置,保存在 attribute-remappings 中;
    b. 从 *current-user* 中获取用户的权限设置,保存在 login_attributes 中;
    c. 调用 attr-remapping->parameter
    ;; 示例 attribute-remappings
    {user_id [:dimension [:field-id 3]]}
    
    ;; 示例 login_attributes
    {user_id 1, quantity 2}
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值