Merge branch 'security-guest-comments' into 'master'
[master]Fixed ability to comment on and edit/delete comments on locked or confidential issues See merge request gitlab/gitlabhq!2612
This commit is contained in:
parent
ffd1c4cd45
commit
e122e14ac6
13 changed files with 188 additions and 35 deletions
|
@ -6,6 +6,7 @@ module NotesActions
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
prepend_before_action :normalize_create_params, only: [:create]
|
||||
before_action :set_polling_interval_header, only: [:index]
|
||||
before_action :require_noteable!, only: [:index, :create]
|
||||
before_action :authorize_admin_note!, only: [:update, :destroy]
|
||||
|
@ -247,6 +248,15 @@ module NotesActions
|
|||
DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
|
||||
end
|
||||
|
||||
# Avoids checking permissions in the wrong object - this ensures that the object we checked permissions for
|
||||
# is the object we're actually creating a note in.
|
||||
def normalize_create_params
|
||||
params[:note].try do |note|
|
||||
note[:noteable_id] = params[:target_id]
|
||||
note[:noteable_type] = params[:target_type].classify
|
||||
end
|
||||
end
|
||||
|
||||
def note_project
|
||||
strong_memoize(:note_project) do
|
||||
next nil unless project
|
||||
|
|
|
@ -26,7 +26,7 @@ module Emails
|
|||
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
|
||||
end
|
||||
|
||||
def note_snippet_email(recipient_id, note_id)
|
||||
def note_project_snippet_email(recipient_id, note_id)
|
||||
setup_note_mail(note_id, recipient_id)
|
||||
|
||||
@snippet = @note.noteable
|
||||
|
|
|
@ -324,7 +324,7 @@ class Note < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def to_ability_name
|
||||
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
|
||||
for_snippet? ? noteable.class.name.underscore : noteable_type.underscore
|
||||
end
|
||||
|
||||
def can_be_discussion_note?
|
||||
|
|
|
@ -2,4 +2,6 @@
|
|||
|
||||
class CommitPolicy < BasePolicy
|
||||
delegate { @subject.project }
|
||||
|
||||
rule { can?(:download_code) }.enable :read_commit
|
||||
end
|
||||
|
|
|
@ -9,8 +9,17 @@ class NotePolicy < BasePolicy
|
|||
|
||||
condition(:editable, scope: :subject) { @subject.editable? }
|
||||
|
||||
condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") }
|
||||
|
||||
rule { ~editable }.prevent :admin_note
|
||||
|
||||
# If user can't read the issue/MR/etc then they should not be allowed to do anything to their own notes
|
||||
rule { ~can_read_noteable }.policy do
|
||||
prevent :read_note
|
||||
prevent :admin_note
|
||||
prevent :resolve_note
|
||||
end
|
||||
|
||||
rule { is_author }.policy do
|
||||
enable :read_note
|
||||
enable :admin_note
|
||||
|
|
5
changelogs/unreleased/security-guest-comments.yml
Normal file
5
changelogs/unreleased/security-guest-comments.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed ability to comment on locked/confidential issues.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
5
changelogs/unreleased/security-guest-comments_2.yml
Normal file
5
changelogs/unreleased/security-guest-comments_2.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed ability of guest users to edit/delete comments on locked or confidential issues.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -283,14 +283,14 @@ describe Projects::NotesController do
|
|||
|
||||
def post_create(extra_params = {})
|
||||
post :create, {
|
||||
note: { note: 'some other note' },
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
target_type: 'merge_request',
|
||||
target_id: merge_request.id,
|
||||
note_project_id: forked_project.id,
|
||||
in_reply_to_discussion_id: existing_comment.discussion_id
|
||||
}.merge(extra_params)
|
||||
note: { note: 'some other note', noteable_id: merge_request.id },
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
target_type: 'merge_request',
|
||||
target_id: merge_request.id,
|
||||
note_project_id: forked_project.id,
|
||||
in_reply_to_discussion_id: existing_comment.discussion_id
|
||||
}.merge(extra_params)
|
||||
end
|
||||
|
||||
context 'when the note_project_id is not correct' do
|
||||
|
@ -324,6 +324,30 @@ describe Projects::NotesController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when target_id and noteable_id do not match' do
|
||||
let(:locked_issue) { create(:issue, :locked, project: project) }
|
||||
let(:issue) {create(:issue, project: project)}
|
||||
|
||||
before do
|
||||
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_member(user).destroy
|
||||
end
|
||||
|
||||
it 'uses target_id and ignores noteable_id' do
|
||||
request_params = {
|
||||
note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
|
||||
target_type: 'issue',
|
||||
target_id: issue.id,
|
||||
project_id: project,
|
||||
namespace_id: project.namespace
|
||||
}
|
||||
|
||||
expect { post :create, request_params }.to change { issue.notes.count }.by(1)
|
||||
.and change { locked_issue.notes.count }.by(0)
|
||||
expect(response).to have_gitlab_http_status(302)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the merge request discussion is locked' do
|
||||
before do
|
||||
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
|
||||
|
@ -376,35 +400,60 @@ describe Projects::NotesController do
|
|||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
let(:request_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :json,
|
||||
note: {
|
||||
note: "New comment"
|
||||
context "should update the note with a valid issue" do
|
||||
let(:request_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :json,
|
||||
note: {
|
||||
note: "New comment"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(note.author)
|
||||
project.add_developer(note.author)
|
||||
end
|
||||
before do
|
||||
sign_in(note.author)
|
||||
project.add_developer(note.author)
|
||||
end
|
||||
|
||||
it "updates the note" do
|
||||
expect { put :update, request_params }.to change { note.reload.note }
|
||||
it "updates the note" do
|
||||
expect { put :update, request_params }.to change { note.reload.note }
|
||||
end
|
||||
end
|
||||
context "doesnt update the note" do
|
||||
let(:issue) { create(:issue, :confidential, project: project) }
|
||||
let(:note) { create(:note, noteable: issue, project: project) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it "disallows edits when the issue is confidential and the user has guest permissions" do
|
||||
request_params = {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :json,
|
||||
note: {
|
||||
note: "New comment"
|
||||
}
|
||||
}
|
||||
expect { put :update, request_params }.not_to change { note.reload.note }
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
let(:request_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :js
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :js
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -522,7 +522,7 @@ describe Notify do
|
|||
let(:project_snippet) { create(:project_snippet, project: project) }
|
||||
let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) }
|
||||
|
||||
subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
|
||||
subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
|
||||
|
||||
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
|
||||
let(:model) { project_snippet }
|
||||
|
|
|
@ -517,7 +517,7 @@ describe Note do
|
|||
|
||||
describe '#to_ability_name' do
|
||||
it 'returns snippet for a project snippet note' do
|
||||
expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet')
|
||||
expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet')
|
||||
end
|
||||
|
||||
it 'returns personal_snippet for a personal snippet note' do
|
||||
|
|
|
@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do
|
|||
return @policies if @policies
|
||||
|
||||
noteable ||= issue
|
||||
note = create(:note, noteable: noteable, author: user, project: project)
|
||||
note = if noteable.is_a?(Commit)
|
||||
create(:note_on_commit, commit_id: noteable.id, author: user, project: project)
|
||||
else
|
||||
create(:note, noteable: noteable, author: user, project: project)
|
||||
end
|
||||
|
||||
@policies = described_class.new(user, note)
|
||||
end
|
||||
|
||||
shared_examples_for 'a discussion with a private noteable' do
|
||||
let(:noteable) { issue }
|
||||
let(:policy) { policies(noteable) }
|
||||
|
||||
context 'when the note author can no longer see the noteable' do
|
||||
it 'can not edit nor read the note' do
|
||||
expect(policy).to be_disallowed(:admin_note)
|
||||
expect(policy).to be_disallowed(:resolve_note)
|
||||
expect(policy).to be_disallowed(:read_note)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the note author can still see the noteable' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'can edit the note' do
|
||||
expect(policy).to be_allowed(:admin_note)
|
||||
expect(policy).to be_allowed(:resolve_note)
|
||||
expect(policy).to be_allowed(:read_note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is private' do
|
||||
let(:project) { create(:project, :private, :repository) }
|
||||
|
||||
context 'when the noteable is a commit' do
|
||||
it_behaves_like 'a discussion with a private noteable' do
|
||||
let(:noteable) { project.repository.head_commit }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is public' do
|
||||
context 'when the note author is not a project member' do
|
||||
it 'can edit a note' do
|
||||
|
@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the noteable is a snippet' do
|
||||
context 'when the noteable is a project snippet' do
|
||||
it 'can edit note' do
|
||||
policies = policies(create(:project_snippet, project: project))
|
||||
policies = policies(create(:project_snippet, :public, project: project))
|
||||
|
||||
expect(policies).to be_allowed(:admin_note)
|
||||
expect(policies).to be_allowed(:resolve_note)
|
||||
expect(policies).to be_allowed(:read_note)
|
||||
end
|
||||
|
||||
context 'when it is private' do
|
||||
it_behaves_like 'a discussion with a private noteable' do
|
||||
let(:noteable) { create(:project_snippet, :private, project: project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the noteable is a personal snippet' do
|
||||
it 'can edit note' do
|
||||
policies = policies(create(:personal_snippet, :public))
|
||||
|
||||
expect(policies).to be_allowed(:admin_note)
|
||||
expect(policies).to be_allowed(:resolve_note)
|
||||
expect(policies).to be_allowed(:read_note)
|
||||
end
|
||||
|
||||
context 'when it is private' do
|
||||
it 'can not edit nor read the note' do
|
||||
policies = policies(create(:personal_snippet, :private))
|
||||
|
||||
expect(policies).to be_disallowed(:admin_note)
|
||||
expect(policies).to be_disallowed(:resolve_note)
|
||||
expect(policies).to be_disallowed(:read_note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a discussion is confidential' do
|
||||
before do
|
||||
issue.update_attribute(:confidential, true)
|
||||
end
|
||||
|
||||
it_behaves_like 'a discussion with a private noteable'
|
||||
end
|
||||
|
||||
context 'when a discussion is locked' do
|
||||
|
|
Loading…
Reference in a new issue