mirror of
https://github.com/penpot/penpot.git
synced 2026-08-10 14:59:08 +00:00
🐛 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
This commit is contained in:
parent
a131e40a6d
commit
37743e6d46
@ -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
|
||||
@ -395,60 +440,75 @@
|
||||
:fn (fn [token-id]
|
||||
(let [token (u/locate-token file-id id token-id)]
|
||||
(when (some? token)
|
||||
(token-proxy plugin-id file-id id token-id))))}
|
||||
(token-proxy plugin-id file-id id token-id))))
|
||||
|
||||
:addToken
|
||||
{:enumerable false
|
||||
:schema (fn [args]
|
||||
(let [tokens-tree (-> (u/locate-tokens-lib file-id)
|
||||
(ctob/get-tokens id)
|
||||
;; Convert to the adecuate format for schema
|
||||
(ctob/tokens-tree))]
|
||||
[:tuple (-> (cfo/make-token-schema
|
||||
tokens-tree
|
||||
(cto/dtcg-token-type->token-type (-> args (first) (get "type")))
|
||||
nil)
|
||||
;; Don't allow plugins to set the id
|
||||
(sm/dissoc-key :id)
|
||||
;; Instruct the json decoder in obj/reify not to process map keys (:key-fn below)
|
||||
;; and set a converter that changes DTCG types to internal types (:decode/json).
|
||||
;; E.g. "FontFamilies" -> :font-family or "BorderWidth" -> :stroke-width
|
||||
(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)
|
||||
:addToken
|
||||
{:enumerable false
|
||||
:schema (fn [args]
|
||||
(let [tokens-tree (-> (u/locate-tokens-lib file-id)
|
||||
(ctob/get-tokens id)
|
||||
;; Convert to the adecuate format for schema
|
||||
(ctob/tokens-tree))]
|
||||
[:tuple (-> (cfo/make-token-schema
|
||||
tokens-tree
|
||||
(cto/dtcg-token-type->token-type (-> args (first) (get "type")))
|
||||
nil)
|
||||
;; Don't allow plugins to set the id
|
||||
(sm/dissoc-key :id)
|
||||
;; Instruct the json decoder in obj/reify not to process map keys (:key-fn below)
|
||||
;; and set a converter that changes DTCG types to internal types (:decode/json).
|
||||
;; E.g. "FontFamilies" -> :font-family or "BorderWidth" -> :stroke-width
|
||||
(sm/update-properties assoc :decode/json cfo/convert-dtcg-token))]))
|
||||
:decode/options {:key-fn identity}
|
||||
:fn (fn [attrs]
|
||||
(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
|
||||
|
||||
@ -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,286 @@
|
||||
(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-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)))))))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user