From 7ccc52fbdfe64d60fc5d08a31d4fff6cbd914558 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 28 Jul 2026 12:55:58 +0000 Subject: [PATCH] :bug: Restrict webhook edit/delete to team members only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the creator-id fallback from get-webhooks-permissions. Previously, the webhook creator could always edit/delete their webhook even after being removed from the team. Now can-edit comes from team role only — removed users get :not-found. Webhooks are NOT deleted on member removal; the team owns them and team admins/owners manage them. AI-assisted-by: mimo-v2.5 --- backend/src/app/rpc/commands/webhooks.clj | 10 +-- .../test/backend_tests/rpc_webhooks_test.clj | 88 +++++++++++++++++++ 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index 867ac46903..85051e8ad7 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -23,11 +23,9 @@ [cuerdas.core :as str])) (defn get-webhooks-permissions - [conn profile-id team-id creator-id] + [conn profile-id team-id] (let [permissions (t/get-permissions conn profile-id team-id) - - can-edit (boolean (or (:can-edit permissions) - (= profile-id creator-id)))] + can-edit (boolean (:can-edit permissions))] (assoc permissions :can-edit can-edit))) (def has-webhook-edit-permissions? @@ -137,7 +135,7 @@ ::sm/params schema:update-webhook} [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}] (let [whook (-> (db/get pool :webhook {:id id}) (decode-row))] - (check-webhook-edition-permissions! pool profile-id (:team-id whook) (:profile-id whook)) + (check-webhook-edition-permissions! pool profile-id (:team-id whook)) (validate-webhook! cfg whook params) (update-webhook! cfg whook params))) @@ -151,7 +149,7 @@ ::db/transaction true} [{:keys [::db/conn]} {:keys [::rpc/profile-id id]}] (let [whook (-> (db/get conn :webhook {:id id}) decode-row)] - (check-webhook-edition-permissions! conn profile-id (:team-id whook) (:profile-id whook)) + (check-webhook-edition-permissions! conn profile-id (:team-id whook)) (db/delete! conn :webhook {:id id}) nil)) diff --git a/backend/test/backend_tests/rpc_webhooks_test.clj b/backend/test/backend_tests/rpc_webhooks_test.clj index d05743c8a1..df4ae3a622 100644 --- a/backend/test/backend_tests/rpc_webhooks_test.clj +++ b/backend/test/backend_tests/rpc_webhooks_test.clj @@ -286,3 +286,91 @@ (t/is (th/ex-info? error)) (t/is (= (:type error-data) :restriction)) (t/is (= (:code error-data) :webhooks-quote-reached)))))) + +(t/deftest removed-user-cannot-edit-webhook + (with-mocks [http-mock {:target 'app.http.client/req + :return {:status 200}}] + + (let [owner (th/create-profile* 1 {:is-active true}) + editor (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 editor) + :role :editor}) + + (let [params {::th/type :create-webhook + ::rpc/profile-id (:id editor) + :team-id (:id team) + :uri (u/uri "http://example.com") + :mtype "application/json"} + out (th/command! params)] + + (t/is (nil? (:error out))) + (let [whook (:result out)] + + (th/reset-mock! http-mock) + + (t/testing "owner can edit editor's webhook (team owns it)" + (let [params {::th/type :update-webhook + ::rpc/profile-id (:id owner) + :id (:id whook) + :uri (u/uri "http://example.com/updated") + :mtype "application/transit+json" + :is-active true} + out (th/command! params)] + (t/is (nil? (:error out))) + (t/is (= 1 (:call-count @http-mock))))) + + (th/reset-mock! http-mock) + + (t/testing "remove editor from team" + (let [params {::th/type :delete-team-member + ::rpc/profile-id (:id owner) + :team-id (:id team) + :member-id (:id editor)} + out (th/command! params)] + (t/is (nil? (:error out))))) + + (th/reset-mock! http-mock) + + (t/testing "removed editor cannot update webhook" + (let [params {::th/type :update-webhook + ::rpc/profile-id (:id editor) + :id (:id whook) + :uri (u/uri "http://example.com/evil") + :mtype "application/transit+json" + :is-active true} + out (th/command! params)] + (t/is (= 0 (:call-count @http-mock))) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)) + (t/is (= (:code error-data) :object-not-found))))) + + (th/reset-mock! http-mock) + + (t/testing "removed editor cannot delete webhook" + (let [params {::th/type :delete-webhook + ::rpc/profile-id (:id editor) + :id (:id whook)} + out (th/command! params)] + (t/is (= 0 (:call-count @http-mock))) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)) + (t/is (= (:code error-data) :object-not-found))))) + + (th/reset-mock! http-mock) + + (t/testing "owner can still delete editor's webhook" + (let [params {::th/type :delete-webhook + ::rpc/profile-id (:id owner) + :id (:id whook)} + out (th/command! params)] + (t/is (nil? (:error out))) + (t/is (nil? (:result out))) + (let [rows (th/db-exec! ["select * from webhook"])] + (t/is (= 0 (count rows)))))))))))