diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index a0706eaa46cfc309930e4be73be040063ab29744..dd8c5d49cf44db89b260ba564349d0a4eea51ec6 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -18,6 +18,7 @@ class IssuePolicy < IssuablePolicy prevent :read_issue_iid prevent :update_issue prevent :admin_issue + prevent :create_note end rule { locked }.policy do diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb index 777f933cdcd839fb69c3ae03e52ea372bbf29787..16e67a06d5652a23829b05704ade87d84f4a8d64 100644 --- a/app/policies/personal_snippet_policy.rb +++ b/app/policies/personal_snippet_policy.rb @@ -28,5 +28,8 @@ class PersonalSnippetPolicy < BasePolicy rule { anonymous }.prevent :comment_personal_snippet - rule { can?(:comment_personal_snippet) }.enable :award_emoji + rule { can?(:comment_personal_snippet) }.policy do + enable :create_note + enable :award_emoji + end end diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb index 7dafa33bb99ed16892293164f962b5ea6bc58148..e5e005cee6d3927a46fc5c2065f60bb633c78ba6 100644 --- a/app/policies/project_snippet_policy.rb +++ b/app/policies/project_snippet_policy.rb @@ -43,4 +43,6 @@ class ProjectSnippetPolicy < BasePolicy enable :update_project_snippet enable :admin_project_snippet end + + rule { ~can?(:read_project_snippet) }.prevent :create_note end diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb index 7b92fe6fe14a91a9ab31a2d998e8081cfc7c1072..bae98ede5612ff5edd646dde4d6b2f37c9f5b8c3 100644 --- a/app/services/notes/build_service.rb +++ b/app/services/notes/build_service.rb @@ -9,7 +9,7 @@ module Notes if in_reply_to_discussion_id.present? discussion = find_discussion(in_reply_to_discussion_id) - unless discussion + unless discussion && can?(current_user, :create_note, discussion.noteable) note = Note.new note.errors.add(:base, 'Discussion to reply to cannot be found') return note @@ -34,19 +34,8 @@ module Notes if project project.notes.find_discussion(discussion_id) else - discussion = Note.find_discussion(discussion_id) - noteable = discussion.noteable - - return nil unless noteable_without_project?(noteable) - - discussion + Note.find_discussion(discussion_id) end end - - def noteable_without_project?(noteable) - return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable) - - false - end end end diff --git a/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml b/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml new file mode 100644 index 0000000000000000000000000000000000000000..2f76064d8a48252b13f47142b6d10b4d15abb861 --- /dev/null +++ b/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml @@ -0,0 +1,5 @@ +--- +title: Prevent unauthorized replies when discussion is locked or confidential +merge_request: +author: +type: security diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb index ba9730d268589cce04208dfcbdb2ec5dcc5e0e8f..d8f4be8ada1c70e22acef14c35680a578b820300 100644 --- a/lib/gitlab/email/handler/reply_processing.rb +++ b/lib/gitlab/email/handler/reply_processing.rb @@ -56,7 +56,7 @@ module Gitlab raise ProjectNotFound unless author.can?(:read_project, project) end - raise UserNotAuthorizedError unless author.can?(permission, project || noteable) + raise UserNotAuthorizedError unless author.can?(permission, try(:noteable) || project) end def verify_record!(record:, invalid_exception:, record_name:) diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index b1f48c15c21bfb3f1d25911198b922232f4222e8..e5420ea6bea44dc852542b5bfb2f167399f7d3d7 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -118,6 +118,43 @@ describe Gitlab::Email::Handler::CreateNoteHandler do end end + shared_examples "checks permissions on noteable" do + context "when user has access" do + before do + project.add_reporter(user) + end + + it "creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + end + end + + context "when user does not have access" do + it "raises UserNotAuthorizedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) + end + end + end + + context "when discussion is locked" do + before do + noteable.update_attribute(:discussion_locked, true) + end + + it_behaves_like "checks permissions on noteable" + end + + context "when issue is confidential" do + let(:issue) { create(:issue, project: project) } + let(:note) { create(:note, noteable: issue, project: project) } + + before do + issue.update_attribute(:confidential, true) + end + + it_behaves_like "checks permissions on noteable" + end + context "when everything is fine" do before do setup_attachment diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 5ec04b99957080d317f8c0af7e1438e623f0044f..677613b798005d7d4fc1e28ed4e02a1b00faaf1c 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -48,7 +48,7 @@ describe SentNotification do let(:note) { create(:diff_note_on_merge_request) } it 'creates a new SentNotification' do - expect { described_class.record_note(note, user.id) }.to change { described_class.count }.by(1) + expect { described_class.record_note(note, note.author.id) }.to change { described_class.count }.by(1) end end diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb index 3809692b37357e0dfb2e285d394a7c0744ef8ab5..d7036cc4171c916d483d4eeeb375bed4dd5d77c8 100644 --- a/spec/policies/personal_snippet_policy_spec.rb +++ b/spec/policies/personal_snippet_policy_spec.rb @@ -14,6 +14,13 @@ describe PersonalSnippetPolicy do ] end + let(:comment_permissions) do + [ + :comment_personal_snippet, + :create_note + ] + end + def permissions(user) described_class.new(user, snippet) end @@ -26,7 +33,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -37,7 +44,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*comment_permissions) is_expected.to be_allowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -48,7 +55,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*comment_permissions) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end @@ -63,7 +70,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -74,7 +81,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*comment_permissions) is_expected.to be_allowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -85,7 +92,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -96,7 +103,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*comment_permissions) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end @@ -111,7 +118,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -122,7 +129,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -133,7 +140,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*comment_permissions) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -144,7 +151,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*comment_permissions) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index 4d32e06b553d0c7204382ccc9266190efd536f0a..d6329e845799c0ff28681a22b035bb2108f9bbe3 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -41,7 +41,7 @@ describe ProjectSnippetPolicy do subject { abilities(regular_user, :public) } it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -50,7 +50,7 @@ describe ProjectSnippetPolicy do subject { abilities(external_user, :public) } it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -70,7 +70,7 @@ describe ProjectSnippetPolicy do subject { abilities(regular_user, :internal) } it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -79,7 +79,7 @@ describe ProjectSnippetPolicy do subject { abilities(external_user, :internal) } it do - expect_disallowed(:read_project_snippet) + expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -92,7 +92,7 @@ describe ProjectSnippetPolicy do end it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -112,7 +112,7 @@ describe ProjectSnippetPolicy do subject { abilities(regular_user, :private) } it do - expect_disallowed(:read_project_snippet) + expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -123,7 +123,7 @@ describe ProjectSnippetPolicy do subject { described_class.new(regular_user, snippet) } it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_allowed(*author_permissions) end end @@ -136,7 +136,7 @@ describe ProjectSnippetPolicy do end it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -149,7 +149,7 @@ describe ProjectSnippetPolicy do end it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end end @@ -158,7 +158,7 @@ describe ProjectSnippetPolicy do subject { abilities(create(:admin), :private) } it do - expect_allowed(:read_project_snippet) + expect_allowed(:read_project_snippet, :create_note) expect_allowed(*author_permissions) end end diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index ff85c261cd422c3f9337ff9d391f6e5f4ee91c04..9aaccb4bffe04b7cc9cc9fc51efa1221ca452dc1 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -45,6 +45,15 @@ describe Notes::BuildService do end end + context 'when user has no access to discussion' do + it 'sets an error' do + another_user = create(:user) + new_note = described_class.new(project, another_user, note: 'Test', in_reply_to_discussion_id: note.discussion_id).execute + + expect(new_note.errors[:base]).to include('Discussion to reply to cannot be found') + end + end + context 'personal snippet note' do def reply(note, user = nil) user ||= create(:user) diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 80b015d4cd0fbb8c015a23ddf3072d3f5bc82585..1b9ba42cfd6e3e67339ddeb5a75f604447a6bfca 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -127,6 +127,10 @@ describe Notes::CreateService do create(:diff_note_on_merge_request, noteable: merge_request, project: project_with_repo) end + before do + project_with_repo.add_maintainer(user) + end + context 'when eligible to have a note diff file' do let(:new_opts) do opts.merge(in_reply_to_discussion_id: nil,