* add saved question extraction prototype * fix additional failure cases * refactor: reconciler * revert me: manual skill file edit for source swapping * set data permissions to view for databases referenced in the query * rename data app collection and group * prevent agents from removing savedQuestionSourceId * docs: clarify data app query location * chore: cleanup query-sync canonical module * refactor: split query tests into different files * Swap query source for data app query hooks and apply dynamic filters on top of it (#79584) * code improvements and bug-fixes for data apps permissions * Add data apps model imports * address reviews on data apps permissions * run sync queries before vite build * Add data app resource link (#79878) add button to view data app resources * Show permission error when users cannot access a data app (#79874) show permission error page when user do not have permission to view data apps * Add data app group link in overflow menu (#79873) Add data app group link * Store and reconcile data app entity ids in data_app.yaml (#79833) * Store data app resource entity IDs * make entity ids in data_app.yaml authoritative * add top-level value after path key * simplify data_app.yaml manifest parser * only allow changing data app collection if empty * consolidate backend test helpers and remove redundant tests * chore: restore README changes * ci: fix failing tests * Add support of actions to Data App reconciler (#79842) Add support of actions to Data App reconciler * Fix bugs/inconsistency and add more e2e for data apps reconciler/syncing (#80002) * Update semantic schema skill about preventing segments and measures in dynamic parts of a query in Data Apps (#80288) * Grant view data access on a table-by-table basis in data app groups (#80189) * grant view data on table instead of database level * refactor table scan in reconciler * update e2e tests for permission grants * Grant data app view permissions per table * Skip unchanged data app permissions * add e2e data app test for sandboxing * reset host app sources after runs * Preserve sandboxing for data app users (#80406) * add e2e data app test for sandboxing * improve e2e tests for data apps * Remove sandbox E2E from table permissions change * preserve existing sandboxing from sandboxed groups in data apps * assert that we grant non-sandboxed tables * Explain data app sandbox exclusion * Add data app sandbox E2E coverage * Exercise sandbox preservation in data app E2E * Disable saved question sources in data apps (#80139) * Disable saved question sources in data apps * Preserve question-only schema scope * Remove the saved question schema parameter * Copy table-sourced metrics into data app collection and do not codegen card-sourced metrics (#80086) * copy table-sourced metrics into the data app collections * copy table-sourced metrics into the data app collections * Address metric sync review feedback * Accept keyword metric display in query response * Fix data app metric reconciliation ordering * Remove old lockfile format * exclude card-sourced metrics from schema generator * do not use card terms in metrics to avoid ambiguity with card-sourced metrics * fix model imports * Fix data app metric reconciliation * refactor: cleanup reconciler unit tests * Fix duplicated metric client helper * add safe metrics path * Handle unsupported metric dependencies * Fix data app CI test regressions * Fix "Re-adding removed query fails to adopt deleted card" (#80341) * Clean-up data apps permissions endpoints (#80825) * Remove entity ids from data_app.yaml (#81317) Remove data app entity ids from data_app.yaml * Adjust data apps permissions code (#81093) * Remove the data app table permission grants (#81584) * Add permissions_group.is_data_app_group and hide data app permission groups from UI (#81705) * Add data app user management and access warnings (#81839) * Address data app user access review feedback * additional e2e test cases for bugs in manage users page * fix failing frontend type check on unit test * fix migration id collision for data apps * combine migration changes into one file * fix data app metric sync after trashing copied metrics * fix cli runtime dependency packaging for embedding sdk * avoid loading data app bundle during resource setup * prevent redundant data app collection permission updates * allow admins to remove data app members after token expires * update docs and add e2e test for missing resource collection case * isolate data app sync using transactions * do not leave user on an empty page when removing the last member on the page * reserve sandbox-host slug in data app config * ignore generated data app action fixtures * move queries into db.clj * preserve legacy permissions when adding data app members * regenerate the kondo modules config The data-apps module reaches for :model/Permissions and :model/DataPermissions, neither of which was recorded in :model-imports, so metabase.core.modules-test fails on the current tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nNKkhQjjiUSM7qwLReyfP * reject data app queries whose metrics read another card Sync copies a referenced metric but rewrites nothing inside the copy, and copies nothing that copy in turn reads, so those references still point at the originals. The app publishes successfully and then fails for every viewer without access to the originals' collections. Checking while resolving the query, rather than at codegen, also catches a metric edited into that shape after its schema was generated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nNKkhQjjiUSM7qwLReyfP * exclude metrics that read a saved card anywhere from generated schemas source-card-id only reads stage 0, so a table-sourced metric that joins a saved question passed the codegen filter. The CLI checks the whole query and rejects such a metric, aborting sync-resources for any app that uses it. Checking the whole query here keeps codegen consistent with what sync supports, and removes the database round trip that asked which of the referenced cards were metrics -- the step that let a saved-question join through in the first place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nNKkhQjjiUSM7qwLReyfP * resolve copied resources' table dependencies through the server Numeric source-table entries miss implicit joins: a copied model querying Orders and reading Products.Category through a foreign key names Products nowhere, so it was left out of the app's permission warnings. Run the same metadata-based lookup the query endpoint uses, through a new endpoint the CLI calls for copied models, actions and metrics, and drop the JSON walk it replaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nNKkhQjjiUSM7qwLReyfP * cover the create-queries grant the database defaults leave on app groups An app group that predates the first non-audit database is given create-queries :query-builder-and-native by set-new-database-permissions!, alongside the :blocked view-data it special-cases for app groups. A user with data access through another group would gain native query authoring just by joining the app. The reconciler already resets it, but nothing failed when :perms/create-queries was dropped from its list. The pairing cannot be produced through set-database-permission!, which cascades view-data to :unrestricted for any create-queries value other than :no, so the test writes the row the way the defaults insert it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nNKkhQjjiUSM7qwLReyfP * prevent models based on saved questions from being copied * return to a valid member page after overlapping removals * do not resolve query objects when either enabled flag is false * prevent shorthand properties and spreads from overriding generated ids * prevent adding duplicate members and retry only members that are failed to add * define schema for data app query endpoint * reconcile only the current data app group during resource setup * expand the schema fingerprint * add data app group field in permission group schemas * prevent new databases from granting query creation to data app groups * throw validation errors when invalid query spec fields are passed * do not fail validation on columns metadata * add enabled metadata to query definition schema * accept more data app specific query metadata and binning options --------- Co-authored-by: Timofey Kachalov <timofei@metabase.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1107 lines
81 KiB
Clojure
1107 lines
81 KiB
Clojure
(ns ^:mb/driver-tests metabase-enterprise.advanced-permissions.common-test
|
|
(:require
|
|
[clojure.test :refer :all]
|
|
[metabase-enterprise.advanced-permissions.common :as advanced-permissions.common]
|
|
[metabase-enterprise.impersonation.util-test :as advanced-perms.api.tu]
|
|
[metabase.driver :as driver]
|
|
[metabase.permissions-rest.data-permissions.graph :as data-perms.graph]
|
|
[metabase.permissions.models.data-permissions :as data-perms]
|
|
[metabase.permissions.models.permissions :as perms]
|
|
[metabase.permissions.models.permissions-group :as perms-group]
|
|
[metabase.permissions.test-util :as perms.test-util]
|
|
[metabase.test :as mt]
|
|
[metabase.test.data.sql :as sql.tx]
|
|
[metabase.test.fixtures :as fixtures]
|
|
[metabase.upload.impl-test :as upload-test]
|
|
[metabase.util :as u]
|
|
[metabase.util.quick-task :as quick-task]
|
|
[metabase.warehouse-schema.models.field-values :as field-values]
|
|
[metabase.warehouses-rest.api :as api.database]
|
|
[metabase.warehouses.models.database :as database]
|
|
[toucan2.core :as t2]))
|
|
|
|
(use-fixtures :once (fixtures/initialize :db :test-users :row-lock))
|
|
|
|
(deftest current-user-test
|
|
(testing "GET /api/user/current returns additional fields if advanced-permissions is enabled"
|
|
(mt/with-premium-features #{:advanced-permissions}
|
|
(letfn [(user-permissions [user]
|
|
(-> (mt/user-http-request user :get 200 "user/current")
|
|
:permissions))]
|
|
(testing "admins should have full advanced permissions"
|
|
(is (=? {:can_access_setting true
|
|
:can_access_subscription true
|
|
:can_access_monitoring true
|
|
:can_access_data_model true
|
|
:is_group_manager false
|
|
:can_access_db_details true}
|
|
(user-permissions :crowberto))))
|
|
(testing "non-admin users should only have subscriptions enabled by default"
|
|
(is (=? {:can_access_setting false
|
|
:can_access_subscription true
|
|
:can_access_monitoring false
|
|
:can_access_data_model false
|
|
:is_group_manager false
|
|
:can_access_db_details false}
|
|
(user-permissions :rasta))))
|
|
(testing "can_access_data_model is true if a user has any data model perms"
|
|
(let [[id-1 id-2 id-3 id-4] (map u/the-id (database/tables (mt/db)))]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas {"PUBLIC" {id-1 :all
|
|
id-2 :none
|
|
id-3 :none
|
|
id-4 :none}}}}}
|
|
(is (partial= {:can_access_data_model true}
|
|
(user-permissions :rasta))))))
|
|
(testing "can_access_db_details is true if a user has any details perms"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :yes}}
|
|
(is (partial= {:can_access_db_details true}
|
|
(user-permissions :rasta)))))
|
|
(testing "can_access_transforms requires the transforms permission, not just view-data"
|
|
(perms.test-util/with-data-analyst-role! (mt/user->id :rasta)
|
|
(testing "false when the user has view-data but no transforms permission on any database"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:transforms :no}}
|
|
(is (partial= {:can_access_transforms false}
|
|
(user-permissions :rasta)))))
|
|
(testing "true when the user has the transforms permission on a database"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:transforms :yes}}
|
|
(is (partial= {:can_access_transforms true}
|
|
(user-permissions :rasta)))))))
|
|
(testing "can_access_transforms is true for superusers regardless of the transforms permission"
|
|
(is (partial= {:can_access_transforms true}
|
|
(user-permissions :crowberto))))))))
|
|
|
|
(deftest current-user-query-permissions-published-table-test
|
|
(testing "GET /api/user/current can_create_queries respects published tables"
|
|
(mt/with-premium-features #{:library}
|
|
(letfn [(user-permissions [user]
|
|
(-> (mt/user-http-request user :get 200 "user/current")
|
|
:permissions))]
|
|
(testing "user with collection permission on published table should have can_create_queries true"
|
|
(mt/with-temp [:model/Collection collection {:type "library-data"}
|
|
:model/Table _table {:db_id (mt/id)
|
|
:is_published true
|
|
:collection_id (:id collection)}]
|
|
(perms/grant-collection-read-permissions! (perms-group/all-users) (:id collection))
|
|
(mt/with-no-data-perms-for-all-users!
|
|
(is (partial= {:can_create_queries true
|
|
:can_create_native_queries false}
|
|
(user-permissions :rasta))))))))))
|
|
|
|
(deftest new-database-view-data-permission-levels-test
|
|
(mt/with-additional-premium-features #{:sandboxes :advanced-permissions}
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/PermissionsGroup {group-id :id} {}]
|
|
(let [perm-value (fn [db-id] (t2/select-one-fn :perm_value
|
|
:model/DataPermissions
|
|
:db_id db-id
|
|
:group_id group-id
|
|
:table_id nil
|
|
:perm_type :perms/view-data))]
|
|
(testing "A new database defaults to `:unrestricted` if no other perms are set"
|
|
(mt/with-temp [:model/Database {db-id-2 :id} {}]
|
|
(is (= :unrestricted (perm-value db-id-2)))))
|
|
(testing "A new database defaults to `:blocked` if the group has `:blocked` for any other database"
|
|
(data-perms/set-database-permission! group-id db-id :perms/view-data :blocked)
|
|
(mt/with-temp [:model/Database {db-id-2 :id} {}]
|
|
(is (= :blocked (perm-value db-id-2)))))
|
|
(testing "A new database defaults to `:blocked` if the group has any connection impersonation"
|
|
(data-perms/set-database-permission! group-id db-id :perms/view-data :unrestricted)
|
|
(mt/with-temp [:model/ConnectionImpersonation _ {:group_id group-id
|
|
:db_id db-id}
|
|
:model/Database {db-id-2 :id} {}]
|
|
(is (= :blocked (perm-value db-id-2)))))
|
|
(testing "A new database defaults to `:blocked` if the group has a sandbox for any table"
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id db-id}
|
|
:model/Sandbox _ {:group_id group-id
|
|
:table_id table-id}
|
|
:model/Database {db-id-2 :id} {}]
|
|
(is (= :blocked (perm-value db-id-2)))))))))
|
|
|
|
(deftest new-database-view-data-permission-levels-without-premium-features-test
|
|
(testing "A new database fails CLOSED to :blocked for a sandboxed group even when premium features are unavailable (UXW-4927)"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/PermissionsGroup {group-id :id} {}
|
|
:model/Table {table-id :id} {:db_id db-id}
|
|
:model/Sandbox _ {:group_id group-id :table_id table-id}]
|
|
;; New tables/databases must inherit an existing sandbox regardless of whether the sandboxes /
|
|
;; advanced-permissions token features are currently readable -- otherwise a transient feature-check
|
|
;; failure during sync leaks the new database to the sandboxed group.
|
|
(doseq [features [#{} #{:advanced-permissions} #{:sandboxes}]]
|
|
(testing (format "premium features = %s" (pr-str features))
|
|
(mt/with-premium-features features
|
|
(is (= {group-id :blocked}
|
|
(advanced-permissions.common/new-database-view-data-permission-levels [group-id])))))))))
|
|
|
|
(deftest new-table-view-data-permission-levels-test
|
|
(mt/with-additional-premium-features #{:sandboxes :advanced-permissions}
|
|
(mt/with-temp [:model/PermissionsGroup {group-id :id} {}
|
|
:model/Database {db-id :id} {}
|
|
:model/Table {table-id-1 :id} {:db_id db-id :schema "PUBLIC"}
|
|
:model/Table {table-id-2 :id} {:db_id db-id :schema "PUBLIC"}]
|
|
(let [perm-value (fn [table-id] (t2/select-one-fn :perm_value
|
|
:model/DataPermissions
|
|
:db_id db-id
|
|
:group_id group-id
|
|
:table_id table-id
|
|
:perm_type :perms/view-data))]
|
|
(testing "A new table gets `:blocked` view-data perms if any tables in the DB are `:blocked`"
|
|
(data-perms/set-table-permission! group-id table-id-1 :perms/view-data :blocked)
|
|
(data-perms/set-table-permission! group-id table-id-2 :perms/view-data :unrestricted)
|
|
(mt/with-temp [:model/Table {table-id-3 :id} {:db_id db-id :schema "PUBLIC"}]
|
|
;; Check that no DB-level perm is set
|
|
(is (nil? (perm-value nil)))
|
|
(is (= :blocked (perm-value table-id-3)))))
|
|
(testing "A new table defaults to `:blocked` if the group has a sandbox for any existing table"
|
|
(data-perms/set-table-permission! group-id table-id-1 :perms/view-data :unrestricted)
|
|
(mt/with-temp [:model/Sandbox _ {:group_id group-id
|
|
:table_id table-id-1}
|
|
:model/Table {table-id-3 :id} {:db_id db-id :schema "PUBLIC"}]
|
|
(is (nil? (perm-value nil)))
|
|
(is (= :unrestricted (perm-value table-id-1)))
|
|
(is (= :blocked (perm-value table-id-3)))))))))
|
|
|
|
(deftest new-view-data-permission-levels-block-data-app-groups-test
|
|
(testing "data-app groups block newly-synced tables/databases even with no `:blocked` row (full manifest)"
|
|
(mt/with-additional-premium-features #{:sandboxes :advanced-permissions}
|
|
(mt/with-temp [:model/PermissionsGroup {app-group-id :id} {:is_data_app_group true}
|
|
:model/PermissionsGroup {ctl-group-id :id} {} ; a normal, non-data-app group
|
|
:model/DataApp _ {:name "perm-test-app" :display_name "Perm Test"
|
|
:bundle_path "app/dist/index.js"
|
|
:permission_group_id app-group-id}
|
|
:model/Database {db-id :id} {}
|
|
:model/Table {table-id-1 :id} {:db_id db-id :schema "PUBLIC"}
|
|
:model/Table {table-id-2 :id} {:db_id db-id :schema "PUBLIC"}]
|
|
;; effective (coalesced) view-data — the control group inherits the DB-level value with no
|
|
;; table row of its own, so a raw-row check would see nil.
|
|
(let [effective (fn [group-id table-id]
|
|
(data-perms/table-permission-for-groups #{group-id} :perms/view-data db-id table-id))]
|
|
(testing "a late table is blocked for the data-app group but not for a normal group"
|
|
;; a "full manifest" for both groups: all existing tables :unrestricted, so no `:blocked` row exists
|
|
(doseq [group-id [app-group-id ctl-group-id]]
|
|
(data-perms/set-table-permission! group-id table-id-1 :perms/view-data :unrestricted)
|
|
(data-perms/set-table-permission! group-id table-id-2 :perms/view-data :unrestricted))
|
|
(mt/with-temp [:model/Table {table-id-3 :id} {:db_id db-id :schema "PUBLIC"}]
|
|
(is (= :blocked (effective app-group-id table-id-3))
|
|
"the late table is blocked for the data-app group")
|
|
(is (= :unrestricted (effective ctl-group-id table-id-3))
|
|
"the same late table is NOT blocked for a normal group — the rule is scoped to app groups")
|
|
(is (= :unrestricted (effective app-group-id table-id-1))
|
|
"the app's declared tables stay unrestricted")
|
|
;; the going-granular apply keeps a clean per-table shape, so the permissions graph the
|
|
;; admin panel loads still builds (M1's db-level+table-override mix used to 500 it).
|
|
(is (map? (data-perms.graph/api-graph))
|
|
"the whole permissions graph still builds")))
|
|
(testing "a newly-added database is blocked only for the data-app group"
|
|
(let [levels (advanced-permissions.common/new-database-view-data-permission-levels
|
|
[app-group-id ctl-group-id])]
|
|
(is (= :blocked (levels app-group-id)) "blocked for the data-app group")
|
|
(is (not= :blocked (levels ctl-group-id)) "not blocked for a normal group"))))))))
|
|
|
|
(deftest new-table-view-data-permission-levels-without-premium-features-test
|
|
(testing "A newly-synced table fails CLOSED to :blocked for a sandboxed group even when premium features are unavailable (UXW-4927)"
|
|
;; This is the incident path: a table is discovered during sync while the sandboxes /
|
|
;; advanced-permissions token features momentarily read as absent. The new-table default must still
|
|
;; block the sandboxed group instead of defaulting it to :unrestricted (a silent, permanent leak).
|
|
(mt/with-temp [:model/PermissionsGroup {group-id :id} {}
|
|
:model/Database {db-id :id} {}
|
|
:model/Table {table-id-1 :id} {:db_id db-id :schema "PUBLIC"}
|
|
:model/Sandbox _ {:group_id group-id :table_id table-id-1}]
|
|
(let [perm-value (fn [table-id] (t2/select-one-fn :perm_value
|
|
:model/DataPermissions
|
|
:db_id db-id
|
|
:group_id group-id
|
|
:table_id table-id
|
|
:perm_type :perms/view-data))]
|
|
(doseq [features [#{} #{:advanced-permissions} #{:sandboxes}]]
|
|
(testing (format "premium features = %s" (pr-str features))
|
|
(mt/with-premium-features features
|
|
(mt/with-temp [:model/Table {new-table-id :id} {:db_id db-id :schema "PUBLIC"}]
|
|
(is (nil? (perm-value nil))
|
|
"No DB-level perm is written")
|
|
(is (= :blocked (perm-value new-table-id))
|
|
"New table blocks the sandboxed group instead of leaking :unrestricted")))))))))
|
|
|
|
(deftest new-group-view-data-permission-levels-test
|
|
(mt/with-additional-premium-features #{:sandboxes :advanced-permissions}
|
|
(mt/with-temp [:model/Database {db-id :id} {}]
|
|
(let [all-users-group-id (u/the-id (perms-group/all-users))]
|
|
(testing "A new group defaults to `:unrestricted` for a DB if All Users has `:unrestricted`"
|
|
(data-perms/set-database-permission! all-users-group-id db-id :perms/view-data :unrestricted)
|
|
(is (= {db-id :unrestricted} (advanced-permissions.common/new-group-view-data-permission-levels [db-id]))))
|
|
(testing "A new group defaults to `:blocked` for a DB if All Users has `:blocked`"
|
|
(data-perms/set-database-permission! all-users-group-id db-id :perms/view-data :blocked)
|
|
(is (= {db-id :blocked} (advanced-permissions.common/new-group-view-data-permission-levels [db-id]))))
|
|
(testing "A new group defaults to `:blocked` if All Users has any connection impersonation"
|
|
(data-perms/set-database-permission! all-users-group-id db-id :perms/view-data :unrestricted)
|
|
(advanced-perms.api.tu/with-impersonations! {:impersonations [{:db-id db-id
|
|
:attribute "impersonation_attr"
|
|
:attributes {"impersonation_attr" "impersonation_role"}}]}
|
|
(is (= {db-id :blocked} (advanced-permissions.common/new-group-view-data-permission-levels [db-id])))))
|
|
(testing "A new database defaults to `:blocked` if All Users group has any sandbox"
|
|
(data-perms/set-database-permission! all-users-group-id db-id :perms/view-data :unrestricted)
|
|
(mt/with-temp [:model/Card {card-id :id} {}
|
|
:model/Table {table-id :id} {:db_id db-id}
|
|
:model/Sandbox _ {:table_id table-id
|
|
:group_id all-users-group-id
|
|
:card_id card-id
|
|
:attribute_remappings {"foo" 1}}]
|
|
(is (= {db-id :blocked} (advanced-permissions.common/new-group-view-data-permission-levels [db-id])))))))))
|
|
|
|
(deftest new-group-view-data-permission-levels-without-premium-features-test
|
|
(testing "A new group fails CLOSED to :blocked when All Users has a sandbox even when premium features are unavailable (UXW-4927)"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table {table-id :id} {:db_id db-id}
|
|
:model/Card {card-id :id} {}
|
|
:model/Sandbox _ {:table_id table-id
|
|
:group_id (u/the-id (perms-group/all-users))
|
|
:card_id card-id
|
|
:attribute_remappings {"foo" 1}}]
|
|
(doseq [features [#{} #{:advanced-permissions} #{:sandboxes}]]
|
|
(testing (format "premium features = %s" (pr-str features))
|
|
(mt/with-premium-features features
|
|
(is (= {db-id :blocked}
|
|
(advanced-permissions.common/new-group-view-data-permission-levels [db-id])))))))))
|
|
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
;;; | Data model permission enforcement |
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
|
|
(deftest fetch-databases-test
|
|
(testing "GET /api/database?include_editable_data_model=true"
|
|
(letfn [(get-test-db
|
|
([] (get-test-db "database?include_editable_data_model=true"))
|
|
([url] (->> (mt/user-http-request :rasta :get 200 url)
|
|
:data
|
|
(filter (fn [db] (= (mt/id) (:id db))))
|
|
first)))]
|
|
(testing "Sanity check: a non-admin can fetch a DB when they have full data access and data model perms"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas :all}}}
|
|
(is (partial= {:id (mt/id)} (get-test-db)))))
|
|
(testing "A non-admin cannot fetch a DB for which they do not have data model perms if
|
|
include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas :none}}}
|
|
(is (= nil (get-test-db)))))
|
|
(let [[id-1 id-2 id-3 id-4] (map u/the-id (database/tables (mt/db)))]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas {"PUBLIC" {id-1 :all
|
|
id-2 :none
|
|
id-3 :none
|
|
id-4 :none}}}}}
|
|
(testing "If a non-admin has data model perms for a single table in a DB, the DB is returned when listing
|
|
all DBs"
|
|
(is (partial= {:id (mt/id)} (get-test-db))))
|
|
(testing "if include=tables, only tables with data model perms are included"
|
|
(is (= [id-1] (->> (get-test-db "database?include_editable_data_model=true&include=tables")
|
|
:tables
|
|
(map :id)))))))))
|
|
(doseq [query-param ["exclude_uneditable_details=true"
|
|
"include_only_uploadable=true"]]
|
|
(testing (format "GET /api/database?%s" query-param)
|
|
(letfn [(get-test-db
|
|
([] (get-test-db (str "database?" query-param)))
|
|
([url] (->> (mt/user-http-request :rasta :get 200 url)
|
|
:data
|
|
(filter (fn [db] (= (mt/id) (:id db))))
|
|
first)))]
|
|
(testing "Sanity check: a non-admin can fetch a DB when they have 'manage' access"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :yes}}
|
|
(is (partial= {:id (mt/id)} (get-test-db)))))
|
|
(testing "A non-admin cannot fetch a DB for which they do not not have 'manage' access"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :no}}
|
|
(is (= nil (get-test-db)))))))))
|
|
|
|
(deftest fetch-database-test
|
|
(testing "GET /api/database/:id?include_editable_data_model=true"
|
|
(testing "A non-admin without data model perms for a DB cannot fetch the DB when include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas :none}}}
|
|
(mt/user-http-request :rasta :get 403 (format "database/%d?include_editable_data_model=true" (mt/id)))))
|
|
(testing "A non-admin with only data model perms for a DB can fetch the DB when include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d?include_editable_data_model=true" (mt/id)))))))
|
|
|
|
(deftest fetch-database-metadata-test
|
|
(testing "GET /api/database/:id/metadata?include_editable_data_model=true"
|
|
(let [[id-1 id-2 id-3 id-4] (map u/the-id (database/tables (mt/db)))]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas {"PUBLIC" {id-1 :all
|
|
id-2 :none
|
|
id-3 :none
|
|
id-4 :none}}}}}
|
|
(let [tables (->> (mt/user-http-request :rasta
|
|
:get
|
|
200
|
|
(format "database/%d/metadata?include_editable_data_model=true" (mt/id)))
|
|
:tables)]
|
|
(is (= [id-1] (map :id tables))))))
|
|
(testing "A user with data model perms can still fetch a DB name and tables if they have block perms for a DB"
|
|
(let [[id-1 id-2 id-3 id-4] (map u/the-id (database/tables (mt/db)))]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" {id-1 :all
|
|
id-2 :none
|
|
id-3 :none
|
|
id-4 :none}}}}}
|
|
(let [result (mt/user-http-request :rasta
|
|
:get
|
|
200
|
|
(format "database/%d/metadata?include_editable_data_model=true" (mt/id)))]
|
|
(is (= (mt/id) (:id result)))
|
|
(is (= (:name (mt/db)) (:name result)))
|
|
(is (= [id-1] (map :id (:tables result))))))))))
|
|
|
|
(deftest fetch-id-fields-test
|
|
(testing "GET /api/database/:id/idfields?include_editable_data_model=true"
|
|
(testing "A non-admin without data model perms for a DB cannot fetch id fields when include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas :none}}}
|
|
(mt/user-http-request :rasta :get 403 (format "database/%d/idfields?include_editable_data_model=true" (mt/id)))))
|
|
(testing "A non-admin with only data model perms for a DB can fetch id fields when include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/idfields?include_editable_data_model=true" (mt/id)))))))
|
|
|
|
(deftest get-schema-with-advanced-perms-test
|
|
(testing "Permissions: We can verify include_editable_data_model flag works for the `/:id/schema/:schema` endpoint"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table t1 {:db_id db-id :schema "schema1" :name "t1"}
|
|
:model/Table _t2 {:db_id db-id :schema "schema2"}
|
|
:model/Table t3 {:db_id db-id :schema "schema1" :name "t3"}]
|
|
(testing "If a non-admin has data model perms, but no data perms"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(testing "user can access schema due to data-model perms"
|
|
(is (= ["t1" "t3"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/%s" db-id "schema1"))))))
|
|
(testing "include_editable_data_model=true also returns values"
|
|
(is (= ["t1" "t3"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/%s" db-id "schema1")
|
|
:include_editable_data_model true)))))))
|
|
(testing "If include_editable_data_model=true and a non-admin does not have data model perms, it should respond
|
|
with a 404"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :none}}}
|
|
(is (= "Not found."
|
|
(mt/user-http-request :rasta :get 404 (format "database/%d/schema/%s" db-id "schema1")
|
|
:include_editable_data_model true)))))
|
|
(testing "If include_editable_data_model=true and a non-admin has data model perms for a single table in a schema,
|
|
the table is returned"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"schema1" {(u/the-id t1) :all
|
|
(u/the-id t3) :none}}}}}
|
|
(is (= ["t1"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/%s" db-id "schema1")
|
|
:include_editable_data_model true)))))))))
|
|
|
|
(deftest get-schema-with-empty-name-and-advanced-perms-test
|
|
(testing "Permissions: We can verify include_editable_data_model flag works for the `/:id/schema/` endpoint"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table t1 {:db_id db-id :schema nil :name "t1"}
|
|
:model/Table _t2 {:db_id db-id :schema "public"}
|
|
:model/Table t3 {:db_id db-id :schema "" :name "t3"}]
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(testing "user can access schema due to data-model perms - returns tables with both nil and \"\" schema"
|
|
(is (= ["t1" "t3"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/" db-id))))))
|
|
(testing "include_editable_data_model=true also returns tables with both `nil` and \"\" as schema"
|
|
(is (= ["t1" "t3"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/" db-id)
|
|
:include_editable_data_model true))))))
|
|
(testing "If include_editable_data_model=true and a non-admin does not have data model perms, it should respond
|
|
with a 404"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :none}}}
|
|
(is (= "Not found."
|
|
(mt/user-http-request :rasta :get 404 (format "database/%d/schema/" db-id)
|
|
:include_editable_data_model true)))))
|
|
(testing "If include_editable_data_model=true and a non-admin has data model perms for a single table in an empty
|
|
string schema, it should return the table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"" {(u/the-id t1) :all
|
|
(u/the-id t3) :none}}}}}
|
|
(is (= ["t1"]
|
|
(map :name (mt/user-http-request :rasta :get 200 (format "database/%d/schema/" db-id)
|
|
:include_editable_data_model true)))))))))
|
|
|
|
(deftest get-schemas-with-advanced-perms-test
|
|
(testing "Permissions: We can verify include_editable_data_model flag works for the `/:id/:schemas` endpoint"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table t1 {:db_id db-id, :schema "schema1", :name "t1"}
|
|
:model/Table _t2 {:db_id db-id, :schema "schema2"}
|
|
:model/Table _t3 {:db_id db-id, :schema "schema1", :name "t3"}]
|
|
(testing "If a non-admin has data model perms, but no data perms"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(testing "user can access schemas due to data-model perms"
|
|
(is (= ["schema1" "schema2"]
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id)))))
|
|
(testing "include_editable_data_model=true also returns values"
|
|
(is (= ["schema1" "schema2"]
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id)
|
|
:include_editable_data_model true))))
|
|
(testing "and if the database doesn't exist, it should be a 404"
|
|
(is (= "Not found."
|
|
(mt/user-http-request :rasta :get 404 (format "database/%d/schemas" Integer/MAX_VALUE)
|
|
:include_editable_data_model true))))))
|
|
(testing "If include_editable_data_model=true and a non-admin does not have data model perms, it should return []"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :none}}}
|
|
(is (= []
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id)
|
|
:include_editable_data_model true)))))
|
|
(testing "If include_editable_data_model=true and a non-admin has data model perms for a schema,
|
|
it should return the schema"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"schema1" :all}}}}
|
|
(is (= ["schema1"]
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id)
|
|
:include_editable_data_model true)))))
|
|
(testing "If include_editable_data_model=true and a non-admin has data model perms for a single table in a schema,
|
|
it should return the schema"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"schema1" {(u/the-id t1) :all}}}}}
|
|
(is (= ["schema1"]
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id)
|
|
:include_editable_data_model true))))))))
|
|
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
;;; | can-query and can-write-metadata filter tests |
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
|
|
(deftest list-databases-can-write-metadata-filter-test
|
|
(testing "GET /api/database with can-write-metadata=true filters to only databases with editable tables"
|
|
(mt/with-temp [:model/Database {db-1-id :id} {:name "Editable DB"}
|
|
:model/Database {db-2-id :id} {:name "Not Editable DB"}
|
|
:model/Table t1 {:db_id db-1-id :name "table1" :active true}
|
|
:model/Table _ {:db_id db-2-id :name "table2" :active true}]
|
|
(mt/with-all-users-data-perms-graph! {db-1-id {:view-data :unrestricted
|
|
:create-queries :query-builder
|
|
:data-model {:schemas {"" {(u/the-id t1) :all}}}}
|
|
db-2-id {:view-data :unrestricted
|
|
:create-queries :query-builder
|
|
:data-model {:schemas :none}}}
|
|
(let [response (->> (mt/user-http-request :rasta :get 200 "database" :can-write-metadata true)
|
|
:data
|
|
(filter #(#{db-1-id db-2-id} (:id %))))]
|
|
(is (= 1 (count response)))
|
|
(is (= "Editable DB" (-> response first :name))))))))
|
|
|
|
(deftest list-databases-with-tables-can-write-metadata-filter-test
|
|
(testing "GET /api/database?include=tables&can-write-metadata=true filters tables within databases"
|
|
(mt/with-temp [:model/Database {db-id :id} {:name "Test DB"}
|
|
:model/Table t1 {:db_id db-id :name "editable_table" :active true}
|
|
:model/Table t2 {:db_id db-id :name "not_editable_table" :active true}]
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :query-builder
|
|
:data-model {:schemas {"" {(u/the-id t1) :all
|
|
(u/the-id t2) :none}}}}}
|
|
(let [response (->> (mt/user-http-request :rasta :get 200 "database" :include "tables" :can-write-metadata true)
|
|
:data
|
|
(filter #(= (:id %) db-id))
|
|
first)]
|
|
(is (= 1 (count (:tables response))))
|
|
(is (= "editable_table" (-> response :tables first :name))))))))
|
|
|
|
(deftest list-schemas-can-write-metadata-filter-test
|
|
(testing "GET /api/database/:id/schemas with can-write-metadata=true filters to only schemas with editable tables"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table t1 {:db_id db-id :schema "editable_schema" :name "t1" :active true}
|
|
:model/Table _ {:db_id db-id :schema "not_editable_schema" :name "t2" :active true}]
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :query-builder
|
|
:data-model {:schemas {"editable_schema" {(u/the-id t1) :all}
|
|
"not_editable_schema" :none}}}}
|
|
(is (= ["editable_schema"]
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d/schemas" db-id) :can-write-metadata true)))))))
|
|
|
|
(deftest list-schema-tables-can-write-metadata-filter-test
|
|
(testing "GET /api/database/:id/schema/:schema with can-write-metadata=true filters to only editable tables"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table t1 {:db_id db-id :schema "test_schema" :name "editable_table" :active true}
|
|
:model/Table t2 {:db_id db-id :schema "test_schema" :name "not_editable_table" :active true}]
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :query-builder
|
|
:data-model {:schemas {"test_schema" {(u/the-id t1) :all
|
|
(u/the-id t2) :none}}}}}
|
|
(let [response (mt/user-http-request :rasta :get 200 (format "database/%d/schema/%s" db-id "test_schema") :can-write-metadata true)]
|
|
(is (= 1 (count response)))
|
|
(is (= "editable_table" (-> response first :name))))))))
|
|
|
|
(deftest get-field-hydrated-target-with-advanced-perms-test
|
|
(testing "GET /api/field/:id"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table table1 {:db_id db-id, :schema "schema1"}
|
|
:model/Table table2 {:db_id db-id, :schema "schema2"}
|
|
:model/Field fk-field {:table_id (:id table1)}
|
|
:model/Field field {:table_id (:id table2)
|
|
:semantic_type :type/FK
|
|
:fk_target_field_id (:id fk-field)}]
|
|
(let [expected-target (-> fk-field
|
|
(update :base_type u/qualified-name)
|
|
(update :visibility_type u/qualified-name))
|
|
get-field (fn []
|
|
(mt/user-http-request :rasta :get 200 (format "field/%d?include_editable_data_model=true" (:id field))))]
|
|
(testing "target should be hydrated if a non-admin has data model perms for the DB"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas :all}}}
|
|
(is (= expected-target
|
|
(:target (get-field))))))
|
|
(testing "target should not be hydrated if a non-admin does not have data model perms for the target's schema"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" :none
|
|
"schema2" :all}}}}
|
|
(is (nil? (:target (get-field))))))
|
|
(testing "target should not be hydrated if a non-admin does not have data model perms for the target's table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" {(:id table1) :none}
|
|
"schema2" {(:id table2) :all}}}}}
|
|
(is (nil? (:target (get-field))))))
|
|
(testing "target should be hydrated if a non-admin does have data model perms for the target's table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" {(:id table1) :all}
|
|
"schema2" {(:id table2) :all}}}}}
|
|
(is (= expected-target
|
|
(:target (get-field))))))))))
|
|
|
|
(deftest update-field-hydrated-target-with-advanced-perms-test
|
|
(testing "PUT /api/field/:id"
|
|
(mt/with-temp [:model/Database {db-id :id} {}
|
|
:model/Table table1 {:db_id db-id, :schema "schema1"}
|
|
:model/Table table2 {:db_id db-id, :schema "schema2"}
|
|
:model/Table table3 {:db_id db-id, :schema "schema3"}
|
|
:model/Field fk-field-1 {:table_id (:id table1)}
|
|
:model/Field fk-field-2 {:table_id (:id table2)}
|
|
:model/Field field {:table_id (:id table3)
|
|
:semantic_type :type/FK
|
|
:fk_target_field_id (:id fk-field-1)}]
|
|
(let [expected-target (-> fk-field-2
|
|
(update :base_type u/qualified-name)
|
|
(update :visibility_type u/qualified-name))
|
|
update-target (fn []
|
|
(mt/user-http-request :rasta :put 200 (format "field/%d" (:id field)) (assoc field :fk_target_field_id (:id fk-field-2))))]
|
|
(testing "target should be hydrated if a non-admin has data model perms for the DB"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas :all}}}
|
|
(is (= expected-target
|
|
(:target (update-target))))))
|
|
(testing "target should not be hydrated if a non-admin does not have data model perms for the target's schema"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" :none
|
|
"schema2" :none
|
|
"schema3" :all}}}}
|
|
(is (nil? (:target (update-target))))))
|
|
(testing "target should not be hydrated if a non-admin does not have data model perms for the target's table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" {(:id table1) :all}
|
|
"schema2" {(:id table2) :none}
|
|
"schema3" {(:id table3) :all}}}}}
|
|
(is (nil? (:target (update-target))))))
|
|
(testing "target should be hydrated if a non-admin does have data model perms for the target's table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {"schema1" {(:id table1) :none}
|
|
"schema2" {(:id table2) :all}
|
|
"schema3" {(:id table3) :all}}}}}
|
|
(is (= expected-target
|
|
(:target (update-target))))))))))
|
|
|
|
(deftest update-field-test
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}
|
|
:model/Table {table-id-2 :id} {:db_id (mt/id) :schema "PUBLIC"}
|
|
:model/Field {field-id :id, table-id :table_id} {:name "Field" :table_id table-id}]
|
|
(let [{table-id :id, schema :schema, db-id :db_id} (t2/select-one :model/Table :id table-id)]
|
|
(testing "PUT /api/field/:id"
|
|
(let [endpoint (format "field/%d" field-id)]
|
|
(testing "a non-admin cannot update field metadata if the advanced-permissions feature flag is not present"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {schema {table-id :all}}}}}
|
|
(mt/with-premium-features #{}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Field Test 4"}))))
|
|
(testing "a non-admin cannot update field metadata if they have no data model permissions for the DB"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas :none}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Field Test 2"})))
|
|
(testing "a non-admin cannot update field metadata if they only have data model permissions for other schemas"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {schema :none
|
|
"different schema" :all}}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Field Test 2"})))
|
|
(testing "a non-admin cannot update field metadata if they only have data model permissions for other tables"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {schema {table-id :none
|
|
table-id-2 :all}}}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Field Test 2"})))
|
|
(testing "a non-admin can update field metadata if they have data model perms for the DB"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Field Test 2"})))
|
|
(testing "a non-admin can update field metadata if they have data model perms for the schema"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {schema :all}}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Field Test 3"})))
|
|
(testing "a non-admin can update field metadata if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:data-model {:schemas {schema {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Field Test 3"})))))
|
|
(testing "POST /api/field/:id/rescan_values"
|
|
(testing "A non-admin can trigger a rescan of field values if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {schema {table-id :none}}}}}
|
|
(mt/user-http-request :rasta :post 403 (format "field/%d/rescan_values" field-id)))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {schema {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "field/%d/rescan_values" field-id))))
|
|
(testing "A non-admin with no data access can trigger a re-scan of field values if they have data model perms"
|
|
(t2/delete! :model/FieldValues :field_id (mt/id :venues :price))
|
|
(is (= nil (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" {(mt/id :venues) :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "field/%d/rescan_values" (mt/id :venues :price))))
|
|
(is (= [1 2 3 4] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))))
|
|
(testing "POST /api/field/:id/discard_values"
|
|
(testing "A non-admin can discard field values if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {schema {table-id :none}}}}}
|
|
(mt/user-http-request :rasta :post 403 (format "field/%d/discard_values" field-id)))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {schema {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "field/%d/discard_values" field-id))))
|
|
(testing "A non-admin with no data access can discard field values if they have data model perms"
|
|
(is (= [1 2 3 4] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" {(mt/id :venues) :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "field/%d/discard_values" (mt/id :venues :price))))
|
|
(is (= nil (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price)))))))))
|
|
|
|
(deftest get-field-with-advanced-perms-test
|
|
(testing "GET /api/field/:id?include_editable_data_model=true"
|
|
(testing "A non-admin can fetch a field when they have data model perms if include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(is (partial= {:id (mt/id :users :name)}
|
|
(mt/user-http-request :rasta :get 200 (format "field/%d?include_editable_data_model=true" (mt/id :users :name)))))))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas {"PUBLIC" {(mt/id :categories) :all
|
|
(mt/id :users) :none}}}}}
|
|
(testing "A non-admin can fetch a field for which they have data model perms if include_editable_data_model=true"
|
|
(is (partial= {:id (mt/id :categories :name)}
|
|
(mt/user-http-request :rasta :get 200 (format "field/%d?include_editable_data_model=true" (mt/id :categories :name))))))
|
|
(testing "A non-admin cannot fetch a field for which they do not have data model perms if include_editable_data_model=true"
|
|
(is (= "You don't have permissions to do that."
|
|
(mt/user-http-request :rasta :get 403 (format "field/%d?include_editable_data_model=true" (mt/id :users :name)))))))))
|
|
|
|
(deftest update-table-test
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}
|
|
:model/Table {table-id-2 :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "PUT /api/table/:id"
|
|
(let [endpoint (format "table/%d" table-id)]
|
|
(testing "a non-admin cannot update table metadata if the advanced-permissions feature flag is not present"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas :all}}}
|
|
(mt/with-premium-features #{}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Table Test 2"}))))
|
|
(testing "a non-admin cannot update table metadata if they have no data model permissions for the DB"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas :none}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Table Test 2"})))
|
|
(testing "a non-admin cannot update table metadata if they only have data model permissions for other schemas"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" :none
|
|
"different schema" :all}}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Table Test 2"})))
|
|
(testing "a non-admin cannot update table metadata if they only have data model permissions for other tables"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :none
|
|
table-id-2 :all}}}}}
|
|
(mt/user-http-request :rasta :put 403 endpoint {:name "Table Test 2"})))
|
|
(testing "a non-admin can update table metadata if they have data model perms for the DB"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Table Test 2"})))
|
|
(testing "a non-admin can update table metadata if they have data model perms for the schema"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" :all}}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Table Test 3"})))
|
|
(testing "a non-admin can update table metadata if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :put 200 endpoint {:name "Table Test 3"})))))))
|
|
|
|
(deftest table-rescan-values-test
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "POST /api/table/:id/rescan_values"
|
|
;; Manually activate Field values since they are not created during sync (#53387)
|
|
(field-values/get-or-create-full-field-values! (t2/select-one :model/Field :id (mt/id :venues :price)))
|
|
(testing "A non-admin can trigger a rescan of field values if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :none}}}}}
|
|
(mt/user-http-request :rasta :post 403 (format "table/%d/rescan_values" table-id)))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "table/%d/rescan_values" table-id))))
|
|
(testing "A non-admin with no data access can trigger a re-scan of field values if they have data model perms"
|
|
(t2/update! :model/FieldValues :field_id (mt/id :venues :price) {:values [10 20 30 40]})
|
|
(is (= [10 20 30 40] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))
|
|
(mt/with-dynamic-fn-redefs [quick-task/submit-task! (fn [task] (task))]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" {(mt/id :venues) :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "table/%d/rescan_values" (mt/id :venues)))))
|
|
(is (= [1 2 3 4] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))))))
|
|
|
|
(deftest table-discard-values-test
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "POST /api/table/:id/discard_values"
|
|
(testing "A non-admin can discard field values if they have data model perms for the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :none}}}}}
|
|
(mt/user-http-request :rasta :post 403 (format "table/%d/discard_values" table-id)))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "table/%d/discard_values" table-id)))))))
|
|
|
|
(deftest table-fields-ordering-test
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "POST /api/table/:id/fields/order"
|
|
(testing "A non-admin can set a custom field ordering if they have data model perms for the table"
|
|
(mt/with-temp [:model/Field {field-1-id :id} {:table_id table-id}
|
|
:model/Field {field-2-id :id} {:table_id table-id}]
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :none}}}}}
|
|
(mt/user-http-request :rasta :put 403 (format "table/%d/fields/order" table-id)
|
|
[field-2-id field-1-id]))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(is (= {:success true}
|
|
(mt/user-http-request :rasta :put 200 (format "table/%d/fields/order" table-id)
|
|
[field-2-id field-1-id])))))))))
|
|
|
|
(deftest audit-log-generated-when-table-manual-scan
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "An audit log entry is generated when a manually triggered re-scan occurs"
|
|
(mt/with-additional-premium-features #{:audit-app}
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :post 200 (format "table/%d/rescan_values" table-id))))
|
|
(is (= table-id (:model_id (mt/latest-audit-log-entry :table-manual-scan))))
|
|
(is (= table-id (-> (mt/latest-audit-log-entry :table-manual-scan) :details :id))))))
|
|
|
|
(deftest fetch-table-test
|
|
(testing "GET /api/table/:id"
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "A non-admin without self-service perms for a table cannot fetch the table normally"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no}}
|
|
(mt/user-http-request :rasta :get 403 (format "table/%d?include_editable_data_model=true" table-id))))
|
|
(testing "A non-admin without self-service perms for a table can fetch the table if they have data model perms for
|
|
the DB"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :get 200 (format "table/%d?include_editable_data_model=true" table-id))))
|
|
(testing "A non-admin without self-service perms for a table can fetch the table if they have data model perms for
|
|
the schema"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" :all}}}}
|
|
(mt/user-http-request :rasta :get 200 (format "table/%d?include_editable_data_model=true" table-id))))
|
|
(testing "A non-admin without self-service perms for a table can fetch the table if they have data model perms for
|
|
the table"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas {"PUBLIC" {table-id :all}}}}}
|
|
(mt/user-http-request :rasta :get 200 (format "table/%d?include_editable_data_model=true" table-id)))))))
|
|
|
|
(deftest fetch-query-metadata-test
|
|
(testing "GET /api/table/:id/query_metadata?include_editable_data_model=true"
|
|
(mt/with-temp [:model/Table {table-id :id} {:db_id (mt/id) :schema "PUBLIC"}]
|
|
(testing "A non-admin without data model perms for a table cannot fetch the query metadata when
|
|
include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native
|
|
:data-model {:schemas :none}}}
|
|
(mt/user-http-request :rasta :get 403
|
|
(format "table/%d/query_metadata?include_editable_data_model=true" table-id))))
|
|
(testing "A non-admin with only data model perms for a table can fetch the query metadata when
|
|
include_editable_data_model=true"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :unrestricted
|
|
:create-queries :no
|
|
:data-model {:schemas :all}}}
|
|
(mt/user-http-request :rasta :get 200
|
|
(format "table/%d/query_metadata?include_editable_data_model=true" table-id)))))))
|
|
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
;;; | Database details permission enforcement |
|
|
;;; +----------------------------------------------------------------------------------------------------------------+
|
|
|
|
(deftest non-admin-update-database-test
|
|
(testing "PUT /api/database/:id"
|
|
(mt/with-temp [:model/Database {db-id :id}]
|
|
(testing "A non-admin cannot update database metadata if the advanced-permissions feature flag is not present"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :yes}}
|
|
(mt/with-premium-features #{}
|
|
(is (= "You don't have permissions to do that."
|
|
(mt/user-http-request :rasta :put 403 (format "database/%d" db-id) {:name "Database Test"}))))))
|
|
(testing "A non-admin cannot update database metadata if they do not have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :no}}
|
|
(is (= "You don't have permissions to do that."
|
|
(mt/user-http-request :rasta :put 403 (format "database/%d" db-id) {:name "Database Test"})))))
|
|
(testing "A non-admin can update database metadata if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :yes}}
|
|
(is (=? {:id db-id}
|
|
(mt/user-http-request :rasta :put 200 (format "database/%d" db-id) {:name "Database Test"}))))))))
|
|
|
|
(deftest non-admin-delete-database-test
|
|
(mt/with-temp [:model/Database {db-id :id}]
|
|
(testing "A non-admin cannot delete a database even if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :yes}}
|
|
(mt/user-http-request :rasta :delete 403 (format "database/%d" db-id))))))
|
|
|
|
(deftest non-admin-sync-schema-test
|
|
(mt/test-helpers-set-global-values!
|
|
(mt/with-temp [:model/Database {db-id :id} {:engine "h2", :details (:details (mt/db))}]
|
|
;; Don't actually run the sync task in this test — just test the API-level permission enforcement
|
|
(mt/with-dynamic-fn-redefs [quick-task/submit-task! (constantly nil)]
|
|
(testing "A non-admin cannot trigger a sync of the DB schema if they do not have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :no}}
|
|
(mt/user-http-request :rasta :post 403 (format "database/%d/sync_schema" db-id))))
|
|
(testing "A non-admin can trigger a sync of the DB schema if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :yes}}
|
|
(mt/user-http-request :rasta :post 200 (format "database/%d/sync_schema" db-id))))))))
|
|
|
|
(deftest non-admin-discard-field-values-test
|
|
(mt/with-temp [:model/Database {db-id :id} {:engine "h2", :details (:details (mt/db))}
|
|
:model/Table {table-id :id} {:db_id db-id}
|
|
:model/Field {field-id :id} {:table_id table-id}
|
|
:model/FieldValues _ {:field_id field-id, :values [1 2 3 4]}]
|
|
(testing "A non-admin cannot discard field values if they do not have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :no}}
|
|
(mt/user-http-request :rasta :post 403 (format "database/%d/discard_values" db-id))))
|
|
(testing "A non-admin can discard field values if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:details :yes}}
|
|
(mt/user-http-request :rasta :post 200 (format "database/%d/discard_values" db-id))))
|
|
(testing "A non-admin with blocked data access can discard field values if they have DB details permissions"
|
|
(t2/insert! :model/FieldValues {:field_id field-id, :values [1 2 3 4]})
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(mt/user-http-request :rasta :post 200 (format "database/%d/discard_values" db-id)))
|
|
(is (= nil (t2/select-one-fn :values :model/FieldValues, :field_id field-id))))))
|
|
|
|
(deftest non-admin-rescan-field-values-test
|
|
;; Use the shared test database so we can verify that the rescan actually succeeds (field values change)
|
|
(mt/test-helpers-set-global-values!
|
|
;; Manually activate Field values since they are not created during sync (#53387)
|
|
(field-values/get-or-create-full-field-values! (t2/select-one :model/Field :id (mt/id :venues :price)))
|
|
(with-redefs [api.database/*rescan-values-async* false]
|
|
(testing "A non-admin cannot rescan field values if they do not have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :no}}
|
|
(mt/user-http-request :rasta :post 403 (format "database/%d/rescan_values" (mt/id)))))
|
|
(testing "A non-admin can rescan field values if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :yes}}
|
|
(mt/user-http-request :rasta :post 200 (format "database/%d/rescan_values" (mt/id)))))
|
|
(testing "A non-admin with blocked data access can rescan field values if they have DB details permissions"
|
|
(t2/update! :model/FieldValues :field_id (mt/id :venues :price) {:values [10 20 30 40]})
|
|
(is (= [10 20 30 40] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(mt/user-http-request :rasta :post 200 (format "database/%d/rescan_values" (mt/id))))
|
|
(is (= [1 2 3 4] (t2/select-one-fn :values :model/FieldValues, :field_id (mt/id :venues :price))))))))
|
|
|
|
(deftest non-admin-fetch-database-test
|
|
(mt/with-temp [:model/Database {db-id :id}]
|
|
(testing "A non-admin cannot fetch the DB if they do not have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :no
|
|
:details :no}}
|
|
(mt/user-http-request :rasta :get 403 (format "database/%d?exclude_uneditable_details=true" db-id))))
|
|
(testing "A non-admin can fetch the DB if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d?exclude_uneditable_details=true" db-id))))
|
|
(testing "A non-admin with blocked data access can fetch the DB if they have DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d?exclude_uneditable_details=true" db-id))))
|
|
(testing "The returned database contains a :details field for a user with DB details permissions"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(is (partial= {:details {}}
|
|
(mt/user-http-request :rasta :get 200 (format "database/%d?exclude_uneditable_details=true" db-id))))))))
|
|
|
|
(deftest actions-test
|
|
(mt/with-temp-copy-of-db
|
|
(mt/with-actions-test-data
|
|
(mt/with-actions [{:keys [action-id model-id]} {}]
|
|
(testing "Executing dashcard with action"
|
|
(mt/with-temp [:model/Dashboard {dashboard-id :id} {}
|
|
:model/DashboardCard {dashcard-id :id} {:dashboard_id dashboard-id
|
|
:action_id action-id
|
|
:card_id model-id}]
|
|
(mt/with-full-data-perms-for-all-users!
|
|
(let [execute-path (format "dashboard/%s/dashcard/%s/execute"
|
|
dashboard-id
|
|
dashcard-id)]
|
|
(testing "Works with access to the DB not blocked"
|
|
(mt/with-actions-enabled
|
|
(is (= {:rows-affected 1}
|
|
(mt/user-http-request :rasta :post 200 execute-path
|
|
{:parameters {"id" 1}})))))
|
|
(testing "Fails with access to the DB blocked"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:view-data :blocked
|
|
:create-queries :no
|
|
:details :yes}}
|
|
(mt/with-actions-enabled
|
|
(is (partial= {:message "You don't have permissions to do that."}
|
|
(mt/user-http-request :rasta :post 403 execute-path
|
|
{:parameters {"id" 1}}))))))))))))))
|
|
|
|
(defmacro with-all-users-as-settings-managers [& body]
|
|
`(try
|
|
(perms/grant-application-permissions! (perms-group/all-users) :setting)
|
|
(do ~@body)
|
|
(finally
|
|
(perms/revoke-application-permissions! (perms-group/all-users) :setting))))
|
|
|
|
(deftest settings-managers-can-have-uploads-db-access-revoked
|
|
(mt/with-premium-features #{:advanced-permissions}
|
|
(with-all-users-as-settings-managers
|
|
(mt/user-http-request :crowberto :put 204 "setting/" {:uploads-settings {}})
|
|
(testing "Upload DB can be set with the right permission"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :yes}}
|
|
(mt/user-http-request :rasta :put 204 "setting/" {:uploads-settings {:db_id (mt/id) :schema_name nil :table_prefix nil}})))
|
|
(testing "Upload DB cannot be set without the right permission"
|
|
(mt/with-all-users-data-perms-graph! {(mt/id) {:details :no}}
|
|
(mt/user-http-request :rasta :put 403 "setting/" {:uploads-settings {:db_id (mt/id) :schema_name nil :table_prefix nil}}))))))
|
|
|
|
(deftest settings-managers-can-enable-and-disable-uploads-on-dwh
|
|
(mt/with-premium-features #{:advanced-permissions}
|
|
(with-all-users-as-settings-managers
|
|
(mt/with-temp [:model/Database {db-id :id} {:is_attached_dwh true}
|
|
:model/PermissionsGroup {group-id :id} {}
|
|
:model/User {user-id :id} {}
|
|
:model/PermissionsGroupMembership {} {:user_id user-id
|
|
:group_id group-id}]
|
|
(mt/with-no-data-perms-for-all-users!
|
|
(mt/with-restored-data-perms-for-groups! [group-id]
|
|
(testing "Can enable uploads on DWH"
|
|
(mt/user-http-request user-id :put 204 "setting/" {:uploads-settings {:db_id db-id :schema_name nil :table_prefix nil}}))
|
|
(testing "Can disable uploads on DWH"
|
|
(mt/user-http-request user-id :put 204 "setting/" {:uploads-settings {}}))
|
|
(testing "Admins can do this too"
|
|
(mt/user-http-request :crowberto :put 204 "setting/" {:uploads-settings {:db_id db-id :schema_name nil :table_prefix nil}})
|
|
(mt/user-http-request :crowberto :put 204 "setting/" {:uploads-settings {}}))))))))
|
|
|
|
(deftest upload-csv-test
|
|
(mt/test-drivers (mt/normal-drivers-with-feature :uploads :schemas)
|
|
(mt/dataset (mt/dataset-definition "advanced_permissions" [])
|
|
(testing "Uploads should be blocked without data access"
|
|
(let [schema-name (sql.tx/session-schema driver/*driver*)]
|
|
(upload-test/with-upload-table! [table (upload-test/create-upload-table!)]
|
|
(let [db-id (mt/id)
|
|
upload-csv! (fn []
|
|
(upload-test/do-with-uploaded-example-csv!
|
|
{:grant-permission? false
|
|
:schema-name (:schema table)
|
|
:table-prefix "uploaded_magic_"}
|
|
identity))]
|
|
(mt/with-temp [:model/Table _ {:db_id db-id :schema schema-name}]
|
|
(doseq [[schema-perms can-upload? description]
|
|
[[:query-builder true "Data permissions on schema should succeed"]
|
|
[:no false "No data permissions on schema should fail"]
|
|
[{(:id table) :query-builder} false "Data permissions on table should fail"]]]
|
|
(testing description
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries {"some_schema" :query-builder
|
|
schema-name schema-perms}}}
|
|
(if can-upload?
|
|
(is (some? (upload-csv!)))
|
|
(is (thrown-with-msg?
|
|
clojure.lang.ExceptionInfo
|
|
#"You don't have permissions to do that\."
|
|
(upload-csv!))))))))
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries :query-builder-and-native}}
|
|
(is (some? (upload-csv!)))))))))))
|
|
|
|
(deftest update-csv-data-perms-test
|
|
(mt/test-drivers (mt/normal-drivers-with-feature :uploads)
|
|
(mt/dataset (mt/dataset-definition "advanced_permissions" [])
|
|
(doseq [action [:metabase.upload/append :metabase.upload/replace]]
|
|
(testing (format "CSV %s should be blocked without data access to the schema" action)
|
|
(let [schema-name (sql.tx/session-schema driver/*driver*)]
|
|
(upload-test/with-upload-table!
|
|
[table-a (upload-test/create-upload-table! :schema-name schema-name)]
|
|
(upload-test/with-upload-table!
|
|
[table-b (upload-test/create-upload-table! :schema-name schema-name)]
|
|
(let [db-id (mt/id)
|
|
append-csv! #(upload-test/update-csv-with-defaults!
|
|
action
|
|
:table-id (:id table-a)
|
|
:user-id (mt/user->id :rasta))]
|
|
(doseq [[schema-perms can-append? test-string]
|
|
[[:query-builder true "Data permissions on schema should succeed"]
|
|
[:no false "No permissions on schema should fail"]
|
|
[{(:id table-a) :query-builder} true "Data permissions on table should succeed"]
|
|
[{(:id table-b) :query-builder} false "Data permissions only on another table in the same schema should fail"]]]
|
|
(testing test-string
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries {schema-name schema-perms}}}
|
|
(if can-append?
|
|
(is (some? (append-csv!)))
|
|
(is (thrown-with-msg?
|
|
clojure.lang.ExceptionInfo
|
|
#"You don't have permissions to do that\."
|
|
(append-csv!))))))))))))))))
|
|
|
|
(deftest update-csv-block-perms-test
|
|
(mt/test-drivers (mt/normal-drivers-with-feature :uploads)
|
|
(mt/dataset (mt/dataset-definition "advanced_permissions" [])
|
|
(doseq [action [:metabase.upload/append :metabase.upload/replace]]
|
|
(testing (format "We will block %s if the user has blocked data access to the database, even if they have native query editing"
|
|
action)
|
|
(upload-test/with-upload-table!
|
|
[table-a (upload-test/create-upload-table!)]
|
|
(let [db-id (mt/id)
|
|
append-csv! #(upload-test/update-csv-with-defaults!
|
|
action
|
|
:table-id (:id table-a)
|
|
:user-id (mt/user->id :rasta))]
|
|
(testing "With blocked perms it should fail"
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :blocked
|
|
:create-queries :no}}
|
|
(is (thrown-with-msg?
|
|
clojure.lang.ExceptionInfo
|
|
#"You don't have permissions to do that\."
|
|
(append-csv!))))))))))))
|
|
|
|
(deftest get-database-can-upload-test
|
|
(mt/test-drivers (mt/normal-drivers-with-feature :uploads :schemas)
|
|
(mt/dataset (mt/dataset-definition "advanced_permissions" [])
|
|
(testing "GET /api/database and GET /api/database/:id responses should include can_upload depending on unrestricted data access to the upload schema"
|
|
(mt/with-model-cleanup [:model/Table]
|
|
(let [schema-name (sql.tx/session-schema driver/*driver*)
|
|
db-id (mt/id)]
|
|
(upload-test/with-upload-table! [table (upload-test/create-upload-table! :schema-name schema-name)]
|
|
(mt/with-temp [:model/Table _ {:db_id db-id :schema "some_schema"}
|
|
:model/Table _ {:db_id db-id :schema schema-name}]
|
|
(doseq [[schema-perms can-upload?] {:query-builder true
|
|
:no false
|
|
{(:id table) :query-builder} false}]
|
|
(testing (format "can_upload should be %s if the user has %s access to the upload schema"
|
|
can-upload? schema-perms)
|
|
(mt/with-all-users-data-perms-graph! {db-id {:view-data :unrestricted
|
|
:create-queries {"some_schema" :query-builder
|
|
schema-name schema-perms}}}
|
|
(testing "GET /api/database"
|
|
(let [result (->> (mt/user-http-request :rasta :get 200 "database")
|
|
:data
|
|
(filter #(= (:id %) db-id))
|
|
first)]
|
|
(is (= can-upload? (:can_upload result)))))
|
|
(testing "GET /api/database/:id"
|
|
(let [result (mt/user-http-request :rasta :get 200 (format "database/%d" db-id))]
|
|
(is (= can-upload? (:can_upload result))))))))))))))))
|