Skip to content
Snippets Groups Projects
Commit 1b082a4c authored by Felipe's avatar Felipe
Browse files

Check if user can read issue before being assigned

parent 77deeb12
No related branches found
No related tags found
1 merge request!7980Check if user can read project before being assigned to issue
Pipeline #
Showing
with 189 additions and 34 deletions
......@@ -93,9 +93,9 @@ def award_emojis_loaded?
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 @@ def create_task_status_note(issuable)
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 @@ def filter_params(issuable_ability_name = :issue)
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 @@ def create_issuable(issuable, attributes, label_ids:)
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 @@ def update(issuable)
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 @@ def hook_data(issue, action)
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 @@ def execute_hooks(merge_request, action = 'open', oldrev = nil)
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
......
---
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 @@
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 @@ def bulk_update(issues, extra_params = {})
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 @@ def bulk_update(issues, extra_params = {})
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 @@
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 @@ def update_issue(opts)
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 @@
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 @@
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
......
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 @@
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 @@
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}")
}
......
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 @@
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment