Merge branch '22592-can-set-due-date-through-slash-commands-even-though-i-m-not-authorized-to' into 'master'
Fix permission for setting an issue's due date ## What does this MR do? This merge request ensure the current user can `:admin_issue` in order to change the issue's `due_date`, in `BaseIssuableService` and in `SlashCommands::InterpretService`. Closes #22592 ## Are there points in the code the reviewer needs to double check? No. ## Does this MR meet the acceptance criteria? - [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added - Tests - [x] Added for this feature/bug - [ ] All builds are passing - [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [x] Branch has no merge conflicts with `master` (if you do - rebase it please) - [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) See merge request !6539
This commit is contained in:
commit
578488ee7e
|
@ -7,6 +7,7 @@ v 8.13.0 (unreleased)
|
||||||
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
|
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
|
||||||
- Add more tests for calendar contribution (ClemMakesApps)
|
- Add more tests for calendar contribution (ClemMakesApps)
|
||||||
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
|
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
|
||||||
|
- Fix permission for setting an issue's due date
|
||||||
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
|
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
|
||||||
- Only update issuable labels if they have been changed
|
- Only update issuable labels if they have been changed
|
||||||
- Revoke button in Applications Settings underlines on hover.
|
- Revoke button in Applications Settings underlines on hover.
|
||||||
|
|
|
@ -50,6 +50,7 @@ class IssuableBaseService < BaseService
|
||||||
params.delete(:remove_label_ids)
|
params.delete(:remove_label_ids)
|
||||||
params.delete(:label_ids)
|
params.delete(:label_ids)
|
||||||
params.delete(:assignee_id)
|
params.delete(:assignee_id)
|
||||||
|
params.delete(:due_date)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ module SlashCommands
|
||||||
params '<in 2 days | this Friday | December 31st>'
|
params '<in 2 days | this Friday | December 31st>'
|
||||||
condition do
|
condition do
|
||||||
issuable.respond_to?(:due_date) &&
|
issuable.respond_to?(:due_date) &&
|
||||||
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
|
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||||
end
|
end
|
||||||
command :due do |due_date_param|
|
command :due do |due_date_param|
|
||||||
due_date = Chronic.parse(due_date_param).try(:to_date)
|
due_date = Chronic.parse(due_date_param).try(:to_date)
|
||||||
|
@ -208,7 +208,7 @@ module SlashCommands
|
||||||
issuable.persisted? &&
|
issuable.persisted? &&
|
||||||
issuable.respond_to?(:due_date) &&
|
issuable.respond_to?(:due_date) &&
|
||||||
issuable.due_date? &&
|
issuable.due_date? &&
|
||||||
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
|
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||||
end
|
end
|
||||||
command :remove_due_date do
|
command :remove_due_date do
|
||||||
@updates[:due_date] = nil
|
@updates[:due_date] = nil
|
||||||
|
|
|
@ -25,32 +25,78 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
|
||||||
describe 'adding a due date from note' do
|
describe 'adding a due date from note' do
|
||||||
let(:issue) { create(:issue, project: project) }
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
|
||||||
it 'does not create a note, and sets the due date accordingly' do
|
context 'when the current user can update the due date' do
|
||||||
write_note("/due 2016-08-28")
|
it 'does not create a note, and sets the due date accordingly' do
|
||||||
|
write_note("/due 2016-08-28")
|
||||||
|
|
||||||
expect(page).not_to have_content '/due 2016-08-28'
|
expect(page).not_to have_content '/due 2016-08-28'
|
||||||
expect(page).to have_content 'Your commands have been executed!'
|
expect(page).to have_content 'Your commands have been executed!'
|
||||||
|
|
||||||
issue.reload
|
issue.reload
|
||||||
|
|
||||||
expect(issue.due_date).to eq Date.new(2016, 8, 28)
|
expect(issue.due_date).to eq Date.new(2016, 8, 28)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the current user cannot update the due date' do
|
||||||
|
let(:guest) { create(:user) }
|
||||||
|
before do
|
||||||
|
project.team << [guest, :guest]
|
||||||
|
logout
|
||||||
|
login_with(guest)
|
||||||
|
visit namespace_project_issue_path(project.namespace, project, issue)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a note, and sets the due date accordingly' do
|
||||||
|
write_note("/due 2016-08-28")
|
||||||
|
|
||||||
|
expect(page).to have_content '/due 2016-08-28'
|
||||||
|
expect(page).not_to have_content 'Your commands have been executed!'
|
||||||
|
|
||||||
|
issue.reload
|
||||||
|
|
||||||
|
expect(issue.due_date).to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'removing a due date from note' do
|
describe 'removing a due date from note' do
|
||||||
let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
|
let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
|
||||||
|
|
||||||
it 'does not create a note, and removes the due date accordingly' do
|
context 'when the current user can update the due date' do
|
||||||
expect(issue.due_date).to eq Date.new(2016, 8, 28)
|
it 'does not create a note, and removes the due date accordingly' do
|
||||||
|
expect(issue.due_date).to eq Date.new(2016, 8, 28)
|
||||||
|
|
||||||
write_note("/remove_due_date")
|
write_note("/remove_due_date")
|
||||||
|
|
||||||
expect(page).not_to have_content '/remove_due_date'
|
expect(page).not_to have_content '/remove_due_date'
|
||||||
expect(page).to have_content 'Your commands have been executed!'
|
expect(page).to have_content 'Your commands have been executed!'
|
||||||
|
|
||||||
issue.reload
|
issue.reload
|
||||||
|
|
||||||
expect(issue.due_date).to be_nil
|
expect(issue.due_date).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the current user cannot update the due date' do
|
||||||
|
let(:guest) { create(:user) }
|
||||||
|
before do
|
||||||
|
project.team << [guest, :guest]
|
||||||
|
logout
|
||||||
|
login_with(guest)
|
||||||
|
visit namespace_project_issue_path(project.namespace, project, issue)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a note, and sets the due date accordingly' do
|
||||||
|
write_note("/remove_due_date")
|
||||||
|
|
||||||
|
expect(page).to have_content '/remove_due_date'
|
||||||
|
expect(page).not_to have_content 'Your commands have been executed!'
|
||||||
|
|
||||||
|
issue.reload
|
||||||
|
|
||||||
|
expect(issue.due_date).to eq Date.new(2016, 8, 28)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,16 +20,38 @@ describe Issues::CreateService, services: true do
|
||||||
let(:opts) do
|
let(:opts) do
|
||||||
{ title: 'Awesome issue',
|
{ title: 'Awesome issue',
|
||||||
description: 'please fix',
|
description: 'please fix',
|
||||||
assignee: assignee,
|
assignee_id: assignee.id,
|
||||||
label_ids: labels.map(&:id),
|
label_ids: labels.map(&:id),
|
||||||
milestone_id: milestone.id }
|
milestone_id: milestone.id,
|
||||||
|
due_date: Date.tomorrow }
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(issue).to be_valid }
|
it 'creates the issue with the given params' do
|
||||||
it { expect(issue.title).to eq('Awesome issue') }
|
expect(issue).to be_persisted
|
||||||
it { expect(issue.assignee).to eq assignee }
|
expect(issue.title).to eq('Awesome issue')
|
||||||
it { expect(issue.labels).to match_array labels }
|
expect(issue.assignee).to eq assignee
|
||||||
it { expect(issue.milestone).to eq milestone }
|
expect(issue.labels).to match_array labels
|
||||||
|
expect(issue.milestone).to eq milestone
|
||||||
|
expect(issue.due_date).to eq Date.tomorrow
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current user cannot admin issues in the project' do
|
||||||
|
let(:guest) { create(:user) }
|
||||||
|
before do
|
||||||
|
project.team << [guest, :guest]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters out params that cannot be set without the :admin_issue permission' do
|
||||||
|
issue = described_class.new(project, guest, opts).execute
|
||||||
|
|
||||||
|
expect(issue).to be_persisted
|
||||||
|
expect(issue.title).to eq('Awesome issue')
|
||||||
|
expect(issue.assignee).to be_nil
|
||||||
|
expect(issue.labels).to be_empty
|
||||||
|
expect(issue.milestone).to be_nil
|
||||||
|
expect(issue.due_date).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'creates a pending todo for new assignee' do
|
it 'creates a pending todo for new assignee' do
|
||||||
attributes = {
|
attributes = {
|
||||||
|
|
|
@ -32,55 +32,84 @@ describe Issues::UpdateService, services: true do
|
||||||
described_class.new(project, user, opts).execute(issue)
|
described_class.new(project, user, opts).execute(issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "valid params" do
|
context 'valid params' do
|
||||||
before do
|
let(:opts) do
|
||||||
opts = {
|
{
|
||||||
title: 'New title',
|
title: 'New title',
|
||||||
description: 'Also please fix',
|
description: 'Also please fix',
|
||||||
assignee_id: user2.id,
|
assignee_id: user2.id,
|
||||||
state_event: 'close',
|
state_event: 'close',
|
||||||
label_ids: [label.id]
|
label_ids: [label.id],
|
||||||
|
due_date: Date.tomorrow
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
it 'updates the issue with the given params' do
|
||||||
update_issue(opts)
|
update_issue(opts)
|
||||||
|
|
||||||
|
expect(issue).to be_valid
|
||||||
|
expect(issue.title).to eq 'New title'
|
||||||
|
expect(issue.description).to eq 'Also please fix'
|
||||||
|
expect(issue.assignee).to eq user2
|
||||||
|
expect(issue).to be_closed
|
||||||
|
expect(issue.labels).to match_array [label]
|
||||||
|
expect(issue.due_date).to eq Date.tomorrow
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current user cannot admin issues in the project' do
|
||||||
|
let(:guest) { create(:user) }
|
||||||
|
before do
|
||||||
|
project.team << [guest, :guest]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters out params that cannot be set without the :admin_issue permission' do
|
||||||
|
described_class.new(project, guest, opts).execute(issue)
|
||||||
|
|
||||||
|
expect(issue).to be_valid
|
||||||
|
expect(issue.title).to eq 'New title'
|
||||||
|
expect(issue.description).to eq 'Also please fix'
|
||||||
|
expect(issue.assignee).to eq user3
|
||||||
|
expect(issue.labels).to be_empty
|
||||||
|
expect(issue.milestone).to be_nil
|
||||||
|
expect(issue.due_date).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(issue).to be_valid }
|
context 'with background jobs processed' do
|
||||||
it { expect(issue.title).to eq('New title') }
|
before do
|
||||||
it { expect(issue.assignee).to eq(user2) }
|
perform_enqueued_jobs do
|
||||||
it { expect(issue).to be_closed }
|
update_issue(opts)
|
||||||
it { expect(issue.labels.count).to eq(1) }
|
end
|
||||||
it { expect(issue.labels.first.title).to eq(label.name) }
|
end
|
||||||
|
|
||||||
it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
|
it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
|
||||||
deliveries = ActionMailer::Base.deliveries
|
deliveries = ActionMailer::Base.deliveries
|
||||||
email = deliveries.last
|
email = deliveries.last
|
||||||
recipients = deliveries.last(2).map(&:to).flatten
|
recipients = deliveries.last(2).map(&:to).flatten
|
||||||
expect(recipients).to include(user2.email, user3.email)
|
expect(recipients).to include(user2.email, user3.email)
|
||||||
expect(email.subject).to include(issue.title)
|
expect(email.subject).to include(issue.title)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates system note about issue reassign' do
|
it 'creates system note about issue reassign' do
|
||||||
note = find_note('Reassigned to')
|
note = find_note('Reassigned to')
|
||||||
|
|
||||||
expect(note).not_to be_nil
|
expect(note).not_to be_nil
|
||||||
expect(note.note).to include "Reassigned to \@#{user2.username}"
|
expect(note.note).to include "Reassigned to \@#{user2.username}"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates system note about issue label edit' do
|
it 'creates system note about issue label edit' do
|
||||||
note = find_note('Added ~')
|
note = find_note('Added ~')
|
||||||
|
|
||||||
expect(note).not_to be_nil
|
expect(note).not_to be_nil
|
||||||
expect(note.note).to include "Added ~#{label.id} label"
|
expect(note.note).to include "Added ~#{label.id} label"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates system note about title change' do
|
it 'creates system note about title change' do
|
||||||
note = find_note('Changed title:')
|
note = find_note('Changed title:')
|
||||||
|
|
||||||
expect(note).not_to be_nil
|
expect(note).not_to be_nil
|
||||||
expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
|
expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe SlashCommands::InterpretService, services: true do
|
describe SlashCommands::InterpretService, services: true do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:empty_project, :public) }
|
||||||
let(:user) { create(:user) }
|
let(:developer) { create(:user) }
|
||||||
let(:issue) { create(:issue, project: project) }
|
let(:issue) { create(:issue, project: project) }
|
||||||
let(:milestone) { create(:milestone, project: project, title: '9.10') }
|
let(:milestone) { create(:milestone, project: project, title: '9.10') }
|
||||||
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
|
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
|
||||||
let(:bug) { create(:label, project: project, title: 'Bug') }
|
let(:bug) { create(:label, project: project, title: 'Bug') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.team << [user, :developer]
|
project.team << [developer, :developer]
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let(:service) { described_class.new(project, user) }
|
let(:service) { described_class.new(project, developer) }
|
||||||
let(:merge_request) { create(:merge_request, source_project: project) }
|
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||||
|
|
||||||
shared_examples 'reopen command' do
|
shared_examples 'reopen command' do
|
||||||
|
@ -45,13 +45,13 @@ describe SlashCommands::InterpretService, services: true do
|
||||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||||
_, updates = service.execute(content, issuable)
|
_, updates = service.execute(content, issuable)
|
||||||
|
|
||||||
expect(updates).to eq(assignee_id: user.id)
|
expect(updates).to eq(assignee_id: developer.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'unassign command' do
|
shared_examples 'unassign command' do
|
||||||
it 'populates assignee_id: nil if content contains /unassign' do
|
it 'populates assignee_id: nil if content contains /unassign' do
|
||||||
issuable.update(assignee_id: user.id)
|
issuable.update(assignee_id: developer.id)
|
||||||
_, updates = service.execute(content, issuable)
|
_, updates = service.execute(content, issuable)
|
||||||
|
|
||||||
expect(updates).to eq(assignee_id: nil)
|
expect(updates).to eq(assignee_id: nil)
|
||||||
|
@ -124,7 +124,7 @@ describe SlashCommands::InterpretService, services: true do
|
||||||
|
|
||||||
shared_examples 'done command' do
|
shared_examples 'done command' do
|
||||||
it 'populates todo_event: "done" if content contains /done' do
|
it 'populates todo_event: "done" if content contains /done' do
|
||||||
TodoService.new.mark_todo(issuable, user)
|
TodoService.new.mark_todo(issuable, developer)
|
||||||
_, updates = service.execute(content, issuable)
|
_, updates = service.execute(content, issuable)
|
||||||
|
|
||||||
expect(updates).to eq(todo_event: 'done')
|
expect(updates).to eq(todo_event: 'done')
|
||||||
|
@ -141,7 +141,7 @@ describe SlashCommands::InterpretService, services: true do
|
||||||
|
|
||||||
shared_examples 'unsubscribe command' do
|
shared_examples 'unsubscribe command' do
|
||||||
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
|
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
|
||||||
issuable.subscribe(user)
|
issuable.subscribe(developer)
|
||||||
_, updates = service.execute(content, issuable)
|
_, updates = service.execute(content, issuable)
|
||||||
|
|
||||||
expect(updates).to eq(subscription_event: 'unsubscribe')
|
expect(updates).to eq(subscription_event: 'unsubscribe')
|
||||||
|
@ -209,12 +209,12 @@ describe SlashCommands::InterpretService, services: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'assign command' do
|
it_behaves_like 'assign command' do
|
||||||
let(:content) { "/assign @#{user.username}" }
|
let(:content) { "/assign @#{developer.username}" }
|
||||||
let(:issuable) { issue }
|
let(:issuable) { issue }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'assign command' do
|
it_behaves_like 'assign command' do
|
||||||
let(:content) { "/assign @#{user.username}" }
|
let(:content) { "/assign @#{developer.username}" }
|
||||||
let(:issuable) { merge_request }
|
let(:issuable) { merge_request }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -380,5 +380,56 @@ describe SlashCommands::InterpretService, services: true do
|
||||||
let(:content) { '/remove_due_date' }
|
let(:content) { '/remove_due_date' }
|
||||||
let(:issuable) { merge_request }
|
let(:issuable) { merge_request }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when current_user cannot :admin_issue' do
|
||||||
|
let(:visitor) { create(:user) }
|
||||||
|
let(:issue) { create(:issue, project: project, author: visitor) }
|
||||||
|
let(:service) { described_class.new(project, visitor) }
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { "/assign @#{developer.username}" }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { '/unassign' }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { "/milestone %#{milestone.title}" }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { '/remove_milestone' }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { %(/unlabel ~"#{inprogress.title}") }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { %(/relabel ~"#{inprogress.title}") }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { '/due tomorrow' }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'empty command' do
|
||||||
|
let(:content) { '/remove_due_date' }
|
||||||
|
let(:issuable) { issue }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue