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

行级权限

  • 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}
    
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Metabase是一种开源的业务智能(BI)工具,具备丰富的数据分析和可视化功能。作为一个强大的BI工具,Metabase也提供了二次开发的功能,使用户可以根据自己的需求进行定制和扩展。 Metabase二次开发工具包括以下几个方面: 1. Metabase API:Metabase提供了强大的API,可以通过编程方式对Metabase进行定制和扩展。开发人员可以使用API来获取数据、创建和修改问题(queries)、设置图表和仪表盘等。通过使用API,用户可以更精确地控制Metabase的功能。 2. 使用自定义查询:Metabase支持使用SQL进行查询,用户可以利用自己熟悉的数据库查询语言进行复杂查询。通过编写自定义查询语句,用户可以在Metabase实现更复杂的数据分析和报表需求。 3. 自定义数据可视化:Metabase提供了多种图表类型和可视化效果,但有时可能无法完全满足用户的需求。用户可以通过定制CSS来自定义图表的样式和外观,以实现更好的数据可视化效果。 4. 自定义插件和扩展:Metabase还支持开发者通过创建插件或扩展,来增加新的功能和特性。通过插件机制,用户可以将自己开发的功能集成到Metabase中,以满足特定的业务需求。 Metabase二次开发工具使开发人员能够根据具体业务需求对Metabase进行定制和扩展。无论是通过API进行编程,使用自定义查询语句,定制数据可视化,还是通过插件和扩展添加新功能,Metabase都提供了丰富的扩展性和灵活性,使用户能够深度定制、个性化地使用Metabase进行数据分析和可视化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值