From 64858317adc4f017fe589342155faba9df31f093 Mon Sep 17 00:00:00 2001 From: Gosia Ksionek Date: Fri, 5 Apr 2019 18:49:46 +0000 Subject: [PATCH] Add part of needed code Add columns to store project creation settings Add project creation level column in groups and default project creation column in application settings Remove obsolete line from schema Update migration with project_creation_level column existence check Rename migrations to avoid conflicts Update migration methods Update migration method --- app/controllers/admin/groups_controller.rb | 3 +- app/controllers/groups_controller.rb | 3 +- app/helpers/application_settings_helper.rb | 1 + app/helpers/namespaces_helper.rb | 7 ++ .../application_setting_implementation.rb | 1 + app/models/group.rb | 4 + app/models/user.rb | 40 +++++- app/policies/group_policy.rb | 11 ++ .../_visibility_and_access.html.haml | 4 +- .../groups/_group_admin_settings.html.haml | 4 + .../groups/settings/_permissions.html.haml | 1 + .../_project_creation_level.html.haml | 3 + .../projects/_new_project_fields.html.haml | 6 +- ...s-to-create-projects-in-groups-to-core.yml | 5 + config/initializers/1_settings.rb | 1 + ...lt_project_creation_application_setting.rb | 22 ++++ ...dd_project_creation_level_to_namespaces.rb | 22 ++++ db/schema.rb | 2 + doc/user/group/index.md | 11 ++ lib/api/settings.rb | 3 +- lib/gitlab/access.rb | 21 ++++ locale/gitlab.pot | 15 +++ .../application_settings_controller_spec.rb | 7 ++ .../admin/groups_controller_spec.rb | 6 + spec/controllers/groups_controller_spec.rb | 7 ++ spec/factories/groups.rb | 1 + spec/features/groups/group_settings_spec.rb | 8 ++ spec/features/projects/new_project_spec.rb | 19 +++ .../projects/user_creates_project_spec.rb | 27 +++++ spec/helpers/namespaces_helper_spec.rb | 72 ++++++++++- spec/models/group_spec.rb | 8 ++ spec/policies/group_policy_spec.rb | 114 ++++++++++++++++++ spec/requests/api/settings_spec.rb | 4 +- 33 files changed, 447 insertions(+), 16 deletions(-) create mode 100644 app/views/groups/settings/_project_creation_level.html.haml create mode 100644 changelogs/unreleased/move-allow-developers-to-create-projects-in-groups-to-core.yml create mode 100644 db/migrate/20190311132500_add_default_project_creation_application_setting.rb create mode 100644 db/migrate/20190311132527_add_project_creation_level_to_namespaces.rb diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index e0ecdb0c0e9..15f7ef881c8 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -89,7 +89,8 @@ class Admin::GroupsController < Admin::ApplicationController :request_access_enabled, :visibility_level, :require_two_factor_authentication, - :two_factor_grace_period + :two_factor_grace_period, + :project_creation_level ] end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 87b8ef03313..e936d771502 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -187,7 +187,8 @@ class GroupsController < Groups::ApplicationController :create_chat_team, :chat_team_name, :require_two_factor_authentication, - :two_factor_grace_period + :two_factor_grace_period, + :project_creation_level ] end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index e635f608237..e275e4278a4 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -137,6 +137,7 @@ module ApplicationSettingsHelper :default_artifacts_expire_in, :default_branch_protection, :default_group_visibility, + :default_project_creation, :default_project_visibility, :default_projects_limit, :default_snippet_visibility, diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index ea3bcfc791a..572d68cb4a3 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -49,6 +49,13 @@ module NamespacesHelper end end + def namespaces_options_with_developer_maintainer_access(options = {}) + selected = options.delete(:selected) || :current_user + options[:groups] = current_user.manageable_groups_with_routes(include_groups_with_developer_maintainer_access: true) + + namespaces_options(selected, options) + end + private # Many importers create a temporary Group, so use the real diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 265aa1d4965..b413ffddb9d 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -26,6 +26,7 @@ module ApplicationSettingImplementation default_artifacts_expire_in: '30 days', default_branch_protection: Settings.gitlab['default_branch_protection'], default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_project_creation: Settings.gitlab['default_project_creation'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_projects_limit: Settings.gitlab['default_projects_limit'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], diff --git a/app/models/group.rb b/app/models/group.rb index ac66815705c..8bc9b75f0a9 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -404,6 +404,10 @@ class Group < Namespace Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true) end + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end + private def update_two_factor_requirement diff --git a/app/models/user.rb b/app/models/user.rb index 259889995d3..d3524bfd6ae 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -105,6 +105,7 @@ class User < ApplicationRecord has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group + has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group has_many :owned_or_maintainers_groups, -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, through: :group_members, @@ -883,7 +884,12 @@ class User < ApplicationRecord # rubocop: enable CodeReuse/ServiceClass def several_namespaces? - owned_groups.any? || maintainers_groups.any? + union_sql = ::Gitlab::SQL::Union.new( + [owned_groups, + maintainers_groups, + groups_with_developer_maintainer_project_access]).to_sql + + ::Group.from("(#{union_sql}) #{::Group.table_name}").any? end def namespace_id @@ -1169,12 +1175,24 @@ class User < ApplicationRecord @manageable_namespaces ||= [namespace] + manageable_groups end - def manageable_groups - Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants + def manageable_groups(include_groups_with_developer_maintainer_access: false) + owned_and_maintainer_group_hierarchy = Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants + + if include_groups_with_developer_maintainer_access + union_sql = ::Gitlab::SQL::Union.new( + [owned_and_maintainer_group_hierarchy, + groups_with_developer_maintainer_project_access]).to_sql + + ::Group.from("(#{union_sql}) #{::Group.table_name}") + else + owned_and_maintainer_group_hierarchy + end end - def manageable_groups_with_routes - manageable_groups.eager_load(:route).order('routes.path') + def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false) + manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access) + .eager_load(:route) + .order('routes.path') end def namespaces @@ -1573,4 +1591,16 @@ class User < ApplicationRecord ensure Gitlab::ExclusiveLease.cancel(lease_key, uuid) end + + def groups_with_developer_maintainer_project_access + project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS] + + if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS + project_creation_levels << nil + end + + developer_groups_hierarchy = ::Gitlab::ObjectHierarchy.new(developer_groups).base_and_descendants + ::Group.where(id: developer_groups_hierarchy.select(:id), + project_creation_level: project_creation_levels) + end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index db49d3bed9c..eb2e536e8e9 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -35,6 +35,14 @@ class GroupPolicy < BasePolicy with_options scope: :subject, score: 0 condition(:request_access_enabled) { @subject.request_access_enabled } + condition(:create_projects_disabled) do + @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS + end + + condition(:developer_maintainer_access) do + @subject.project_creation_level == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS + end + rule { public_group }.policy do enable :read_group enable :read_list @@ -115,6 +123,9 @@ class GroupPolicy < BasePolicy rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster + rule { developer & developer_maintainer_access }.enable :create_projects + rule { create_projects_disabled }.prevent :create_projects + def access_level return GroupMember::NO_ACCESS if @user.nil? diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index 8122d81f578..03ef2924617 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -5,7 +5,9 @@ .form-group = f.label :default_branch_protection, class: 'label-bold' = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' - = render_if_exists 'admin/application_settings/project_creation_level', form: f, application_setting: @application_setting + .form-group + = f.label s_('ProjectCreationLevel|Default project creation protection'), class: 'label-bold' + = f.select :default_project_creation, options_for_select(Gitlab::Access.project_creation_options, @application_setting.default_project_creation), {}, class: 'form-control' .form-group.visibility-level-setting = f.label :default_project_visibility, class: 'label-bold' = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new) diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml index ff59013ed67..7390c42aba2 100644 --- a/app/views/groups/_group_admin_settings.html.haml +++ b/app/views/groups/_group_admin_settings.html.haml @@ -9,6 +9,10 @@ = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') %br/ %span.descr This setting can be overridden in each project. +.form-group.row + = f.label s_('ProjectCreationLevel|Allowed to create projects'), class: 'col-form-label col-sm-2' + .col-sm-10 + = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control' .form-group.row = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0' diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 6b0a6e7ed99..0a14830c666 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -18,6 +18,7 @@ %span.descr.text-muted= share_with_group_lock_help_text(@group) = render 'groups/settings/lfs', f: f + = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/two_factor_auth', f: f = render_if_exists 'groups/member_lock_setting', f: f, group: @group diff --git a/app/views/groups/settings/_project_creation_level.html.haml b/app/views/groups/settings/_project_creation_level.html.haml new file mode 100644 index 00000000000..9f711e6aade --- /dev/null +++ b/app/views/groups/settings/_project_creation_level.html.haml @@ -0,0 +1,3 @@ +.form-group + = f.label s_('ProjectCreationLevel|Allowed to create projects'), class: 'label-bold' + = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, group.project_creation_level), {}, class: 'form-control' diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 5129f11875c..1c1c7d832bd 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -19,9 +19,9 @@ = root_url - namespace_id = namespace_id_from(params) = f.select(:namespace_id, - namespaces_options(namespace_id || :current_user, - display_path: true, - extra_group: namespace_id), + namespaces_options_with_developer_maintainer_access(selected: namespace_id, + display_path: true, + extra_group: namespace_id), {}, { class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }}) diff --git a/changelogs/unreleased/move-allow-developers-to-create-projects-in-groups-to-core.yml b/changelogs/unreleased/move-allow-developers-to-create-projects-in-groups-to-core.yml new file mode 100644 index 00000000000..34fd0c1b787 --- /dev/null +++ b/changelogs/unreleased/move-allow-developers-to-create-projects-in-groups-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Move allow developers to create projects in groups to Core +merge_request: 25975 +author: +type: added diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 01ffcade931..3c426cdb969 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -126,6 +126,7 @@ Settings['issues_tracker'] ||= {} # GitLab # Settings['gitlab'] ||= Settingslogic.new({}) +Settings.gitlab['default_project_creation'] ||= ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS Settings.gitlab['default_projects_limit'] ||= 100000 Settings.gitlab['default_branch_protection'] ||= 2 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? diff --git a/db/migrate/20190311132500_add_default_project_creation_application_setting.rb b/db/migrate/20190311132500_add_default_project_creation_application_setting.rb new file mode 100644 index 00000000000..87427ad2930 --- /dev/null +++ b/db/migrate/20190311132500_add_default_project_creation_application_setting.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddDefaultProjectCreationApplicationSetting < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + unless column_exists?(:application_settings, :default_project_creation) + add_column(:application_settings, :default_project_creation, :integer, default: 2, null: false) + end + end + + def down + if column_exists?(:application_settings, :default_project_creation) + remove_column(:application_settings, :default_project_creation) + end + end +end diff --git a/db/migrate/20190311132527_add_project_creation_level_to_namespaces.rb b/db/migrate/20190311132527_add_project_creation_level_to_namespaces.rb new file mode 100644 index 00000000000..159e0a95ace --- /dev/null +++ b/db/migrate/20190311132527_add_project_creation_level_to_namespaces.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddProjectCreationLevelToNamespaces < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + unless column_exists?(:namespaces, :project_creation_level) + add_column :namespaces, :project_creation_level, :integer + end + end + + def down + unless column_exists?(:namespaces, :project_creation_level) + remove_column :namespaces, :project_creation_level, :integer + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b20fe4b3d39..1a50c6efbc7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -177,6 +177,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do t.string "runners_registration_token_encrypted" t.integer "local_markdown_version", default: 0, null: false t.integer "first_day_of_week", default: 0, null: false + t.integer "default_project_creation", default: 2, null: false t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree end @@ -1391,6 +1392,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do t.integer "cached_markdown_version" t.string "runners_token" t.string "runners_token_encrypted" + t.integer "project_creation_level" t.boolean "auto_devops_enabled" t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 3bcfd30079d..2f1cadb2bbc 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -151,6 +151,17 @@ There are two different ways to add a new project to a group: ![Select group](img/select_group_dropdown.png) +### Default project creation level + +Group owners or administrators can allow users with the +Developer role to create projects under groups. + +By default, [Developers and Maintainers](../permissions.md##group-members-permissions) can create projects under agroup, but this can be changed either within the group settings for a group, or +be set globally by a GitLab administrator in the Admin area +at **Settings > General > Visibility and access controls**. + +Available settings are `No one`, `Maintainers`, or `Developers + Maintainers`. + ## Transfer projects into groups Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace). diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d742c6c97c1..d96cdc31212 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -40,7 +40,8 @@ module API end optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts" - optional :default_branch_protection, type: Integer, values: Gitlab::Access.protection_values, desc: 'Determine if developers can push to master' + optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group' + optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to master' optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility' optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects' diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 6c8ca8f219c..6eb08f674c2 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -24,6 +24,11 @@ module Gitlab PROTECTION_FULL = 2 PROTECTION_DEV_CAN_MERGE = 3 + # Default project creation level + NO_ONE_PROJECT_ACCESS = 0 + MAINTAINER_PROJECT_ACCESS = 1 + DEVELOPER_MAINTAINER_PROJECT_ACCESS = 2 + class << self delegate :values, to: :options @@ -85,6 +90,22 @@ module Gitlab def human_access_with_none(access) options_with_none.key(access) end + + def project_creation_options + { + s_('ProjectCreationLevel|No one') => NO_ONE_PROJECT_ACCESS, + s_('ProjectCreationLevel|Maintainers') => MAINTAINER_PROJECT_ACCESS, + s_('ProjectCreationLevel|Developers + Maintainers') => DEVELOPER_MAINTAINER_PROJECT_ACCESS + } + end + + def project_creation_values + project_creation_options.values + end + + def project_creation_level_name(name) + project_creation_options.key(name) + end end def human_access diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 554bb55f9ef..1166134347c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6418,6 +6418,21 @@ msgstr "" msgid "ProjectActivityRSS|Subscribe" msgstr "" +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Maintainers" +msgstr "" + +msgid "ProjectCreationLevel|Maintainers" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + msgid "ProjectFileTree|Name" msgstr "" diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 9af472df74e..1a7be4c9a85 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -85,6 +85,13 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end + + it 'updates the default_project_creation for string value' do + put :update, params: { application_setting: { default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 647fce0ecef..22165faa625 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -60,5 +60,11 @@ describe Admin::GroupsController do expect(response).to redirect_to(admin_group_path(group)) expect(group.users).not_to include group_user end + + it 'updates the project_creation_level successfully' do + expect do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } } + end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS) + end end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 2b803e7151f..4a28a27da79 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -349,6 +349,13 @@ describe GroupsController do expect(assigns(:group).errors).not_to be_empty expect(assigns(:group).path).not_to eq('new_path') end + + it 'updates the project_creation_level successfully' do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to have_gitlab_http_status(302) + expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe '#ensure_canonical_path' do diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index dcef8571f41..18a0c2ec731 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -4,6 +4,7 @@ FactoryBot.define do path { name.downcase.gsub(/\s/, '_') } type 'Group' owner nil + project_creation_level ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS after(:create) do |group| if group.owner diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 378e4d5febc..5cef5f0521f 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -77,6 +77,14 @@ describe 'Edit group settings' do end end + describe 'project creation level menu' do + it 'shows the selection menu' do + visit edit_group_path(group) + + expect(page).to have_content('Allowed to create projects') + end + end + describe 'edit group avatar' do before do visit edit_group_path(group) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 75c72a68069..b54ea929978 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -252,4 +252,23 @@ describe 'New project' do end end end + + context 'Namespace selector' do + context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + visit new_project_path(namespace_id: group.id) + end + + it 'selects the group namespace' do + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id option[selected]') + + expect(namespace.text).to eq group.full_path + end + end + end + end end diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb index 8d7e2883b2a..c0932539131 100644 --- a/spec/features/projects/user_creates_project_spec.rb +++ b/spec/features/projects/user_creates_project_spec.rb @@ -54,4 +54,31 @@ describe 'User creates a project', :js do expect(project.namespace).to eq(subgroup) end end + + context 'in a group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + end + + it 'creates a new project' do + visit(new_project_path) + + fill_in :project_name, with: 'a-new-project' + fill_in :project_path, with: 'a-new-project' + + page.find('.js-select-namespace').click + page.find("div[role='option']", text: group.full_path).click + + page.within('#content-body') do + click_button('Create project') + end + + expect(page).to have_content("Project 'a-new-project' was successfully created") + + project = Project.find_by(name: 'a-new-project') + expect(project.namespace).to eq(group) + end + end end diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 7ccbdcd1332..601f864ef36 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -1,10 +1,38 @@ require 'spec_helper' -describe NamespacesHelper do +describe NamespacesHelper, :postgresql do let!(:admin) { create(:admin) } - let!(:admin_group) { create(:group, :private) } + let!(:admin_project_creation_level) { nil } + let!(:admin_group) do + create(:group, + :private, + project_creation_level: admin_project_creation_level) + end let!(:user) { create(:user) } - let!(:user_group) { create(:group, :private) } + let!(:user_project_creation_level) { nil } + let!(:user_group) do + create(:group, + :private, + project_creation_level: user_project_creation_level) + end + let!(:subgroup1) do + create(:group, + :private, + parent: admin_group, + project_creation_level: nil) + end + let!(:subgroup2) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + end + let!(:subgroup3) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end before do admin_group.add_owner(admin) @@ -105,5 +133,43 @@ describe NamespacesHelper do helper.namespaces_options end end + + describe 'include_groups_with_developer_maintainer_access parameter' do + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set for a project' do + let!(:admin_project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + + it 'returns groups where user is a developer' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).not_to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set globally' do + it 'return groups where default is not overridden' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index ad3e3061b9a..e6e7298a043 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -959,4 +959,12 @@ describe Group do end end end + + describe 'project_creation_level' do + it 'outputs the default one if it is nil' do + group = create(:group, project_creation_level: nil) + + expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation) + end + end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index dc98baca6dc..59f3a961d50 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -347,6 +347,120 @@ describe GroupPolicy do end end + context "create_projects" do + context 'when group has no project creation level set' do + let(:group) { create(:group, project_creation_level: nil) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to no one' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_disallowed(:create_projects) } + end + end + + context 'when group has project creation level set to maintainer only' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to developers + maintainer' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + end + it_behaves_like 'clusterable policies' do let(:clusterable) { create(:group) } let(:cluster) do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f33eb5b9e02..f869325e892 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -44,6 +44,7 @@ describe API::Settings, 'Settings' do put api("/application/settings", admin), params: { default_projects_limit: 3, + default_project_creation: 2, password_authentication_enabled_for_web: false, repository_storages: ['custom'], plantuml_enabled: true, @@ -64,12 +65,13 @@ describe API::Settings, 'Settings' do performance_bar_allowed_group_path: group.full_path, instance_statistics_visibility_private: true, diff_max_patch_bytes: 150_000, - default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE, + default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, local_markdown_version: 3 } expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['default_project_creation']).to eq(::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) expect(json_response['password_authentication_enabled_for_web']).to be_falsey expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['plantuml_enabled']).to be_truthy