From ca4968682f6f0eaeea98cf78d381a3cef46b3b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cam=20Sau=CC=88l?= <cammsaul@gmail.com> Date: Tue, 7 Jul 2015 05:23:53 -0700 Subject: [PATCH] can_read and can_write are now hydrated via methods --- src/metabase/models/card.clj | 17 +++++--- src/metabase/models/common.clj | 46 --------------------- src/metabase/models/dashboard.clj | 12 +++++- src/metabase/models/database.clj | 3 +- src/metabase/models/hydrate.clj | 13 +++++- src/metabase/models/interface.clj | 61 +++++++++++++++++++++------- test/metabase/models/common_test.clj | 26 ------------ 7 files changed, 81 insertions(+), 97 deletions(-) delete mode 100644 test/metabase/models/common_test.clj diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj index 86e91a60aa3..9c2ebd5cdea 100644 --- a/src/metabase/models/card.clj +++ b/src/metabase/models/card.clj @@ -2,8 +2,7 @@ (:require [korma.core :refer :all, :exclude [defentity]] [metabase.api.common :refer [*current-user-id*]] [metabase.db :refer :all] - (metabase.models [common :refer :all] - [interface :refer :all] + (metabase.models [interface :refer :all] [user :refer [User]]))) (def ^:const display-types @@ -19,6 +18,14 @@ :table :timeseries}) +(defrecord CardInstance [] + clojure.lang.IFn + (invoke [this k] + (get this k))) + +(extend-ICanReadWrite CardInstance :read :public-perms, :write :public-perms) + + (defentity Card [(table :report_card) (hydration-keys card) @@ -26,10 +33,10 @@ timestamped] (post-select [_ {:keys [creator_id] :as card}] - (-> (assoc card - :creator (delay (sel :one User :id creator_id))) - assoc-permissions-sets)) + (map->CardInstance (assoc card :creator (delay (sel :one User :id creator_id))))) (pre-cascade-delete [_ {:keys [id]}] (cascade-delete 'metabase.models.dashboard-card/DashboardCard :card_id id) (cascade-delete 'metabase.models.card-favorite/CardFavorite :card_id id))) + +(extend-ICanReadWrite CardEntity :read :public-perms, :write :public-perms) diff --git a/src/metabase/models/common.clj b/src/metabase/models/common.clj index adb7a0a0980..b9fcc6005ab 100644 --- a/src/metabase/models/common.clj +++ b/src/metabase/models/common.clj @@ -15,8 +15,6 @@ "US/Pacific" "America/Costa_Rica"]) -;;; ALLEN'S PERMISSIONS IMPLEMENTATION - (def ^:const perms-none 0) (def ^:const perms-read 1) (def ^:const perms-readwrite 2) @@ -27,50 +25,6 @@ {:id perms-readwrite :name "Read & Write"}]) -;;; CAM'S PERMISSIONS IMPL -;; (TODO - need to use one or the other) - -(defn public-permissions - "Return the set of public permissions for some object with key `:public_perms`. Possible permissions are `:read` and `:write`." - [{:keys [public_perms]}] - (check public_perms 500 "Can't check public permissions: object doesn't have :public_perms.") - ({0 #{} - 1 #{:read} - 2 #{:read :write}} public_perms)) - -(defn user-permissions - "Return the set of current user's permissions for some object with keys `:creator_id` and `:public_perms`." - [{:keys [creator_id public_perms] :as obj}] - (check creator_id 500 "Can't check user permissions: object doesn't have :creator_id." - public_perms 500 "Can't check user permissions: object doesn't have :public_perms.") - (cond (:is_superuser *current-user*) #{:read :write} ; superusers have full access to everything - (= creator_id *current-user-id*) #{:read :write} ; if user created OBJ they have all permissions - (<= perms-read public_perms) #{:read} ; if the object is public then everyone gets :read - :else #{})) ; default is user has no permissions a.k.a private - -(defn user-can? - "Check if *current-user* has a given PERMISSION for OBJ. - PERMISSION should be either `:read` or `:write`." - [permission obj] - (contains? @(:user-permissions-set obj) permission)) - -(defn assoc-permissions-sets - "Associates the following delays with OBJ: - - * `:public-permissions-set` - * `:user-permissions-set` - * `:can_read` - * `:can_write` - - Note that these delays depend upon the presence of `creator_id`, and `public_perms` fields in OBJ." - [obj] - (u/assoc* obj - :public-permissions-set (delay (public-permissions <>)) - :user-permissions-set (delay (user-permissions <>)) - :can_read (delay (user-can? :read <>)) - :can_write (delay (user-can? :write <>)))) - - (defn name->human-readable-name "Convert a string NAME of some object like a `Table` or `Field` to one more friendly to humans. diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj index caa588809ba..6c0ae81d38d 100644 --- a/src/metabase/models/dashboard.clj +++ b/src/metabase/models/dashboard.clj @@ -7,6 +7,14 @@ [user :refer [User]]) [metabase.util :as u])) +(defrecord DashboardInstance [] + clojure.lang.IFn + (invoke [this k] + (get this k))) + +(extend-ICanReadWrite DashboardInstance :read :public-perms, :write :public-perms) + + (defentity Dashboard [(table :report_dashboard) timestamped] @@ -16,7 +24,9 @@ (assoc :creator (delay (sel :one User :id creator_id)) :description (u/jdbc-clob->str description) :ordered_cards (delay (sel :many DashboardCard :dashboard_id id (order :created_at :asc)))) - assoc-permissions-sets)) + map->DashboardInstance)) (pre-cascade-delete [_ {:keys [id]}] (cascade-delete DashboardCard :dashboard_id id))) + +(extend-ICanReadWrite DashboardEntity :read :public-perms, :write :public-perms) diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj index 338dbaf9f3d..1683ae7ed60 100644 --- a/src/metabase/models/database.clj +++ b/src/metabase/models/database.clj @@ -2,8 +2,7 @@ (:require [korma.core :refer :all, :exclude [defentity]] [metabase.api.common :refer [*current-user*]] [metabase.db :refer :all] - (metabase.models [common :refer [assoc-permissions-sets]] - [interface :refer :all]))) + [metabase.models.interface :refer :all])) (defrecord DatabaseInstance [] ;; preserve normal IFn behavior so things like ((sel :one Database) :id) work correctly diff --git a/src/metabase/models/hydrate.clj b/src/metabase/models/hydrate.clj index 5a9037aedfd..0234b7e094f 100644 --- a/src/metabase/models/hydrate.clj +++ b/src/metabase/models/hydrate.clj @@ -1,6 +1,7 @@ (ns metabase.models.hydrate "Functions for deserializing and hydrating fields in objects fetched from the DB." (:require [metabase.db :refer [sel]] + [metabase.models.interface :as models] [metabase.util :as u])) (declare batched-hydrate @@ -131,14 +132,22 @@ (if (can-batched-hydrate? results k) (batched-hydrate results k) (simple-hydrate results k))) +(def ^:private hydration-k->method + "Methods that can be used to hydrate corresponding keys." + {:can_read #(models/can-read? %) + :can_write #(models/can-write? %)}) + (defn- simple-hydrate "Hydrate keyword K in results by dereferencing corresponding delays when applicable." [results k] {:pre [(keyword? k)]} (map (fn [result] (let [v (k result)] - (if-not (delay? v) result ; if v isn't a delay it's either already hydrated or nil. - (assoc result k @v)))) ; don't barf on nil; just no-op + (cond + (delay? v) (assoc result k @v) ; hydrate delay if possible + (and (not v) + (hydration-k->method k)) (assoc result k ((hydration-k->method k) result)) ; otherwise if no value exists look for a method we can use for hydration + :else result))) ; otherwise don't barf, v may already be hydrated results)) (defn- already-hydrated? diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj index 7cb1af3c617..996ce6b3339 100644 --- a/src/metabase/models/interface.clj +++ b/src/metabase/models/interface.clj @@ -8,20 +8,50 @@ metabase.db.internal [metabase.util :as u])) +;;; ## ---------------------------------------- PERMISSIONS CHECKING ---------------------------------------- + (defprotocol ICanReadWrite (can-read? [obj] [entity ^Integer id]) (can-write? [obj] [entity ^Integer id])) +(defn- superuser? [& _] + (:is_superuser @@(resolve 'metabase.api.common/*current-user*))) + +(defn- owner? + ([{:keys [creator_id], :as obj}] + (assert creator_id "Can't check user permissions: object doesn't have :creator_id.") + (or (superuser?) + (= creator_id @(resolve 'metabase.api.common/*current-user-id*)))) + ([entity id] + (or (superuser?) + (owner? (entity id))))) + +(defn- has-public-perms? [rw] + (let [perms (delay (require 'metabase.models.common) + (case rw + :r @(resolve 'metabase.models.common/perms-read) + :w @(resolve 'metabase.models.common/perms-readwrite)))] + (fn -has-public-perms? + ([{:keys [public_perms], :as obj}] + (assert public_perms "Can't check user permissions: object doesn't have :public_perms.") + (or (owner? obj) + (>= public_perms @perms))) + ([entity id] + (or (owner? entity id) + (-has-public-perms? (entity id))))))) + (defn extend-ICanReadWrite "Add standard implementations of `can-read?` and `can-write?` to KLASS." [klass & {:keys [read write]}] - (let [key->method #(case % - :always (constantly true) - :superuser (fn [& args] - (:is_superuser @@(resolve 'metabase.api.common/*current-user*))))] + (let [key->method (fn [rw v] + (case v + :always (constantly true) + :public-perms (has-public-perms? rw) + :owner #'owner? + :superuser #'superuser?))] (extend klass - ICanReadWrite {:can-read? (key->method read) - :can-write? (key->method write)}))) + ICanReadWrite {:can-read? (key->method :r read) + :can-write? (key->method :w write)}))) ;;; ## ---------------------------------------- ENTITIES ---------------------------------------- @@ -102,15 +132,16 @@ (defn -invoke-entity "Basically the same as `(sel :one Entity :id id)`." ; TODO - deduplicate with sel [entity id] - (when (metabase.config/config-bool :mb-db-logging) - (clojure.tools.logging/debug - "DB CALL: " (:name entity) id)) - (let [[obj] (k/select (assoc entity :fields (::default-fields entity)) - (k/where {:id id}) - (k/limit 1))] - (some->> obj - (internal-post-select entity) - (post-select entity)))) + (when id + (when (metabase.config/config-bool :mb-db-logging) + (clojure.tools.logging/debug + "DB CALL: " (:name entity) id)) + (let [[obj] (k/select (assoc entity :fields (::default-fields entity)) + (k/where {:id id}) + (k/limit 1))] + (some->> obj + (internal-post-select entity) + (post-select entity))))) (defn- update-updated-at [obj] (assoc obj :updated_at (u/new-sql-timestamp))) diff --git a/test/metabase/models/common_test.clj b/test/metabase/models/common_test.clj deleted file mode 100644 index 2ac473653d0..00000000000 --- a/test/metabase/models/common_test.clj +++ /dev/null @@ -1,26 +0,0 @@ -(ns metabase.models.common-test - (:require [expectations :refer :all] - [metabase.api.common :refer [*current-user-id*]] - [metabase.models.common :refer :all])) - -;;; tests for PUBLIC-PERMISSIONS - -(expect #{} - (public-permissions {:public_perms 0})) - -(expect #{:read} - (public-permissions {:public_perms 1})) - -(expect #{:read :write} - (public-permissions {:public_perms 2})) - - -;;; tests for USER-PERMISSIONS - -;; creator can read + write -(expect #{:read :write} - (binding [*current-user-id* 100] - (user-permissions {:creator_id 100 - :public_perms 0}))) - -;; TODO - write tests for the rest of the `user-permissions` -- GitLab