mirror of
https://github.com/penpot/penpot.git
synced 2026-09-02 01:59:17 +00:00
🐛 Prevent admin from removing team owner in delete-team-member (#11368)
Add owner protection to ::delete-team-member RPC command. Previously, a team admin could remove the team owner, permanently locking them out of their team and all resources. Changes: - Fetch target member data before deletion - Validate member exists (return :not-found if not) - Reject removal if target is owner and caller is not owner This mirrors the existing protection in update-team-member-role. Closes #11367 AI-assisted-by: qwen3.7-plus
This commit is contained in:
parent
326d83e780
commit
45f0153e8f
@ -944,8 +944,10 @@
|
||||
::sm/params schema:delete-team-member
|
||||
::db/transaction true}
|
||||
[{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [::rpc/profile-id team-id member-id] :as params}]
|
||||
(let [team (get-team conn :profile-id profile-id :team-id team-id)
|
||||
perms (get-permissions conn profile-id team-id)]
|
||||
(let [team (get-team conn :profile-id profile-id :team-id team-id)
|
||||
perms (get-permissions conn profile-id team-id)
|
||||
members (get-team-members conn team-id)
|
||||
member (d/seek #(= member-id (:id %)) members)]
|
||||
(when-not (or (:is-owner perms)
|
||||
(:is-admin perms))
|
||||
(ex/raise :type :validation
|
||||
@ -955,6 +957,15 @@
|
||||
(ex/raise :type :validation
|
||||
:code :cant-remove-yourself))
|
||||
|
||||
(when-not member
|
||||
(ex/raise :type :not-found
|
||||
:code :member-does-not-exist))
|
||||
|
||||
(when (and (:is-owner member)
|
||||
(not (:is-owner perms)))
|
||||
(ex/raise :type :validation
|
||||
:code :cant-remove-owner))
|
||||
|
||||
(db/delete! conn :team-profile-rel {:profile-id member-id
|
||||
:team-id team-id})
|
||||
|
||||
|
||||
@ -1328,3 +1328,83 @@
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock)))))))
|
||||
|
||||
(t/deftest admin-cannot-remove-team-owner
|
||||
(let [owner (th/create-profile* 1 {:is-active true})
|
||||
admin (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id owner)})]
|
||||
|
||||
(th/create-team-role* {:team-id (:id team)
|
||||
:profile-id (:id admin)
|
||||
:role :admin})
|
||||
|
||||
(let [out (th/command! {::th/type :delete-team-member
|
||||
::rpc/profile-id (:id admin)
|
||||
:team-id (:id team)
|
||||
:member-id (:id owner)})]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :cant-remove-owner)))))
|
||||
|
||||
(t/deftest owner-can-remove-another-owner
|
||||
(let [owner1 (th/create-profile* 1 {:is-active true})
|
||||
owner2 (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id owner1)})]
|
||||
|
||||
(th/create-team-role* {:team-id (:id team)
|
||||
:profile-id (:id owner2)
|
||||
:role :owner})
|
||||
|
||||
(let [out (th/command! {::th/type :delete-team-member
|
||||
::rpc/profile-id (:id owner1)
|
||||
:team-id (:id team)
|
||||
:member-id (:id owner2)})]
|
||||
(t/is (th/success? out)))))
|
||||
|
||||
(t/deftest owner-can-remove-admin
|
||||
(let [owner (th/create-profile* 1 {:is-active true})
|
||||
admin (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id owner)})]
|
||||
|
||||
(th/create-team-role* {:team-id (:id team)
|
||||
:profile-id (:id admin)
|
||||
:role :admin})
|
||||
|
||||
(let [out (th/command! {::th/type :delete-team-member
|
||||
::rpc/profile-id (:id owner)
|
||||
:team-id (:id team)
|
||||
:member-id (:id admin)})]
|
||||
(t/is (th/success? out)))))
|
||||
|
||||
(t/deftest admin-can-remove-admin
|
||||
(let [owner (th/create-profile* 1 {:is-active true})
|
||||
admin1 (th/create-profile* 2 {:is-active true})
|
||||
admin2 (th/create-profile* 3 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id owner)})]
|
||||
|
||||
(th/create-team-role* {:team-id (:id team)
|
||||
:profile-id (:id admin1)
|
||||
:role :admin})
|
||||
|
||||
(th/create-team-role* {:team-id (:id team)
|
||||
:profile-id (:id admin2)
|
||||
:role :admin})
|
||||
|
||||
(let [out (th/command! {::th/type :delete-team-member
|
||||
::rpc/profile-id (:id admin1)
|
||||
:team-id (:id team)
|
||||
:member-id (:id admin2)})]
|
||||
(t/is (th/success? out)))))
|
||||
|
||||
(t/deftest delete-nonexistent-member-returns-not-found
|
||||
(let [owner (th/create-profile* 1 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id owner)})
|
||||
fake-id (uuid/next)]
|
||||
|
||||
(let [out (th/command! {::th/type :delete-team-member
|
||||
::rpc/profile-id (:id owner)
|
||||
:team-id (:id team)
|
||||
:member-id fake-id})]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :not-found))
|
||||
(t/is (th/ex-of-code? (:error out) :member-does-not-exist)))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user