Read about our upcoming Code of Conduct on this issue

This instance will be upgraded to Heptapod 0.28.1 on 2022-01-26 at 16:00 UTC+1 (a few minutes of down time)

notify.rb 6.8 KB
Newer Older
gfyoung's avatar
gfyoung committed
1
2
# frozen_string_literal: true

3
class Notify < ApplicationMailer
4
  include ActionDispatch::Routing::PolymorphicRoutes
5
  include GitlabRoutingHelper
6
  include EmailsHelper
7
  include IssuablesHelper
8

9
10
11
  include Emails::Issues
  include Emails::MergeRequests
  include Emails::Notes
12
  include Emails::PagesDomains
13
  include Emails::Projects
14
  include Emails::Profile
15
  include Emails::Pipelines
16
  include Emails::Members
17
  include Emails::AutoDevops
18
  include Emails::RemoteMirrors
19
  include Emails::Releases
20

21
  helper MilestonesHelper
22
23
24
25
26
  helper MergeRequestsHelper
  helper DiffHelper
  helper BlobHelper
  helper EmailsHelper
  helper MembersHelper
27
  helper AvatarsHelper
28
  helper GitlabRoutingHelper
29
  helper IssuablesHelper
gitlabhq's avatar
gitlabhq committed
30

Steven Burgart's avatar
Steven Burgart committed
31
32
  def test_email(recipient_email, subject, body)
    mail(to: recipient_email,
33
34
35
         subject: subject,
         body: body.html_safe,
         content_type: 'text/html'
Gabriel Mazetto's avatar
Gabriel Mazetto committed
36
        )
37
38
  end

39
40
41
42
43
44
45
46
47
48
49
50
51
52
  # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
  # "corp.company.com" and "company.com".
  # Respects set tld length so "company.co.uk" won't match "somethingelse.uk"
  def self.allowed_email_domains
    domain_parts = Gitlab.config.gitlab.host.split(".")
    allowed_domains = []
    begin
      allowed_domains << domain_parts.join(".")
      domain_parts.shift
    end while domain_parts.length > ActionDispatch::Http::URL.tld_length

    allowed_domains
  end

53
54
55
56
57
  def can_send_from_user_email?(sender)
    sender_domain = sender.email.split("@").last
    self.class.allowed_email_domains.include?(sender_domain)
  end

58
59
  private

60
61
  # Return an email address that displays the name of the sender.
  # Only the displayed name changes; the actual email address is always the same.
62
  def sender(sender_id, send_from_user_email: false, sender_name: nil)
63
    return unless sender = User.find(sender_id)
64

65
    address = default_sender_address
66
    address.display_name = sender_name.presence || sender.name
67

68
69
    if send_from_user_email && can_send_from_user_email?(sender)
      address.address = sender.email
70
    end
71
72

    address.format
73
74
  end

75
76
77
78
79
80
81
  # Formats arguments into a String suitable for use as an email subject
  #
  # extra - Extra Strings to be inserted into the subject
  #
  # Examples
  #
  #   >> subject('Lorem ipsum')
82
  #   => "Lorem ipsum"
83
84
85
86
87
  #
  #   # Automatically inserts Project name when @project is set
  #   >> @project = Project.last
  #   => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  #   >> subject('Lorem ipsum')
88
  #   => "Ruby on Rails | Lorem ipsum "
89
90
91
  #
  #   # Accepts multiple arguments
  #   >> subject('Lorem ipsum', 'Dolor sit amet')
92
  #   => "Lorem ipsum | Dolor sit amet"
93
  def subject(*extra)
gfyoung's avatar
gfyoung committed
94
95
96
97
98
99
100
101
    subject = []

    subject << @project.name if @project
    subject << @group.name if @group
    subject.concat(extra) if extra.present?
    subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?

    subject.join(' | ')
102
  end
103
104
105
106
107
108
109
110
111

  # Return a string suitable for inclusion in the 'Message-Id' mail header.
  #
  # The message-id is generated from the unique URL to a model object.
  def message_id(model)
    model_name = model.class.model_name.singular_route_key
    "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>"
  end

112
  def mail_thread(model, headers = {})
113
    add_project_headers
114
    add_unsubscription_headers_and_links
Alex Kalderimis's avatar
Alex Kalderimis committed
115
    add_model_headers(model)
116

117
    headers['X-GitLab-Reply-Key'] = reply_key
Douwe Maan's avatar
Douwe Maan committed
118

119
120
    @reason = headers['X-GitLab-NotificationReason']

Douwe Maan's avatar
Douwe Maan committed
121
    if Gitlab::IncomingEmail.enabled? && @sent_notification
122
123
124
      headers['Reply-To'] = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)).tap do |address|
        address.display_name = reply_display_name(model)
      end
125

126
      fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>"
127
      headers['References'] ||= []
128
      headers['References'].unshift(fallback_reply_message_id)
129

130
      @reply_by_email = true
Douwe Maan's avatar
Douwe Maan committed
131
132
133
    end

    mail(headers)
134
135
  end

136
137
138
139
140
  # `model` is used on EE code
  def reply_display_name(_model)
    @project.full_name
  end

141
142
143
144
145
  # Send an email that starts a new conversation thread,
  # with headers suitable for grouping by thread in email clients.
  #
  # See: mail_answer_thread
  def mail_new_thread(model, headers = {})
146
    headers['Message-ID'] = message_id(model)
147
148
149
150

    mail_thread(model, headers)
  end

151
152
153
154
155
156
157
158
  # Send an email that responds to an existing conversation thread,
  # with headers suitable for grouping by thread in email clients.
  #
  # For grouping emails by thread, email clients heuristics require the answers to:
  #
  #  * have a subject that begin by 'Re: '
  #  * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID'
  #
Douwe Maan's avatar
Douwe Maan committed
159
  def mail_answer_thread(model, headers = {})
160
    headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
161
    headers['In-Reply-To'] = message_id(model)
162
    headers['References'] = [message_id(model)]
163

164
    headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject]
Douwe Maan's avatar
Douwe Maan committed
165

166
    mail_thread(model, headers)
167
  end
Douwe Maan's avatar
Douwe Maan committed
168

169
170
  def mail_answer_note_thread(model, note, headers = {})
    headers['Message-ID'] = message_id(note)
171
172
    headers['In-Reply-To'] = message_id(note.references.last)
    headers['References'] = note.references.map { |ref| message_id(ref) }
173
174
175

    headers['X-GitLab-Discussion-ID'] = note.discussion.id if note.part_of_discussion?

176
    headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject]
177
178
179
180

    mail_thread(model, headers)
  end

Douwe Maan's avatar
Douwe Maan committed
181
  def reply_key
182
    @reply_key ||= SentNotification.reply_key
Douwe Maan's avatar
Douwe Maan committed
183
  end
184

Alex Kalderimis's avatar
Alex Kalderimis committed
185
186
187
188
189
190
191
192
193
194
195
196
  # This method applies threading headers to the email to identify
  # the instance we are discussing.
  #
  # All model instances must have `#id`, and may implement `#iid`.
  def add_model_headers(object)
    # Use replacement so we don't strip the module.
    prefix = "X-GitLab-#{object.class.name.gsub(/::/, '-')}"

    headers["#{prefix}-ID"] = object.id
    headers["#{prefix}-IID"] = object.iid if object.respond_to?(:iid)
  end

197
198
199
200
201
  def add_project_headers
    return unless @project

    headers['X-GitLab-Project'] = @project.name
    headers['X-GitLab-Project-Id'] = @project.id
202
    headers['X-GitLab-Project-Path'] = @project.full_path
203
    headers['List-Id'] = "#{@project.full_path} <#{create_list_id_string(@project)}>"
204
  end
205
206
207
208
209
210
211
212
213
214

  def add_unsubscription_headers_and_links
    return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable?

    list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)]
    if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
      list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}"
    end

    headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
Douwe Maan's avatar
Douwe Maan committed
215
    @unsubscribe_url = unsubscribe_sent_notification_url(@sent_notification)
216
  end
gitlabhq's avatar
gitlabhq committed
217
end
218
219

Notify.prepend_if_ee('EE::Notify')