Commit 5eeb2ecc authored by Sean McGivern's avatar Sean McGivern

Merge branch 'ajk-198120-private-design-comments' into 'master'

Ensure events on designs are visible to the appropriate users

Closes #198120

See merge request gitlab-org/gitlab!26708
parents 392e50ff 31f7cd05
...@@ -135,29 +135,11 @@ class Event < ApplicationRecord ...@@ -135,29 +135,11 @@ class Event < ApplicationRecord
super(presenter_class: ::EventPresenter) super(presenter_class: ::EventPresenter)
end end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil) def visible_to_user?(user = nil)
if push_action? || commit_note? return false unless capability.present?
Ability.allowed?(user, :download_code, project)
elsif membership_changed? Ability.allowed?(user, capability, permission_object)
Ability.allowed?(user, :read_project, project)
elsif created_project_action?
Ability.allowed?(user, :read_project, project)
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif personal_snippet_note? || project_snippet_note?
Ability.allowed?(user, :read_snippet, note_target)
elsif milestone?
Ability.allowed?(user, :read_milestone, project)
else
false # No other event types are visible
end
end end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def resource_parent def resource_parent
project || group project || group
...@@ -364,8 +346,38 @@ class Event < ApplicationRecord ...@@ -364,8 +346,38 @@ class Event < ApplicationRecord
Event._to_partial_path Event._to_partial_path
end end
protected
def capability
@capability ||= begin
if push_action? || commit_note?
:download_code
elsif membership_changed? || created_project_action?
:read_project
elsif issue? || issue_note?
:read_issue
elsif merge_request? || merge_request_note?
:read_merge_request
elsif personal_snippet_note? || project_snippet_note?
:read_snippet
elsif milestone?
:read_milestone
end
end
end
private private
def permission_object
if note?
note_target
elsif target_id.present?
target
else
project
end
end
def push_action_name def push_action_name
if new_ref? if new_ref?
"pushed new" "pushed new"
......
...@@ -18,15 +18,21 @@ module EE ...@@ -18,15 +18,21 @@ module EE
scope :epics, -> { where(target_type: 'Epic') } scope :epics, -> { where(target_type: 'Epic') }
end end
override :visible_to_user? override :capability
def visible_to_user?(user = nil) def capability
if epic? @capability ||= begin
Ability.allowed?(user, :read_epic, target) if epic? || epic_note?
elsif epic_note? :read_epic
Ability.allowed?(user, :read_epic, note_target) elsif design_note?
else :read_design
super else
end super
end
end
end
def design_note?
note? && note.for_design?
end end
def epic_note? def epic_note?
......
---
title: Ensure design events are correctly visible
merge_request: 26708
author:
type: fixed
...@@ -9,5 +9,15 @@ FactoryBot.modify do ...@@ -9,5 +9,15 @@ FactoryBot.modify do
action { Event::CREATED } action { Event::CREATED }
project { nil } project { nil }
end end
trait :for_design do
transient do
design { create(:design, issue: create(:issue, project: project)) }
note { create(:note, project: project, noteable: design) }
end
action { Event::COMMENTED }
target { note }
end
end end
end end
...@@ -8,7 +8,17 @@ FactoryBot.modify do ...@@ -8,7 +8,17 @@ FactoryBot.modify do
end end
trait :on_design do trait :on_design do
noteable { create(:design, :with_file, issue: create(:issue, project: project)) } transient do
issue { create(:issue, project: project) }
end
noteable { create(:design, :with_file, issue: issue) }
after(:build) do |note|
next if note.project == note.noteable.project
# note validations require consistency between these two objects
note.project = note.noteable.project
end
end end
trait :with_review do trait :with_review do
......
...@@ -77,7 +77,6 @@ describe Notify do ...@@ -77,7 +77,6 @@ describe Notify do
let_it_be(:note) do let_it_be(:note) do
create(:diff_note_on_design, create(:diff_note_on_design,
noteable: design, noteable: design,
project: design.project,
note: "Hello #{recipient.to_reference}") note: "Hello #{recipient.to_reference}")
end end
......
...@@ -398,20 +398,20 @@ describe DesignManagement::Design do ...@@ -398,20 +398,20 @@ describe DesignManagement::Design do
# Note: Cache invalidation tests are in `design_user_notes_count_service_spec.rb` # Note: Cache invalidation tests are in `design_user_notes_count_service_spec.rb`
it 'returns a count of user-generated notes' do it 'returns a count of user-generated notes' do
create(:diff_note_on_design, noteable: design, project: design.project) create(:diff_note_on_design, noteable: design)
is_expected.to eq(1) is_expected.to eq(1)
end end
it 'does not count notes on other designs' do it 'does not count notes on other designs' do
second_design = create(:design, :with_file) second_design = create(:design, :with_file)
create(:diff_note_on_design, noteable: second_design, project: second_design.project) create(:diff_note_on_design, noteable: second_design)
is_expected.to eq(0) is_expected.to eq(0)
end end
it 'does not count system notes' do it 'does not count system notes' do
create(:diff_note_on_design, system: true, noteable: design, project: design.project) create(:diff_note_on_design, system: true, noteable: design)
is_expected.to eq(0) is_expected.to eq(0)
end end
......
...@@ -12,7 +12,7 @@ describe DiffNote do ...@@ -12,7 +12,7 @@ describe DiffNote do
context 'diff files' do context 'diff files' do
let(:design) { create(:design, :with_file, versions_count: 2) } let(:design) { create(:design, :with_file, versions_count: 2) }
let(:diff_note) { create(:diff_note_on_design, noteable: design, project: design.project) } let(:diff_note) { create(:diff_note_on_design, noteable: design) }
describe '#latest_diff_file' do describe '#latest_diff_file' do
it 'does not return a diff file' do it 'does not return a diff file' do
......
...@@ -7,33 +7,107 @@ describe Event do ...@@ -7,33 +7,107 @@ describe Event do
let_it_be(:non_member) { create(:user) } let_it_be(:non_member) { create(:user) }
let_it_be(:member) { create(:user) } let_it_be(:member) { create(:user) }
let_it_be(:guest) { create(:user) } let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:author) { create(:author) } let_it_be(:author) { create(:author) }
let_it_be(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
let_it_be(:project) { create(:project) }
let(:users) { [non_member, member, reporter, guest, author, admin] }
let(:epic) { create(:epic, group: group, author: author) } let(:epic) { create(:epic, group: group, author: author) }
let(:note_on_epic) { create(:note, :on_epic, noteable: epic) } let(:note_on_epic) { create(:note, :on_epic, noteable: epic) }
let(:event) { described_class.new(group: group, target: target, author: author) } let(:event) { described_class.new(group: group, target: target, author: author) }
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
group.add_developer(member)
group.add_guest(guest) project.add_developer(member)
project.add_guest(guest)
project.add_reporter(reporter)
if defined?(group)
group.add_developer(member)
group.add_guest(guest)
end
end
RSpec::Matchers.define :be_visible_to do |user|
match do |event|
event.visible_to_user?(user)
end
failure_message do |event|
"expected that #{event} should be visible to #{user}"
end
failure_message_when_negated do |event|
"expected that #{event} would not be visible to #{user}"
end
end
RSpec::Matchers.define :have_access_to do |event|
match do |user|
event.visible_to_user?(user)
end
failure_message do |user|
"expected that #{event} should be visible to #{user}"
end
failure_message_when_negated do |user|
"expected that #{event} would not be visible to #{user}"
end
end end
shared_examples 'visible to group members only' do shared_examples 'visible to group members only' do
it 'is not visible to other users' do it 'is not visible to other users', :aggregate_failures do
expect(event.visible_to_user?(non_member)).to eq false expect(event).not_to be_visible_to(non_member)
expect(event.visible_to_user?(member)).to eq true expect(event).not_to be_visible_to(author)
expect(event.visible_to_user?(guest)).to eq true
expect(event.visible_to_user?(admin)).to eq true expect(event).to be_visible_to(member)
expect(event).to be_visible_to(guest)
expect(event).to be_visible_to(admin)
end end
end end
shared_examples 'visible to everybody' do shared_examples 'visible to everybody' do
it 'is visible to other users' do it 'is visible to other users', :aggregate_failures do
expect(event.visible_to_user?(non_member)).to eq true expect(users).to all(have_access_to(event))
expect(event.visible_to_user?(member)).to eq true end
expect(event.visible_to_user?(guest)).to eq true end
expect(event.visible_to_user?(admin)).to eq true
context 'design event' do
include DesignManagementTestHelpers
before do
enable_design_management
end
it_behaves_like 'visible to group members only' do
let(:event) { create(:event, :for_design, project: project) }
end
context 'the event refers to a design on a confidential issue' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, :confidential, project: project) }
let(:note) { create(:note, :on_design, issue: issue) }
let(:event) { create(:event, project: project, target: note) }
let(:assignees) do
create_list(:user, 3).each { |user| issue.assignees << user }
end
it 'visible to group reporters, the issue author, and assignees', :aggregate_failures do
expect(event).not_to be_visible_to(non_member)
expect(event).not_to be_visible_to(guest)
expect(event).to be_visible_to(reporter)
expect(event).to be_visible_to(member)
expect(event).to be_visible_to(admin)
expect(event).to be_visible_to(issue.author)
expect(assignees).to all(have_access_to(event))
end
end end
end end
......
...@@ -318,7 +318,7 @@ describe 'Getting designs related to an issue' do ...@@ -318,7 +318,7 @@ describe 'Getting designs related to an issue' do
end end
describe 'a design with note annotations' do describe 'a design with note annotations' do
let_it_be(:note) { create(:diff_note_on_design, noteable: design, project: design.project) } let_it_be(:note) { create(:diff_note_on_design, noteable: design) }
let(:design_query) do let(:design_query) do
<<~NODE <<~NODE
......
...@@ -11,7 +11,7 @@ describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_ ...@@ -11,7 +11,7 @@ describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_
describe '#count' do describe '#count' do
it 'returns the count of notes' do it 'returns the count of notes' do
create_list(:diff_note_on_design, 3, noteable: design, project: design.project) create_list(:diff_note_on_design, 3, noteable: design)
expect(subject.count).to eq(3) expect(subject.count).to eq(3)
end end
...@@ -33,7 +33,7 @@ describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_ ...@@ -33,7 +33,7 @@ describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_
end end
it 'changes when a note is destroyed' do it 'changes when a note is destroyed' do
note = create(:diff_note_on_design, noteable: design, project: design.project) note = create(:diff_note_on_design, noteable: design)
expect do expect do
Notes::DestroyService.new(note.project, note.author).execute(note) Notes::DestroyService.new(note.project, note.author).execute(note)
......
...@@ -11,7 +11,7 @@ describe Notes::PostProcessService do ...@@ -11,7 +11,7 @@ describe Notes::PostProcessService do
subject { described_class.new(note).execute } subject { described_class.new(note).execute }
def create_note(in_reply_to: nil) def create_note(in_reply_to: nil)
create(:diff_note_on_design, noteable: noteable, project: noteable.project, in_reply_to: in_reply_to) create(:diff_note_on_design, noteable: noteable, in_reply_to: in_reply_to)
end end
context 'when the note is the start of a new discussion' do context 'when the note is the start of a new discussion' do
......
...@@ -19,7 +19,6 @@ describe EE::NotificationService, :mailer do ...@@ -19,7 +19,6 @@ describe EE::NotificationService, :mailer do
let_it_be(:note) do let_it_be(:note) do
create(:diff_note_on_design, create(:diff_note_on_design,
noteable: design, noteable: design,
project: project,
note: "Hello #{dev.to_reference}, G'day #{stranger.to_reference}") note: "Hello #{dev.to_reference}, G'day #{stranger.to_reference}")
end end
......
...@@ -132,7 +132,7 @@ describe EE::SystemNotes::DesignManagementService do ...@@ -132,7 +132,7 @@ describe EE::SystemNotes::DesignManagementService do
let(:design) { create(:design, :with_file, issue: issue) } let(:design) { create(:design, :with_file, issue: issue) }
let(:author) { create(:user) } let(:author) { create(:user) }
let(:discussion_note) do let(:discussion_note) do
create(:diff_note_on_design, noteable: design, author: author, project: project) create(:diff_note_on_design, noteable: design, author: author)
end end
let(:action) { 'designs_discussion_added' } let(:action) { 'designs_discussion_added' }
......
...@@ -341,7 +341,6 @@ describe TodoService do ...@@ -341,7 +341,6 @@ describe TodoService do
let(:note) do let(:note) do
build(:diff_note_on_design, build(:diff_note_on_design,
project: project,
noteable: design, noteable: design,
author: author, author: author,
note: "Hey #{john_doe.to_reference}") note: "Hey #{john_doe.to_reference}")
......
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