Commit 689840d5 authored by Lee Tickett's avatar Lee Tickett Committed by Heinrich Lee Yu

Default confidentiality of replies & observe permissions

parent cb5ba178
...@@ -319,6 +319,7 @@ class Note < ApplicationRecord ...@@ -319,6 +319,7 @@ class Note < ApplicationRecord
def noteable_assignee_or_author?(user) def noteable_assignee_or_author?(user)
return false unless user return false unless user
return false unless noteable.respond_to?(:author_id)
return noteable.assignee_or_author?(user) if [MergeRequest, Issue].include?(noteable.class) return noteable.assignee_or_author?(user) if [MergeRequest, Issue].include?(noteable.class)
noteable.author_id == user.id noteable.author_id == user.id
......
...@@ -57,6 +57,10 @@ class NotePolicy < BasePolicy ...@@ -57,6 +57,10 @@ class NotePolicy < BasePolicy
enable :resolve_note enable :resolve_note
end end
rule { can_read_confidential }.policy do
enable :mark_note_as_confidential
end
rule { confidential & ~can_read_confidential }.policy do rule { confidential & ~can_read_confidential }.policy do
prevent :read_note prevent :read_note
prevent :admin_note prevent :admin_note
......
...@@ -3,32 +3,36 @@ ...@@ -3,32 +3,36 @@
module Notes module Notes
class BuildService < ::BaseService class BuildService < ::BaseService
def execute def execute
should_resolve = false
in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id) in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id)
discussion = nil
if in_reply_to_discussion_id.present? if in_reply_to_discussion_id.present?
discussion = find_discussion(in_reply_to_discussion_id) discussion = find_discussion(in_reply_to_discussion_id)
unless discussion && can?(current_user, :create_note, discussion.noteable) return discussion_not_found 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
end
discussion = discussion.convert_to_discussion! if discussion.can_convert_to_discussion? discussion = discussion.convert_to_discussion! if discussion.can_convert_to_discussion?
params.merge!(discussion.reply_attributes) params.merge!(discussion.reply_attributes)
should_resolve = discussion.resolved?
end end
new_note(params, discussion)
end
private
def new_note(params, discussion)
note = Note.new(params) note = Note.new(params)
note.project = project note.project = project
note.author = current_user note.author = current_user
if should_resolve parent_confidential = discussion&.confidential?
note.resolve_without_save(current_user) can_set_confidential = can?(current_user, :mark_note_as_confidential, note)
end
return discussion_not_found if parent_confidential && !can_set_confidential
note.confidential = (parent_confidential.nil? && can_set_confidential ? params.delete(:confidential) : parent_confidential)
note.resolve_without_save(current_user) if discussion&.resolved?
note note
end end
...@@ -39,5 +43,11 @@ module Notes ...@@ -39,5 +43,11 @@ module Notes
Note.find_discussion(discussion_id) Note.find_discussion(discussion_id)
end end
end end
def discussion_not_found
note = Note.new
note.errors.add(:base, _('Discussion to reply to cannot be found'))
note
end
end end
end end
---
title: Default confidentiality of replies
merge_request: 54122
author: Lee Tickett @leetickett
type: added
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Notes::BuildService do RSpec.describe Notes::BuildService do
include AdminModeHelper
let(:note) { create(:discussion_note_on_issue) } let(:note) { create(:discussion_note_on_issue) }
let(:project) { note.project } let(:project) { note.project }
let(:author) { note.author } let(:author) { note.author }
...@@ -147,6 +149,123 @@ RSpec.describe Notes::BuildService do ...@@ -147,6 +149,123 @@ RSpec.describe Notes::BuildService do
end end
end end
context 'confidential comments' do
before do
project.add_reporter(author)
end
context 'when replying to a confidential comment' do
let(:note) { create(:note_on_issue, confidential: true) }
context 'when the user can read confidential comments' do
subject do
described_class.new(
project,
author,
note: 'Test',
in_reply_to_discussion_id: note.discussion_id,
confidential: false
).execute
end
it '`confidential` param is ignored and set to `true`' do
expect(subject.confidential).to be_truthy
end
end
context 'when the user cannot read confidential comments' do
let(:another_user) { create(:user) }
subject do
described_class.new(
project,
another_user,
note: 'Test',
in_reply_to_discussion_id: note.discussion_id,
confidential: false
).execute
end
it 'returns `Discussion to reply to cannot be found` error' do
expect(subject.errors.first).to include("Discussion to reply to cannot be found")
end
end
end
context 'when replying to a public comment' do
let(:note) { create(:note_on_issue, confidential: false) }
subject do
described_class.new(
project,
author,
note: 'Test',
in_reply_to_discussion_id: note.discussion_id,
confidential: true
).execute
end
it '`confidential` param is ignored and set to `false`' do
expect(subject.confidential).to be_falsey
end
end
context 'when creating a new comment' do
context 'when the `confidential` note flag is set to `true`' do
context 'when the user is allowed (reporter)' do
subject { described_class.new(project, author, note: 'Test', noteable: merge_request, confidential: true).execute }
it 'note `confidential` flag is set to `true`' do
expect(subject.confidential).to be_truthy
end
end
context 'when the user is allowed (issuable author)' do
let(:another_user) { create(:user) }
let(:issue) { create(:issue, author: another_user) }
subject { described_class.new(project, another_user, note: 'Test', noteable: issue, confidential: true).execute }
it 'note `confidential` flag is set to `true`' do
expect(subject.confidential).to be_truthy
end
end
context 'when the user is allowed (admin)' do
before do
enable_admin_mode!(another_user)
end
let(:another_user) { create(:admin) }
subject { described_class.new(project, another_user, note: 'Test', noteable: merge_request, confidential: true).execute }
it 'note `confidential` flag is set to `true`' do
expect(subject.confidential).to be_truthy
end
end
context 'when the user is not allowed' do
let(:another_user) { create(:user) }
subject { described_class.new(project, another_user, note: 'Test', noteable: merge_request, confidential: true).execute }
it 'note `confidential` flag is set to `false`' do
expect(subject.confidential).to be_falsey
end
end
end
context 'when the `confidential` note flag is set to `false`' do
subject { described_class.new(project, author, note: 'Test', noteable: merge_request, confidential: false).execute }
it 'note `confidential` flag is set to `false`' do
expect(subject.confidential).to be_falsey
end
end
end
end
it 'builds a note without saving it' do it 'builds a note without saving it' do
new_note = described_class.new(project, new_note = described_class.new(project,
author, author,
......
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