From 796a76ff32e50e0ab8fb5db71cce1eb5fd396404 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Aug 2026 11:06:09 +0000 Subject: [PATCH] :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 --- frontend/src/app/plugins/flex.cljs | 10 ++- frontend/src/app/plugins/page.cljs | 19 +++- .../frontend_tests/plugins/flex_test.cljs | 40 +++++++++ .../frontend_tests/plugins/page_test.cljs | 87 +++++++++++++++++++ frontend/test/frontend_tests/runner.cljs | 2 + 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 frontend/test/frontend_tests/plugins/flex_test.cljs 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/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/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/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/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 31896b987e..eedb9b78d1 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -41,6 +41,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] @@ -137,6 +138,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