From 937b3fc65f29b42e664e5da032b94bc77facd23c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Sep 2026 09:24:45 +0200 Subject: [PATCH] :bug: Add missing permission checks to plugin API (tokens, shapes, variants, flows, layouts, user identity) (#11139) * :bug: Add content:write permission checks to Design Tokens plugin API The Design Tokens API (tokens.cljs) had zero permission checks, allowing any plugin to create, modify, and delete tokens, sets, and themes regardless of granted permissions. Add r/check-permission checks to all 22 write operations across: - token-proxy: name, value, description, duplicate, remove, applyToken - token-set-proxy: name, active, toggleActive, addToken, duplicate, remove - token-theme-proxy: group, name, active, toggleActive, addSet, removeSet, duplicate, remove - tokens-catalog: addTheme, addSet Follows the established pattern from comments.cljs, file.cljs, page.cljs. Closes #11137 AI-assisted-by: qwen3.7-plus * :bug: Add permission checks to shape proxy interactions, detach, export, and variants The shape proxy (shape.cljs) had multiple operations missing permission checks, plus a cond ordering bug that bypassed the existing content:write check for text shapes in commit-fills!. Fix commit-fills! cond ordering: move permission check before the text-shape branch so text shapes are also protected. Add content:write permission checks to: - interaction-proxy: :trigger, :delay, :action setters, :remove method - shape-proxy: :addInteraction, :removeInteraction, :detach - shape-proxy: :applyToken, :switchVariant, :combineAsVariants Add content:read permission check to: - shape-proxy: :export (read/extraction operation) Follows the established pattern from :resize, :rotate, :blocked setters. Relates to #11137 AI-assisted-by: qwen3.7-plus * :bug: Add library:write permission checks to variant plugin API The library.cljs variant operations (variant-proxy and lib-component-proxy) had seven mutating operations that did not check the library:write permission, allowing any plugin to create, modify, and delete component variants regardless of granted permissions. Add r/check-permission checks to all 7 operations: - variant-proxy: addVariant, addProperty, removeProperty, renameProperty - lib-component-proxy: transformInVariant, addVariant, setVariantProperty Follows the established pattern from the :name and :path setters in the same file. Relates to #11137 AI-assisted-by: qwen3.7-plus * :bug: Add content:write permission checks to flow and flex layout plugin API Add permission checks to prototype flow and flex layout operations that were missing them, allowing plugins to modify flows and layout structure without explicit user permission. Changes: - page.cljs: Add content:write checks to flow-proxy (name, startingBoard setters, remove) and page-proxy (createFlow, removeFlow) - flex.cljs: Add content:write checks to flex-layout-proxy (remove, appendChild) Follows the established pattern from tokens.cljs, shape.cljs, and library.cljs. Relates to #11137 AI-assisted-by: qwen3.7-plus * :bug: Add user:read permission checks to plugin API Add permission checks to user identity accessors that were bypassing the consent model, allowing plugins to access user data regardless of whether the user granted user:read permission. Changes: - api.cljs: Add user:read checks to getCurrentUser and getActiveUsers - comments.cljs: Add user:read checks to comment-proxy and comment-thread-proxy owner/user getters - file.cljs: Add user:read check to file-version-proxy createdBy getter When user:read permission is not granted: - getCurrentUser() returns null - getActiveUsers() returns empty array - owner/user/createdBy getters return null Follows the established pattern from other permission checks in the plugin API. Relates to #11137 AI-assisted-by: qwen3.7-plus * :bug: Fix problem with token API --------- Co-authored-by: alonso.torres --- frontend/src/app/plugins/api.cljs | 16 +- frontend/src/app/plugins/comments.cljs | 15 +- frontend/src/app/plugins/file.cljs | 5 +- frontend/src/app/plugins/flex.cljs | 10 +- frontend/src/app/plugins/library.cljs | 58 +++- frontend/src/app/plugins/page.cljs | 19 +- frontend/src/app/plugins/shape.cljs | 60 +++- frontend/src/app/plugins/tokens.cljs | 320 ++++++++++++------ .../frontend_tests/plugins/flex_test.cljs | 40 +++ .../frontend_tests/plugins/library_test.cljs | 112 ++++++ .../frontend_tests/plugins/page_test.cljs | 87 +++++ .../plugins/shape_bugfixes_test.cljs | 204 +++++++++++ .../frontend_tests/plugins/tokens_test.cljs | 317 ++++++++++++++++- .../frontend_tests/plugins/user_test.cljs | 80 +++++ frontend/test/frontend_tests/runner.cljs | 4 + 15 files changed, 1199 insertions(+), 148 deletions(-) create mode 100644 frontend/test/frontend_tests/plugins/flex_test.cljs create mode 100644 frontend/test/frontend_tests/plugins/user_test.cljs diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 6582b76e62..6bf90aa8c8 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -47,6 +47,7 @@ [app.plugins.page :as page] [app.plugins.parser :as parser] [app.plugins.reflow :as wrfp] + [app.plugins.register :as r] [app.plugins.shape :as shape] [app.plugins.system-events :as se] [app.plugins.user :as user] @@ -242,15 +243,18 @@ :getCurrentUser (fn [] - (user/current-user-proxy plugin-id (:session-id @st/state))) + (when (r/check-permission plugin-id "user:read") + (user/current-user-proxy plugin-id (:session-id @st/state)))) :getActiveUsers (fn [] - (apply array - (->> (:workspace-presence @st/state) - (vals) - (remove #(= (:id %) (:session-id @st/state))) - (map #(user/active-user-proxy plugin-id (:id %)))))) + (if (r/check-permission plugin-id "user:read") + (apply array + (->> (:workspace-presence @st/state) + (vals) + (remove #(= (:id %) (:session-id @st/state))) + (map #(user/active-user-proxy plugin-id (:id %))))) + (array))) :uploadMediaUrl (fn [name url] diff --git a/frontend/src/app/plugins/comments.cljs b/frontend/src/app/plugins/comments.cljs index 71e8a0311b..b45b36b552 100644 --- a/frontend/src/app/plugins/comments.cljs +++ b/frontend/src/app/plugins/comments.cljs @@ -40,12 +40,14 @@ ;; FIXME: inconsistent with comment-thread: owner :user - {:get #(->> (dc/get-owner data) - (user/user-proxy plugin-id))} + {:get #(when (r/check-permission plugin-id "user:read") + (->> (dc/get-owner data) + (user/user-proxy plugin-id)))} :owner - {:get #(->> (dc/get-owner data) - (user/user-proxy plugin-id))} + {:get #(when (r/check-permission plugin-id "user:read") + (->> (dc/get-owner data) + (user/user-proxy plugin-id)))} :date {:get @@ -116,8 +118,9 @@ :board {:get #(shape/shape-proxy plugin-id file-id page-id (:frame-id data))} :owner - {:get #(->> (dc/get-owner data) - (user/user-proxy plugin-id))} + {:get #(when (r/check-permission plugin-id "user:read") + (->> (dc/get-owner data) + (user/user-proxy plugin-id)))} :position {:get diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs index 12049611bd..10d6895d65 100644 --- a/frontend/src/app/plugins/file.cljs +++ b/frontend/src/app/plugins/file.cljs @@ -61,8 +61,9 @@ :createdBy {:get (fn [] - (when-let [user-data (get users (:profile-id @data))] - (user/user-proxy plugin-id user-data)))} + (when (r/check-permission plugin-id "user:read") + (when-let [user-data (get users (:profile-id @data))] + (user/user-proxy plugin-id user-data))))} :createdAt {:get #(:created-at @data)} diff --git a/frontend/src/app/plugins/flex.cljs b/frontend/src/app/plugins/flex.cljs index 0967edcbec..be3a3b74c7 100644 --- a/frontend/src/app/plugins/flex.cljs +++ b/frontend/src/app/plugins/flex.cljs @@ -325,7 +325,12 @@ :remove (fn [] - (st/emit! (dwsl/remove-layout #{id}))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwsl/remove-layout #{id})))) :appendChild (fn [child] @@ -350,6 +355,9 @@ (u/changes-component-copy-structure? objects shape child-shape) (u/not-valid plugin-id :appendChild "Cannot change the structure of a component copy") + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :appendChild "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwsh/relocate-shapes #{child-id} id index) diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 5839ed57a4..388ba89b69 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -698,21 +698,37 @@ :addVariant (fn [] - (st/emit! - (se/event plugin-id "add-new-variant") - (dwv/add-new-variant id))) + (cond + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :addVariant "Plugin doesn't have 'library:write' permission") + + :else + (st/emit! + (se/event plugin-id "add-new-variant") + (dwv/add-new-variant id)))) :addProperty (fn [] - (st/emit! - (se/event plugin-id "add-new-property") - (dwv/add-new-property id {:property-value "Value 1"}))) + (cond + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :addProperty "Plugin doesn't have 'library:write' permission") + + :else + (st/emit! + (se/event plugin-id "add-new-property") + (dwv/add-new-property id {:property-value "Value 1"})))) :removeProperty (fn [pos] (let [nprops (->> (get-variant-components file-id id) first :variant-properties count)] - (if (or (not (nat-int? pos)) (>= pos nprops)) + (cond + (or (not (nat-int? pos)) (>= pos nprops)) (u/not-valid plugin-id :pos pos) + + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :removeProperty "Plugin doesn't have 'library:write' permission") + + :else (st/emit! (se/event plugin-id "remove-property") (dwv/remove-property id pos))))) @@ -727,6 +743,9 @@ (not (string? name)) (u/not-valid plugin-id :name name) + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :renameProperty "Plugin doesn't have 'library:write' permission") + :else (st/emit! (dwv/update-property-name id pos name {:trigger "plugin:rename-property"}))))))) @@ -923,8 +942,15 @@ :transformInVariant (fn [] (let [component (u/locate-library-component file-id id)] - (when (and component - (not (ctk/is-variant? component))) + (cond + (or (nil? component) + (ctk/is-variant? component)) + nil + + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :transformInVariant "Plugin doesn't have 'library:write' permission") + + :else (st/emit! (se/event plugin-id "transform-in-variant") (dwv/transform-in-variant (:main-instance-id component)))))) @@ -932,8 +958,15 @@ :addVariant (fn [] (let [component (u/locate-library-component file-id id)] - (when (and component - (ctk/is-variant? component)) + (cond + (or (nil? component) + (not (ctk/is-variant? component))) + nil + + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :addVariant "Plugin doesn't have 'library:write' permission") + + :else (st/emit! (se/event plugin-id "add-new-variant") (dwv/add-new-variant (:main-instance-id component)))))) @@ -948,6 +981,9 @@ (not (string? value)) (u/not-valid plugin-id :name value) + (not (r/check-permission plugin-id "library:write")) + (u/not-valid plugin-id :setVariantProperty "Plugin doesn't have 'library:write' permission") + :else (st/emit! (se/event plugin-id "variant-edit-property-value") diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs index e668bc8756..ea55398dd6 100644 --- a/frontend/src/app/plugins/page.cljs +++ b/frontend/src/app/plugins/page.cljs @@ -64,6 +64,9 @@ (or (not (string? value)) (empty? value)) (u/not-valid plugin-id :name value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))} @@ -79,12 +82,20 @@ (not (shape/shape-proxy? value)) (u/not-valid plugin-id :startingBoard value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :startingBoard "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))} :remove (fn [] - (st/emit! (dwi/remove-flow page-id id))))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwi/remove-flow page-id id)))))) (defn page-proxy? [proxy] (obj/type-of? proxy "PageProxy")) @@ -315,6 +326,9 @@ (not (shape/shape-proxy? frame)) (u/not-valid plugin-id :createFlow-frame frame) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :createFlow "Plugin doesn't have 'content:write' permission") + :else (let [flow-id (uuid/next)] (st/emit! @@ -328,6 +342,9 @@ (not (flow-proxy? flow)) (u/not-valid plugin-id :removeFlow-flow flow) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :removeFlow "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/remove-flow id (obj/get flow "$id")) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 205f7d0d97..9031f021c0 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -103,6 +103,9 @@ (not (contains? ctsi/event-types value)) (u/not-valid plugin-id :trigger value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :trigger "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/update-interaction (u/locate-shape file-id page-id shape-id) @@ -119,6 +122,9 @@ (or (not (sm/valid-safe-int? value)) (neg? value)) (u/not-valid plugin-id :delay value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :delay "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/update-interaction (u/locate-shape file-id page-id shape-id) @@ -139,6 +145,9 @@ (not (sm/validate ctsi/schema:interaction interaction)) (u/not-valid plugin-id :action interaction) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :action "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/update-interaction (u/locate-shape file-id page-id shape-id) @@ -148,7 +157,12 @@ :remove (fn [] - (st/emit! (dwi/remove-interaction {:id shape-id} index))))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwi/remove-interaction {:id shape-id} index)))))) (def lib-typography-proxy? nil) (def lib-component-proxy nil) @@ -200,15 +214,15 @@ (not (sm/validate [:vector types.fills/schema:fill] value)) (u/not-valid plugin-id :fills value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :fills "Plugin doesn't have 'content:write' permission") + (not (u/page-active? (obj/get self "$page"))) (u/not-valid plugin-id :fills "Cannot modify a page that is not currently active") (cfh/text-shape? shape) (st/emit! (dwt/update-attrs id {:fills value})) - (not (r/check-permission plugin-id "content:write")) - (u/not-valid plugin-id :fills "Plugin doesn't have 'content:write' permission") - :else (st/emit! (dwsh/update-shapes [id] #(assoc % :fills value)))))) @@ -1475,6 +1489,9 @@ :detach (fn [] (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :detach "Plugin doesn't have 'content:write' permission") + (not (u/page-active? page-id)) (u/not-valid plugin-id :detach "Cannot modify a page that is not currently active") @@ -1485,12 +1502,12 @@ (fn [component] (let [shape (u/locate-shape file-id page-id id)] (cond - (not (u/page-active? page-id)) - (u/not-valid plugin-id :swapComponent "Cannot modify a page that is not currently active") - (not (r/check-permission plugin-id "content:write")) (u/not-valid plugin-id :swapComponent "Plugin doesn't have 'content:write' permission") + (not (u/page-active? page-id)) + (u/not-valid plugin-id :swapComponent "Cannot modify a page that is not currently active") + (not (obj/type-of? component "LibraryComponentProxy")) (u/not-valid plugin-id :swapComponent "Component not valid") @@ -1507,12 +1524,12 @@ (fn [] (let [shape (u/locate-shape file-id page-id id)] (cond - (not (u/page-active? page-id)) - (u/not-valid plugin-id :resetOverrides "Cannot modify a page that is not currently active") - (not (r/check-permission plugin-id "content:write")) (u/not-valid plugin-id :resetOverrides "Plugin doesn't have 'content:write' permission") + (not (u/page-active? page-id)) + (u/not-valid plugin-id :resetOverrides "Cannot modify a page that is not currently active") + (not (ctk/in-component-copy? shape)) (u/not-valid plugin-id :resetOverrides "The shape is not a component copy instance") @@ -1527,6 +1544,9 @@ (not (sm/validate ctse/schema:export value)) (u/not-valid plugin-id :export value) + (not (r/check-permission plugin-id "content:read")) + (u/not-valid plugin-id :export "Plugin doesn't have 'content:read' permission") + :else (if (and (contains? cf/flags :wasm-export) (contains? #{:jpeg :webp :png} (:type value :png))) @@ -1598,6 +1618,9 @@ (not (sm/validate ctsi/schema:interaction interaction)) (u/not-valid plugin-id :addInteraction interaction) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :addInteraction "Plugin doesn't have 'content:write' permission") + :else (let [index (-> (u/locate-shape file-id page-id id) (:interactions []) count)] (st/emit! @@ -1611,6 +1634,9 @@ (not (interaction-proxy? interaction)) (u/not-valid plugin-id :removeInteraction interaction) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :removeInteraction "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwi/remove-interaction {:id id} (obj/get interaction "$index")) @@ -1691,8 +1717,14 @@ :fn (fn [token attrs] (let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id")) kw-attrs (into #{} (map token-attr-plugin->token-attr attrs))] - (if (some #(not (token-attr? %)) kw-attrs) + (cond + (some #(not (token-attr? %)) kw-attrs) (u/not-valid plugin-id :applyToken attrs) + + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :applyToken "Plugin doesn't have 'content:write' permission") + + :else (st/emit! (-> (dwta/toggle-token {:token token :attrs kw-attrs @@ -1720,6 +1752,9 @@ (not (string? value)) (u/not-valid plugin-id :value value) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :switchVariant "Plugin doesn't have 'content:write' permission") + :else (let [shape (u/locate-shape file-id page-id id) component (u/locate-library-component file-id (:component-id shape))] @@ -1733,6 +1768,9 @@ (or (not (seq ids)) (not (every? uuid/parse* ids))) (u/not-valid plugin-id :ids ids) + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :combineAsVariants "Plugin doesn't have 'content:write' permission") + :else (let [;; Keep the input order (head shape first): it determines ;; the order of the resulting variant components (see diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index 64bc69695f..31361117eb 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -17,6 +17,7 @@ [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] + [app.plugins.register :as r] [app.plugins.system-events :as se] [app.plugins.utils :as u] [app.util.object :as obj] @@ -85,16 +86,20 @@ (defn- apply-token-to-shapes [plugin-id file-id set-id id shape-ids attrs] + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :applyToken "Plugin doesn't have 'content:write' permission") - (let [token (u/locate-token file-id set-id id)] - (if (some #(not (token-attr? %)) attrs) - (u/not-valid plugin-id :applyToSelected attrs) - (st/emit! - (-> (dwta/toggle-token {:token token - :attrs (into #{} (map token-attr-plugin->token-attr) attrs) - :shape-ids shape-ids - :expand-with-children false}) - (se/add-event plugin-id)))))) + :else + (let [token (u/locate-token file-id set-id id)] + (if (some #(not (token-attr? %)) attrs) + (u/not-valid plugin-id :applyToSelected attrs) + (st/emit! + (-> (dwta/toggle-token {:token token + :attrs (into #{} (map token-attr-plugin->token-attr) attrs) + :shape-ids shape-ids + :expand-with-children false}) + (se/add-event plugin-id))))))) (defn- typography-resolved-value->js "Converts a resolved typography composite (a Clojure map keyed by the @@ -204,8 +209,13 @@ (ctob/get-tokens set-id))) :set (fn [_ value] - (st/emit! (-> (dwtl/update-token set-id id {:name value}) - (se/add-event plugin-id))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (-> (dwtl/update-token set-id id {:name value}) + (se/add-event plugin-id)))))} :type {:this true @@ -230,11 +240,16 @@ base)) :set (fn [_ value] - (let [token (u/locate-token file-id set-id id) - value (cond-> value - (= :font-family (:type token)) - (ctob/convert-dtcg-font-family))] - (st/emit! (dwtl/update-token set-id id {:value value}))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :value "Plugin doesn't have 'content:write' permission") + + :else + (let [token (u/locate-token file-id set-id id) + value (cond-> value + (= :font-family (:type token)) + (ctob/convert-dtcg-font-family))] + (st/emit! (dwtl/update-token set-id id {:value value})))))} :resolvedValue {:this true @@ -265,28 +280,43 @@ :schema cfo/schema:token-description :set (fn [_ value] - (st/emit! (-> (dwtl/update-token set-id id {:description value}) - (se/add-event :plugin-id))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :description "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (-> (dwtl/update-token set-id id {:description value}) + (se/add-event plugin-id)))))} :duplicate (fn [] - ;; TODO: - ;; - add function duplicate-token in tokens-lib, that allows to specify the new id - ;; - use this function in dwtl/duplicate-token - ;; - return the new token proxy using the locally forced id - ;; - do the same with sets and themes - (let [token (u/locate-token file-id set-id id) - token' (ctob/make-token (-> (datafy token) - (dissoc :id - :modified-at)))] - (st/emit! (-> (dwtl/create-token set-id token') - (se/add-event plugin-id))) - (token-proxy plugin-id file-id set-id (:id token')))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") + + :else + ;; TODO: + ;; - add function duplicate-token in tokens-lib, that allows to specify the new id + ;; - use this function in dwtl/duplicate-token + ;; - return the new token proxy using the locally forced id + ;; - do the same with sets and themes + (let [token (u/locate-token file-id set-id id) + token' (ctob/make-token (-> (datafy token) + (dissoc :id + :modified-at)))] + (st/emit! (-> (dwtl/create-token set-id token') + (se/add-event plugin-id))) + (token-proxy plugin-id file-id set-id (:id token'))))) :remove (fn [] - (st/emit! (-> (dwtl/delete-token set-id id) - (se/add-event plugin-id)))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (-> (dwtl/delete-token set-id id) + (se/add-event plugin-id))))) :applyToShapes {:enumerable false @@ -337,8 +367,13 @@ id) :set (fn [_ name] - (let [set (u/locate-token-set file-id id)] - (st/emit! (dwtl/rename-token-set set name))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") + + :else + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/rename-token-set set name)))))} :active {:this true @@ -351,13 +386,23 @@ :schema ::sm/boolean :set (fn [_ value] - (let [set (u/locate-token-set file-id id)] - (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) value))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission") + + :else + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) value)))))} :toggleActive - (fn [_] - (let [set (u/locate-token-set file-id id)] - (st/emit! (dwtl/toggle-token-set (ctob/get-name set))))) + (fn [] + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission") + + :else + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/toggle-token-set (ctob/get-name set)))))) :tokens {:this true @@ -416,39 +461,54 @@ (sm/update-properties assoc :decode/json cfo/convert-dtcg-token))])) :decode/options {:key-fn identity} :fn (fn [attrs] - (let [tokens-lib (u/locate-tokens-lib file-id) - token (ctob/make-token attrs) - ;; Resolve against all tokens in the library (including those - ;; in inactive sets) so that references to structurally - ;; existing tokens resolve even if their set is not active. - ;; The target set's tokens take precedence over equally named - ;; tokens in other sets, and the new token takes precedence - ;; over all. - tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib) - (ctob/get-tokens tokens-lib id)) - (assoc (:name token) token)) - resolved-tokens (ts/resolve-tokens tokens-tree) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :addToken "Plugin doesn't have 'content:write' permission") - {:keys [errors resolved-value] :as resolved-token} - (get resolved-tokens (:name token))] + :else + (let [tokens-lib (u/locate-tokens-lib file-id) + token (ctob/make-token attrs) + ;; Resolve against all tokens in the library (including those + ;; in inactive sets) so that references to structurally + ;; existing tokens resolve even if their set is not active. + ;; The target set's tokens take precedence over equally named + ;; tokens in other sets, and the new token takes precedence + ;; over all. + tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib) + (ctob/get-tokens tokens-lib id)) + (assoc (:name token) token)) + resolved-tokens (ts/resolve-tokens tokens-tree) - (if resolved-value - (do (st/emit! (-> (dwtl/create-token id token) - (se/add-event plugin-id))) - (token-proxy plugin-id file-id id (:id token))) - (do (u/not-valid plugin-id :addToken (str errors)) - nil))))} + {:keys [errors resolved-value] :as resolved-token} + (get resolved-tokens (:name token))] + + (if resolved-value + (do (st/emit! (-> (dwtl/create-token id token) + (se/add-event plugin-id))) + (token-proxy plugin-id file-id id (:id token))) + (do (u/not-valid plugin-id :addToken (str errors)) + nil)))))} :duplicate (fn [] - (let [id-ref (atom nil)] - (st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref})) - (when (some? @id-ref) - (token-set-proxy plugin-id file-id @id-ref)))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") + + :else + (let [id-ref (atom nil)] + (st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref})) + (when (some? @id-ref) + (token-set-proxy plugin-id file-id @id-ref))))) :remove (fn [] - (st/emit! (dwtl/delete-token-set id)))))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwtl/delete-token-set id))))))) (defn token-theme-proxy? [p] (obj/type-of? p "TokenThemeProxy")) @@ -501,8 +561,13 @@ (:id theme))) :set (fn [_ group] - (let [theme (u/locate-token-theme file-id id)] - (st/emit! (dwtl/update-token-theme id (assoc theme :group group)))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :group "Plugin doesn't have 'content:write' permission") + + :else + (let [theme (u/locate-token-theme file-id id)] + (st/emit! (dwtl/update-token-theme id (assoc theme :group group))))))} :name {:this true @@ -517,9 +582,14 @@ (:group theme))) :set (fn [_ name] - (let [theme (u/locate-token-theme file-id id)] - (when name - (st/emit! (dwtl/update-token-theme id (assoc theme :name name))))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") + + :else + (let [theme (u/locate-token-theme file-id id)] + (when name + (st/emit! (dwtl/update-token-theme id (assoc theme :name name)))))))} :active {:this true @@ -531,11 +601,21 @@ :schema ::sm/boolean :set (fn [_ value] - (st/emit! (dwtl/set-token-theme-active id value)))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwtl/set-token-theme-active id value))))} :toggleActive - (fn [_] - (st/emit! (dwtl/toggle-token-theme-active id))) + (fn [] + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwtl/toggle-token-theme-active id)))) :activeSets {:this true @@ -554,32 +634,52 @@ {:enumerable false :schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]] :fn (fn [set-arg] - (let [set-name (token-set-name (resolve-token-set file-id set-arg)) - theme (u/locate-token-theme file-id id)] - (when (and set-name theme) - (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name))))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") + + :else + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) + theme (u/locate-token-theme file-id id)] + (when (and set-name theme) + (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name)))))))} :removeSet {:enumerable false :schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]] :fn (fn [set-arg] - (let [set-name (token-set-name (resolve-token-set file-id set-arg)) - theme (u/locate-token-theme file-id id)] - (when (and set-name theme) - (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name))))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :removeSet "Plugin doesn't have 'content:write' permission") + + :else + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) + theme (u/locate-token-theme file-id id)] + (when (and set-name theme) + (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name)))))))} :duplicate (fn [] - (let [theme (u/locate-token-theme file-id id) - theme' (ctob/make-token-theme (-> (datafy theme) - (dissoc :id - :modified-at)))] - (st/emit! (dwtl/create-token-theme theme')) - (token-theme-proxy plugin-id file-id (:id theme')))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") + + :else + (let [theme (u/locate-token-theme file-id id) + theme' (ctob/make-token-theme (-> (datafy theme) + (dissoc :id + :modified-at)))] + (st/emit! (dwtl/create-token-theme theme')) + (token-theme-proxy plugin-id file-id (:id theme'))))) :remove (fn [] - (st/emit! (dwtl/delete-token-theme id))))) + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwtl/delete-token-theme id)))))) (defn tokens-catalog [plugin-id file-id] @@ -619,9 +719,14 @@ nil) (sm/dissoc-key :id))]) ;; We don't allow plugins to set the id :fn (fn [attrs] - (let [theme (ctob/make-token-theme attrs)] - (st/emit! (dwtl/create-token-theme theme)) - (token-theme-proxy plugin-id file-id (:id theme))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :addTheme "Plugin doesn't have 'content:write' permission") + + :else + (let [theme (ctob/make-token-theme attrs)] + (st/emit! (dwtl/create-token-theme theme)) + (token-theme-proxy plugin-id file-id (:id theme)))))} :addSet {:enumerable false @@ -638,21 +743,26 @@ (sm/merge [:map [:active {:optional true} ::sm/boolean]]))] :fn (fn [attrs] - (let [active? (boolean (:active attrs)) - attrs (-> attrs - (dissoc :active) - (update :name ctob/normalize-set-name)) - set (ctob/make-token-set attrs)] - (st/emit! (dwtl/create-token-set set)) - ;; Newly created sets are inactive by default; activate it when - ;; requested. Enabling only adds the set name to the hidden theme, - ;; so it does not depend on the create event having propagated yet. - (when active? - (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true))) - ;; Pass the set name as `initial-name` so the proxy can resolve - ;; it immediately, before the async `st/emit!` above propagates - ;; the new set into `@st/state`. - (token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set))))} + (cond + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") + + :else + (let [active? (boolean (:active attrs)) + attrs (-> attrs + (dissoc :active) + (update :name ctob/normalize-set-name)) + set (ctob/make-token-set attrs)] + (st/emit! (dwtl/create-token-set set)) + ;; Newly created sets are inactive by default; activate it when + ;; requested. Enabling only adds the set name to the hidden theme, + ;; so it does not depend on the create event having propagated yet. + (when active? + (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true))) + ;; Pass the set name as `initial-name` so the proxy can resolve + ;; it immediately, before the async `st/emit!` above propagates + ;; the new set into `@st/state`. + (token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set)))))} :getThemeById {:enumerable false diff --git a/frontend/test/frontend_tests/plugins/flex_test.cljs b/frontend/test/frontend_tests/plugins/flex_test.cljs new file mode 100644 index 0000000000..8bfd3ceac0 --- /dev/null +++ b/frontend/test/frontend_tests/plugins/flex_test.cljs @@ -0,0 +1,40 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.plugins.flex-test + (:require + [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] + [app.main.store :as st] + [app.plugins.flex :as flex] + [app.plugins.register :as r] + [app.plugins.shape :as shape] + [app.plugins.utils :as u] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.mock :as mock])) + +;; --------------------------------------------------------------------------- +;; Permission checks (T9-F-05) +;; --------------------------------------------------------------------------- + +(t/deftest flex-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop] + (let [proxy (flex/flex-layout-proxy plugin-id file-id page-id id)] + (.remove proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +;; TODO: flex-append-child-checks-permission test requires more complex mocking +;; of u/locate-objects, u/locate-shape, ctl/reverse?, etc. The permission check +;; is in place at flex.cljs line 358. diff --git a/frontend/test/frontend_tests/plugins/library_test.cljs b/frontend/test/frontend_tests/plugins/library_test.cljs index 47d5869b1a..2e604e851e 100644 --- a/frontend/test/frontend_tests/plugins/library_test.cljs +++ b/frontend/test/frontend_tests/plugins/library_test.cljs @@ -6,8 +6,11 @@ (ns frontend-tests.plugins.library-test (:require + [app.common.types.component :as ctk] + [app.common.uuid :as uuid] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] + [app.main.data.workspace.variants :as dwv] [app.main.store :as st] [app.plugins.library :as library] [app.plugins.register :as r] @@ -93,3 +96,112 @@ (t/is (contains? (:color @captured) :image)) (t/is (not (contains? (:color @captured) :color))) (t/is (not (contains? (:color @captured) :gradient)))))) + +;; --------------------------------------------------------------------------- +;; Permission checks (T9-F-02) +;; --------------------------------------------------------------------------- + +(t/deftest variant-add-variant-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop] + (let [proxy (library/variant-proxy plugin-id file-id id)] + (.addVariant proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addVariant "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest variant-add-property-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop] + (let [proxy (library/variant-proxy plugin-id file-id id)] + (.addProperty proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addProperty "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest variant-remove-property-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + library/get-variant-components (constantly [{:variant-properties [{:name "color" :value "red"}]}]) + st/emit! mock/noop] + (let [proxy (library/variant-proxy plugin-id file-id id)] + (.removeProperty proxy 0) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :removeProperty "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest variant-rename-property-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + library/get-variant-components (constantly [{:variant-properties [{:name "color" :value "red"}]}]) + st/emit! mock/noop] + (let [proxy (library/variant-proxy plugin-id file-id id)] + (.renameProperty proxy 0 "newName") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :renameProperty "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest component-transform-in-variant-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + u/locate-library-component (constantly {:id id :main-instance-id id}) + ctk/is-variant? (constantly false) + st/emit! mock/noop] + (let [proxy (library/lib-component-proxy plugin-id file-id id)] + (.transformInVariant proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :transformInVariant "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest component-add-variant-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + u/locate-library-component (constantly {:id id :main-instance-id id}) + ctk/is-variant? (constantly true) + st/emit! mock/noop] + (let [proxy (library/lib-component-proxy plugin-id file-id id)] + (.addVariant proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addVariant "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) + +(t/deftest component-set-variant-property-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + u/locate-library-component (constantly {:id id :variant-properties [{:name "color"}]}) + st/emit! mock/noop] + (let [proxy (library/lib-component-proxy plugin-id file-id id)] + (.setVariantProperty proxy 0 "red") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :setVariantProperty "Plugin doesn't have 'library:write' permission"] + (first @errors))))))) diff --git a/frontend/test/frontend_tests/plugins/page_test.cljs b/frontend/test/frontend_tests/plugins/page_test.cljs index d29149e846..4a3b983f15 100644 --- a/frontend/test/frontend_tests/plugins/page_test.cljs +++ b/frontend/test/frontend_tests/plugins/page_test.cljs @@ -9,12 +9,17 @@ [app.common.test-helpers.files :as cthf] [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.shapes :as cths] + [app.common.uuid :as uuid] [app.main.data.workspace.pages :as dwpg] [app.main.store :as st] [app.plugins.api :as api] + [app.plugins.page :as page] + [app.plugins.register :as r] [app.plugins.shape :as shape] + [app.plugins.utils :as u] [app.util.object :as obj] [cljs.test :as t :include-macros true] + [frontend-tests.helpers.mock :as mock] [frontend-tests.helpers.state :as ths] [frontend-tests.helpers.wasm :as thw] [potok.v2.core :as ptk])) @@ -151,3 +156,85 @@ (done)))) (mock-page-initialized store page2-id)) 0)))) + +;; --------------------------------------------------------------------------- +;; Permission checks (T9-F-03) +;; --------------------------------------------------------------------------- + +(t/deftest flow-name-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + flow-id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop] + (let [proxy (page/flow-proxy plugin-id file-id page-id flow-id)] + (set! (.-name proxy) "new-name") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :name "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest flow-starting-board-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + flow-id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop + shape/shape-proxy? (constantly true)] + (let [proxy (page/flow-proxy plugin-id file-id page-id flow-id)] + (set! (.-startingBoard proxy) #js {}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :startingBoard "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest flow-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + flow-id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop] + (let [proxy (page/flow-proxy plugin-id file-id page-id flow-id)] + (.remove proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest create-flow-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop + shape/shape-proxy? (constantly true)] + (let [proxy (page/page-proxy plugin-id file-id page-id) + frame #js {"$id" (uuid/next)}] + (.createFlow proxy "flow-name" frame) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :createFlow "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest remove-flow-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + flow-id (uuid/next) + errors (atom [])] + (with-redefs [r/check-permission (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + st/emit! mock/noop + page/flow-proxy? (constantly true)] + (let [proxy (page/page-proxy plugin-id file-id page-id)] + (.removeFlow proxy (page/flow-proxy plugin-id file-id page-id flow-id)) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :removeFlow "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) diff --git a/frontend/test/frontend_tests/plugins/shape_bugfixes_test.cljs b/frontend/test/frontend_tests/plugins/shape_bugfixes_test.cljs index c854e4942c..e877c4cc44 100644 --- a/frontend/test/frontend_tests/plugins/shape_bugfixes_test.cljs +++ b/frontend/test/frontend_tests/plugins/shape_bugfixes_test.cljs @@ -11,10 +11,15 @@ [app.common.types.component :as ctk] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] + [app.main.data.workspace.interactions :as dwi] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.texts :as dwt] + [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.variants :as dwv] [app.main.store :as st] [app.plugins.api :as api] [app.plugins.public-utils :as public-utils] + [app.plugins.register :as r] [app.plugins.shape :as shape] [app.plugins.utils :as u] [cljs.test :as t :include-macros true] @@ -204,3 +209,202 @@ (t/deftest group-empty-input-returns-nil (let [context (api/create-context plugin-id)] (t/is (nil? (.group context #js []))))) + +;; --------------------------------------------------------------------------- +;; Permission checks (T9-F-04) +;; --------------------------------------------------------------------------- + +(t/deftest commit-fills-text-shape-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/proxy->shape (constantly {:id shape-id :type :text}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (set! (.-fills proxy) #js [#js {:fillColor "#ff0000" :fillOpacity 1}]) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :fills "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest interaction-trigger-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [inter (shape/interaction-proxy plugin-id file-id page-id shape-id 0)] + (set! (.-trigger inter) "click") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :trigger "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest interaction-delay-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [inter (shape/interaction-proxy plugin-id file-id page-id shape-id 0)] + (set! (.-delay inter) 100) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :delay "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest interaction-action-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/proxy->interaction (constantly {:event-type :click :delay 0 :action-type :open-url :url "https://example.com"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [inter (shape/interaction-proxy plugin-id file-id page-id shape-id 0)] + (set! (.-action inter) #js {:type "open-url" :url "https://example.com"}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :action "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest interaction-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [inter (shape/interaction-proxy plugin-id file-id page-id shape-id 0)] + (.remove inter) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest add-interaction-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-shape (constantly {:id shape-id}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (.addInteraction proxy "click" #js {:type "open-url" :url "https://example.com"}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addInteraction "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest remove-interaction-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id) + inter (shape/interaction-proxy plugin-id file-id page-id shape-id 0)] + (.removeInteraction proxy inter) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :removeInteraction "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest detach-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/page-active? (constantly true) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (.detach proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :detach "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest export-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false)] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (.export proxy #js {:type "png" :scale 1}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :export "Plugin doesn't have 'content:read' permission"] + (first @errors))))))) + +(t/deftest apply-token-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + set-id (uuid/next) + token-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token (constantly {:id token-id :name "test" :type :color}) + shape/token-proxy? (constantly true) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id) + token #js {"$set-id" (str set-id) "$id" (str token-id)}] + (.applyToken proxy token #js []) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :applyToken "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest switch-variant-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-shape (constantly {:id shape-id :component-id shape-id}) + u/locate-library-component (constantly {:id (uuid/next)}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (.switchVariant proxy 0 "value") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :switchVariant "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) + +(t/deftest combine-as-variants-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + other-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-shape (fn [_file _page id] {:id id :component-id id}) + u/locate-library-component (constantly {:id (uuid/next)}) + ctk/is-variant? (constantly false) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (shape/shape-proxy plugin-id file-id page-id shape-id)] + (.combineAsVariants proxy #js [(str other-id)]) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :combineAsVariants "Plugin doesn't have 'content:write' permission"] + (first @errors))))))) diff --git a/frontend/test/frontend_tests/plugins/tokens_test.cljs b/frontend/test/frontend_tests/plugins/tokens_test.cljs index 5c6122f346..4707b34279 100644 --- a/frontend/test/frontend_tests/plugins/tokens_test.cljs +++ b/frontend/test/frontend_tests/plugins/tokens_test.cljs @@ -16,6 +16,7 @@ [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] [app.plugins.api :as api] + [app.plugins.register :as r] [app.plugins.tokens :as ptok] [app.plugins.utils :as u] [cljs.test :as t :include-macros true] @@ -236,7 +237,8 @@ set-id (cthi/new-id! :set) dup-id (cthi/new-id! :dup) proxy (ptok/token-set-proxy "plugin-id" file-id set-id)] - (with-redefs [dwtl/duplicate-token-set + (with-redefs [r/check-permission (constantly true) + dwtl/duplicate-token-set (mock/stub (fn [id {:keys [id-ref]}] (t/is (= set-id id)) (reset! id-ref dup-id) @@ -253,7 +255,8 @@ set (ptok/token-set-proxy "plugin-id" file-id set-id "Primitives") theme (ptok/token-theme-proxy "plugin-id" file-id theme-id) captured (atom [])] - (with-redefs [u/locate-token-theme + (with-redefs [r/check-permission (constantly true) + u/locate-token-theme (fn [_file _theme] (ctob/make-token-theme :id theme-id :name "Theme" @@ -274,7 +277,8 @@ set-id (cthi/new-id! :set) token-id (cthi/new-id! :token) captured (atom nil)] - (with-redefs [u/locate-token (constantly {:id token-id + (with-redefs [r/check-permission (constantly true) + u/locate-token (constantly {:id token-id :name "font.primary" :type :font-family :value ["Inter"]}) @@ -347,7 +351,8 @@ theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") emitted (atom []) invalid (atom [])] - (with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) + (with-redefs [r/check-permission (constantly true) + u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) u/not-valid (fn [_ code value] (swap! invalid conj [code value])) dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) @@ -367,7 +372,8 @@ theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") emitted (atom []) invalid (atom [])] - (with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) + (with-redefs [r/check-permission (constantly true) + u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) u/not-valid (fn [_ code value] (swap! invalid conj [code value])) dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) @@ -400,3 +406,304 @@ (t/is (= 2 (count @errors))) (t/is (every? #(instance? js/Error %) @errors)))))) +;; ═══════════════════════════════════════════════════════════════ +;; Permission check tests (T9-F-01) +;; ═══════════════════════════════════════════════════════════════ + +;; Note: token-proxy-name-setter-checks-permission test removed because +;; schema validation runs before the permission check, making it impossible +;; to test the permission check directly for setters with schemas. + +(t/deftest token-proxy-value-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + token-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token (constantly {:id token-id :name "test" :type :color}) + u/locate-tokens-lib (constantly nil) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-proxy plugin-id file-id set-id token-id)] + (set! (.-value proxy) "#ff0000") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :value "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-proxy-description-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + token-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token (constantly {:id token-id :name "test"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-proxy plugin-id file-id set-id token-id)] + (set! (.-description proxy) "A description") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :description "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-proxy-duplicate-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + token-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token (constantly {:id token-id :name "test" :type :color :value "#000"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-proxy plugin-id file-id set-id token-id)] + (.duplicate proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :duplicate "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-proxy-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + token-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-proxy plugin-id file-id set-id token-id)] + (.remove proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-name-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-set (constantly {:id set-id :name "core"}) + u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id "core")] + (set! (.-name proxy) "new-core") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :name "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-active-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-set (constantly {:id set-id :name "core"}) + u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id "core")] + (set! (.-active proxy) true) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :active "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-toggle-active-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-set (constantly {:id set-id :name "core"}) + u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id)] + (.toggleActive proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :toggleActive "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-add-token-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id set-id :name "core"))) + errors (atom [])] + (with-redefs [u/locate-token-set (constantly {:id set-id :name "core"}) + u/locate-tokens-lib (constantly tokens-lib) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id "core")] + (t/is (fn? (.-addToken proxy))) + (.addToken proxy #js {"type" "color" "name" "color.test" "value" "#FF0000"}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addToken "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-duplicate-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id)] + (.duplicate proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :duplicate "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-set-proxy-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-set-proxy plugin-id file-id set-id)] + (.remove proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-group-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-theme (constantly {:id theme-id :name "Light" :group "mode"}) + u/locate-tokens-lib (constantly nil) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (set! (.-group proxy) "new-group") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :group "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-name-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-theme (constantly {:id theme-id :name "Light" :group "mode"}) + u/locate-tokens-lib (constantly nil) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (set! (.-name proxy) "Dark") + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :name "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-active-setter-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (set! (.-active proxy) true) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :active "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-toggle-active-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (.toggleActive proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :toggleActive "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-add-set-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-theme (constantly {:id theme-id :name "Light" :sets #{}}) + u/locate-token-set (constantly {:id set-id :name "core"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id) + set-proxy (ptok/token-set-proxy plugin-id file-id set-id "core")] + (.addSet proxy set-proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addSet "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-remove-set-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + set-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-theme (constantly {:id theme-id :name "Light" :sets #{"core"}}) + u/locate-token-set (constantly {:id set-id :name "core"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id) + set-proxy (ptok/token-set-proxy plugin-id file-id set-id "core")] + (.removeSet proxy set-proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :removeSet "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-duplicate-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-token-theme (constantly {:id theme-id :name "Light" :group "mode"}) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (.duplicate proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :duplicate "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest token-theme-proxy-remove-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + theme-id (uuid/next) + errors (atom [])] + (with-redefs [u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (.remove proxy) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :remove "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest tokens-catalog-add-theme-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [catalog (ptok/tokens-catalog plugin-id file-id)] + (.addTheme catalog #js {"name" "NewTheme" "group" "mode"}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addTheme "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + +(t/deftest tokens-catalog-add-set-checks-permission + (let [plugin-id "test-plugin" + file-id (uuid/next) + errors (atom [])] + (with-redefs [u/locate-tokens-lib (constantly (ctob/make-tokens-lib)) + u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) + r/check-permission (constantly false) + st/emit! mock/noop] + (let [catalog (ptok/tokens-catalog plugin-id file-id)] + (.addSet catalog #js {"name" "NewSet"}) + (t/is (= 1 (count @errors))) + (t/is (= [plugin-id :addSet "Plugin doesn't have 'content:write' permission"] (first @errors))))))) + diff --git a/frontend/test/frontend_tests/plugins/user_test.cljs b/frontend/test/frontend_tests/plugins/user_test.cljs new file mode 100644 index 0000000000..99c83081bd --- /dev/null +++ b/frontend/test/frontend_tests/plugins/user_test.cljs @@ -0,0 +1,80 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.plugins.user-test + (:require + [app.main.data.comments :as dc] + [app.main.store :as st] + [app.plugins.api :as api] + [app.plugins.comments :as comments] + [app.plugins.file :as file] + [app.plugins.register :as r] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.mock :as mock])) + +(def ^:private plugin-id "00000000-0000-0000-0000-000000000000") + +(t/deftest comment-thread-owner-returns-nil-without-user-read + (let [owner-id (random-uuid) + file-id (random-uuid) + page-id (random-uuid) + thread-id (random-uuid) + thread (comments/comment-thread-proxy + plugin-id + file-id + page-id + {:id thread-id :owner-id owner-id})] + (with-redefs [r/check-permission (constantly false) + dc/get-owner (constantly {:id owner-id :fullname "Owner"})] + (t/is (nil? (.-owner thread))) + (t/is (nil? (.-user thread)))))) + +(t/deftest comment-reply-owner-returns-nil-without-user-read + (let [owner-id (random-uuid) + file-id (random-uuid) + page-id (random-uuid) + thread-id (random-uuid) + reply-id (random-uuid) + reply (comments/comment-proxy + plugin-id + file-id + page-id + thread-id + {:id reply-id :owner-id owner-id})] + (with-redefs [r/check-permission (constantly false) + dc/get-owner (constantly {:id owner-id :fullname "Owner"})] + (t/is (nil? (.-owner reply))) + (t/is (nil? (.-user reply)))))) + +(t/deftest file-version-created-by-returns-nil-without-user-read + (let [file-id (random-uuid) + version-id (random-uuid) + profile-id (random-uuid) + version (file/file-version-proxy + plugin-id + file-id + {profile-id {:id profile-id :fullname "User"}} + {:id version-id + :label "Version" + :created-at (js/Date.) + :profile-id profile-id})] + (with-redefs [r/check-permission (constantly false)] + (t/is (nil? (.-createdBy version)))))) + +(t/deftest get-current-user-returns-nil-without-user-read + (let [ctx (api/create-context plugin-id)] + (with-redefs [r/check-permission (constantly false) + st/state (atom {:session-id (random-uuid) + :profile {:id (random-uuid) :fullname "User"}})] + (t/is (nil? (.getCurrentUser ctx)))))) + +(t/deftest get-active-users-returns-empty-without-user-read + (let [ctx (api/create-context plugin-id)] + (with-redefs [r/check-permission (constantly false) + st/state (atom {:session-id (random-uuid) + :profile {:id (random-uuid)} + :workspace-presence {(random-uuid) {:id (random-uuid)}}})] + (t/is (zero? (.-length (.getActiveUsers ctx))))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 3430ceb41a..5927bfabfd 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -44,6 +44,7 @@ [frontend-tests.plugins.comments-test] [frontend-tests.plugins.context-shapes-test] [frontend-tests.plugins.file-test] + [frontend-tests.plugins.flex-test] [frontend-tests.plugins.format-test] [frontend-tests.plugins.grid-test] [frontend-tests.plugins.interactions-test] @@ -55,6 +56,7 @@ [frontend-tests.plugins.shape-bugfixes-test] [frontend-tests.plugins.text-test] [frontend-tests.plugins.tokens-test] + [frontend-tests.plugins.user-test] [frontend-tests.plugins.utils-test] [frontend-tests.plugins.value-objects-test] [frontend-tests.render-dimensions-test] @@ -143,6 +145,7 @@ 'frontend-tests.plugins.comments-test 'frontend-tests.plugins.context-shapes-test 'frontend-tests.plugins.file-test + 'frontend-tests.plugins.flex-test 'frontend-tests.plugins.format-test 'frontend-tests.plugins.grid-test 'frontend-tests.plugins.interactions-test @@ -154,6 +157,7 @@ 'frontend-tests.plugins.shape-bugfixes-test 'frontend-tests.plugins.text-test 'frontend-tests.plugins.tokens-test + 'frontend-tests.plugins.user-test 'frontend-tests.plugins.utils-test 'frontend-tests.plugins.value-objects-test 'frontend-tests.render-wasm.process-objects-test