From 31ce3c99c2e5e96f8389760552749cd63ddc46bb Mon Sep 17 00:00:00 2001 From: Marius Balteanu Date: Mon, 27 Oct 2025 20:35:17 +0000 Subject: [PATCH] Allow administrators to disable webhooks from settings (#29664). Patch by Katsuya HIDAKA (user:hidakatsuya). git-svn-id: https://svn.redmine.org/redmine/trunk@24073 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/webhooks_controller.rb | 5 +++++ app/helpers/settings_helper.rb | 2 +- app/models/webhook.rb | 6 ++++++ app/views/my/account.html.erb | 2 +- app/views/settings/_api.html.erb | 4 +++- config/locales/en.yml | 2 ++ config/settings.yml | 2 ++ test/functional/my_controller_test.rb | 14 +++++++++++++ test/functional/webhooks_controller_test.rb | 10 ++++++++++ test/unit/webhook_test.rb | 22 +++++++++++++++++++++ 10 files changed, 66 insertions(+), 3 deletions(-) diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 98ebe07fa..eb6e4b806 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -4,6 +4,7 @@ class WebhooksController < ApplicationController self.main_menu = false before_action :require_login + before_action :check_enabled before_action :authorize before_action :find_webhook, only: [:edit, :update, :destroy] @@ -62,4 +63,8 @@ class WebhooksController < ApplicationController def authorize deny_access unless User.current.allowed_to?(:use_webhooks, nil, global: true) end + + def check_enabled + render_403 unless Webhook.enabled? + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 0bec658a4..85eac4a37 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -25,7 +25,7 @@ module SettingsHelper {:name => 'display', :partial => 'settings/display', :label => :label_display}, {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication}, - {:name => 'api', :partial => 'settings/api', :label => :label_api}, + {:name => 'integrations', :partial => 'settings/api', :label => :label_integrations}, {:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural}, {:name => 'users', :partial => 'settings/users', :label => :label_user_plural}, {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking}, diff --git a/app/models/webhook.rb b/app/models/webhook.rb index dd18f579e..51dffa1f5 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -42,9 +42,15 @@ class Webhook < ApplicationRecord before_validation ->(hook){ hook.projects = hook.projects.to_a & hook.setable_projects } + def self.enabled? + Setting.webhooks_enabled? + end + # Triggers the given event for the given object, scheduling qualifying hooks # to be called. def self.trigger(event, object) + return unless enabled? + hooks_for(event, object).each do |hook| payload = hook.payload(event, object) WebhookJob.perform_later(hook.id, payload.to_json) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index b516dd842..4073a3a0c 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -1,7 +1,7 @@
<%= additional_emails_link(@user) %> <%= link_to(sprite_icon('key', l(:button_change_password)), { :action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %> -<%= link_to sprite_icon('webhook', l(:label_webhook_plural)), webhooks_path, class: 'icon icon-webhook' if @user.allowed_to?(:use_webhooks, nil, global: true) %> +<%= link_to sprite_icon('webhook', l(:label_webhook_plural)), webhooks_path, class: 'icon icon-webhook' if Webhook.enabled? && @user.allowed_to?(:use_webhooks, nil, global: true) %> <%= link_to(sprite_icon('apps', l('label_oauth_authorized_application_plural')), oauth_authorized_applications_path, :class => 'icon icon-applications') if Setting.rest_api_enabled? %> <%= call_hook(:view_my_account_contextual, :user => @user)%>
diff --git a/app/views/settings/_api.html.erb b/app/views/settings/_api.html.erb index 6ee401037..3fb584ef9 100644 --- a/app/views/settings/_api.html.erb +++ b/app/views/settings/_api.html.erb @@ -1,9 +1,11 @@ -<%= form_tag({:action => 'edit', :tab => 'api'}) do %> +<%= form_tag({:action => 'edit', :tab => 'integrations'}) do %>

<%= setting_check_box :rest_api_enabled %>

<%= setting_check_box :jsonp_enabled %>

+ +

<%= setting_check_box :webhooks_enabled %>

<%= submit_tag l(:button_save) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index e8ab8cd25..97b662999 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -505,6 +505,7 @@ en: setting_thumbnails_size: Thumbnails size (in pixels) setting_non_working_week_days: Non-working days setting_jsonp_enabled: Enable JSONP support + setting_webhooks_enabled: Enable webhooks setting_default_projects_tracker_ids: Default trackers for new projects setting_mail_handler_excluded_filenames: Exclude attachments by name setting_force_default_language_for_anonymous: Force default language for anonymous users @@ -1176,6 +1177,7 @@ en: label_webhook_new: New webhook label_webhook_edit: Edit webhook label_webhook_events: Events + label_integrations: Integrations webhook_events_issue: Issues webhook_events_issue_created: Issue created diff --git a/config/settings.yml b/config/settings.yml index 753cd5b49..10447842d 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -336,6 +336,8 @@ rest_api_enabled: jsonp_enabled: default: 0 security_notifications: 1 +webhooks_enabled: + default: 1 default_notification_option: default: 'only_assigned' emails_header: diff --git a/test/functional/my_controller_test.rb b/test/functional/my_controller_test.rb index 3066f68e0..20f7f1062 100644 --- a/test/functional/my_controller_test.rb +++ b/test/functional/my_controller_test.rb @@ -398,6 +398,20 @@ class MyControllerTest < Redmine::ControllerTest assert_select 'select[name=?]', 'user[language]' end + def test_my_account_should_toggle_webhook_link_with_setting + User.find(2).roles.first.add_permission!(:use_webhooks) + + get :account + assert_response :success + assert_select 'a.icon-webhook', 1 + + with_settings webhooks_enabled: '0' do + get :account + assert_response :success + assert_select 'a.icon-webhook', 0 + end + end + def test_my_account_with_avatar_enabled_should_link_to_edit_avatar with_settings :gravatar_enabled => '1' do Redmine::Configuration.with 'avatar_server_url' => 'https://gravatar.com' do diff --git a/test/functional/webhooks_controller_test.rb b/test/functional/webhooks_controller_test.rb index 220636b2e..64fda1b6f 100644 --- a/test/functional/webhooks_controller_test.rb +++ b/test/functional/webhooks_controller_test.rb @@ -27,6 +27,16 @@ class WebhooksControllerTest < Redmine::ControllerTest assert_select 'td', text: @other_hook.url, count: 0 end + test "should return not found when disabled" do + with_settings webhooks_enabled: '0' do + get :index + assert_response :forbidden + + get :new + assert_response :forbidden + end + end + test "should get new" do get :new assert_response :success diff --git a/test/unit/webhook_test.rb b/test/unit/webhook_test.rb index df0ed2240..2443a4d01 100644 --- a/test/unit/webhook_test.rb +++ b/test/unit/webhook_test.rb @@ -168,6 +168,28 @@ class WebhookTest < ActiveSupport::TestCase end end + test "enabled? should follow setting flag" do + assert Webhook.enabled? + + with_settings webhooks_enabled: '0' do + assert_not Webhook.enabled? + end + + with_settings webhooks_enabled: '1' do + assert Webhook.enabled? + end + end + + test "trigger should not enqueue jobs when disabled" do + create_hook + + with_settings webhooks_enabled: '0' do + assert_no_enqueued_jobs do + Webhook.trigger('issue.created', @issue) + end + end + end + test "should compute payload" do hook = create_hook payload = hook.payload('issue.created', @issue)