(ns metabase-enterprise.sandbox.models.params.field-values (:require [metabase-enterprise.advanced-permissions.api.util :as advanced-perms.api.u] [metabase-enterprise.sandbox.api.table :as table] [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]] [metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions :as row-level-restrictions] [metabase.api.common :as api] [metabase.lib.util.match :as lib.util.match] [metabase.models :refer [Field PermissionsGroupMembership]] [metabase.models.field :as field] [metabase.models.field-values :as field-values] [metabase.models.params.field-values :as params.field-values] [metabase.public-settings.premium-features :refer [defenterprise]] [metabase.util :as u] [toucan2.core :as t2])) | |
(comment api/keep-me) | |
Check if a field is sandboxed. | (defn field-is-sandboxed? [{:keys [table], :as field}] ;; slight optimization: for the `field-id->field-values` version we can batched hydrate `:table` to avoid having to ;; make a bunch of calls to fetch Table. For `get-or-create-field-values` we don't hydrate `:table` so we can fall ;; back to fetching it manually with `field/table` (table/only-sandboxed-perms? (or table (field/table field)))) |
Find the GTAP for current user that apply to table | (defn- table-id->gtap [table-id] (let [group-ids (t2/select-fn-set :group_id PermissionsGroupMembership :user_id api/*current-user-id*) gtaps (t2/select GroupTableAccessPolicy :group_id [:in group-ids] :table_id table-id)] (when gtaps (row-level-restrictions/assert-one-gtap-per-table gtaps) ;; there shold be only one gtap per table and we only need one table here ;; see docs in [[metabase.models.permissions]] for more info (t2/hydrate (first gtaps) :card)))) |
Returns the gtap attributes for current user that applied to The gtap-attributes is a list with 2 elements:
1. card-id - for GTAP that use a saved question
2. the timestamp when the saved question was last updated
3. a map:
if query is mbql query:
- with key is the user-attribute that applied to the table that For example we have an GTAP rules {:card_id 1 ;; a mbql query :attribute_remappings {"State" [:dimension [:field 3 nil]]}} And users with login-attributes {"State" "CA"} ;; (field-id->gtap-attributes-for-current-user (t2/select-one Field :id 3)) ;; -> [1, {"State" "CA"}] | (defn- field->gtap-attributes-for-current-user [{:keys [table_id] :as _field}] (when-let [gtap (table-id->gtap table_id)] (let [login-attributes (:login_attributes @api/*current-user*) attribute_remappings (:attribute_remappings gtap) field-ids (t2/select-fn-set :id Field :table_id table_id)] [(:card_id gtap) (-> gtap :card :updated_at) (if (= :native (get-in gtap [:card :query_type])) ;; For sandbox that uses native query, we can't narrow down to the exact attribute ;; that affect the current table. So we just hash the whole login-attributes of users. ;; This makes hashing a bit less efficient but it ensures that user get a new hash ;; if they change login attributes login-attributes (into {} (for [[k v] attribute_remappings ;; get attribute that map to fields of the same table :when (contains? field-ids (lib.util.match/match-one v ;; new style with {:stage-number } [:dimension [:field field-id _] _] field-id ;; old style without stage number [:dimension [:field field-id _]] field-id))] {k (get login-attributes k)})))]))) |
Fetch existing FieldValues for a sequence of | (defenterprise field-id->field-values-for-current-user :feature :sandboxes [field-ids] (let [fields (when (seq field-ids) (t2/hydrate (t2/select Field :id [:in (set field-ids)]) :table)) {unsandboxed-fields false sandboxed-fields true} (group-by (comp boolean field-is-sandboxed?) fields)] (merge ;; use the normal OSS batched implementation for any Fields that aren't subject to sandboxing. (when (seq unsandboxed-fields) (params.field-values/default-field-id->field-values-for-current-user (map u/the-id unsandboxed-fields))) ;; for sandboxed fields, fetch the sandboxed values individually. (into {} (for [{field-id :id, :as field} sandboxed-fields] [field-id (select-keys (params.field-values/get-or-create-advanced-field-values! :sandbox field) [:values :human_readable_values :field_id])]))))) |
Fetch cached FieldValues for a | (defenterprise get-or-create-field-values-for-current-user!* :feature :sandboxes [field] (cond (field-is-sandboxed? field) (params.field-values/get-or-create-advanced-field-values! :sandbox field) ;; Impersonation can have row-level security enforced by the database, so we still need to store field values per-user. ;; TODO: only do this for DBs with impersonation in effect (and api/*current-user-id* (advanced-perms.api.u/impersonated-user?)) (params.field-values/get-or-create-advanced-field-values! :impersonation field) :else (params.field-values/default-get-or-create-field-values-for-current-user! field))) |
Returns a hash-key for linked-filter FieldValues if the field is sandboxed, otherwise fallback to the OSS impl. | (defenterprise hash-key-for-linked-filters :feature :sandboxes [field-id constraints] (let [field (t2/select-one Field :id field-id)] (if (field-is-sandboxed? field) (str (hash (concat [field-id constraints] (field->gtap-attributes-for-current-user field)))) (field-values/default-hash-key-for-linked-filters field-id constraints)))) |
Returns a hash-key for FieldValues if the field is sandboxed, otherwise fallback to the OSS impl. | (defenterprise hash-key-for-sandbox :feature :sandboxes [field-id] (let [field (t2/select-one Field :id field-id)] (when (field-is-sandboxed? field) (str (hash (concat [field-id] (field->gtap-attributes-for-current-user field))))))) |