Skip to content

Commit

Permalink
Write path for download permission graphs (#20759)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahmoss committed Mar 14, 2022
1 parent 946978d commit faf55fb
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 25 deletions.
@@ -0,0 +1,105 @@
(ns metabase-enterprise.advanced-permissions.models.permissions
(:require [metabase.models.permissions :as perms]
[metabase.util.schema :as su]
[schema.core :as s]))

;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Shared Util Functions |
;;; +----------------------------------------------------------------------------------------------------------------+

(defn- all-schemas-path
[perm-type perm-value db-id]
(perms/base->feature-perms-path perm-type perm-value (perms/all-schemas-path db-id)))

(defn- grant-permissions-for-all-schemas!
[perm-type perm-value group-id db-id]
(perms/grant-permissions! group-id (all-schemas-path perm-type perm-value db-id)))

(defn- revoke-permissions!
{:arglists '([perm-type perm-value group-id db-id]
[perm-type perm-value group-id db-id schema-name]
[perm-type perm-value group-id db-id schema-name table-or-id])}
[perm-type perm-value group-id & path-components]
(perms/delete-related-permissions! group-id
(apply (partial perms/feature-perms-path perm-type perm-value) path-components)))


;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Download permissions |
;;; +----------------------------------------------------------------------------------------------------------------+

(defn- revoke-download-permissions!
{:arglists '([group-id db-id]
[group-id db-id schema-name]
[group-id db-id schema-name table-or-id])}
[group-id & path-components]
(apply (partial revoke-permissions! :download :full group-id) path-components)
(apply (partial revoke-permissions! :download :limited group-id) path-components))

(defn- update-table-download-permissions!
[group-id db-id schema table-id new-table-perms]
(condp = new-table-perms
:full
(do
(revoke-download-permissions! group-id db-id schema table-id)
(perms/grant-permissions! group-id (perms/feature-perms-path :download :full db-id schema table-id)))

:limited
(do
(revoke-download-permissions! group-id db-id schema table-id)
(perms/grant-permissions! group-id (perms/feature-perms-path :download :limited db-id schema table-id)))

:none
(revoke-download-permissions! group-id db-id schema table-id)))

(defn- update-schema-download-permissions!
[group-id db-id schema new-schema-perms]
(condp = new-schema-perms
:full
(do
(revoke-download-permissions! group-id db-id schema)
(perms/grant-permissions! group-id (perms/feature-perms-path :download :full db-id schema)))

:limited
(do
(revoke-download-permissions! group-id db-id schema)
(perms/grant-permissions! group-id (perms/feature-perms-path :download :limited db-id schema)))

:none
(revoke-download-permissions! group-id db-id schema)

(when (map? new-schema-perms)
(doseq [[table-id table-perms] new-schema-perms]
(update-table-download-permissions! group-id db-id schema table-id table-perms)))))

(s/defn update-db-download-permissions!
"Update the download permissions graph for a database.
This mostly works similar to [[metabase.models.permission/update-db-data-access-permissions!]], with a few key
differences:
- Permissions have three levels: full, limited, and none.
- Native query download permissions are fully inferred from the non-native download permissions. For more details,
see the docstring for [[metabase.models.permissions/update-native-download-permissions!]]."
[group-id :- su/IntGreaterThanZero db-id :- su/IntGreaterThanZero new-download-perms :- perms/DownloadPermissionsGraph]
(when-let [schemas (:schemas new-download-perms)]
(condp = schemas
:full
(do
(revoke-download-permissions! group-id db-id)
(grant-permissions-for-all-schemas! :download :full group-id db-id))

:limited
(do
(revoke-download-permissions! group-id db-id)
(grant-permissions-for-all-schemas! :download :limited group-id db-id))

:none
(revoke-download-permissions! group-id db-id)

(when (map? schemas)
(doseq [[schema new-schema-perms] (seq schemas)]
(update-schema-download-permissions! group-id db-id schema new-schema-perms)))))
;; We need to call update-native-download-permissions! whenever any download permissions are changed, but after we've
;; updated non-native donwload permissions. This is because native download permissions are fully computed from the
;; non-native download permissions.
(perms/update-native-download-permissions! group-id db-id))
@@ -0,0 +1,78 @@
(ns metabase-enterprise.advanced-permissions.models.permissions-test
(:require [clojure.test :refer :all]
[metabase-enterprise.advanced-permissions.models.permissions :as ee-perms]
[metabase.models :refer [Permissions PermissionsGroup]]
[metabase.models.database :as database]
[metabase.models.permissions :as perms]
[metabase.public-settings.premium-features-test :as premium-features-test]
[metabase.test :as mt]
[metabase.util :as u]))

(defn- download-perms-by-group-id [group-id]
(get-in (perms/data-perms-graph) [:groups group-id (mt/id) :download]))

(deftest update-db-download-permissions-test
(premium-features-test/with-premium-features #{:advanced-permissions}
(mt/with-model-cleanup [Permissions]
(mt/with-temp PermissionsGroup [{group-id :id}]
(testing "Download perms for all schemas can be set and revoked"
(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas :full})
(is (= {:schemas :full, :native :full}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas :limited})
(is (= {:schemas :limited, :native :limited}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas :none})
(is (nil? (download-perms-by-group-id group-id))))

(testing "Download perms for individual schemas can be set and revoked"
(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" :full}})
(is (= {:schemas {"PUBLIC" :full} :native :full}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" :limited}})
(is (= {:schemas {"PUBLIC" :limited} :native :limited}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" :none}})
(is (nil? (download-perms-by-group-id group-id))))

(testing "Download perms for individual tables can be set and revoked"
(let [[id-1 id-2 id-3 id-4] (map u/the-id (database/tables (mt/db)))]
(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-1 :full
id-2 :full
id-3 :full
id-4 :full}}})
(is (= {:schemas {"PUBLIC" {id-1 :full id-2 :full id-3 :full id-4 :full}}
:native :full}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-1 :limited}}})
(is (= {:schemas {"PUBLIC" {id-1 :limited id-2 :full id-3 :full id-4 :full}}
:native :limited}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-2 :none}}})
(is (= {:schemas {"PUBLIC" {id-1 :limited id-3 :full id-4 :full}}}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-1 :none
id-3 :none
id-4 :none}}})
(is (nil? (download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-1 :full
id-2 :full
id-3 :limited
id-4 :limited}}})
(is (= {:schemas {"PUBLIC" {id-1 :full id-2 :full id-3 :limited id-4 :limited}}
:native :limited}
(download-perms-by-group-id group-id)))

(ee-perms/update-db-download-permissions! group-id (mt/id) {:schemas {"PUBLIC" {id-3 :full
id-4 :full}}})
(is (= {:schemas {"PUBLIC" {id-1 :full id-2 :full id-3 :full id-4 :full}}
:native :full}
(download-perms-by-group-id group-id)))))))))
12 changes: 7 additions & 5 deletions src/metabase/api/permission_graph.clj
Expand Up @@ -37,7 +37,7 @@
(s/def ::id (s/with-gen (s/or :kw->int (s/and keyword? #(re-find #"^\d+$" (name %))))
#(gen/fmap (comp keyword str) (s/gen pos-int?))))

(s/def ::native (s/or :str->kw #{"write" "none"}
(s/def ::native (s/or :str->kw #{"write" "none" "full" "limited"}
:nil->none nil?))

;;; ------------------------------------------------ Data Permissions ------------------------------------------------
Expand All @@ -51,27 +51,29 @@

(s/def ::table-perms-granular (s/keys :opt-un [::read ::query]))

(s/def ::table-perms (s/or :str->kw #{"all" "segmented" "none"}
(s/def ::table-perms (s/or :str->kw #{"all" "segmented" "none" "full" "limited"}
:identity ::table-perms-granular))

(s/def ::table-graph (s/map-of ::id ::table-perms
:conform-keys true))

(s/def ::schema-perms (s/or :str->kw #{"all" "segmented" "none"}
(s/def ::schema-perms (s/or :str->kw #{"all" "segmented" "none" "full" "limited"}
:identity ::table-graph))

;; {:groups {1 {:data {:schemas {"PUBLIC" ::schema-perms}}}}}
(s/def ::schema-graph (s/map-of ::schema-name ::schema-perms
:conform-keys true))

;; {:groups {1 {:data {:schemas ::schemas}}}}
(s/def ::schemas (s/or :str->kw #{"all" "segmented" "none" "block"}
(s/def ::schemas (s/or :str->kw #{"all" "segmented" "none" "block" "full" "limited"}
:nil->none nil?
:identity ::schema-graph))

(s/def ::data (s/keys :opt-un [::native ::schemas]))

(s/def ::db-perms (s/keys :opt-un [::data]))
(s/def ::download (s/keys :opt-un [::native ::schemas]))

(s/def ::db-perms (s/keys :opt-un [::data ::download]))

(s/def ::db-graph (s/map-of ::id ::db-perms
:conform-keys true))
Expand Down

0 comments on commit faf55fb

Please sign in to comment.