Check if user can read issue before being assigned
This commit is contained in:
parent
77deeb12f7
commit
1b082a4c33
14 changed files with 189 additions and 34 deletions
|
@ -93,9 +93,9 @@ module Issuable
|
|||
|
||||
def update_assignee_cache_counts
|
||||
# make sure we flush the cache for both the old *and* new assignees(if they exist)
|
||||
previous_assignee = User.find_by_id(assignee_id_was)
|
||||
previous_assignee.try(:update_cache_counts)
|
||||
assignee.try(:update_cache_counts)
|
||||
previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
|
||||
previous_assignee.update_cache_counts if previous_assignee
|
||||
assignee.update_cache_counts if assignee
|
||||
end
|
||||
|
||||
# We want to use optimistic lock for cases when only title or description are involved
|
||||
|
|
|
@ -36,14 +36,10 @@ class IssuableBaseService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def filter_params(issuable_ability_name = :issue)
|
||||
filter_assignee
|
||||
filter_milestone
|
||||
filter_labels
|
||||
def filter_params(issuable)
|
||||
ability_name = :"admin_#{issuable.to_ability_name}"
|
||||
|
||||
ability = :"admin_#{issuable_ability_name}"
|
||||
|
||||
unless can?(current_user, ability, project)
|
||||
unless can?(current_user, ability_name, project)
|
||||
params.delete(:milestone_id)
|
||||
params.delete(:labels)
|
||||
params.delete(:add_label_ids)
|
||||
|
@ -52,14 +48,35 @@ class IssuableBaseService < BaseService
|
|||
params.delete(:assignee_id)
|
||||
params.delete(:due_date)
|
||||
end
|
||||
|
||||
filter_assignee(issuable)
|
||||
filter_milestone
|
||||
filter_labels
|
||||
end
|
||||
|
||||
def filter_assignee
|
||||
if params[:assignee_id] == IssuableFinder::NONE
|
||||
params[:assignee_id] = ''
|
||||
def filter_assignee(issuable)
|
||||
return unless params[:assignee_id].present?
|
||||
|
||||
assignee_id = params[:assignee_id]
|
||||
|
||||
if assignee_id.to_s == IssuableFinder::NONE
|
||||
params[:assignee_id] = ""
|
||||
else
|
||||
params.delete(:assignee_id) unless assignee_can_read?(issuable, assignee_id)
|
||||
end
|
||||
end
|
||||
|
||||
def assignee_can_read?(issuable, assignee_id)
|
||||
new_assignee = User.find_by_id(assignee_id)
|
||||
|
||||
return false unless new_assignee.present?
|
||||
|
||||
ability_name = :"read_#{issuable.to_ability_name}"
|
||||
resource = issuable.persisted? ? issuable : project
|
||||
|
||||
can?(new_assignee, ability_name, resource)
|
||||
end
|
||||
|
||||
def filter_milestone
|
||||
milestone_id = params[:milestone_id]
|
||||
return unless milestone_id
|
||||
|
@ -138,7 +155,7 @@ class IssuableBaseService < BaseService
|
|||
|
||||
def create(issuable)
|
||||
merge_slash_commands_into_params!(issuable)
|
||||
filter_params
|
||||
filter_params(issuable)
|
||||
|
||||
params.delete(:state_event)
|
||||
params[:author] ||= current_user
|
||||
|
@ -180,7 +197,7 @@ class IssuableBaseService < BaseService
|
|||
change_state(issuable)
|
||||
change_subscription(issuable)
|
||||
change_todo(issuable)
|
||||
filter_params
|
||||
filter_params(issuable)
|
||||
old_labels = issuable.labels.to_a
|
||||
old_mentioned_users = issuable.mentioned_users.to_a
|
||||
|
||||
|
|
|
@ -17,10 +17,6 @@ module Issues
|
|||
|
||||
private
|
||||
|
||||
def filter_params
|
||||
super(:issue)
|
||||
end
|
||||
|
||||
def execute_hooks(issue, action = 'open')
|
||||
issue_data = hook_data(issue, action)
|
||||
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
|
||||
|
|
|
@ -38,10 +38,6 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def filter_params
|
||||
super(:merge_request)
|
||||
end
|
||||
|
||||
def merge_requests_for(branch)
|
||||
origin_merge_requests = @project.origin_merge_requests
|
||||
.opened.where(source_branch: branch).to_a
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
---
|
||||
title: Fix issuable assignee update bug when previous assignee is null
|
||||
title: Check if user can read project before being assigned to issue
|
||||
merge_request:
|
||||
author:
|
||||
|
|
|
@ -44,21 +44,40 @@ describe Issue, "Issuable" do
|
|||
it { expect(described_class).to respond_to(:assigned) }
|
||||
end
|
||||
|
||||
describe "after_save" do
|
||||
describe "before_save" do
|
||||
describe "#update_cache_counts" do
|
||||
context "when previous assignee exists" do
|
||||
it "user updates cache counts" do
|
||||
before do
|
||||
assignee = create(:user)
|
||||
issue.project.team << [assignee, :developer]
|
||||
issue.update(assignee: assignee)
|
||||
end
|
||||
|
||||
it "updates cache counts for new assignee" do
|
||||
user = create(:user)
|
||||
|
||||
expect(user).to receive(:update_cache_counts)
|
||||
|
||||
issue.update(assignee: user)
|
||||
end
|
||||
|
||||
it "updates cache counts for previous assignee" do
|
||||
old_assignee = issue.assignee
|
||||
allow(User).to receive(:find_by_id).with(old_assignee.id).and_return(old_assignee)
|
||||
|
||||
expect(old_assignee).to receive(:update_cache_counts)
|
||||
|
||||
issue.update(assignee: nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "when previous assignee does not exist" do
|
||||
it "does not raise error" do
|
||||
issue.update(assignee_id: "")
|
||||
before{ issue.update(assignee: nil) }
|
||||
|
||||
expect { issue.update(assignee_id: user) }.not_to raise_error
|
||||
it "updates cache count for the new assignee" do
|
||||
expect_any_instance_of(User).to receive(:update_cache_counts)
|
||||
|
||||
issue.update(assignee: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,10 @@ describe Issuable::BulkUpdateService, services: true do
|
|||
|
||||
context 'when the new assignee ID is a valid user' do
|
||||
it 'succeeds' do
|
||||
result = bulk_update(issue, assignee_id: create(:user).id)
|
||||
new_assignee = create(:user)
|
||||
project.team << [new_assignee, :developer]
|
||||
|
||||
result = bulk_update(issue, assignee_id: new_assignee.id)
|
||||
|
||||
expect(result[:success]).to be_truthy
|
||||
expect(result[:count]).to eq(1)
|
||||
|
@ -60,15 +63,16 @@ describe Issuable::BulkUpdateService, services: true do
|
|||
|
||||
it 'updates the assignee to the use ID passed' do
|
||||
assignee = create(:user)
|
||||
project.team << [assignee, :developer]
|
||||
|
||||
expect { bulk_update(issue, assignee_id: assignee.id) }
|
||||
.to change { issue.reload.assignee }.from(user).to(assignee)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the new assignee ID is -1' do
|
||||
it 'unassigns the issues' do
|
||||
expect { bulk_update(issue, assignee_id: -1) }
|
||||
context "when the new assignee ID is #{IssuableFinder::NONE}" do
|
||||
it "unassigns the issues" do
|
||||
expect { bulk_update(issue, assignee_id: IssuableFinder::NONE) }
|
||||
.to change { issue.reload.assignee }.to(nil)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -135,6 +135,8 @@ describe Issues::CreateService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'issuable create service'
|
||||
|
||||
it_behaves_like 'new issuable record that supports slash commands'
|
||||
|
||||
context 'for a merge request' do
|
||||
|
|
|
@ -142,6 +142,17 @@ describe Issues::UpdateService, services: true do
|
|||
|
||||
update_issue(confidential: true)
|
||||
end
|
||||
|
||||
it 'does not update assignee_id with unauthorized users' do
|
||||
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
update_issue(confidential: true)
|
||||
non_member = create(:user)
|
||||
original_assignee = issue.assignee
|
||||
|
||||
update_issue(assignee_id: non_member.id)
|
||||
|
||||
expect(issue.reload.assignee_id).to eq(original_assignee.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'todos' do
|
||||
|
|
|
@ -84,6 +84,8 @@ describe MergeRequests::CreateService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'issuable create service'
|
||||
|
||||
context 'while saving references to issues that the created merge request closes' do
|
||||
let(:first_issue) { create(:issue, project: project) }
|
||||
let(:second_issue) { create(:issue, project: project) }
|
||||
|
|
|
@ -5,6 +5,8 @@ describe Notes::SlashCommandsService, services: true do
|
|||
let(:project) { create(:empty_project) }
|
||||
let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
|
||||
let(:assignee) { create(:user) }
|
||||
|
||||
before { project.team << [assignee, :master] }
|
||||
end
|
||||
|
||||
shared_examples 'note on noteable that does not support slash commands' do
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
shared_examples 'issuable create service' do
|
||||
context 'asssignee_id' do
|
||||
let(:assignee) { create(:user) }
|
||||
|
||||
before { project.team << [user, :master] }
|
||||
|
||||
it 'removes assignee_id when user id is invalid' do
|
||||
opts = { title: 'Title', description: 'Description', assignee_id: -1 }
|
||||
|
||||
issuable = described_class.new(project, user, opts).execute
|
||||
|
||||
expect(issuable.assignee_id).to be_nil
|
||||
end
|
||||
|
||||
it 'removes assignee_id when user id is 0' do
|
||||
opts = { title: 'Title', description: 'Description', assignee_id: 0 }
|
||||
|
||||
issuable = described_class.new(project, user, opts).execute
|
||||
|
||||
expect(issuable.assignee_id).to be_nil
|
||||
end
|
||||
|
||||
it 'saves assignee when user id is valid' do
|
||||
project.team << [assignee, :master]
|
||||
opts = { title: 'Title', description: 'Description', assignee_id: assignee.id }
|
||||
|
||||
issuable = described_class.new(project, user, opts).execute
|
||||
|
||||
expect(issuable.assignee_id).to eq(assignee.id)
|
||||
end
|
||||
|
||||
context "when issuable feature is private" do
|
||||
before do
|
||||
project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
|
||||
project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC]
|
||||
|
||||
levels.each do |level|
|
||||
it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
|
||||
project.update(visibility_level: level)
|
||||
opts = { title: 'Title', description: 'Description', assignee_id: assignee.id }
|
||||
|
||||
issuable = described_class.new(project, user, opts).execute
|
||||
|
||||
expect(issuable.assignee_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,8 @@ shared_examples 'new issuable record that supports slash commands' do
|
|||
let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
|
||||
let(:issuable) { described_class.new(project, user, params).execute }
|
||||
|
||||
before { project.team << [assignee, :master ] }
|
||||
|
||||
context 'with labels in command only' do
|
||||
let(:example_params) do
|
||||
{
|
||||
|
@ -55,7 +57,7 @@ shared_examples 'new issuable record that supports slash commands' do
|
|||
context 'with assignee and milestone in params and command' do
|
||||
let(:example_params) do
|
||||
{
|
||||
assignee: build_stubbed(:user),
|
||||
assignee: create(:user),
|
||||
milestone_id: double(:milestone),
|
||||
description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
shared_examples 'issuable update service' do
|
||||
def update_issuable(opts)
|
||||
described_class.new(project, user, opts).execute(open_issuable)
|
||||
end
|
||||
|
||||
context 'changing state' do
|
||||
before { expect(project).to receive(:execute_hooks).once }
|
||||
|
||||
|
@ -14,4 +18,52 @@ shared_examples 'issuable update service' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'asssignee_id' do
|
||||
it 'does not update assignee when assignee_id is invalid' do
|
||||
open_issuable.update(assignee_id: user.id)
|
||||
|
||||
update_issuable(assignee_id: -1)
|
||||
|
||||
expect(open_issuable.reload.assignee).to eq(user)
|
||||
end
|
||||
|
||||
it 'unassigns assignee when user id is 0' do
|
||||
open_issuable.update(assignee_id: user.id)
|
||||
|
||||
update_issuable(assignee_id: 0)
|
||||
|
||||
expect(open_issuable.assignee_id).to be_nil
|
||||
end
|
||||
|
||||
it 'saves assignee when user id is valid' do
|
||||
update_issuable(assignee_id: user.id)
|
||||
|
||||
expect(open_issuable.assignee_id).to eq(user.id)
|
||||
end
|
||||
|
||||
it 'does not update assignee_id when user cannot read issue' do
|
||||
non_member = create(:user)
|
||||
original_assignee = open_issuable.assignee
|
||||
|
||||
update_issuable(assignee_id: non_member.id)
|
||||
|
||||
expect(open_issuable.assignee_id).to eq(original_assignee.id)
|
||||
end
|
||||
|
||||
context "when issuable feature is private" do
|
||||
levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC]
|
||||
|
||||
levels.each do |level|
|
||||
it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
|
||||
assignee = create(:user)
|
||||
project.update(visibility_level: level)
|
||||
feature_visibility_attr = :"#{open_issuable.model_name.plural}_access_level"
|
||||
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
|
||||
|
||||
expect{ update_issuable(assignee_id: assignee) }.not_to change{ open_issuable.assignee }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue