🐛 Add user:read permission checks to plugin API

Add permission checks to user identity accessors that were bypassing
the consent model, allowing plugins to access user data regardless
of whether the user granted user:read permission.

Changes:
- api.cljs: Add user:read checks to getCurrentUser and getActiveUsers
- comments.cljs: Add user:read checks to comment-proxy and
  comment-thread-proxy owner/user getters
- file.cljs: Add user:read check to file-version-proxy createdBy getter

When user:read permission is not granted:
- getCurrentUser() returns null
- getActiveUsers() returns empty array
- owner/user/createdBy getters return null

Follows the established pattern from other permission checks in the
plugin API.

Relates to #11137

AI-assisted-by: qwen3.7-plus
This commit is contained in:
Andrey Antukh 2026-08-18 11:34:06 +00:00
parent 796a76ff32
commit 73e35555ab
5 changed files with 104 additions and 14 deletions

View File

@ -47,6 +47,7 @@
[app.plugins.local-storage :as local-storage]
[app.plugins.page :as page]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.system-events :as se]
[app.plugins.user :as user]
@ -242,15 +243,18 @@
:getCurrentUser
(fn []
(user/current-user-proxy plugin-id (:session-id @st/state)))
(when (r/check-permission plugin-id "user:read")
(user/current-user-proxy plugin-id (:session-id @st/state))))
:getActiveUsers
(fn []
(apply array
(->> (:workspace-presence @st/state)
(vals)
(remove #(= (:id %) (:session-id @st/state)))
(map #(user/active-user-proxy plugin-id (:id %))))))
(if (r/check-permission plugin-id "user:read")
(apply array
(->> (:workspace-presence @st/state)
(vals)
(remove #(= (:id %) (:session-id @st/state)))
(map #(user/active-user-proxy plugin-id (:id %)))))
(array)))
:uploadMediaUrl
(fn [name url]

View File

@ -40,12 +40,14 @@
;; FIXME: inconsistent with comment-thread: owner
:user
{:get #(->> (dc/get-owner data)
(user/user-proxy plugin-id))}
{:get #(when (r/check-permission plugin-id "user:read")
(->> (dc/get-owner data)
(user/user-proxy plugin-id)))}
:owner
{:get #(->> (dc/get-owner data)
(user/user-proxy plugin-id))}
{:get #(when (r/check-permission plugin-id "user:read")
(->> (dc/get-owner data)
(user/user-proxy plugin-id)))}
:date
{:get
@ -116,8 +118,9 @@
:board {:get #(shape/shape-proxy plugin-id file-id page-id (:frame-id data))}
:owner
{:get #(->> (dc/get-owner data)
(user/user-proxy plugin-id))}
{:get #(when (r/check-permission plugin-id "user:read")
(->> (dc/get-owner data)
(user/user-proxy plugin-id)))}
:position
{:get

View File

@ -61,8 +61,9 @@
:createdBy
{:get
(fn []
(when-let [user-data (get users (:profile-id @data))]
(user/user-proxy plugin-id user-data)))}
(when (r/check-permission plugin-id "user:read")
(when-let [user-data (get users (:profile-id @data))]
(user/user-proxy plugin-id user-data))))}
:createdAt
{:get #(:created-at @data)}

View File

@ -0,0 +1,80 @@
;; 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.user-test
(:require
[app.main.data.comments :as dc]
[app.main.store :as st]
[app.plugins.api :as api]
[app.plugins.comments :as comments]
[app.plugins.file :as file]
[app.plugins.register :as r]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.mock :as mock]))
(def ^:private plugin-id "00000000-0000-0000-0000-000000000000")
(t/deftest comment-thread-owner-returns-nil-without-user-read
(let [owner-id (random-uuid)
file-id (random-uuid)
page-id (random-uuid)
thread-id (random-uuid)
thread (comments/comment-thread-proxy
plugin-id
file-id
page-id
{:id thread-id :owner-id owner-id})]
(with-redefs [r/check-permission (constantly false)
dc/get-owner (constantly {:id owner-id :fullname "Owner"})]
(t/is (nil? (.-owner thread)))
(t/is (nil? (.-user thread))))))
(t/deftest comment-reply-owner-returns-nil-without-user-read
(let [owner-id (random-uuid)
file-id (random-uuid)
page-id (random-uuid)
thread-id (random-uuid)
reply-id (random-uuid)
reply (comments/comment-proxy
plugin-id
file-id
page-id
thread-id
{:id reply-id :owner-id owner-id})]
(with-redefs [r/check-permission (constantly false)
dc/get-owner (constantly {:id owner-id :fullname "Owner"})]
(t/is (nil? (.-owner reply)))
(t/is (nil? (.-user reply))))))
(t/deftest file-version-created-by-returns-nil-without-user-read
(let [file-id (random-uuid)
version-id (random-uuid)
profile-id (random-uuid)
version (file/file-version-proxy
plugin-id
file-id
{profile-id {:id profile-id :fullname "User"}}
{:id version-id
:label "Version"
:created-at (js/Date.)
:profile-id profile-id})]
(with-redefs [r/check-permission (constantly false)]
(t/is (nil? (.-createdBy version))))))
(t/deftest get-current-user-returns-nil-without-user-read
(let [ctx (api/create-context plugin-id)]
(with-redefs [r/check-permission (constantly false)
st/state (atom {:session-id (random-uuid)
:profile {:id (random-uuid) :fullname "User"}})]
(t/is (nil? (.getCurrentUser ctx))))))
(t/deftest get-active-users-returns-empty-without-user-read
(let [ctx (api/create-context plugin-id)]
(with-redefs [r/check-permission (constantly false)
st/state (atom {:session-id (random-uuid)
:profile {:id (random-uuid)}
:workspace-presence {(random-uuid) {:id (random-uuid)}}})]
(t/is (zero? (.-length (.getActiveUsers ctx)))))))

View File

@ -53,6 +53,7 @@
[frontend-tests.plugins.shape-bugfixes-test]
[frontend-tests.plugins.text-test]
[frontend-tests.plugins.tokens-test]
[frontend-tests.plugins.user-test]
[frontend-tests.plugins.utils-test]
[frontend-tests.plugins.value-objects-test]
[frontend-tests.render-dimensions-test]
@ -150,6 +151,7 @@
'frontend-tests.plugins.shape-bugfixes-test
'frontend-tests.plugins.text-test
'frontend-tests.plugins.tokens-test
'frontend-tests.plugins.user-test
'frontend-tests.plugins.utils-test
'frontend-tests.plugins.value-objects-test
'frontend-tests.render-wasm.process-objects-test