diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 50550fc787d1a94ed9ea753d00ab3d83e7a66527..55ff0d97075b7ac39aa7e94f94cc1c3b188cbcbf 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -343,6 +343,8 @@ def visible_attributes :commit_email_hostname, :protected_ci_variables, :local_markdown_version, + :mailgun_signing_key, + :mailgun_events_enabled, :snowplow_collector_hostname, :snowplow_cookie_domain, :snowplow_enabled, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 3804f745520b1d04463b5692d7ab9b698f43043a..cbde2db803a39df6b10182880da68b3e700156a8 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -172,6 +172,11 @@ def self.kroki_formats_attributes addressable_url: { enforce_sanitization: true }, if: :gitpod_enabled + validates :mailgun_signing_key, + presence: true, + length: { maximum: 255 }, + if: :mailgun_events_enabled + validates :snowplow_collector_hostname, presence: true, hostname: true, @@ -552,6 +557,7 @@ def self.kroki_formats_attributes attr_encrypted :secret_detection_token_revocation_token, encryption_options_base_32_aes_256_gcm attr_encrypted :cloud_license_auth_token, encryption_options_base_32_aes_256_gcm attr_encrypted :external_pipeline_validation_service_token, encryption_options_base_32_aes_256_gcm + attr_encrypted :mailgun_signing_key, encryption_options_base_32_aes_256_gcm.merge(encode: false) validates :disable_feed_token, inclusion: { in: [true, false], message: _('must be a boolean value') } diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index a92ae710959992a1553f3f9ef4951f12e0af75bd..5b88d7c4a437c1b886717e31365b6832a3c76b8d 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -104,6 +104,8 @@ def defaults issues_create_limit: 300, local_markdown_version: 0, login_recaptcha_protection_enabled: false, + mailgun_signing_key: nil, + mailgun_events_enabled: false, max_artifacts_size: Settings.artifacts['max_size'], max_attachment_size: Settings.gitlab['max_attachment_size'], max_import_size: 0, diff --git a/app/views/admin/application_settings/_mailgun.html.haml b/app/views/admin/application_settings/_mailgun.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..6204f7df5dcc3444da7364da50d5cd84eb3421fb --- /dev/null +++ b/app/views/admin/application_settings/_mailgun.html.haml @@ -0,0 +1,25 @@ +- return unless Feature.enabled?(:mailgun_events_receiver) + +- expanded = integration_expanded?('mailgun_') +%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Mailgun') + %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = _('Configure the %{link} integration.').html_safe % { link: link_to(_('Mailgun events'), 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', target: '_blank') } + .settings-content + = form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-mailgun-settings'), html: { class: 'fieldset-form', id: 'mailgun-settings' } do |f| + = form_errors(@application_setting) if expanded + + %fieldset + .form-group + .form-check + = f.check_box :mailgun_events_enabled, class: 'form-check-input' + = f.label :mailgun_events_enabled, _('Enable Mailgun event receiver'), class: 'form-check-label' + .form-group + = f.label :mailgun_signing_key, _('Mailgun HTTP webhook signing key'), class: 'label-light' + = f.text_field :mailgun_signing_key, class: 'form-control gl-form-input' + + = f.submit _('Save changes'), class: 'gl-button btn btn-confirm' diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index 0fbbef0261382af154b5a3ba0e328c1b03f588b2..53bdbcd713728293a690a4bdd7d4fb06ac3e7ca1 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -107,6 +107,7 @@ = render_if_exists 'admin/application_settings/maintenance_mode_settings_form' = render 'admin/application_settings/gitpod' = render 'admin/application_settings/kroki' += render 'admin/application_settings/mailgun' = render 'admin/application_settings/plantuml' = render 'admin/application_settings/sourcegraph' = render_if_exists 'admin/application_settings/slack' diff --git a/config/application.rb b/config/application.rb index 6da5c872cfacc36134e4d9f0569e98df5c82aa06..6526be15cd4e510a9662c59b5e6df3bbe87e47dc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -151,6 +151,7 @@ class Application < Rails::Application elasticsearch_password search jwt + mailgun_signing_key otp_attempt sentry_dsn trace diff --git a/config/feature_flags/development/mailgun_events_receiver.yml b/config/feature_flags/development/mailgun_events_receiver.yml new file mode 100644 index 0000000000000000000000000000000000000000..119d8d34f210e8a49396f45394d8dff60eff685f --- /dev/null +++ b/config/feature_flags/development/mailgun_events_receiver.yml @@ -0,0 +1,8 @@ +--- +name: mailgun_events_receiver +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64249 +rollout_issue_url: +milestone: '14.1' +type: development +group: group::expansion +default_enabled: false diff --git a/db/migrate/20210616185947_add_mailgun_settings_to_application_setting.rb b/db/migrate/20210616185947_add_mailgun_settings_to_application_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..8447ff79d12cbe783f4d67a071cc75d7ee5ae864 --- /dev/null +++ b/db/migrate/20210616185947_add_mailgun_settings_to_application_setting.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddMailgunSettingsToApplicationSetting < ActiveRecord::Migration[6.1] + def change + add_column :application_settings, :encrypted_mailgun_signing_key, :binary + add_column :application_settings, :encrypted_mailgun_signing_key_iv, :binary + + add_column :application_settings, :mailgun_events_enabled, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20210616185947 b/db/schema_migrations/20210616185947 new file mode 100644 index 0000000000000000000000000000000000000000..30275f102dcb52aaff8744420d64867a25f0d445 --- /dev/null +++ b/db/schema_migrations/20210616185947 @@ -0,0 +1 @@ +8d73f4b4b716176afe5a9b0ee3a4ef28bbbc2fe944a18fb66afa8cf8f763e8ac \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 62c559c1c02621fe79cf0f80a7c91d67ea76cdc8..56a48be6c4b67db6440e7e8b193357054925deae 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9522,6 +9522,9 @@ CREATE TABLE application_settings ( diff_max_lines integer DEFAULT 50000 NOT NULL, diff_max_files integer DEFAULT 1000 NOT NULL, valid_runner_registrars character varying[] DEFAULT '{project,group}'::character varying[], + encrypted_mailgun_signing_key bytea, + encrypted_mailgun_signing_key_iv bytea, + mailgun_events_enabled boolean DEFAULT false NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), diff --git a/doc/api/settings.md b/doc/api/settings.md index ce62cd193f7958dc696908e1c4cc58ce6ea62809..d49dca96dfdf33d33f8f4fe60b9345a3fc1fea39 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -328,6 +328,8 @@ listed in the descriptions of the relevant settings. | `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.| | `keep_latest_artifact` | boolean | no | Prevent the deletion of the artifacts from the most recent successful jobs, regardless of the expiry time. Enabled by default. | | `local_markdown_version` | integer | no | Increase this value when any cached Markdown should be invalidated. | +| `mailgun_signing_key` | string | no | The Mailgun HTTP webhook signing key for receiving events from webhook | +| `mailgun_events_enabled` | boolean | no | Enable Mailgun event receiver. | | `maintenance_mode_message` | string | no | **(PREMIUM)** Message displayed when instance is in maintenance mode. | | `maintenance_mode` | boolean | no | **(PREMIUM)** When instance is in maintenance mode, non-administrative users can sign in with read-only access and make read-only API requests. | | `max_artifacts_size` | integer | no | Maximum artifacts size in MB. | diff --git a/lib/api/settings.rb b/lib/api/settings.rb index b4f8320cb74574eb3390d0dc6737355b5473a9fa..952bf09b1b137f4aaef02937f484f948d269aeeb 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -160,6 +160,10 @@ def filter_attributes_using_license(attrs) optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated' optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5 + optional :mailgun_events_enabled, type: Grape::API::Boolean, desc: 'Enable Mailgun event receiver' + given mailgun_events_enabled: ->(val) { val } do + requires :mailgun_signing_key, type: String, desc: 'The Mailgun HTTP webhook signing key for receiving events from webhook' + end optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking' given snowplow_enabled: ->(val) { val } do requires :snowplow_collector_hostname, type: String, desc: 'The Snowplow collector hostname' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4c7d19131bcb93a6111a615345564bfe077446a9..6419a679fb1f02ca1013839dcdef4c9b1f0292af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8205,6 +8205,9 @@ msgstr "" msgid "Configure storage path settings." msgstr "" +msgid "Configure the %{link} integration." +msgstr "" + msgid "Configure the way a user creates a new account." msgstr "" @@ -11935,6 +11938,9 @@ msgstr "" msgid "Enable Kroki" msgstr "" +msgid "Enable Mailgun event receiver" +msgstr "" + msgid "Enable PlantUML" msgstr "" @@ -19715,6 +19721,15 @@ msgstr "" msgid "Made this issue confidential." msgstr "" +msgid "Mailgun" +msgstr "" + +msgid "Mailgun HTTP webhook signing key" +msgstr "" + +msgid "Mailgun events" +msgstr "" + msgid "Maintenance mode" msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 5a4c47b9dc534b9d61effc704023697e775fe6be..965bda8b4d64a68c14d76b4581e63732eb598e7d 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -269,7 +269,10 @@ end context 'Integrations page' do + let(:mailgun_events_receiver_enabled) { true } + before do + stub_feature_flags(mailgun_events_receiver: mailgun_events_receiver_enabled) visit general_admin_application_settings_path end @@ -282,6 +285,28 @@ expect(page).to have_content "Application settings saved successfully" expect(current_settings.hide_third_party_offers).to be true end + + context 'when mailgun_events_receiver feature flag is enabled' do + it 'enabling Mailgun events', :aggregate_failures do + page.within('.as-mailgun') do + check 'Enable Mailgun event receiver' + fill_in 'Mailgun HTTP webhook signing key', with: 'MAILGUN_SIGNING_KEY' + click_button 'Save changes' + end + + expect(page).to have_content 'Application settings saved successfully' + expect(current_settings.mailgun_events_enabled).to be true + expect(current_settings.mailgun_signing_key).to eq 'MAILGUN_SIGNING_KEY' + end + end + + context 'when mailgun_events_receiver feature flag is disabled' do + let(:mailgun_events_receiver_enabled) { false } + + it 'does not have mailgun' do + expect(page).not_to have_selector('.as-mailgun') + end + end end context 'when Service Templates are enabled' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index de0027506b9271ff45fb9f6b78142d1f84b67204..80471a09bbd233448c1be93bea478fe03c92f207 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -258,6 +258,19 @@ def many_usernames(num = 100) it { is_expected.to allow_value(nil).for(:snowplow_collector_hostname) } end + context 'when mailgun_events_enabled is enabled' do + before do + setting.mailgun_events_enabled = true + end + + it { is_expected.to validate_presence_of(:mailgun_signing_key) } + it { is_expected.to validate_length_of(:mailgun_signing_key).is_at_most(255) } + end + + context 'when mailgun_events_enabled is not enabled' do + it { is_expected.not_to validate_presence_of(:mailgun_signing_key) } + end + context "when user accepted let's encrypt terms of service" do before do expect do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 4a4aeaea7149b194c9b2d16d97eb45887ea680ea..4008b57a1cf228f4e753021b8f31bd8ee541a144 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -127,6 +127,8 @@ spam_check_endpoint_enabled: true, spam_check_endpoint_url: 'grpc://example.com/spam_check', spam_check_api_key: 'SPAM_CHECK_API_KEY', + mailgun_events_enabled: true, + mailgun_signing_key: 'MAILGUN_SIGNING_KEY', disabled_oauth_sign_in_sources: 'unknown', import_sources: 'github,bitbucket', wiki_page_max_content_bytes: 12345, @@ -175,6 +177,8 @@ expect(json_response['spam_check_endpoint_enabled']).to be_truthy expect(json_response['spam_check_endpoint_url']).to eq('grpc://example.com/spam_check') expect(json_response['spam_check_api_key']).to eq('SPAM_CHECK_API_KEY') + expect(json_response['mailgun_events_enabled']).to be(true) + expect(json_response['mailgun_signing_key']).to eq('MAILGUN_SIGNING_KEY') expect(json_response['disabled_oauth_sign_in_sources']).to eq([]) expect(json_response['import_sources']).to match_array(%w(github bitbucket)) expect(json_response['wiki_page_max_content_bytes']).to eq(12345) @@ -493,6 +497,15 @@ end end + context "missing mailgun_signing_key value when mailgun_events_enabled is true" do + it "returns a blank parameter error message" do + put api("/application/settings", admin), params: { mailgun_events_enabled: true } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('mailgun_signing_key is missing') + end + end + context "personal access token prefix settings" do context "handles validation errors" do it "fails to update the settings with too long prefix" do