From 9fada844a77987a679ba82a976132e324d81b7e5 Mon Sep 17 00:00:00 2001 From: Noah Moss <32746338+noahmoss@users.noreply.github.com> Date: Tue, 28 Mar 2023 12:59:22 -0400 Subject: [PATCH] Allow sandboxing based on FK even if user doesn't have self-service access to the linked table (#29449) * bypass perm checks for linked tables in a sandbox * clean ns * fix test * add test * uncomment test --- .../middleware/row_level_restrictions.clj | 41 ++++++++++++------- .../sandbox/api/field_test.clj | 2 +- .../row_level_restrictions_test.clj | 15 ++++++- .../metabase_enterprise/sandbox/test_util.clj | 4 +- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj index a7b70976d80..a576b14a843 100644 --- a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj +++ b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj @@ -19,7 +19,6 @@ [metabase.models.permissions-group-membership :refer [PermissionsGroupMembership]] [metabase.models.query.permissions :as query-perms] - [metabase.models.table :refer [Table]] [metabase.plugins.classloader :as classloader] [metabase.query-processor.error-type :as qp.error-type] [metabase.query-processor.middleware.fetch-source-query @@ -245,24 +244,38 @@ preprocess-source-query (source-query-form-ensure-metadata table-id card-id))) -(s/defn ^:private gtap->perms-set :- #{perms/PathSchema} - "Calculate the set of permissions needed to run the query associated with a GTAP; this set of permissions is excluded +(defn- sandbox->table-ids + "Returns the set of table IDs which are used by the given sandbox. These are the sandboxed table itself, as well as + any linked tables referenced via fields in the attribute remappings. This is the set of tables which need to be + excluded from subsequent permission checks in order to run the sandboxed query." + [{table-id :table_id, attribute-remappings :attribute_remappings}] + (->> + (for [target-field-clause (vals attribute-remappings)] + (mbql.u/match-one target-field-clause + [:field (field-id :guard integer?) _] + (t2/select-one-fn :table_id Field :id field-id))) + (cons table-id) + (remove nil?) + set)) + +(s/defn ^:private sandbox->perms-set :- #{perms/PathSchema} + "Calculate the set of permissions needed to run the query associated with a sandbox; this set of permissions is excluded during the normal QP perms check. - Background: when applying GTAPs, we don't want the QP perms check middleware to throw an Exception if the Current - User doesn't have permissions to run the underlying GTAP query, which will likely be greater than what they actually - have. (For example, a User might have segmented query perms for Table 15, which is why we're applying a GTAP in the - first place; the actual perms required to normally run the underlying GTAP query is more likely something like - *full* query perms for Table 15.) The QP perms check middleware subtracts this set from the set of required - permissions, allowing the user to run their GTAPped query." - [{card-id :card_id, table-id :table_id}] + Background: when applying sandboxing, we don't want the QP perms check middleware to throw an Exception if the Current + User doesn't have permissions to run the underlying sandboxed query, which will likely be greater than what they + actually have. (For example, a User might have sandboxed query perms for Table 15, which is why we're applying a + sandbox in the first place; the actual perms required to normally run the underlying sandbox query is more likely + something like *full* query perms for Table 15.) The QP perms check middleware subtracts this set from the set of + required permissions, allowing the user to run their sandboxed query." + [{card-id :card_id :as sandbox}] (if card-id (qp.store/cached card-id (query-perms/perms-set (t2/select-one-fn :dataset_query Card :id card-id), :throw-exceptions? true)) - #{(perms/table-query-path (t2/select-one Table :id table-id))})) + (set (map perms/table-query-path (sandbox->table-ids sandbox))))) -(defn- gtaps->perms-set [gtaps] - (set (mapcat gtap->perms-set gtaps))) +(defn- sandboxes->perms-set [sandboxes] + (set (mapcat sandbox->perms-set sandboxes))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -319,7 +332,7 @@ original-query (-> sandboxed-query (assoc ::original-metadata (expected-cols original-query)) - (update-in [::qp.perms/perms :gtaps] (fn [perms] (into (set perms) (gtaps->perms-set (vals table-id->gtap))))))))) + (update-in [::qp.perms/perms :gtaps] (fn [perms] (into (set perms) (sandboxes->perms-set (vals table-id->gtap))))))))) (def ^:private default-recursion-limit 20) (def ^:private ^:dynamic *recursion-limit* default-recursion-limit) diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj index 409ee4f0900..3f71d432cee 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj @@ -47,7 +47,7 @@ (letfn [(fetch-values [user field] (-> (mt/user-http-request user :get 200 (format "field/%d/values" (mt/id :venues field))) (update :values (partial take 3))))] - ;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. n + ;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. When ;; fetching FieldValues for `venue.name` should do an ad-hoc fetch and only return the names of venues in ;; that category. (is (= {:field_id (mt/id :venues :name) diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj index 666f528343f..22f4bc00a33 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj @@ -718,8 +718,6 @@ (is (= [[9]] (mt/rows result))))))))))) - - (deftest remapped-fks-test (testing "Sandboxing should work with remapped FK columns (#14629)" (mt/dataset sample-dataset @@ -768,6 +766,19 @@ "Rustic Paper Wallet"] ; <- Includes the remapped column (first (mt/rows result)))))))))))) +(deftest sandboxing-linked-table-perms + (testing "Sandboxing based on a column in a linked table should work even if the user doesn't have self-service query + permissions for the linked table (#15105)" + (mt/dataset sample-dataset + (met/with-gtaps (mt/$ids orders + {:gtaps {:orders {:remappings {"user_id" [:dimension $user_id->people.id]}}} + :attributes {"user_id" 1}}) + (mt/with-test-user :rasta + (is (= [11] + (-> (mt/run-mbql-query orders {:aggregation [[:count]]}) + mt/rows + first)))))))) + (deftest drill-thru-on-joins-test (testing "should work on questions with joins, with sandboxed target table, where target fields cannot be filtered (#13642)" ;; Sandbox ORDERS and PRODUCTS diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj index 7405a8970ec..df093769da2 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj @@ -95,8 +95,8 @@ `(do-with-gtaps-for-user (fn [] ~gtaps-and-attributes-map) ~test-user-name-or-user-id (fn [~'&group] ~@body))) (defmacro with-gtaps - "Execute `body` with `gtaps` and optionally user `attributes` in effect. All underlying objects and permissions are - created automatically. + "Execute `body` with `gtaps` and optionally user `attributes` in effect, for the :rasta test user. All underlying + objects and permissions are created automatically. `gtaps-and-attributes-map` is a map containing `:gtaps` and optionally `:attributes`; see the `WithGTAPsArgs` schema in this namespace. -- GitLab