mirror of
https://github.com/penpot/penpot.git
synced 2026-08-10 06:48:49 +00:00
🐛 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
This commit is contained in:
parent
37743e6d46
commit
16ac021dce
@ -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
|
||||
|
||||
@ -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)))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user