Convert the permission graph's naive json conversion into the correct types. The strategy here is to use s/conform to tag every value that needs to be converted with the conversion strategy, then postwalk to actually perform the conversion. | (ns metabase.api.permission-graph (:require [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] [clojure.walk :as walk] [metabase.util :as u] [metabase.util.i18n :refer [trs]])) |
(set! *warn-on-reflection* true) | |
convert values from the naively converted json to what we REALLY WANT | (defmulti ^:private convert
{:arglists '([spec-conformed])}
first) |
(defmethod convert :kw->int [[_ k]] (Integer/parseInt (name k))) (defmethod convert :str->kw [[_ s]] (keyword s)) | |
Convert a keyword to string without excluding the namespace. e.g: :schema/name => "schema/name". Primarily used for schema-name since schema are allowed to have "/" and calling (name s) returning a substring after "/". | (defmethod convert :kw->str [[_ s]] (u/qualified-name s)) (defmethod convert :nil->none [[_ _]] :none) (defmethod convert :identity [[_ x]] x) (defmethod convert :global-execute [[_ x]] x) (defmethod convert :db-exeute [[_ x]] x) |
--------------------------------------------------- Common ---------------------------------------------------- | |
(defn- kw-int->int-decoder [kw-int]
(if (int? kw-int)
kw-int
(parse-long (name kw-int)))) | |
Integer malli schema that knows how to decode itself from the :123 sort of shape used in perm-graphs | (def DecodableKwInt
[:int {:decode/perm-graph kw-int->int-decoder}]) |
(def ^:private Id DecodableKwInt) (def ^:private GroupId DecodableKwInt) | |
ids come in as keywordized numbers | (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?)))) |
native permissions | (def ^:private Native [:maybe [:enum :write :none :full :limited]]) |
------------------------------------------------ Data Permissions ------------------------------------------------ | |
Perms that get reused for TablePerms and SchemaPerms | (def ^:private Perms [:enum :all :segmented :none :full :limited :unrestricted :legacy-no-self-service :sandboxed :query-builder :no :blocked]) |
(def ^:private TablePerms
[:or Perms [:map
[:read {:optional true} [:enum :all :none]]
[:query {:optional true} [:enum :all :none :segmented]]]]) | |
(def ^:private SchemaPerms [:or Perms [:map-of Id TablePerms]]) | |
(def ^:private SchemaGraph
[:map-of
[:string {:decode/perm-graph name}]
SchemaPerms]) | |
(def ^:private Schemas
[:or
[:enum
:all
:segmented
:none
:block
:blocked
:full
:limited
:impersonated
:unrestricted
:sandboxed
:legacy-no-self-service
:query-builder-and-native
:query-builder
:no]
SchemaGraph]) | |
(def ^:private DataPerms
[:map
[:native {:optional true} Native]
[:schemas {:optional true} Schemas]]) | |
Data perms that care about how view-data and make-queries are related to one another. If you have write access for native queries, you must have data access to all schemas. | (def StrictDataPerms
[:and
DataPerms
[:fn {:error/fn (fn [_ _] (trs "Invalid DB permissions: If you have write access for native queries, you must have data access to all schemas."))}
(fn [{:keys [native schemas]}]
(not (and (= native :write) schemas (not (#{:all :impersonated} schemas)))))]]) |
like db-graph, but with added validations: - Ensures 'view-data' is not 'blocked' if 'create-queries' is 'query-builder-and-native'. | (def StrictDbGraph
[:schema {:registry {"StrictDataPerms" StrictDataPerms}}
[:map-of
Id
[:and
[:map
[:view-data {:optional true} Schemas]
[:create-queries {:optional true} Schemas]
[:data {:optional true} "StrictDataPerms"]
[:download {:optional true} "StrictDataPerms"]
[:data-model {:optional true} "StrictDataPerms"]
[:details {:optional true} [:enum :yes :no]]]
[:fn {:error/fn (fn [_ _] (trs "Invalid DB permissions: If you have write access for native queries, you must have data access to all schemas."))}
(fn [db-entry]
(let [{:keys [create-queries view-data]} db-entry]
(not (and (= create-queries :query-builder-and-native) (= view-data :blocked)))))]]]]) |
Used to transform, and verify data permissions graph | (def DataPermissionsGraph [:map [:groups [:map-of GroupId [:maybe StrictDbGraph]]]]) |
Top level strict data graph schema expected over the API. Includes revision ID for avoiding concurrent updates. | (def StrictApiPermissionsGraph
[:map
[:revision {:optional true} [:maybe int?]]
[:force {:optional true} [:maybe boolean?]]
[:groups [:map-of GroupId [:maybe StrictDbGraph]]]]) |
--------------------------------------------- Execution Permissions ---------------------------------------------- | |
(s/def ::execute (s/or :str->kw #{"all" "none"})) | |
(s/def ::execute-graph
(s/or :global-execute ::execute
:db-exeute (s/map-of ::id ::execute
:conform-keys true))) | |
(s/def :metabase.api.permission-graph.execution/groups
(s/map-of ::id
::execute-graph
:conform-keys true)) | |
(s/def ::execution-permissions-graph (s/keys :req-un [:metabase.api.permission-graph.execution/groups])) | |
The permissions graph is received as JSON. That JSON is naively converted. This performs a further conversion to convert graph keys and values to the types we want to work with. | (defn converted-json->graph
[spec kwj]
(->> (s/conform spec kwj)
(walk/postwalk (fn [x]
(if (and (vector? x) (get-method convert (first x)))
(convert x)
x))))) |