Commit f17243a0 authored by Sean McGivern's avatar Sean McGivern

Merge branch '4474-custom-additional-text-in-confirmation-email' into 'master'

Add custom additional text to emails

Closes #4474

See merge request gitlab-org/gitlab-ee!5031
parents 0ee47266 b950640b
......@@ -22,5 +22,14 @@
By default GitLab sends emails in HTML and plain text formats so mail
clients can choose what format to use. Disable this option if you only
want to send emails in plain text format.
-# EE-specific start
- if License.feature_available?(:email_additional_text)
.form-group
= f.label :email_additional_text, _('Additional text'), class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :email_additional_text, class: 'form-control', maxlength: Gitlab::CurrentSettings.email_additional_text_character_limit, rows: 4
.help-block
= _('Add additional text to appear in all email communications. %{character_limit} character limit') % { character_limit: number_with_delimiter(Gitlab::CurrentSettings.email_additional_text_character_limit) }
-# EE-specific end
= f.submit 'Save changes', class: "btn btn-success"
......@@ -2,3 +2,12 @@
Unfortunately, your email message to GitLab could not be processed.
= markdown @reason
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
%p
—
%br
%br
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
Unfortunately, your email message to GitLab could not be processed.
\
= @reason
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
\
= '---'
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
......@@ -63,6 +63,10 @@
%tbody
= yield
-# EE-specific start
= render 'layouts/mailer/additional_text'
-# EE-specific end
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
......
......@@ -2,3 +2,4 @@
---
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
<%# EE-specific start %><%= render 'layouts/mailer/additional_text' %><%# EE-specific end %>
\ No newline at end of file
......@@ -31,3 +31,10 @@
adjust your notification settings.
= email_action @target_url
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
%br
%br
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
......@@ -10,3 +10,4 @@
<% end -%>
<%= "You're receiving this email because #{notification_reason_text(@reason)}." %>
<%# EE-specific start %><%= render 'layouts/mailer/additional_text' %><%# EE-specific end %>
\ No newline at end of file
......@@ -6,3 +6,12 @@
%p
You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
%p
&mdash;
%br
%br
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
......@@ -4,3 +4,10 @@ View details: #{admin_projects_url(last_repository_check_failed: 1)}
You are receiving this message because you are a GitLab administrator
for #{Gitlab.config.gitlab.url}.
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
\
= '---'
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180327101207) do
ActiveRecord::Schema.define(version: 20180401213713) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -197,6 +197,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do
t.string "encrypted_external_auth_client_key_iv"
t.string "encrypted_external_auth_client_key_pass"
t.string "encrypted_external_auth_client_key_pass_iv"
t.string "email_additional_text"
end
create_table "approvals", force: :cascade do |t|
......
......@@ -55,6 +55,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
- **(Premium)** [Additional email text](../user/admin_area/settings/email.md): Set a custom message that appears at the bottom of every email.
### Maintaining GitLab
......@@ -100,7 +101,7 @@ created in snippets, wikis, and repos.
- [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
basic Postfix mail server with IMAP authentication on Ubuntu for incoming
emails.
server with IMAP authentication on Ubuntu, to be used with Reply by email.
- **(Premium)** [Additional custom email text](../user/admin_area/settings/email.md): Set a custom message that appears at the bottom of the every email.
- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
[reply by email]: reply_by_email.md
......
......@@ -102,6 +102,7 @@ PUT /application/settings
| `elasticsearch_search` | boolean | no | Enable Elasticsearch search |
| `elasticsearch_url` | string | no | The url to use for connecting to Elasticsearch. Use a comma-separated list to support cluster (e.g., "http://localhost:9200, http://localhost:9201") |
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `email_additional_text` | string | no | **(Premium)** Additional text added to the bottom of every email for legal/auditing/compliance reasons reasons |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `geo_status_timeout` | integer | no | The amount of seconds after which a request to get a secondary node status will time out. |
| `gravatar_enabled` | boolean | no | Enable Gravatar |
......
# Email
## Custom logo
The logo in the header of some emails can be customized, see the [logo customization section](../../../customization/branded_page_and_email_header.md).
## Custom additional text
>[Introduced][ee-5031] in [GitLab Premium][eep] 10.7.
The additional text will appear at the bottom of any email and can be used for
legal/auditing/compliance reasons.
1. Go to **Admin area > Settings** (`/admin/application_settings`).
1. Under the **Email** section, change the **Additional text** field.
1. Hit **Save** for the changes to take effect.
![Admin email settings](img/email_settings.png)
[ee-5031]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5031
[eep]: https://about.gitlab.com/pricing/
......@@ -16,6 +16,10 @@ module EE
attrs += EE::ApplicationSettingsHelper.external_authorization_service_attributes
end
if License.feature_available?(:email_additional_text)
attrs << :email_additional_text
end
attrs
end
end
......
......@@ -81,7 +81,7 @@ module EE
end
def self.possible_licensed_attributes
repository_mirror_attributes + external_authorization_service_attributes
repository_mirror_attributes + external_authorization_service_attributes + [:email_additional_text]
end
end
end
......@@ -9,6 +9,8 @@ module EE
prepended do
include IgnorableColumn
EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT = 10_000
ignore_column :minimum_mirror_sync_time
validates :shared_runners_minutes,
......@@ -40,6 +42,10 @@ module EE
presence: { message: "can't be blank when using aws hosted elasticsearch" },
if: ->(setting) { setting.elasticsearch_indexing? && setting.elasticsearch_aws? }
validates :email_additional_text,
allow_blank: true,
length: { maximum: EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT }
validates :external_authorization_service_default_label,
presence: true,
if: :external_authorization_service_enabled?
......@@ -86,6 +92,7 @@ module EE
elasticsearch_aws: false,
elasticsearch_aws_region: ENV['ELASTIC_REGION'] || 'us-east-1',
elasticsearch_url: ENV['ELASTIC_URL'] || 'http://localhost:9200',
email_additional_text: nil,
mirror_capacity_threshold: Settings.gitlab['mirror_capacity_threshold'],
mirror_max_capacity: Settings.gitlab['mirror_max_capacity'],
mirror_max_delay: Settings.gitlab['mirror_max_delay'],
......@@ -137,6 +144,16 @@ module EE
}
end
def email_additional_text
return false unless email_additional_text_column_exists?
License.feature_available?(:email_additional_text) && super
end
def email_additional_text_character_limit
EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT
end
def external_authorization_service_enabled
License.feature_available?(:external_authorization_service) && super
end
......@@ -164,5 +181,9 @@ module EE
def elasticsearch_search_column_exists?
::Gitlab::Database.cached_column_exists?(:application_settings, :elasticsearch_search)
end
def email_additional_text_column_exists?
::Gitlab::Database.cached_column_exists?(:application_settings, :email_additional_text)
end
end
end
......@@ -38,6 +38,7 @@ class License < ActiveRecord::Base
admin_audit_log
auditor_user
cross_project_pipelines
email_additional_text
db_load_balancing
deploy_board
extended_audit_events
......
- if Gitlab::CurrentSettings.email_additional_text.present?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;" }
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:13px;color:#5c5c5c;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
<% if Gitlab::CurrentSettings.email_additional_text.present? %>
<%= Gitlab::CurrentSettings.email_additional_text %>
<% end %>
\ No newline at end of file
<%= yield -%>
---
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
<%# EE-specific start %><%= render 'layouts/mailer/additional_text' %><%# EE-specific end %>
\ No newline at end of file
......@@ -13,3 +13,10 @@
&mdash;
%br
= link_to "Unsubscribe", @unsubscribe_url
-# EE-specific start
- if Gitlab::CurrentSettings.email_additional_text.present?
%br
%br
= Gitlab::Utils.nlbr(Gitlab::CurrentSettings.email_additional_text)
-# EE-specific end
......@@ -135,6 +135,10 @@
%a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" }
= @merge_request.assignee.name
-# EE-specific start
= render 'layouts/mailer/additional_text'
-# EE-specific end
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
......
......@@ -3,3 +3,4 @@ New response for issue #<%= @issue.iid %>:
Author: <%= @note.author_name %>
<%= @note.note %>
<%# EE-specific start %><%= render 'layouts/mailer/additional_text' %><%# EE-specific end %>
\ No newline at end of file
......@@ -3,3 +3,4 @@ Thank you for your support request! We are tracking your request as ticket #<%=
To unsubscribe from this issue, please paste the following link into your browser:
<%= @unsubscribe_url %>
<%# EE-specific start %><%= render 'layouts/mailer/additional_text' %><%# EE-specific end %>
\ No newline at end of file
......@@ -134,6 +134,10 @@
%a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" }
= @merge_request.assignee.name
-# EE-specific start
= render 'layouts/mailer/additional_text'
-# EE-specific end
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
......
---
title: Add admin setting for custom additional text in emails
merge_request: 5031
author:
type: added
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEmailAdditionalTextToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index", "remove_concurrent_index" or
# "add_column_with_default" you must disable the use of transactions
# as these methods can not run in an existing transaction.
# When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
# that either of them is the _only_ method called in the migration,
# any other changes should go in a separate migration.
# This ensures that upon failure _only_ the index creation or removing fails
# and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :application_settings, :email_additional_text, :string, length: 10_000
end
end
......@@ -105,6 +105,7 @@ module EE
expose(*EE::ApplicationSettingsHelper.external_authorization_service_attributes, if: ->(_instance, _options) do
::License.feature_available?(:external_authorization_service)
end)
expose :email_additional_text, if: ->(_instance, _opts) { ::License.feature_available?(:email_additional_text) }
end
end
......
......@@ -97,6 +97,13 @@ describe Admin::ApplicationSettingsController do
it_behaves_like 'settings for licensed features'
end
context 'additional email footer' do
let(:settings) { { email_additional_text: 'scary legal footer' } }
let(:feature) { :email_additional_text }
it_behaves_like 'settings for licensed features'
end
it 'updates the default_project_creation for string value' do
stub_licensed_features(project_creation_level: true)
put :update, application_setting: { default_project_creation: ::EE::Gitlab::Access::MASTER_PROJECT_ACCESS }
......
require 'spec_helper'
require 'email_spec'
describe DeviseMailer do
describe "#confirmation_instructions" do
let(:unsaved_user) { create(:user, name: 'Jane Doe', email: 'jdoe@example.com') }
let(:custom_text) { 'this is some additional custom text' }
subject { described_class.confirmation_instructions(unsaved_user, 'faketoken', {}) }
before do
stub_licensed_features(email_additional_text: true)
stub_ee_application_setting(email_additional_text: custom_text)
end
it "includes the additonal custom text" do
expect(subject).to have_text custom_text
end
end
end
......@@ -24,6 +24,15 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value(-1).for(:mirror_capacity_threshold) }
it { is_expected.not_to allow_value(subject.mirror_max_capacity + 1).for(:mirror_capacity_threshold) }
describe 'when additional email text is enabled' do
before do
stub_licensed_features(email_additional_text: true)
end
it { is_expected.to allow_value("a" * subject.email_additional_text_character_limit).for(:email_additional_text) }
it { is_expected.not_to allow_value("a" * (subject.email_additional_text_character_limit + 1)).for(:email_additional_text) }
end
describe 'when external authorization service is enabled' do
before do
stub_licensed_features(external_authorization_service: true)
......
......@@ -94,4 +94,11 @@ describe API::Settings, 'EE Settings' do
it_behaves_like 'settings for licensed features'
end
context 'custom email footer' do
let(:settings) { { email_additional_text: 'this is a scary legal footer' } }
let(:feature) { :email_additional_text }
it_behaves_like 'settings for licensed features'
end
end
......@@ -114,6 +114,7 @@ module API
optional :version_check_enabled, type: Boolean, desc: 'Let GitLab inform you when an update is available.'
optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.'
optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
optional :email_additional_text, type: String, desc: 'Additional text added to the bottom of every email for legal/auditing/compliance reasons'
optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
given housekeeping_enabled: ->(val) { val } do
requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance."
......@@ -170,6 +171,10 @@ module API
attrs = attrs.except(*::EE::ApplicationSettingsHelper.external_authorization_service_attributes)
end
unless ::License.feature_available?(:email_additional_text)
attrs = attrs.except(:email_additional_text)
end
# support legacy names, can be removed in v5
if attrs.has_key?(:signin_enabled)
attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
......
......@@ -95,13 +95,13 @@ module Gitlab
args = [ref, oldrev, newrev]
stdout, stderr, status = Open3.capture3(env, path, *args, options)
[status.success?, (stderr.presence || stdout).gsub(/\R/, "<br>").html_safe]
[status.success?, Gitlab::Utils.nlbr(stderr.presence || stdout)]
end
def retrieve_error_message(stderr, stdout)
err_message = stderr.read
err_message = err_message.blank? ? stdout.read : err_message
err_message.gsub(/\R/, "<br>").html_safe
Gitlab::Utils.nlbr(err_message)
end
end
end
......
......@@ -27,6 +27,11 @@ module Gitlab
.gsub(/(\A-+|-+\z)/, '')
end
# Converts newlines into HTML line break elements
def nlbr(str)
ActionView::Base.full_sanitizer.sanitize(str, tags: []).gsub(/\r?\n/, '<br>').html_safe
end
def remove_line_breaks(str)
str.gsub(/\r?\n/, '')
end
......
class EmailRejectionMailerPreview < ActionMailer::Preview
def rejection
EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
end
end
......@@ -58,10 +58,115 @@ class NotifyPreview < ActionMailer::Preview
end
end
def closed_issue_email
Notify.closed_issue_email(user.id, issue.id, user.id).message
end
def issue_status_changed_email
Notify.issue_status_changed_email(user.id, issue.id, 'closed', user.id).message
end
def closed_merge_request_email
Notify.closed_merge_request_email(user.id, issue.id, user.id).message
end
def merge_request_status_email
Notify.merge_request_status_email(user.id, merge_request.id, 'closed', user.id).message
end
def merged_merge_request_email
Notify.merged_merge_request_email(user.id, merge_request.id, user.id).message
end
def member_access_denied_email
Notify.member_access_denied_email('project', project.id, user.id).message
end
def member_access_granted_email
Notify.member_access_granted_email('project', user.id).message
end
def member_access_requested_email
Notify.member_access_requested_email('group', user.id, 'some@example.com').message
end
def member_invite_accepted_email
Notify.member_invite_accepted_email('project', user.id).message
end
def member_invite_declined_email
Notify.member_invite_declined_email(
'project',
project.id,
'invite@example.com',
user.id
).message
end
def member_invited_email
Notify.member_invited_email('project', user.id, '1234').message
end
def pages_domain_enabled_email
cleanup do
pages_domain = PagesDomain.new(domain: 'my.example.com', project: project, verified_at: Time.now, enabled_until: 1.week.from_now)
Notify.pages_domain_enabled_email(pages_domain, user).message
end
end
def pipeline_success_email
Notify.pipeline_success_email(pipeline, pipeline.user.try(:email))
end
def pipeline_failed_email
Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
end
# EE-specific start
def unapproved_merge_request
def add_merge_request_approver_email
Notify.add_merge_request_approver_email(user.id, merge_request.id, user.id).message
end
def issues_csv_email
Notify.issues_csv_email(user, project, '1997,Ford,E350', { truncated: false, rows_expected: 3, rows_written: 3 }).message
end
def approved_merge_request_email
Notify.approved_merge_request_email(user.id, merge_request.id, approver.id).message
end
def unapproved_merge_request_email
Notify.unapproved_merge_request_email(user.id, merge_request.id, approver.id).message
end
def mirror_was_hard_failed_email
Notify.mirror_was_hard_failed_email(project.id, user.id).message
end
def project_mirror_user_changed_email
Notify.project_mirror_user_changed_email(user.id, 'deleted_user_name', project.id).message
end
def send_admin_notification
Notify.send_admin_notification(user.id, 'Email subject from admin', 'Email body from admin').message
end
def send_unsubscribed_notification
Notify.send_unsubscribed_notification(user.id).message
end
def service_desk_new_note_email
cleanup do
note = create_note(noteable_type: 'Issue', noteable_id: issue.id, note: 'Issue note content')
Notify.service_desk_new_note_email(issue.id, note.id).message
end
end
def service_desk_thank_you_email
Notify.service_desk_thank_you_email(issue.id).message
end
# EE-specific end
private
......@@ -70,10 +175,18 @@ class NotifyPreview < ActionMailer::Preview
@project ||= Project.find_by_full_path('gitlab-org/gitlab-test')
end
def issue
@merge_request ||= project.issues.first
end
def merge_request
@merge_request ||= project.merge_requests.first
end
def pipeline
@pipeline = Ci::Pipeline.last
end
def user
@user ||= User.last
end
......@@ -101,16 +214,6 @@ class NotifyPreview < ActionMailer::Preview
email
end
def pipeline_success_email
pipeline = Ci::Pipeline.last
Notify.pipeline_success_email(pipeline, pipeline.user.try(:email))
end
def pipeline_failed_email
pipeline = Ci::Pipeline.last
Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
end
# EE-specific start
def approver
@user ||= User.first
......
class RepositoryCheckMailerPreview < ActionMailer::Preview
def notify
RepositoryCheckMailer.notify(3).message
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment