-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check download perms recursively on referenced card-ids #50307
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,13 +1,16 @@ | ||||||
(ns metabase-enterprise.advanced-permissions.query-processor.middleware.permissions | ||||||
(:require | ||||||
[clojure.set :as set] | ||||||
[clojure.string :as str] | ||||||
[metabase.api.common :as api] | ||||||
[metabase.lib.metadata.protocols :as lib.metadata.protocols] | ||||||
[metabase.models.data-permissions :as data-perms] | ||||||
[metabase.models.query.permissions :as query-perms] | ||||||
[metabase.public-settings.premium-features | ||||||
:as premium-features | ||||||
:refer [defenterprise]] | ||||||
[metabase.query-processor.error-type :as qp.error-type] | ||||||
[metabase.query-processor.store :as qp.store] | ||||||
[metabase.util.i18n :refer [tru]])) | ||||||
|
||||||
(def ^:private max-rows-in-limited-downloads 10000) | ||||||
|
@@ -32,16 +35,23 @@ | |||||
;; Remove the :native key (containing the transpiled MBQL) so that this helper function doesn't think the query is | ||||||
;; a native query. Actual native queries are dispatched to a different method by the :type key. | ||||||
(let [{:keys [table-ids card-ids native?]} (query-perms/query->source-ids (dissoc query :native)) | ||||||
table-perms (if (or native? (seq card-ids)) | ||||||
table-perms (if native? | ||||||
;; If we detect any native subqueries/joins, even with source-card IDs, require full native | ||||||
;; download perms | ||||||
#{(data-perms/native-download-permission-for-user api/*current-user-id* db-id)} | ||||||
(set (map (fn [table-id] | ||||||
(data-perms/table-permission-for-user api/*current-user-id* :perms/download-results db-id table-id)) | ||||||
table-ids)))] | ||||||
;; The download perm level for a query should be equal to the lowest perm level of any table referenced by the query. | ||||||
(or (table-perms :no) | ||||||
(table-perms :ten-thousand-rows) | ||||||
table-ids))) | ||||||
card-perms (set | ||||||
;; If we have any card references in the query, check perms recursively | ||||||
(map (fn [card-id] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: naming anon functions makes stack traces easier to follow
Suggested change
|
||||||
(let [{query :dataset-query} (lib.metadata.protocols/card (qp.store/metadata-provider) card-id)] | ||||||
(current-user-download-perms-level query))) | ||||||
card-ids)) | ||||||
perms (set/union table-perms card-perms)] | ||||||
;; The download perm level for a query should be equal to the lowest perm level of any table referenced by the query. | ||||||
(or (perms :no) | ||||||
(perms :ten-thousand-rows) | ||||||
:one-million-rows))) | ||||||
|
||||||
(defenterprise apply-download-limit | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,8 +80,8 @@ | |
(testing "Native queries are unmodified" | ||
(is (= (native-download-query) (ee.qp.perms/apply-download-limit (native-download-query)))))) | ||
|
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id 'venues) :limited | ||
(mt/id 'checkins) :full}}} | ||
Comment on lines
-83
to
-84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed all these since keywords are used in |
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :limited | ||
(mt/id :checkins) :full}}} | ||
(mt/with-current-user (mt/user->id :rasta) | ||
(testing "A limit is added to MBQL queries if the user has limited download permissions for a table which | ||
the query references" | ||
|
@@ -163,7 +163,7 @@ | |
"A user with limited download perms for a DB has their query results limited" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 3 (csv-row-count results))))}}) | ||
|
@@ -172,7 +172,7 @@ | |
"An admin has full download permissions, even if downloads for All Users are limited" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:user :crowberto | ||
:endpoints [:card :dataset] | ||
|
@@ -183,24 +183,24 @@ | |
"A user with limited download perms for a schema has their query results limited for queries on that schema" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 3 (csv-row-count results))))}})) | ||
|
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id 'users) :full | ||
(mt/id 'categories) :full | ||
(mt/id 'venues) :limited | ||
(mt/id 'checkins) :full | ||
(mt/id 'products) :limited | ||
(mt/id 'people) :limited | ||
(mt/id 'reviews) :limited | ||
(mt/id 'orders) :limited}}} | ||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :users) :full | ||
(mt/id :categories) :full | ||
(mt/id :venues) :limited | ||
(mt/id :checkins) :full | ||
(mt/id :products) :limited | ||
(mt/id :people) :limited | ||
(mt/id :reviews) :limited | ||
(mt/id :orders) :limited}}} | ||
(streaming-test/do-test! | ||
"A user with limited download perms for a table has their query results limited for queries on that table" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for updating these, much cleaner with keywords. |
||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 3 (csv-row-count results))))}}) | ||
|
@@ -209,7 +209,7 @@ | |
"A user with limited download perms for a table still has full download perms for MBQL queries on other tables" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'users) | ||
:query {:source-table (mt/id :users) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 10 (csv-row-count results))))}}) | ||
|
@@ -228,7 +228,7 @@ | |
"A user with no download perms for a DB receives an error response" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] | ||
|
@@ -240,7 +240,7 @@ | |
"An admin can always run download queries, even if the All Users group has no download permissions " | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:user :crowberto | ||
:endpoints [:card :dataset] | ||
|
@@ -251,23 +251,23 @@ | |
"A user with no download perms for a schema receives an error response for download queries on that schema" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] | ||
(is (partial= | ||
{:error "You do not have permissions to download the results of this query."} | ||
results)))}})) | ||
|
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id 'venues) :none | ||
(mt/id 'checkins) :full | ||
(mt/id 'users) :full | ||
(mt/id 'categories) :full}}} | ||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :none | ||
(mt/id :checkins) :full | ||
(mt/id :users) :full | ||
(mt/id :categories) :full}}} | ||
(streaming-test/do-test! | ||
"A user with no download perms for a table receives an error response for download queries on that table" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'venues) | ||
:query {:source-table (mt/id :venues) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] | ||
|
@@ -279,7 +279,7 @@ | |
"A user with no download perms for a table still has full download perms for MBQL queries on other tables" | ||
{:query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id 'users) | ||
:query {:source-table (mt/id :users) | ||
:limit 10}} | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 10 (csv-row-count results))))}}) | ||
|
@@ -295,10 +295,10 @@ | |
|
||
(deftest joins-test | ||
(mt/with-full-data-perms-for-all-users! | ||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id 'venues) :full | ||
(mt/id 'checkins) :full | ||
(mt/id 'users) :limited | ||
(mt/id 'categories) :none}}} | ||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :full | ||
(mt/id :checkins) :full | ||
(mt/id :users) :limited | ||
(mt/id :categories) :none}}} | ||
(with-redefs [ee.qp.perms/max-rows-in-limited-downloads 3] | ||
(streaming-test/do-test! | ||
"A user can't download the results of a query with a join if they have no permissions for one of the tables" | ||
|
@@ -320,3 +320,58 @@ | |
:limit 10}) | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 3 (csv-row-count results))))}}))))) | ||
|
||
(deftest joined-card-test | ||
(testing "Do we correctly check download perms for queries that involve a join between a table and a card? (#50304)" | ||
(mt/with-full-data-perms-for-all-users! | ||
(with-redefs [ee.qp.perms/max-rows-in-limited-downloads 3] | ||
(mt/with-temp [:model/Card {card-id :id} {:dataset_query {:database (mt/id) | ||
:type :query | ||
:query {:source-table (mt/id :venues)}}}] | ||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :full | ||
(mt/id :checkins) :full}}} | ||
(streaming-test/do-test! | ||
"A table joined to a card, both with full download perms" | ||
{:query (mt/mbql-query checkins | ||
{:joins [{:fields [$id] | ||
:source-table (format "card__%d" card-id) | ||
:alias "card" | ||
:condition [:= $checkins.venue_id &card.venues.id] | ||
:strategy :left-join}] | ||
:order-by [[:asc $id]] | ||
:limit 10}) | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 10 (csv-row-count results))))}})) | ||
|
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :limited | ||
(mt/id :checkins) :full}}} | ||
(streaming-test/do-test! | ||
"A table joined to a card, with limited download perms for the card, results in a limited download" | ||
{:query (mt/mbql-query checkins | ||
{:joins [{:fields [$id] | ||
:source-table (format "card__%d" card-id) | ||
:alias "card" | ||
:condition [:= $checkins.venue_id &card.venues.id] | ||
:strategy :left-join}] | ||
:order-by [[:asc $id]] | ||
:limit 10}) | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] (is (= 3 (csv-row-count results))))}})) | ||
|
||
(with-download-perms! (mt/id) {:schemas {"PUBLIC" {(mt/id :venues) :none | ||
(mt/id :checkins) :full}}} | ||
(streaming-test/do-test! | ||
"A table joined to a card, with no download perms for the card, results in blocked download" | ||
{:query (mt/mbql-query checkins | ||
{:joins [{:fields [$id] | ||
:source-table (format "card__%d" card-id) | ||
:alias "card" | ||
:condition [:= $checkins.venue_id &card.venues.id] | ||
:strategy :left-join}] | ||
:order-by [[:asc $id]] | ||
:limit 10}) | ||
:endpoints [:card :dataset] | ||
:assertions {:csv (fn [results] | ||
(is (partial= | ||
{:error "You do not have permissions to download the results of this query."} | ||
results)))}}))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the comment on 35 reads that this query cannot be native, should it be reworded? to something like:
Actually, I don't see why the dissoc matters since a different defmethod will handle :native. If it's clear to you tho please update the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, the dissoc is unnecessary now, I've removed it