Commit b94f9c91 authored by Stan Hu's avatar Stan Hu

Merge branch '300100_allow_guests_to_define_metadata_on_issue_create' into 'master'

Allow guest user to assign issue metadata on issue create

See merge request gitlab-org/gitlab!62816
parents 2f550f60 ae50169e
...@@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy ...@@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy
desc "Issue is confidential" desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? } condition(:confidential, scope: :subject) { @subject.confidential? }
desc "Issue is persisted"
condition(:persisted, scope: :subject) { @subject.persisted? }
rule { confidential & ~can_read_confidential }.policy do rule { confidential & ~can_read_confidential }.policy do
prevent(*create_read_update_admin_destroy(:issue)) prevent(*create_read_update_admin_destroy(:issue))
prevent :read_issue_iid prevent :read_issue_iid
...@@ -40,6 +43,14 @@ class IssuePolicy < IssuablePolicy ...@@ -40,6 +43,14 @@ class IssuePolicy < IssuablePolicy
enable :create_todo enable :create_todo
enable :update_subscription enable :update_subscription
end end
rule { ~persisted & can?(:guest_access) }.policy do
enable :set_issue_metadata
end
rule { persisted & can?(:admin_issue) }.policy do
enable :set_issue_metadata
end
end end
IssuePolicy.prepend_mod_with('IssuePolicy') IssuePolicy.prepend_mod_with('IssuePolicy')
...@@ -28,6 +28,10 @@ class MergeRequestPolicy < IssuablePolicy ...@@ -28,6 +28,10 @@ class MergeRequestPolicy < IssuablePolicy
rule { can_merge }.policy do rule { can_merge }.policy do
enable :accept_merge_request enable :accept_merge_request
end end
rule { can?(:admin_merge_request) }.policy do
enable :set_merge_request_metadata
end
end end
MergeRequestPolicy.prepend_mod_with('MergeRequestPolicy') MergeRequestPolicy.prepend_mod_with('MergeRequestPolicy')
...@@ -27,8 +27,14 @@ class IssuableBaseService < ::BaseProjectService ...@@ -27,8 +27,14 @@ class IssuableBaseService < ::BaseProjectService
can?(current_user, ability_name, issuable) can?(current_user, ability_name, issuable)
end end
def can_set_issuable_metadata?(issuable)
ability_name = :"set_#{issuable.to_ability_name}_metadata"
can?(current_user, ability_name, issuable)
end
def filter_params(issuable) def filter_params(issuable)
unless can_admin_issuable?(issuable) unless can_set_issuable_metadata?(issuable)
params.delete(:milestone) params.delete(:milestone)
params.delete(:milestone_id) params.delete(:milestone_id)
params.delete(:labels) params.delete(:labels)
...@@ -45,6 +51,7 @@ class IssuableBaseService < ::BaseProjectService ...@@ -45,6 +51,7 @@ class IssuableBaseService < ::BaseProjectService
params.delete(:canonical_issue_id) params.delete(:canonical_issue_id)
params.delete(:project) params.delete(:project)
params.delete(:discussion_locked) params.delete(:discussion_locked)
params.delete(:confidential)
end end
filter_assignees(issuable) filter_assignees(issuable)
......
...@@ -20,17 +20,6 @@ module Issues ...@@ -20,17 +20,6 @@ module Issues
super super
end end
override :filter_params
def filter_params(issue)
super
# filter confidential in `Issues::UpdateService` and not in `IssuableBaseService#filter_params`
# because we do allow users that cannot admin issues to set confidential flag when creating an issue
unless can_admin_issuable?(issue)
params.delete(:confidential)
end
end
def before_update(issue, skip_spam_check: false) def before_update(issue, skip_spam_check: false)
return if skip_spam_check return if skip_spam_check
......
...@@ -24,14 +24,6 @@ ...@@ -24,14 +24,6 @@
= render 'shared/form_elements/description', model: issuable, form: form, project: project = render 'shared/form_elements/description', model: issuable, form: form, project: project
- if issuable.respond_to?(:confidential)
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= form.check_box :confidential, class: 'form-check-input'
= form.label :confidential, class: 'form-check-label' do
This issue is confidential and should only be visible to team members with at least Reporter access.
= render 'shared/issuable/form/metadata', issuable: issuable, form: form, project: project, presenter: presenter = render 'shared/issuable/form/metadata', issuable: issuable, form: form, project: project, presenter: presenter
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form = render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
......
...@@ -2,11 +2,19 @@ ...@@ -2,11 +2,19 @@
- issuable = local_assigns.fetch(:issuable) - issuable = local_assigns.fetch(:issuable)
- presenter = local_assigns.fetch(:presenter) - presenter = local_assigns.fetch(:presenter)
- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - return unless can?(current_user, :"set_#{issuable.to_ability_name}_metadata", issuable)
- has_due_date = issuable.has_attribute?(:due_date) - has_due_date = issuable.has_attribute?(:due_date)
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
- if issuable.respond_to?(:confidential)
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= form.check_box :confidential, class: 'form-check-input'
= form.label :confidential, class: 'form-check-label' do
This issue is confidential and should only be visible to team members with at least Reporter access.
%hr %hr
.row .row
%div{ class: (has_due_date ? "col-lg-6" : "col-12") } %div{ class: (has_due_date ? "col-lg-6" : "col-12") }
......
...@@ -35,4 +35,8 @@ class EpicPolicy < BasePolicy ...@@ -35,4 +35,8 @@ class EpicPolicy < BasePolicy
rule { ~anonymous & can?(:read_epic) }.policy do rule { ~anonymous & can?(:read_epic) }.policy do
enable :create_todo enable :create_todo
end end
rule { can?(:admin_epic) }.policy do
enable :set_epic_metadata
end
end end
...@@ -12,6 +12,11 @@ RSpec.describe Epics::CreateService do ...@@ -12,6 +12,11 @@ RSpec.describe Epics::CreateService do
subject { described_class.new(group: group, current_user: user, params: params).execute } subject { described_class.new(group: group, current_user: user, params: params).execute }
describe '#execute' do describe '#execute' do
before do
group.add_reporter(user)
stub_licensed_features(epics: true, subepics: true)
end
it 'creates one epic correctly' do it 'creates one epic correctly' do
allow(NewEpicWorker).to receive(:perform_async) allow(NewEpicWorker).to receive(:perform_async)
...@@ -26,116 +31,111 @@ RSpec.describe Epics::CreateService do ...@@ -26,116 +31,111 @@ RSpec.describe Epics::CreateService do
expect(epic.confidential).to be_truthy expect(epic.confidential).to be_truthy
expect(NewEpicWorker).to have_received(:perform_async).with(epic.id, user.id) expect(NewEpicWorker).to have_received(:perform_async).with(epic.id, user.id)
end end
end
context 'handling fixed dates' do context 'handling fixed dates' do
it 'sets the fixed date correctly' do it 'sets the fixed date correctly' do
date = Date.new(2019, 10, 10) date = Date.new(2019, 10, 10)
params[:start_date_fixed] = date params[:start_date_fixed] = date
params[:start_date_is_fixed] = true params[:start_date_is_fixed] = true
subject subject
epic = Epic.last epic = Epic.last
expect(epic.start_date).to eq(date) expect(epic.start_date).to eq(date)
expect(epic.start_date_fixed).to eq(date) expect(epic.start_date_fixed).to eq(date)
expect(epic.start_date_is_fixed).to be_truthy expect(epic.start_date_is_fixed).to be_truthy
end
end end
end
context 'after_save callback to store_mentions' do context 'after_save callback to store_mentions' do
let(:labels) { create_pair(:group_label, group: group) } let(:labels) { create_pair(:group_label, group: group) }
context 'when mentionable attributes change' do context 'when mentionable attributes change' do
context 'when content has no mentions' do context 'when content has no mentions' do
let(:params) { { title: 'Title', description: "Description with no mentions" } } let(:params) { { title: 'Title', description: "Description with no mentions" } }
it 'calls store_mentions! and saves no mentions' do
expect_next_instance_of(Epic) do |instance|
expect(instance).to receive(:store_mentions!).and_call_original
end
expect { subject }.not_to change { EpicUserMention.count } it 'calls store_mentions! and saves no mentions' do
end expect_next_instance_of(Epic) do |instance|
end expect(instance).to receive(:store_mentions!).and_call_original
end
context 'when content has mentions' do
let(:params) { { title: 'Title', description: "Description with #{user.to_reference}" } }
it 'calls store_mentions! and saves mentions' do expect { subject }.not_to change { EpicUserMention.count }
expect_next_instance_of(Epic) do |instance|
expect(instance).to receive(:store_mentions!).and_call_original
end end
expect { subject }.to change { EpicUserMention.count }.by(1)
end end
end
context 'when mentionable.save fails' do context 'when content has mentions' do
let(:params) { { title: '', label_ids: labels.map(&:id) } } let(:params) { { title: 'Title', description: "Description with #{user.to_reference}" } }
it 'does not call store_mentions and saves no mentions' do it 'calls store_mentions! and saves mentions' do
expect_next_instance_of(Epic) do |instance| expect_next_instance_of(Epic) do |instance|
expect(instance).not_to receive(:store_mentions!).and_call_original expect(instance).to receive(:store_mentions!).and_call_original
end end
expect { subject }.not_to change { EpicUserMention.count }
expect(subject.valid?).to be false
end
end
context 'when description param has quick action' do expect { subject }.to change { EpicUserMention.count }.by(1)
before do end
stub_licensed_features(epics: true, subepics: true)
group.add_developer(user)
end end
context 'for /parent_epic' do context 'when mentionable.save fails' do
it 'assigns parent epic' do let(:params) { { title: '', label_ids: labels.map(&:id) } }
parent_epic = create(:epic, group: group)
description = "/parent_epic #{parent_epic.to_reference}"
params = { title: 'New epic with parent', description: description }
epic = described_class.new(group: group, current_user: user, params: params).execute it 'does not call store_mentions and saves no mentions' do
expect_next_instance_of(Epic) do |instance|
expect(instance).not_to receive(:store_mentions!).and_call_original
end
expect(epic.parent).to eq(parent_epic) expect { subject }.not_to change { EpicUserMention.count }
expect(subject.valid?).to be false
end end
end
context 'when parent epic cannot be assigned' do context 'when description param has quick action' do
it 'does not assign parent epic' do context 'for /parent_epic' do
other_group = create(:group, :private) it 'assigns parent epic' do
parent_epic = create(:epic, group: other_group) parent_epic = create(:epic, group: group)
description = "/parent_epic #{parent_epic.to_reference(group)}" description = "/parent_epic #{parent_epic.to_reference}"
params = { title: 'New epic with parent', description: description } params = { title: 'New epic with parent', description: description }
epic = described_class.new(group: group, current_user: user, params: params).execute epic = described_class.new(group: group, current_user: user, params: params).execute
expect(epic.parent).to eq(nil) expect(epic.parent).to eq(parent_epic)
end end
end
end
context 'for /child_epic' do context 'when parent epic cannot be assigned' do
it 'sets a child epic' do it 'does not assign parent epic' do
child_epic = create(:epic, group: group) other_group = create(:group, :private)
description = "/child_epic #{child_epic.to_reference}" parent_epic = create(:epic, group: other_group)
params = { title: 'New epic with child', description: description } description = "/parent_epic #{parent_epic.to_reference(group)}"
params = { title: 'New epic with parent', description: description }
epic = described_class.new(group: group, current_user: user, params: params).execute epic = described_class.new(group: group, current_user: user, params: params).execute
expect(epic.reload.children).to include(child_epic) expect(epic.parent).to eq(nil)
end
end
end end
context 'when child epic cannot be assigned' do context 'for /child_epic' do
it 'does not set child epic' do it 'sets a child epic' do
other_group = create(:group, :private) child_epic = create(:epic, group: group)
child_epic = create(:epic, group: other_group) description = "/child_epic #{child_epic.to_reference}"
description = "/child_epic #{child_epic.to_reference(group)}"
params = { title: 'New epic with child', description: description } params = { title: 'New epic with child', description: description }
epic = described_class.new(group: group, current_user: user, params: params).execute epic = described_class.new(group: group, current_user: user, params: params).execute
expect(epic.reload.children).to be_empty expect(epic.reload.children).to include(child_epic)
end
context 'when child epic cannot be assigned' do
it 'does not set child epic' do
other_group = create(:group, :private)
child_epic = create(:epic, group: other_group)
description = "/child_epic #{child_epic.to_reference(group)}"
params = { title: 'New epic with child', description: description }
epic = described_class.new(group: group, current_user: user, params: params).execute
expect(epic.reload.children).to be_empty
end
end end
end end
end end
......
...@@ -30,7 +30,7 @@ RSpec.describe "User creates issue" do ...@@ -30,7 +30,7 @@ RSpec.describe "User creates issue" do
end end
end end
context "when signed in as guest" do context "when signed in as guest", :js do
before do before do
project.add_guest(user) project.add_guest(user)
sign_in(user) sign_in(user)
...@@ -38,41 +38,19 @@ RSpec.describe "User creates issue" do ...@@ -38,41 +38,19 @@ RSpec.describe "User creates issue" do
visit(new_project_issue_path(project)) visit(new_project_issue_path(project))
end end
it "creates issue", :js do context 'available metadata' do
page.within(".issue-form") do it 'allows guest to set issue metadata' do
expect(page).to have_no_content("Assign to") page.within(".issue-form") do
.and have_no_content("Labels") expect(page).to have_content("Title")
.and have_no_content("Milestone") .and have_content("Description")
.and have_content("Type")
expect(page.find('#issue_title')['placeholder']).to eq 'Title' .and have_content("Assignee")
expect(page.find('#issue_description')['placeholder']).to eq 'Write a description or drag your files here…' .and have_content("Milestone")
.and have_content("Labels")
.and have_content("Due date")
.and have_content("This issue is confidential and should only be visible to team members with at least Reporter access.")
end
end end
issue_title = "500 error on profile"
fill_in("Title", with: issue_title)
first('.js-md').click
first('.rspec-issuable-form-description').native.send_keys('Description')
click_button("Create issue")
expect(page).to have_content(issue_title)
.and have_content(user.name)
.and have_content(project.name)
expect(page).to have_selector('strong', text: 'Description')
end
it 'does not render the issue type dropdown' do
expect(page).not_to have_selector('.s-issuable-type-filter-dropdown-wrap')
end
end
context "when signed in as developer", :js do
before do
project.add_developer(user)
sign_in(user)
visit(new_project_issue_path(project))
end end
context "when previewing" do context "when previewing" do
......
This diff is collapsed.
...@@ -18,8 +18,8 @@ RSpec.describe Issues::CreateService do ...@@ -18,8 +18,8 @@ RSpec.describe Issues::CreateService do
let_it_be(:labels) { create_pair(:label, project: project) } let_it_be(:labels) { create_pair(:label, project: project) }
before_all do before_all do
project.add_maintainer(user) project.add_guest(user)
project.add_maintainer(assignee) project.add_guest(assignee)
end end
let(:opts) do let(:opts) do
...@@ -88,15 +88,11 @@ RSpec.describe Issues::CreateService do ...@@ -88,15 +88,11 @@ RSpec.describe Issues::CreateService do
expect { issue }.to change { project.open_issues_count }.from(0).to(1) expect { issue }.to change { project.open_issues_count }.from(0).to(1)
end end
context 'when current user cannot admin issues in the project' do context 'when current user cannot set issue metadata in the project' do
let_it_be(:guest) { create(:user) } let_it_be(:non_member) { create(:user) }
before_all do
project.add_guest(guest)
end
it 'filters out params that cannot be set without the :admin_issue permission' do it 'filters out params that cannot be set without the :set_issue_metadata permission' do
issue = described_class.new(project: project, current_user: guest, params: opts).execute issue = described_class.new(project: project, current_user: non_member, params: opts).execute
expect(issue).to be_persisted expect(issue).to be_persisted
expect(issue.title).to eq('Awesome issue') expect(issue.title).to eq('Awesome issue')
...@@ -107,8 +103,8 @@ RSpec.describe Issues::CreateService do ...@@ -107,8 +103,8 @@ RSpec.describe Issues::CreateService do
expect(issue.due_date).to be_nil expect(issue.due_date).to be_nil
end end
it 'creates confidential issues' do it 'can create confidential issues' do
issue = described_class.new(project: project, current_user: guest, params: { confidential: true }).execute issue = described_class.new(project: project, current_user: non_member, params: { confidential: true }).execute
expect(issue.confidential).to be_truthy expect(issue.confidential).to be_truthy
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