Commit 097b63fc authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'move-allow-developers-to-create-projects-in-groups-to-core' into 'master'

Move allow developers to create projects in groups to Core

See merge request gitlab-org/gitlab-ce!25975
parents 8cdda8f7 64858317
...@@ -89,7 +89,8 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -89,7 +89,8 @@ class Admin::GroupsController < Admin::ApplicationController
:request_access_enabled, :request_access_enabled,
:visibility_level, :visibility_level,
:require_two_factor_authentication, :require_two_factor_authentication,
:two_factor_grace_period :two_factor_grace_period,
:project_creation_level
] ]
end end
end end
...@@ -187,7 +187,8 @@ class GroupsController < Groups::ApplicationController ...@@ -187,7 +187,8 @@ class GroupsController < Groups::ApplicationController
:create_chat_team, :create_chat_team,
:chat_team_name, :chat_team_name,
:require_two_factor_authentication, :require_two_factor_authentication,
:two_factor_grace_period :two_factor_grace_period,
:project_creation_level
] ]
end end
......
...@@ -137,6 +137,7 @@ module ApplicationSettingsHelper ...@@ -137,6 +137,7 @@ module ApplicationSettingsHelper
:default_artifacts_expire_in, :default_artifacts_expire_in,
:default_branch_protection, :default_branch_protection,
:default_group_visibility, :default_group_visibility,
:default_project_creation,
:default_project_visibility, :default_project_visibility,
:default_projects_limit, :default_projects_limit,
:default_snippet_visibility, :default_snippet_visibility,
......
...@@ -49,6 +49,13 @@ module NamespacesHelper ...@@ -49,6 +49,13 @@ module NamespacesHelper
end end
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 private
# Many importers create a temporary Group, so use the real # Many importers create a temporary Group, so use the real
......
...@@ -26,6 +26,7 @@ module ApplicationSettingImplementation ...@@ -26,6 +26,7 @@ module ApplicationSettingImplementation
default_artifacts_expire_in: '30 days', default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'], 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_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
......
...@@ -404,6 +404,10 @@ class Group < Namespace ...@@ -404,6 +404,10 @@ class Group < Namespace
Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true) Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true)
end end
def project_creation_level
super || ::Gitlab::CurrentSettings.default_project_creation
end
private private
def update_two_factor_requirement def update_two_factor_requirement
......
...@@ -105,6 +105,7 @@ class User < ApplicationRecord ...@@ -105,6 +105,7 @@ class User < ApplicationRecord
has_many :groups, through: :group_members has_many :groups, through: :group_members
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group 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 :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, has_many :owned_or_maintainers_groups,
-> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
through: :group_members, through: :group_members,
...@@ -883,7 +884,12 @@ class User < ApplicationRecord ...@@ -883,7 +884,12 @@ class User < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def several_namespaces? 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 end
def namespace_id def namespace_id
...@@ -1169,12 +1175,24 @@ class User < ApplicationRecord ...@@ -1169,12 +1175,24 @@ class User < ApplicationRecord
@manageable_namespaces ||= [namespace] + manageable_groups @manageable_namespaces ||= [namespace] + manageable_groups
end end
def manageable_groups def manageable_groups(include_groups_with_developer_maintainer_access: false)
Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants 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 end
def manageable_groups_with_routes def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false)
manageable_groups.eager_load(:route).order('routes.path') manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access)
.eager_load(:route)
.order('routes.path')
end end
def namespaces def namespaces
...@@ -1573,4 +1591,16 @@ class User < ApplicationRecord ...@@ -1573,4 +1591,16 @@ class User < ApplicationRecord
ensure ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid) Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end 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 end
...@@ -35,6 +35,14 @@ class GroupPolicy < BasePolicy ...@@ -35,6 +35,14 @@ class GroupPolicy < BasePolicy
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled } 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 rule { public_group }.policy do
enable :read_group enable :read_group
enable :read_list enable :read_list
...@@ -115,6 +123,9 @@ class GroupPolicy < BasePolicy ...@@ -115,6 +123,9 @@ class GroupPolicy < BasePolicy
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster 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 def access_level
return GroupMember::NO_ACCESS if @user.nil? return GroupMember::NO_ACCESS if @user.nil?
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
.form-group .form-group
= f.label :default_branch_protection, class: 'label-bold' = 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' = 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 .form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'label-bold' = 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) = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/ %br/
%span.descr This setting can be overridden in each project. %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 .form-group.row
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0' = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0'
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
%span.descr.text-muted= share_with_group_lock_help_text(@group) %span.descr.text-muted= share_with_group_lock_help_text(@group)
= render 'groups/settings/lfs', f: f = 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 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group = render_if_exists 'groups/member_lock_setting', f: f, group: @group
......
.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'
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
= root_url = root_url
- namespace_id = namespace_id_from(params) - namespace_id = namespace_id_from(params)
= f.select(:namespace_id, = f.select(:namespace_id,
namespaces_options(namespace_id || :current_user, namespaces_options_with_developer_maintainer_access(selected: namespace_id,
display_path: true, display_path: true,
extra_group: namespace_id), 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: "" }}) { 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: "" }})
......
---
title: Move allow developers to create projects in groups to Core
merge_request: 25975
author:
type: added
...@@ -126,6 +126,7 @@ Settings['issues_tracker'] ||= {} ...@@ -126,6 +126,7 @@ Settings['issues_tracker'] ||= {}
# GitLab # GitLab
# #
Settings['gitlab'] ||= Settingslogic.new({}) Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_project_creation'] ||= ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
Settings.gitlab['default_projects_limit'] ||= 100000 Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2 Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
......
# 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
# 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
...@@ -177,6 +177,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do ...@@ -177,6 +177,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do
t.string "runners_registration_token_encrypted" t.string "runners_registration_token_encrypted"
t.integer "local_markdown_version", default: 0, null: false t.integer "local_markdown_version", default: 0, null: false
t.integer "first_day_of_week", 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 t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end end
...@@ -1391,6 +1392,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do ...@@ -1391,6 +1392,7 @@ ActiveRecord::Schema.define(version: 20190325165127) do
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
t.string "runners_token" t.string "runners_token"
t.string "runners_token_encrypted" t.string "runners_token_encrypted"
t.integer "project_creation_level"
t.boolean "auto_devops_enabled" t.boolean "auto_devops_enabled"
t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree 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 t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree
......
...@@ -151,6 +151,17 @@ There are two different ways to add a new project to a group: ...@@ -151,6 +151,17 @@ There are two different ways to add a new project to a group:
![Select group](img/select_group_dropdown.png) ![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 ## Transfer projects into groups
Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace). Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace).
......
...@@ -40,7 +40,8 @@ module API ...@@ -40,7 +40,8 @@ module API
end end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' 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_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_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_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' optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
......
...@@ -24,6 +24,11 @@ module Gitlab ...@@ -24,6 +24,11 @@ module Gitlab
PROTECTION_FULL = 2 PROTECTION_FULL = 2
PROTECTION_DEV_CAN_MERGE = 3 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 class << self
delegate :values, to: :options delegate :values, to: :options
...@@ -85,6 +90,22 @@ module Gitlab ...@@ -85,6 +90,22 @@ module Gitlab
def human_access_with_none(access) def human_access_with_none(access)
options_with_none.key(access) options_with_none.key(access)
end 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 end
def human_access def human_access
......
...@@ -6418,6 +6418,21 @@ msgstr "" ...@@ -6418,6 +6418,21 @@ msgstr ""
msgid "ProjectActivityRSS|Subscribe" msgid "ProjectActivityRSS|Subscribe"
msgstr "" 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" msgid "ProjectFileTree|Name"
msgstr "" msgstr ""
......
...@@ -85,6 +85,13 @@ describe Admin::ApplicationSettingsController do ...@@ -85,6 +85,13 @@ describe Admin::ApplicationSettingsController do
expect(response).to redirect_to(admin_application_settings_path) expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
end 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 end
describe 'PUT #reset_registration_token' do describe 'PUT #reset_registration_token' do
......
...@@ -60,5 +60,11 @@ describe Admin::GroupsController do ...@@ -60,5 +60,11 @@ describe Admin::GroupsController do
expect(response).to redirect_to(admin_group_path(group)) expect(response).to redirect_to(admin_group_path(group))
expect(group.users).not_to include group_user expect(group.users).not_to include group_user
end 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
end end
...@@ -349,6 +349,13 @@ describe GroupsController do ...@@ -349,6 +349,13 @@ describe GroupsController do
expect(assigns(:group).errors).not_to be_empty expect(assigns(:group).errors).not_to be_empty
expect(assigns(:group).path).not_to eq('new_path') expect(assigns(:group).path).not_to eq('new_path')
end 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 end
describe '#ensure_canonical_path' do describe '#ensure_canonical_path' do
......
...@@ -4,6 +4,7 @@ FactoryBot.define do ...@@ -4,6 +4,7 @@ FactoryBot.define do
path { name.downcase.gsub(/\s/, '_') } path { name.downcase.gsub(/\s/, '_') }
type 'Group' type 'Group'
owner nil owner nil
project_creation_level ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS
after(:create) do |group| after(:create) do |group|
if group.owner if group.owner
......
...@@ -77,6 +77,14 @@ describe 'Edit group settings' do ...@@ -77,6 +77,14 @@ describe 'Edit group settings' do
end end
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 describe 'edit group avatar' do
before do before do
visit edit_group_path(group) visit edit_group_path(group)
......
...@@ -252,4 +252,23 @@ describe 'New project' do ...@@ -252,4 +252,23 @@ describe 'New project' do
end end
end 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 end
...@@ -54,4 +54,31 @@ describe 'User creates a project', :js do ...@@ -54,4 +54,31 @@ describe 'User creates a project', :js do
expect(project.namespace).to eq(subgroup) expect(project.namespace).to eq(subgroup)
end end
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 end
require 'spec_helper' require 'spec_helper'
describe NamespacesHelper do describe NamespacesHelper, :postgresql do
let!(:admin) { create(:admin) } 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) { 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 before do
admin_group.add_owner(admin) admin_group.add_owner(admin)
...@@ -105,5 +133,43 @@ describe NamespacesHelper do ...@@ -105,5 +133,43 @@ describe NamespacesHelper do
helper.namespaces_options helper.namespaces_options
end end
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
end end
...@@ -959,4 +959,12 @@ describe Group do ...@@ -959,4 +959,12 @@ describe Group do
end end
end 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 end
...@@ -347,6 +347,120 @@ describe GroupPolicy do ...@@ -347,6 +347,120 @@ describe GroupPolicy do
end end
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 it_behaves_like 'clusterable policies' do
let(:clusterable) { create(:group) } let(:clusterable) { create(:group) }
let(:cluster) do let(:cluster) do
......
...@@ -44,6 +44,7 @@ describe API::Settings, 'Settings' do ...@@ -44,6 +44,7 @@ describe API::Settings, 'Settings' do
put api("/application/settings", admin), put api("/application/settings", admin),
params: { params: {
default_projects_limit: 3, default_projects_limit: 3,
default_project_creation: 2,
password_authentication_enabled_for_web: false, password_authentication_enabled_for_web: false,
repository_storages: ['custom'], repository_storages: ['custom'],
plantuml_enabled: true, plantuml_enabled: true,
...@@ -64,12 +65,13 @@ describe API::Settings, 'Settings' do ...@@ -64,12 +65,13 @@ describe API::Settings, 'Settings' do
performance_bar_allowed_group_path: group.full_path, performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true, instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000, 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 local_markdown_version: 3
} }
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3) 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['password_authentication_enabled_for_web']).to be_falsey
expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['plantuml_enabled']).to be_truthy expect(json_response['plantuml_enabled']).to be_truthy
......
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