Commit fb931424 authored by Felipe Artur's avatar Felipe Artur

Prevent disclosure of merge request id via email

Do not disclosure merge request id via email for unauthorized users
when closing issues.
parent 1dfbb27f
...@@ -90,6 +90,8 @@ module EmailsHelper ...@@ -90,6 +90,8 @@ module EmailsHelper
when MergeRequest when MergeRequest
merge_request = MergeRequest.find(closed_via[:id]).present merge_request = MergeRequest.find(closed_via[:id]).present
return "" unless Ability.allowed?(@recipient, :read_merge_request, merge_request)
case format case format
when :html when :html
merge_request_link = link_to(merge_request.to_reference, merge_request.web_url) merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
...@@ -102,6 +104,8 @@ module EmailsHelper ...@@ -102,6 +104,8 @@ module EmailsHelper
# Technically speaking this should be Commit but per # Technically speaking this should be Commit but per
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339 # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
# we can't deserialize Commit without custom serializer for ActiveJob # we can't deserialize Commit without custom serializer for ActiveJob
return "" unless Ability.allowed?(@recipient, :download_code, @project)
_("via %{closed_via}") % { closed_via: closed_via } _("via %{closed_via}") % { closed_via: closed_via }
else else
"" ""
......
...@@ -34,6 +34,8 @@ module Emails ...@@ -34,6 +34,8 @@ module Emails
setup_issue_mail(issue_id, recipient_id, closed_via: closed_via) setup_issue_mail(issue_id, recipient_id, closed_via: closed_via)
@updated_by = User.find(updated_by_user_id) @updated_by = User.find(updated_by_user_id)
@recipient = User.find(recipient_id)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end end
......
---
title: Prevent disclosure of merge request ID via email
merge_request:
author:
type: security
...@@ -6,30 +6,62 @@ describe EmailsHelper do ...@@ -6,30 +6,62 @@ describe EmailsHelper do
let(:merge_request) { create(:merge_request) } let(:merge_request) { create(:merge_request) }
let(:merge_request_presenter) { merge_request.present } let(:merge_request_presenter) { merge_request.present }
context "and format is text" do context 'when user can read merge request' do
it "returns plain text" do let(:user) { create(:user) }
expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
before do
merge_request.project.add_developer(user)
self.instance_variable_set(:@recipient, user)
self.instance_variable_set(:@project, merge_request.project)
end
context "and format is text" do
it "returns plain text" do
expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
end
end end
end
context "and format is HTML" do context "and format is HTML" do
it "returns HTML" do it "returns HTML" do
expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") expect(helper.closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
end
end
context "and format is unknown" do
it "returns plain text" do
expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
end
end end
end end
context "and format is unknown" do context 'when user cannot read merge request' do
it "returns plain text" do it "does not have link to merge request" do
expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") expect(helper.closure_reason_text(merge_request)).to be_empty
end end
end end
end end
context 'when given a String' do context 'when given a String' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" } let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
it "returns plain text" do context 'when user can read commits' do
expect(closure_reason_text(closed_via)).to eq("via #{closed_via}") before do
project.add_developer(user)
self.instance_variable_set(:@recipient, user)
self.instance_variable_set(:@project, project)
end
it "returns plain text" do
expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
end
end
context 'when user cannot read commits' do
it "returns plain text" do
expect(closure_reason_text(closed_via)).to be_empty
end
end end
end end
......
...@@ -60,35 +60,63 @@ describe Issues::CloseService do ...@@ -60,35 +60,63 @@ describe Issues::CloseService do
describe '#close_issue' do describe '#close_issue' do
context "closed by a merge request" do context "closed by a merge request" do
before do it 'mentions closure via a merge request' do
perform_enqueued_jobs do perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
end end
end
it 'mentions closure via a merge request' do
email = ActionMailer::Base.deliveries.last email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email) expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title) expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference)) expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference))
end end
context 'when user cannot read merge request' do
it 'does not mention merge request' do
project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
end
email = ActionMailer::Base.deliveries.last
body_text = email.body.parts.map(&:body).join(" ")
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(body_text).not_to include(closing_merge_request.to_reference)
end
end
end end
context "closed by a commit" do context "closed by a commit" do
before do it 'mentions closure via a commit' do
perform_enqueued_jobs do perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_commit) described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
end end
end
it 'mentions closure via a commit' do
email = ActionMailer::Base.deliveries.last email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email) expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title) expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_commit.id)) expect(email.body.parts.map(&:body)).to all(include(closing_commit.id))
end end
context 'when user cannot read the commit' do
it 'does not mention the commit id' do
project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
end
email = ActionMailer::Base.deliveries.last
body_text = email.body.parts.map(&:body).join(" ")
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(body_text).not_to include(closing_commit.id)
end
end
end end
context "valid params" do context "valid params" 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