diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 98ebe07fa..3713eea77 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_404 unless Webhook.enabled? + end end diff --git a/app/models/webhook.rb b/app/models/webhook.rb index dd18f579e..cb0a3383d 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? + Redmine::Configuration['webhooks'] + 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/config/configuration.yml.example b/config/configuration.yml.example index 4e8444ca9..13733a860 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -226,6 +226,11 @@ default: # Webhooks # + # Enables or disables the Webhooks feature (enabled by default). + # When disabled, all related UI will be hidden, and no webhooks will be triggered, + # but existing webhook data will be retained. + # webhooks: true + # # An optional list of hosts and/or IP addresses and/or IP networks which # should NOT be valid as webhook targets. You can add your internal IPs and # hostnames here to avoid possible SSRF attacks. diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb index 1c7fdb3a5..d139f3a3e 100644 --- a/lib/redmine/configuration.rb +++ b/lib/redmine/configuration.rb @@ -27,7 +27,8 @@ module Redmine 'email_delivery' => nil, 'max_concurrent_ajax_uploads' => 2, 'common_mark_enable_hardbreaks' => true, - 'thumbnails_generation_timeout' => 10 + 'thumbnails_generation_timeout' => 10, + 'webhooks' => true } @config = nil diff --git a/lib/redmine/preparation.rb b/lib/redmine/preparation.rb index fec55eb46..be63833a8 100644 --- a/lib/redmine/preparation.rb +++ b/lib/redmine/preparation.rb @@ -50,7 +50,7 @@ module Redmine map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin # Webhooks - map.permission :use_webhooks, {}, :require => :member + map.permission :use_webhooks, {}, :require => :member if Redmine::Configuration['webhooks'] map.project_module :issue_tracking do |map| # Issues diff --git a/test/functional/my_controller_test.rb b/test/functional/my_controller_test.rb index 3066f68e0..788ddd54b 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_configuration + User.find(2).roles.first.add_permission!(:use_webhooks) + + get :account + assert_response :success + assert_select 'a.icon-webhook', 1 + + Redmine::Configuration.with 'webhooks' => false 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..1e617e1ac 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 + Redmine::Configuration.with 'webhooks' => false do + get :index + assert_response :not_found + + get :new + assert_response :not_found + 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..4b92d672a 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 configuration flag" do + assert Webhook.enabled? + + Redmine::Configuration.with 'webhooks' => false do + assert_not Webhook.enabled? + end + + Redmine::Configuration.with 'webhooks' => true do + assert Webhook.enabled? + end + end + + test "trigger should not enqueue jobs when disabled" do + create_hook + + Redmine::Configuration.with 'webhooks' => false 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)