diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 88177b39ab..02499d9a35 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)))))) @@ -1482,6 +1496,9 @@ (not (u/page-active? page-id)) (u/not-valid plugin-id :detach "Cannot modify a page that is not currently active") + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :detach "Plugin doesn't have 'content:write' permission") + :else (st/emit! (dwl/detach-component id)))) @@ -1531,6 +1548,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))) @@ -1602,6 +1622,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! @@ -1615,6 +1638,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")) @@ -1695,8 +1721,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 @@ -1724,6 +1756,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))] @@ -1737,6 +1772,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/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)))))))