Commit da707fde authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '215061-sprints_services' into 'master'

Add Iterations::CreateService and define project/group policies

See merge request gitlab-org/gitlab!29996
parents a89f8c11 b0c9b7e2
......@@ -22,6 +22,7 @@ class License < ApplicationRecord
group_webhooks
issuable_default_templates
issue_weights
iterations
jenkins_integration
ldap_group_sync
member_lock
......
......@@ -11,6 +11,7 @@ module EE
with_scope :subject
condition(:ldap_synced) { @subject.ldap_synced? }
condition(:epics_available) { @subject.feature_available?(:epics) }
condition(:iterations_available) { @subject.feature_available?(:iterations) }
condition(:subepics_available) { @subject.feature_available?(:subepics) }
condition(:contribution_analytics_available) do
@subject.feature_available?(:contribution_analytics)
......@@ -117,6 +118,13 @@ module EE
rule { can?(:read_group) & epics_available }.enable :read_epic
rule { can?(:read_group) & iterations_available }.enable :read_iteration
rule { developer & iterations_available }.policy do
enable :create_iteration
enable :admin_iteration
end
rule { reporter & epics_available }.policy do
enable :create_epic
enable :admin_epic
......
......@@ -14,6 +14,7 @@ module EE
license_management
feature_flag
feature_flags_client
iteration
].freeze
prepended do
......@@ -32,6 +33,9 @@ module EE
with_scope :subject
condition(:packages_disabled) { !@subject.packages_enabled }
with_scope :subject
condition(:iterations_available) { @subject.feature_available?(:iterations) }
with_scope :subject
condition(:requirements_available) { @subject.feature_available?(:requirements) }
......@@ -153,6 +157,8 @@ module EE
enable :read_issue_link
end
rule { can?(:guest_access) & iterations_available }.enable :read_iteration
rule { can?(:reporter_access) }.policy do
enable :admin_board
enable :read_deploy_board
......@@ -177,8 +183,15 @@ module EE
enable :admin_feature_flags_user_lists
end
rule { can?(:developer_access) & iterations_available }.policy do
enable :create_iteration
enable :admin_iteration
end
rule { can?(:public_access) }.enable :read_package
rule { can?(:read_project) & iterations_available }.enable :read_iteration
rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability
rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train
......@@ -191,6 +204,10 @@ module EE
enable :admin_vulnerability_issue_link
end
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin_destroy(:iteration))
end
rule { threat_monitoring_enabled & (auditor | can?(:developer_access)) }.enable :read_threat_monitoring
rule { dependency_scanning_enabled & can?(:download_code) }.enable :read_dependencies
......
# frozen_string_literal: true
class SprintPolicy < BasePolicy
delegate { @subject.resource_parent }
end
# frozen_string_literal: true
module Iterations
class CreateService
include Gitlab::Allowable
# Parent can either a group or a project
attr_accessor :parent, :current_user, :params
def initialize(parent, user, params = {})
@parent, @current_user, @params = parent, user, params.dup
end
def execute
return ::ServiceResponse.error(message: _('Operation not allowed'), http_status: 403) unless
parent.feature_available?(:iterations) && can?(current_user, :create_iteration, parent)
iteration = parent.sprints.new(params)
if iteration.save
::ServiceResponse.success(message: _('New iteration created'), payload: { iteration: iteration })
else
::ServiceResponse.error(message: _('Error creating new iteration'), payload: { errors: iteration.errors })
end
end
end
end
......@@ -21,6 +21,52 @@ describe GroupPolicy do
it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic) }
end
context 'when iterations feature is disabled' do
let(:current_user) { owner }
before do
stub_licensed_features(iterations: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when iterations feature is enabled' do
before do
stub_licensed_features(iterations: true)
end
context 'when user is a developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a guest' do
let(:current_user) { guest }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when project is private' do
let(:group) { create(:group, :public, :owner_subgroup_creation_only) }
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
end
end
context 'when cluster deployments is available' do
let(:current_user) { maintainer }
......
......@@ -45,7 +45,7 @@ describe ProjectPolicy do
%i[
download_code download_wiki_code read_project read_board read_list
read_project_for_iids read_issue_iid read_merge_request_iid read_wiki
read_issue read_label read_issue_link read_milestone
read_issue read_label read_issue_link read_milestone read_iteration
read_snippet read_project_member read_note read_cycle_analytics
read_pipeline read_build read_commit_status read_container_image
read_environment read_deployment read_merge_request read_pages
......@@ -97,6 +97,100 @@ describe ProjectPolicy do
end
end
context 'iterations' do
let(:current_user) { owner }
context 'when feature is disabled' do
before do
stub_licensed_features(iterations: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when feature is enabled' do
before do
stub_licensed_features(iterations: true)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
context 'when issues are disabled but merge requests are enabled' do
before do
project.update!(issues_enabled: false)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when issues are enabled but merge requests are enabled' do
before do
project.update!(merge_requests_enabled: false)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when both issues and merge requests are disabled' do
before do
project.update!(issues_enabled: false, merge_requests_enabled: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a guest' do
let(:current_user) { guest }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is not a member' do
let(:current_user) { build(:user) }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when the project is private' do
let(:project) { create(:project, :private, namespace: owner.namespace) }
before do
project.add_maintainer(maintainer)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
context 'when user is not a member' do
let(:current_user) { build(:user) }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
end
end
end
context 'issues feature' do
subject { described_class.new(owner, project) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Iterations::CreateService do
shared_examples 'iterations create service' do
let_it_be(:user) { create(:user) }
before do
parent.add_developer(user)
end
context 'iterations feature enabled' do
before do
stub_licensed_features(iterations: true)
end
describe '#execute' do
let(:params) do
{
title: 'v2.1.9',
description: 'Patch release to fix security issue'
}
end
let(:response) { described_class.new(parent, user, params).execute }
let(:iteration) { response.payload[:iteration] }
let(:errors) { response.payload[:errors] }
context 'valid params' do
it 'creates an iteration' do
expect(response.success?).to be_truthy
expect(iteration).to be_persisted
expect(iteration.title).to eq('v2.1.9')
end
end
context 'invalid params' do
let(:params) do
{
description: 'Patch release to fix security issue'
}
end
it 'does not create an iteration but returns errors' do
expect(response.error?).to be_truthy
expect(errors.messages).to match({ title: ["can't be blank"] })
end
end
context 'no permissions' do
before do
parent.add_reporter(user)
end
it 'is not allowed' do
expect(response.error?).to be_truthy
expect(response.message).to eq('Operation not allowed')
end
end
end
end
context 'iterations feature disabled' do
before do
stub_licensed_features(iterations: false)
end
describe '#execute' do
let(:params) { { title: 'a' } }
let(:response) { described_class.new(parent, user, params).execute }
it 'is not allowed' do
expect(response.error?).to be_truthy
expect(response.message).to eq('Operation not allowed')
end
end
end
end
context 'for projects' do
let_it_be(:parent, refind: true) { create(:project) }
it_behaves_like 'iterations create service'
end
context 'for groups' do
let_it_be(:parent, refind: true) { create(:group) }
it_behaves_like 'iterations create service'
end
end
......@@ -8380,6 +8380,9 @@ msgstr ""
msgid "Error creating label."
msgstr ""
msgid "Error creating new iteration"
msgstr ""
msgid "Error deleting %{issuableType}"
msgstr ""
......@@ -13738,6 +13741,9 @@ msgstr ""
msgid "New issue title"
msgstr ""
msgid "New iteration created"
msgstr ""
msgid "New label"
msgstr ""
......@@ -14421,6 +14427,9 @@ msgstr ""
msgid "Operation failed. Check pod logs for %{pod_name} for more details."
msgstr ""
msgid "Operation not allowed"
msgstr ""
msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
msgstr ""
......
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