diff --git a/CHANGES.md b/CHANGES.md index f4fd816eba..0e75d2527f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017) - Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007) - Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215) +- [Access Tokens] Look & feel refinement [Taiga #13114](https://tree.taiga.io/project/penpot/us/13114) ### :bug: Bugs fixed @@ -35,12 +36,18 @@ - Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186) - Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128) +## 2.13.3 + +### :bug: Bugs fixed + +- Revert yetti (http server) update, because that caused a regression on multipart uploads + ## 2.13.2 ### :bug: Bugs fixed - Fix modifying shapes by apply negative tokens to border radius [Taiga #13317](https://tree.taiga.io/project/penpot/issue/13317) -- Fix security issue (Path Traversal Vulnerability) on fonts related RPC method +- Fix arbitrary file read security issue on create-font-variant rpc method (https://github.com/penpot/penpot/security/advisories/GHSA-xp3f-g8rq-9px2) ## 2.13.1 diff --git a/backend/deps.edn b/backend/deps.edn index af73aecbc8..27d052c510 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -28,8 +28,8 @@ com.google.guava/guava {:mvn/version "33.4.8-jre"} funcool/yetti - {:git/tag "v11.9" - :git/sha "5fad7a9" + {:git/tag "v11.8" + :git/sha "1d1b33f" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index ed6187e73f..655c00867d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -867,15 +867,15 @@ :on-change on-change :name (dm/str "flex-align-items-" type) :options [{:id (dm/str "align-items-start-" type) - :icon (get-layout-flex-icon :align-items :start is-column) + :icon (get-layout-grid-icon :align-items :start is-column) :label "Align items start" :value "start"} {:id (dm/str "align-items-center-" type) - :icon (get-layout-flex-icon :align-items :center is-column) + :icon (get-layout-grid-icon :align-items :center is-column) :label "Align items center" :value "center"} {:id (dm/str "align-items-end-" type) - :icon (get-layout-flex-icon :align-items :end is-column) + :icon (get-layout-grid-icon :align-items :end is-column) :label "Align items end" :value "end"}]}])) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 3f8d655a5b..014736e4d8 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -175,6 +175,10 @@ (defn shape-proxy? [p] (obj/type-of? p "ShapeProxy")) +;; Cannot use token/token-proxy? here because of circular dependency in applyToShapes in token proxy +(defn token-proxy? [t] + (obj/type-of? t "TokenProxy")) + (defn shape-proxy ([plugin-id id] (shape-proxy plugin-id (:current-file-id @st/state) (:current-page-id @st/state) id)) @@ -1301,16 +1305,19 @@ tokens)))} :applyToken - (fn [token attrs] - (let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id")) - kw-attrs (into #{} (map keyword attrs))] - (if (some #(not (cto/token-attr? %)) kw-attrs) - (u/display-not-valid :applyToken attrs) - (st/emit! - (dwta/toggle-token {:token token - :attrs kw-attrs - :shape-ids [id] - :expand-with-children false}))))) + {:schema [:tuple + [:fn token-proxy?] + [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]] + :fn (fn [token attrs] + (let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id")) + kw-attrs (into #{} (map keyword attrs))] + (if (some #(not (cto/token-attr? %)) kw-attrs) + (u/display-not-valid :applyToken attrs) + (st/emit! + (dwta/toggle-token {:token token + :attrs kw-attrs + :shape-ids [id] + :expand-with-children false})))))} :isVariantHead (fn [] diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index f46722884a..c11f8dc373 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -13,10 +13,10 @@ [app.common.types.tokens-lib :as ctob] [app.common.uuid :as uuid] [app.main.data.style-dictionary :as sd] + [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] - ;; [app.plugins.shape :as shape] [app.plugins.utils :as u] [app.util.object :as obj] [beicon.v2.core :as rx] @@ -35,13 +35,25 @@ :shape-ids shape-ids :expand-with-children false}))))) +(defn- get-resolved-value + [token tokens-tree] + (let [resolved-tokens (ts/resolve-tokens tokens-tree) + resolved-value (-> resolved-tokens + (dm/get-in [(:name token) :resolved-value]) + (ts/tokenscript-symbols->penpot-unit))] + resolved-value)) + (defn token-proxy? [p] (obj/type-of? p "TokenProxy")) +;; Cannot use shape/shape-proxy? here because of circular dependency in applyToken in shape proxy +(defn shape-proxy? [s] + (obj/type-of? s "ShapeProxy")) + (defn token-proxy [plugin-id file-id set-id id] (obj/reify {:name "TokenProxy" - :wrap u/wrap-errors} + :on-error u/handle-error} :$plugin {:enumerable false :get (constantly plugin-id)} :$file-id {:enumerable false :get (constantly file-id)} :$set-id {:enumerable false :get (constantly set-id)} @@ -82,6 +94,26 @@ (fn [_ value] (st/emit! (dwtl/update-token set-id id {:value value})))} + :resolvedValue + {:this true + :enumerable false + :get + (fn [_] + (let [token (u/locate-token file-id set-id id) + tokens-lib (u/locate-tokens-lib file-id) + tokens-tree (ctob/get-tokens-in-active-sets tokens-lib)] + (get-resolved-value token tokens-tree)))} + + :resolvedValueString + {:this true + :enumerable false + :get + (fn [_] + (let [token (u/locate-token file-id set-id id) + tokens-lib (u/locate-tokens-lib file-id) + tokens-tree (ctob/get-tokens-in-active-sets tokens-lib)] + (str (get-resolved-value token tokens-tree))))} + :description {:this true :get @@ -113,14 +145,10 @@ :applyToShapes {:schema [:tuple - ;; FIXME: the schema decoder is interpreting the array of shape-proxys and converting - ;; them to plain maps. For now we adapt the schema to accept it, but the decoder - ;; should be fixed to keep the original proxy objects coming from the plugin. - ;; [:vector [:fn shape/shape-proxy?]] - [:vector [:map [:id ::sm/uuid]]] - [:maybe [:set [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]] + [:vector [:fn shape-proxy?]] + [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]] :fn (fn [shapes attrs] - (apply-token-to-shapes file-id set-id id (map :id shapes) attrs))} + (apply-token-to-shapes file-id set-id id (map #(obj/get % "$id") shapes) attrs))} :applyToSelected {:schema [:tuple [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]] @@ -136,7 +164,7 @@ (defn token-set-proxy [plugin-id file-id id] (obj/reify {:name "TokenSetProxy" - :wrap u/wrap-errors} + :on-error u/handle-error} :$plugin {:enumerable false :get (constantly plugin-id)} :$file-id {:enumerable false :get (constantly file-id)} :$id {:enumerable false :get (constantly id)} @@ -257,7 +285,7 @@ (defn token-theme-proxy [plugin-id file-id id] (obj/reify {:name "TokenThemeProxy" - :wrap u/wrap-errors} + :on-error u/handle-error} :$plugin {:enumerable false :get (constantly plugin-id)} :$file-id {:enumerable false :get (constantly file-id)} :$id {:enumerable false :get (constantly id)} @@ -352,7 +380,7 @@ (defn tokens-catalog [plugin-id file-id] (obj/reify {:name "TokensCatalog" - :wrap u/wrap-errors} + :on-error u/handle-error} :$plugin {:enumerable false :get (constantly plugin-id)} :$id {:enumerable false :get (constantly file-id)} diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 8df47b2995..dfb8242a12 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -223,7 +223,9 @@ (defn display-not-valid [code value] - (.error js/console (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code)) + (if (some? value) + (.error js/console (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code)) + (.error js/console (dm/str "[PENPOT PLUGIN] Value not valid. Code: " code))) nil) (defn reject-not-valid @@ -248,19 +250,12 @@ (let [s (set values)] (if (= (count s) 1) (first s) "mixed"))) -(defn wrap-errors - "Function wrapper to be used in plugin proxies methods to handle errors. - When an exception is thrown, a readable error message is output to the console - and the exception is captured." - [f] - (fn [] - (let [args (js-arguments)] - (try - (.apply f nil args) - (catch :default cause - (display-not-valid (ex-message cause) (obj/stringify args)) - (if-let [explain (-> cause ex-data ::sm/explain)] - (println (sm/humanize-explain explain)) - (js/console.log (ex-data cause))) - (js/console.log (.-stack cause)) - nil))))) \ No newline at end of file +(defn handle-error + "Function to be used in plugin proxies methods to handle errors and print a readable + message to the console." + [cause] + (display-not-valid (ex-message cause) nil) + (if-let [explain (-> cause ex-data ::sm/explain)] + (println (sm/humanize-explain explain)) + (js/console.log (ex-data cause))) + (js/console.log (.-stack cause))) \ No newline at end of file diff --git a/frontend/src/app/util/object.cljc b/frontend/src/app/util/object.cljc index 723444b4d5..bb5ae6c5a4 100644 --- a/frontend/src/app/util/object.cljc +++ b/frontend/src/app/util/object.cljc @@ -10,6 +10,7 @@ (:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify class]) #?(:cljs (:require-macros [app.util.object])) (:require + [app.common.data :as d] [app.common.json :as json] [app.common.schema :as sm] [clojure.core :as c] @@ -156,6 +157,7 @@ this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js}) target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js}) + cause-sym (gensym "cause-") make-sym (fn [pname prefix] @@ -176,6 +178,7 @@ wrap (c/get params :wrap) schema-1 (c/get params :schema-1) this? (c/get params :this false) + on-error (c/get params :on-error) decode-expr (c/get params :decode/fn) @@ -214,7 +217,16 @@ (with-meta {:tag 'function})) val-sym - (gensym (str "val-" (str/slug pname) "-"))] + (gensym (str "val-" (str/slug pname) "-")) + + wrap-error-handling + (if on-error + (fn [expr] + `(try + ~expr + (catch :default ~cause-sym + (~on-error ~cause-sym)))) + identity)] (concat (when wrap @@ -226,8 +238,13 @@ `(fn [] (let [~this-sym (~'js* "this") ~fn-sym ~get-expr] - (.call ~fn-sym ~this-sym ~this-sym))) - get-expr)]) + ~(wrap-error-handling + `(.call ~fn-sym ~this-sym ~this-sym)))) + `(fn [] + (let [~this-sym (~'js* "this") + ~fn-sym ~get-expr] + ~(wrap-error-handling + `(.call ~fn-sym ~this-sym)))))]) (when set-expr [schema-sym schema-n @@ -241,28 +258,35 @@ (make-sym pname "set-fn") `(fn [~val-sym] - (let [~this-sym (~'js* "this") - ~fn-sym ~set-expr + ~(wrap-error-handling + `(let [~this-sym (~'js* "this") + ~fn-sym ~set-expr - ;; We only emit schema and coercer bindings if - ;; schema-n is provided - ~@(if (some? schema-n) - [schema-sym `(if (fn? ~schema-sym) - (~schema-sym ~val-sym) - ~schema-sym) + ;; We only emit schema and coercer bindings if + ;; schema-n is provided + ~@(if (some? schema-n) + [schema-sym + `(if (fn? ~schema-sym) + (~schema-sym ~val-sym) + ~schema-sym) - coercer-sym `(if (nil? ~coercer-sym) - (sm/coercer ~schema-sym) - ~coercer-sym) - val-sym (if (not= decode-expr 'app.common.json/->clj) - `(~decode-sym ~val-sym) - `(~decode-sym ~val-sym ~decode-options)) - val-sym `(~coercer-sym ~val-sym)] - [])] + coercer-sym + `(if (nil? ~coercer-sym) + (sm/coercer ~schema-sym) + ~coercer-sym) - ~(if this? - `(.call ~fn-sym ~this-sym ~this-sym ~val-sym) - `(.call ~fn-sym ~this-sym ~val-sym))))]) + val-sym + (if (not= decode-expr 'app.common.json/->clj) + `(~decode-sym ~val-sym) + `(~decode-sym ~val-sym ~decode-options)) + + val-sym + `(~coercer-sym ~val-sym)] + [])] + + ~(if this? + `(.call ~fn-sym ~this-sym ~this-sym ~val-sym) + `(.call ~fn-sym ~this-sym ~val-sym)))))]) (when fn-expr [schema-sym (or schema-n schema-1) @@ -275,7 +299,12 @@ (make-sym pname "get-fn") `(fn [] (let [~this-sym (~'js* "this") - ~fn-sym ~fn-expr + ~fn-sym ~(if (and (list? fn-expr) + (= 'fn (first fn-expr))) + (let [[sa sb & sother] fn-expr] + `(~sa ~sb ~(wrap-error-handling `(do ~@sother)))) + fn-expr) + ~fn-sym ~(if this? `(.bind ~fn-sym ~this-sym ~this-sym) `(.bind ~fn-sym ~this-sym)) @@ -284,25 +313,31 @@ ;; schema-n or schema-1 is provided ~@(if (or schema-n schema-1) [fn-sym `(fn* [~@(if schema-1 [val-sym] [])] - (let [~@(if schema-n - [val-sym `(into-array (cljs.core/js-arguments))] - []) - ~val-sym ~(if (not= decode-expr 'app.common.json/->clj) - `(~decode-sym ~val-sym) - `(~decode-sym ~val-sym ~decode-options)) + ~(wrap-error-handling + `(let [~@(if schema-n + [val-sym `(into-array (cljs.core/js-arguments))] + []) + ~val-sym + ~(if (not= decode-expr 'app.common.json/->clj) + `(~decode-sym ~val-sym) + `(~decode-sym ~val-sym ~decode-options)) - ~schema-sym (if (fn? ~schema-sym) - (~schema-sym ~val-sym) - ~schema-sym) + ~schema-sym + (if (fn? ~schema-sym) + (~schema-sym ~val-sym) + ~schema-sym) - ~coercer-sym (if (nil? ~coercer-sym) - (sm/coercer ~schema-sym) - ~coercer-sym) + ~coercer-sym + (if (nil? ~coercer-sym) + (sm/coercer ~schema-sym) + ~coercer-sym) - ~val-sym (~coercer-sym ~val-sym)] - ~(if schema-1 - `(~fn-sym ~val-sym) - `(apply ~fn-sym ~val-sym))))] + ~val-sym + (~coercer-sym ~val-sym)] + + ~(if schema-1 + `(~fn-sym ~val-sym) + `(apply ~fn-sym ~val-sym)))))] [])] ~(if wrap `(~wrap-sym ~fn-sym) @@ -375,12 +410,16 @@ (let [definition (first params)] (if (some? definition) (let [definition (if (map? definition) - (c/merge {:wrap (:wrap tmeta)} definition) + (c/merge {:wrap (:wrap tmeta) + :on-error (:on-error tmeta)} + definition) (-> {:enumerable false} (c/merge (meta definition)) (assoc :wrap (:wrap tmeta)) + (assoc :on-error (:on-error tmeta)) (assoc :fn definition) - (dissoc :get :set))) + (dissoc :get :set :line :column) + (d/without-nils))) definition (assoc definition :name (name ckey))] (recur (rest params) diff --git a/plugins/apps/poc-tokens-plugin/src/app/app.component.html b/plugins/apps/poc-tokens-plugin/src/app/app.component.html index 5be709cb57..f5ef5ff940 100644 --- a/plugins/apps/poc-tokens-plugin/src/app/app.component.html +++ b/plugins/apps/poc-tokens-plugin/src/app/app.component.html @@ -113,7 +113,9 @@ class="body-m panel-item token-item" (click)="applyToken(token.id)" > - {{ token.name }} + + {{ token.name }} +