Commit 8b551ee3 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'emails-on-push'

Conflicts:
	app/controllers/projects/services_controller.rb
	app/models/project_services/emails_on_push_service.rb
parents c025c0d5 4658e554
...@@ -45,7 +45,8 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -45,7 +45,8 @@ class Admin::ServicesController < Admin::ApplicationController
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type, :build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch :description, :issues_url, :new_issue_url, :restrict_to_branch,
:send_from_committer_email, :disable_diffs
]) ])
end end
end end
...@@ -53,7 +53,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -53,7 +53,7 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels, :colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events, :push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events :note_events, :send_from_committer_email, :disable_diffs
) )
end end
end end
...@@ -16,28 +16,38 @@ module Emails ...@@ -16,28 +16,38 @@ module Emails
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
def repository_push_email(project_id, recipient, author_id, branch, compare) def repository_push_email(project_id, recipient, author_id, branch, compare, reverse_compare = false, send_from_committer_email = false, disable_diffs = false)
@project = Project.find(project_id) @project = Project.find(project_id)
@author = User.find(author_id) @author = User.find(author_id)
@reverse_compare = reverse_compare
@compare = compare @compare = compare
@commits = Commit.decorate(compare.commits) @commits = Commit.decorate(compare.commits)
@diffs = compare.diffs @diffs = compare.diffs
@branch = branch @branch = branch.gsub("refs/heads/", "")
@disable_diffs = disable_diffs
@subject = "[#{@project.path_with_namespace}][#{@branch}] "
if @commits.length > 1 if @commits.length > 1
@target_url = namespace_project_compare_url(@project.namespace, @target_url = namespace_project_compare_url(@project.namespace,
@project, @project,
from: @commits.first, from: Commit.new(@compare.base),
to: @commits.last) to: Commit.new(@compare.head))
@subject = "#{@commits.length} new commits pushed to repository" @subject << "Deleted " if @reverse_compare
@subject << "#{@commits.length} commits: #{@commits.first.title}"
else else
@target_url = namespace_project_commit_url(@project.namespace, @target_url = namespace_project_commit_url(@project.namespace,
@project, @commits.first) @project, @commits.first)
@subject = @commits.first.title
@subject << "Deleted 1 commit: " if @reverse_compare
@subject << @commits.first.title
end end
mail(from: sender(author_id), @disable_footer = true
mail(from: sender(author_id, send_from_committer_email),
to: recipient, to: recipient,
subject: subject(@subject)) subject: @subject)
end end
end end
end end
...@@ -34,6 +34,20 @@ class Notify < ActionMailer::Base ...@@ -34,6 +34,20 @@ class Notify < ActionMailer::Base
) )
end end
# 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
private private
# The default email address to send emails from # The default email address to send emails from
...@@ -45,10 +59,16 @@ class Notify < ActionMailer::Base ...@@ -45,10 +59,16 @@ class Notify < ActionMailer::Base
# Return an email address that displays the name of the sender. # Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same. # Only the displayed name changes; the actual email address is always the same.
def sender(sender_id) def sender(sender_id, send_from_user_email = false)
if sender = User.find(sender_id) if sender = User.find(sender_id)
address = default_sender_address address = default_sender_address
address.display_name = sender.name address.display_name = sender.name
sender_domain = sender.email.split("@").last
if send_from_user_email && self.class.allowed_email_domains.include?(sender_domain)
address.address = sender.email
end
address.format address.format
end end
end end
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
prop_accessor :send_from_committer_email
prop_accessor :disable_diffs
prop_accessor :recipients prop_accessor :recipients
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :activated?
...@@ -37,14 +39,27 @@ class EmailsOnPushService < Service ...@@ -37,14 +39,27 @@ class EmailsOnPushService < Service
%w(push) %w(push)
end end
def execute(data) def execute(push_data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(push_data[:object_kind])
EmailsOnPushWorker.perform_async(project_id, recipients, data) EmailsOnPushWorker.perform_async(project_id, recipients, push_data, send_from_committer_email?, disable_diffs?)
end
def send_from_committer_email?
self.send_from_committer_email == "1"
end
def disable_diffs?
self.disable_diffs == "1"
end end
def fields def fields
domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ")
[ [
{ type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer",
help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." },
{ type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs",
help: "Don't include possibly sensitive code diffs in notification body." },
{ type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
] ]
end end
......
...@@ -53,14 +53,16 @@ ...@@ -53,14 +53,16 @@
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name] - name = field[:name]
- title = field[:title] || name.humanize
- value = @service.send(name) unless field[:type] == 'password' - value = @service.send(name) unless field[:type] == 'password'
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder] - placeholder = field[:placeholder]
- choices = field[:choices] - choices = field[:choices]
- default_choice = field[:default_choice] - default_choice = field[:default_choice]
- help = field[:help]
.form-group .form-group
= f.label name, class: "control-label" = f.label name, title, class: "control-label"
.col-sm-10 .col-sm-10
- if type == 'text' - if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder = f.text_field name, class: "form-control", placeholder: placeholder
...@@ -72,6 +74,8 @@ ...@@ -72,6 +74,8 @@
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password' - elsif type == 'password'
= f.password_field name, class: 'form-control' = f.password_field name, class: 'form-control'
- if help
%span.help-block= help
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
...@@ -16,6 +16,18 @@ ...@@ -16,6 +16,18 @@
font-size:small; font-size:small;
color:#777 color:#777
} }
pre.commit-message {
white-space: pre-wrap;
}
.file-stats a {
text-decoration: none;
}
.file-stats .new-file {
color: #090;
}
.file-stats .deleted-file {
color: #B00;
}
#{add_email_highlight_css} #{add_email_highlight_css}
%body %body
%div.content %div.content
...@@ -27,5 +39,5 @@ ...@@ -27,5 +39,5 @@
- if @target_url - if @target_url
#{link_to "View it on GitLab", @target_url} #{link_to "View it on GitLab", @target_url}
= email_action @target_url = email_action @target_url
- if @project - if @project && !@disable_footer
You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team.
%h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} %h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
%h4 Commits: - if @reverse_compare
%p
%strong WARNING:
The push did not contain any new commits, but force pushed to delete the commits and changes below.
%h4
= @reverse_compare ? "Deleted commits:" : "Commits:"
%ul %ul
- @commits.each do |commit| - @commits.each do |commit|
...@@ -9,18 +15,48 @@ ...@@ -9,18 +15,48 @@
%div %div
%span by #{commit.author_name} %span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre #{commit.safe_message} %pre.commit-message
= commit.safe_message
%h4 Changes: %h4 #{pluralize @diffs.count, "changed file"}:
- @diffs.each do |diff|
%li %ul
- @diffs.each_with_index do |diff, i|
%li.file-stats
%a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
- if diff.deleted_file
%span.deleted-file
&minus;
= diff.old_path
- elsif diff.renamed_file
= diff.old_path
&rarr;
= diff.new_path
- elsif diff.new_file
%span.new-file
&plus;
= diff.new_path
- else
= diff.new_path
- unless @disable_diffs
%h4 Changes:
- @diffs.each_with_index do |diff, i|
%li{id: "diff-#{i}"}
%a{href: @target_url + "#diff-#{i}"}
- if diff.deleted_file
%strong
= diff.old_path
deleted
- elsif diff.renamed_file
%strong
= diff.old_path
&rarr;
%strong %strong
- if diff.old_path == diff.new_path
= diff.new_path = diff.new_path
- elsif diff.new_path && diff.old_path
#{diff.old_path} &rarr; #{diff.new_path}
- else - else
= diff.new_path || diff.old_path %strong
= diff.new_path
%hr %hr
%pre %pre
= color_email_diff(diff.diff) = color_email_diff(diff.diff)
......
#{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
\ \
Commits: \
- if @reverse_compare
WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
\
\
= @reverse_compare ? "Deleted commits:" : "Commits:"
- @commits.each do |commit| - @commits.each do |commit|
#{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} by #{commit.author_name} #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message} #{commit.safe_message}
\- - - - - \- - - - -
\ \
\ \
Changes: #{pluralize @diffs.count, "changed file"}:
\
- @diffs.each do |diff| - @diffs.each do |diff|
- if diff.deleted_file
\- − #{diff.old_path}
- elsif diff.renamed_file
\- #{diff.old_path}#{diff.new_path}
- elsif diff.new_file
\- + #{diff.new_path}
- else
\- #{diff.new_path}
- unless @disable_diffs
\
\
Changes:
- @diffs.each do |diff|
\ \
\===================================== \=====================================
- if diff.old_path == diff.new_path - if diff.deleted_file
= diff.new_path #{diff.old_path} deleted
- elsif diff.new_path && diff.old_path - elsif diff.renamed_file
#{diff.old_path} &rarr; #{diff.new_path} #{diff.old_path} #{diff.new_path}
- else - else
= diff.new_path || diff.old_path = diff.new_path
\===================================== \=====================================
!= diff.diff != diff.diff
\
- if @compare.timeout - if @compare.timeout
\
\
Huge diff. To prevent performance issues it was hidden Huge diff. To prevent performance issues it was hidden
\
\
View it on GitLab: #{@target_url}
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
%a{href: "#diff-#{i}"} %a{href: "#diff-#{i}"}
%i.fa.fa-minus %i.fa.fa-minus
= diff.old_path = diff.old_path
\-> &rarr;
= diff.new_path = diff.new_path
- elsif diff.new_file - elsif diff.new_file
%span.new-file %span.new-file
......
...@@ -74,14 +74,16 @@ ...@@ -74,14 +74,16 @@
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name] - name = field[:name]
- title = field[:title] || name.humanize
- value = @service.send(name) unless field[:type] == 'password' - value = @service.send(name) unless field[:type] == 'password'
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder] - placeholder = field[:placeholder]
- choices = field[:choices] - choices = field[:choices]
- default_choice = field[:default_choice] - default_choice = field[:default_choice]
- help = field[:help]
.form-group .form-group
= f.label name, class: "control-label" = f.label name, title, class: "control-label"
.col-sm-10 .col-sm-10
- if type == 'text' - if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder = f.text_field name, class: "form-control", placeholder: placeholder
...@@ -93,6 +95,8 @@ ...@@ -93,6 +95,8 @@
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password' - elsif type == 'password'
= f.password_field name, class: 'form-control' = f.password_field name, class: 'form-control'
- if help
%span.help-block= help
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
......
class EmailsOnPushWorker class EmailsOnPushWorker
include Sidekiq::Worker include Sidekiq::Worker
def perform(project_id, recipients, push_data) def perform(project_id, recipients, push_data, send_from_committer_email = false, disable_diffs = false)
project = Project.find(project_id) project = Project.find(project_id)
before_sha = push_data["before"] before_sha = push_data["before"]
after_sha = push_data["after"] after_sha = push_data["after"]
...@@ -15,11 +15,27 @@ class EmailsOnPushWorker ...@@ -15,11 +15,27 @@ class EmailsOnPushWorker
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
# Do not send emails if git compare failed return false if compare.same
return false unless compare && compare.commits.present?
if compare.commits.empty?
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
reverse_compare = true
return false if compare.commits.empty?
end
recipients.split(" ").each do |recipient| recipients.split(" ").each do |recipient|
Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver Notify.repository_push_email(
project_id,
recipient,
author_id,
branch,
compare,
reverse_compare,
send_from_committer_email,
disable_diffs
).deliver
end end
ensure ensure
compare = nil compare = nil
......
...@@ -568,9 +568,10 @@ describe Notify do ...@@ -568,9 +568,10 @@ describe Notify do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits) } let(:commits) { Commit.decorate(compare.commits) }
let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: commits.first, to: commits.last) } let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) }
let(:send_from_committer_email) { false }
subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare, false, send_from_committer_email) }
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -583,7 +584,7 @@ describe Notify do ...@@ -583,7 +584,7 @@ describe Notify do
end end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /#{commits.length} new commits pushed to repository/ is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
end end
it 'includes commits list' do it 'includes commits list' do
...@@ -597,6 +598,58 @@ describe Notify do ...@@ -597,6 +598,58 @@ describe Notify do
it 'contains a link to the diff' do it 'contains a link to the diff' do
is_expected.to have_body_text /#{diff_path}/ is_expected.to have_body_text /#{diff_path}/
end end
it 'doesn not contain the misleading footer' do
is_expected.not_to have_body_text /you are a member of/
end
context "when set to send from committer email if domain matches" do
let(:send_from_committer_email) { true }
before do
allow(Gitlab.config.gitlab).to receive(:host).and_return("gitlab.corp.company.com")
end
context "when the committer email domain is within the GitLab domain" do
before do
user.update_attribute(:email, "user@company.com")
user.confirm!
end
it "is sent from the committer email" do
sender = subject.header[:from].addrs[0]
expect(sender.address).to eq(user.email)
end
end
context "when the committer email domain is not completely within the GitLab domain" do
before do
user.update_attribute(:email, "user@something.company.com")
user.confirm!
end
it "is sent from the default email" do
sender = subject.header[:from].addrs[0]
expect(sender.address).to eq(gitlab_sender)
end
end
context "when the committer email domain is outside the GitLab domain" do
before do
user.update_attribute(:email, "user@mpany.com")
user.confirm!
end
it "is sent from the default email" do
sender = subject.header[:from].addrs[0]
expect(sender.address).to eq(gitlab_sender)
end
end
end
end end
describe 'email on push with a single commit' do describe 'email on push with a single commit' do
......
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