Commit 3839c16a authored by Jan Provaznik's avatar Jan Provaznik

Do not allow to change note's confidentiality

* existing note's confidentiality can not be updated
* it can be set only for new notes

Changelog: changed
parent ce3e9b18
...@@ -97,6 +97,8 @@ class Note < ApplicationRecord ...@@ -97,6 +97,8 @@ class Note < ApplicationRecord
validates :author, presence: true validates :author, presence: true
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ } validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
validate :ensure_confidentiality_not_changed, on: :update
validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note| validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
unless note.noteable.try(:project) == note.project unless note.noteable.try(:project) == note.project
errors.add(:project, 'does not match noteable project') errors.add(:project, 'does not match noteable project')
...@@ -718,6 +720,13 @@ class Note < ApplicationRecord ...@@ -718,6 +720,13 @@ class Note < ApplicationRecord
def noteable_label_url_method def noteable_label_url_method
for_merge_request? ? :project_merge_requests_url : :project_issues_url for_merge_request? ? :project_merge_requests_url : :project_issues_url
end end
def ensure_confidentiality_not_changed
return unless will_save_change_to_attribute?(:confidential)
return unless attribute_change_to_be_saved(:confidential).include?(true)
errors.add(:confidential, _('can not be changed for existing notes'))
end
end end
Note.prepend_mod_with('Note') Note.prepend_mod_with('Note')
...@@ -27,10 +27,7 @@ module Notes ...@@ -27,10 +27,7 @@ module Notes
note.assign_attributes(last_edited_at: Time.current, updated_by: current_user) note.assign_attributes(last_edited_at: Time.current, updated_by: current_user)
end end
note.with_transaction_returning_status do
update_confidentiality(note)
note.save note.save
end
unless only_commands || note.for_personal_snippet? unless only_commands || note.for_personal_snippet?
note.create_new_cross_references!(current_user) note.create_new_cross_references!(current_user)
...@@ -88,15 +85,6 @@ module Notes ...@@ -88,15 +85,6 @@ module Notes
TodoService.new.update_note(note, current_user, old_mentioned_users) TodoService.new.update_note(note, current_user, old_mentioned_users)
end end
# This method updates confidentiality of all discussion notes at once
def update_confidentiality(note)
return unless params.key?(:confidential)
return unless note.is_a?(DiscussionNote) # we don't need to do bulk update for single notes
return unless note.start_of_discussion? # don't update all notes if a response is being updated
Note.id_in(note.discussion.notes.map(&:id)).update_all(confidential: params[:confidential])
end
def track_note_edit_usage_for_issues(note) def track_note_edit_usage_for_issues(note)
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_edited_action(author: note.author) Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_edited_action(author: note.author)
end end
......
...@@ -43868,6 +43868,9 @@ msgstr "" ...@@ -43868,6 +43868,9 @@ msgstr ""
msgid "can contain only lowercase letters, digits, and '_'." msgid "can contain only lowercase letters, digits, and '_'."
msgstr "" msgstr ""
msgid "can not be changed for existing notes"
msgstr ""
msgid "can only be changed by a group admin." msgid "can only be changed by a group admin."
msgstr "" msgstr ""
......
...@@ -105,6 +105,36 @@ RSpec.describe Note do ...@@ -105,6 +105,36 @@ RSpec.describe Note do
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
end end
describe 'confidentiality' do
context 'for existing public note' do
let_it_be(:existing_note) { create(:note) }
it 'is not possible to change the note to confidential' do
existing_note.confidential = true
expect(existing_note).not_to be_valid
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
end
it 'is possible to change confidentiality from nil to false' do
existing_note.confidential = false
expect(existing_note).to be_valid
end
end
context 'for existing confidential note' do
let_it_be(:existing_note) { create(:note, confidential: true) }
it 'is not possible to change the note to public' do
existing_note.confidential = false
expect(existing_note).not_to be_valid
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
end
end
end
end end
describe 'callbacks' do describe 'callbacks' do
......
...@@ -8,7 +8,7 @@ RSpec.describe 'Updating a Note' do ...@@ -8,7 +8,7 @@ RSpec.describe 'Updating a Note' do
let!(:note) { create(:note, note: original_body) } let!(:note) { create(:note, note: original_body) }
let(:original_body) { 'Initial body text' } let(:original_body) { 'Initial body text' }
let(:updated_body) { 'Updated body text' } let(:updated_body) { 'Updated body text' }
let(:params) { { body: updated_body, confidential: true } } let(:params) { { body: updated_body } }
let(:mutation) do let(:mutation) do
variables = params.merge(id: GitlabSchema.id_from_object(note).to_s) variables = params.merge(id: GitlabSchema.id_from_object(note).to_s)
...@@ -28,7 +28,6 @@ RSpec.describe 'Updating a Note' do ...@@ -28,7 +28,6 @@ RSpec.describe 'Updating a Note' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(original_body) expect(note.reload.note).to eq(original_body)
expect(note.confidential).to be_falsey
end end
end end
...@@ -41,46 +40,19 @@ RSpec.describe 'Updating a Note' do ...@@ -41,46 +40,19 @@ RSpec.describe 'Updating a Note' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(updated_body) expect(note.reload.note).to eq(updated_body)
expect(note.confidential).to be_truthy
end end
it 'returns the updated Note' do it 'returns the updated Note' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['body']).to eq(updated_body) expect(mutation_response['note']['body']).to eq(updated_body)
expect(mutation_response['note']['confidential']).to be_truthy
end
context 'when only confidential param is present' do
let(:params) { { confidential: true } }
it 'updates only the note confidentiality' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(original_body)
expect(note.confidential).to be_truthy
end
end
context 'when only body param is present' do
let(:params) { { body: updated_body } }
before do
note.update_column(:confidential, true)
end
it 'updates only the note body' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(updated_body)
expect(note.confidential).to be_truthy
end
end end
context 'when there are ActiveRecord validation errors' do context 'when there are ActiveRecord validation errors' do
let(:updated_body) { '' } let(:params) { { body: '', confidential: true } }
it_behaves_like 'a mutation that returns errors in the response', errors: ["Note can't be blank"] it_behaves_like 'a mutation that returns errors in the response',
errors: ["Note can't be blank", 'Confidential can not be changed for existing notes']
it 'does not update the Note' do it 'does not update the Note' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
......
...@@ -138,45 +138,6 @@ RSpec.describe Notes::UpdateService do ...@@ -138,45 +138,6 @@ RSpec.describe Notes::UpdateService do
end end
end end
context 'setting confidentiality' do
let(:opts) { { confidential: true } }
context 'simple note' do
it 'updates the confidentiality' do
expect { update_note(opts) }.to change { note.reload.confidential }.from(nil).to(true)
end
end
context 'discussion notes' do
let(:note) { create(:discussion_note, project: project, noteable: issue, author: user, note: "Old note #{user2.to_reference}") }
let!(:response_note_1) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note) }
let!(:response_note_2) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note, confidential: false) }
let!(:other_note) { create(:note, project: project, noteable: issue) }
context 'when updating the root note' do
it 'updates the confidentiality of the root note and all the responses' do
update_note(opts)
expect(note.reload.confidential).to be_truthy
expect(response_note_1.reload.confidential).to be_truthy
expect(response_note_2.reload.confidential).to be_truthy
expect(other_note.reload.confidential).to be_falsey
end
end
context 'when updating one of the response notes' do
it 'updates only the confidentiality of the note that is being updated' do
Notes::UpdateService.new(project, user, opts).execute(response_note_1)
expect(note.reload.confidential).to be_falsey
expect(response_note_1.reload.confidential).to be_truthy
expect(response_note_2.reload.confidential).to be_falsey
expect(other_note.reload.confidential).to be_falsey
end
end
end
end
context 'todos' do context 'todos' do
shared_examples 'does not update todos' do shared_examples 'does not update todos' do
it 'keep todos' do it 'keep todos' do
......
...@@ -227,9 +227,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name ...@@ -227,9 +227,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end end
context 'for confidential notes' do context 'for confidential notes' do
before_all do let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note", confidential: true) }
issue_note.update!(confidential: true)
end
it 'falls back to note channel' do it 'falls back to note channel' do
expect(::Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
......
...@@ -306,18 +306,13 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| ...@@ -306,18 +306,13 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end end
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
let(:params) { { body: 'Hello!', confidential: false } } let(:params) { { body: 'Hello!' } }
subject do subject do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params
end end
context 'when eveything is ok' do context 'when eveything is ok' do
before do
note.update!(confidential: true)
end
context 'with multiple params present' do
before do before do
subject subject
end end
...@@ -325,7 +320,6 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| ...@@ -325,7 +320,6 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
it 'returns modified note' do it 'returns modified note' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['body']).to eq('Hello!') expect(json_response['body']).to eq('Hello!')
expect(json_response['confidential']).to be_falsey
end end
it 'updates the note' do it 'updates the note' do
...@@ -334,24 +328,13 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| ...@@ -334,24 +328,13 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end end
end end
context 'when only body param is present' do context 'when also confidential param is set' do
let(:params) { { body: 'Hello!' } } let(:params) { { body: 'Hello!', confidential: true } }
it 'updates only the note text' do
expect { subject }.not_to change { note.reload.confidential }
expect(note.note).to eq('Hello!')
end
end
context 'when only confidential param is present' do
let(:params) { { confidential: false } }
it 'updates only the note text' do it 'fails to update the note' do
expect { subject }.not_to change { note.reload.note } expect { subject }.not_to change { note.reload.note }
expect(note.confidential).to be_falsey expect(response).to have_gitlab_http_status(:bad_request)
end
end end
end end
......
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