diff --git a/ee/app/policies/ee/global_policy.rb b/ee/app/policies/ee/global_policy.rb index 97c9ef1d778b0b399a7144dadcff13866bbb7268..1e5cd81049cfd76e9fc920256c7a4c94135655b2 100644 --- a/ee/app/policies/ee/global_policy.rb +++ b/ee/app/policies/ee/global_policy.rb @@ -21,6 +21,14 @@ module GlobalPolicy ::License.feature_available?(:export_user_permissions) end + condition(:top_level_group_creation_enabled) do + if ::Gitlab.com? + ::Feature.enabled?(:top_level_group_creation_enabled, type: :ops, default_enabled: true) + else + true + end + end + rule { ~anonymous & operations_dashboard_available }.enable :read_operations_dashboard rule { admin }.policy do @@ -46,6 +54,9 @@ module GlobalPolicy end rule { export_user_permissions_available & admin }.enable :export_user_permissions + + rule { can?(:create_group) }.enable :create_group_via_api + rule { ~top_level_group_creation_enabled }.prevent :create_group_via_api end end end diff --git a/ee/changelogs/unreleased/323573-limit-creating-top-level-groups.yml b/ee/changelogs/unreleased/323573-limit-creating-top-level-groups.yml new file mode 100644 index 0000000000000000000000000000000000000000..d41b9b2280d8ff8f71a08d077ed650f129cbff3c --- /dev/null +++ b/ee/changelogs/unreleased/323573-limit-creating-top-level-groups.yml @@ -0,0 +1,5 @@ +--- +title: Add feature flag to block gitlab.com top-level group creation via api +merge_request: 56360 +author: +type: added diff --git a/ee/config/feature_flags/ops/top_level_group_creation_enabled.yml b/ee/config/feature_flags/ops/top_level_group_creation_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..37a05607ba8c57a0d7943d9db33708c9775e1318 --- /dev/null +++ b/ee/config/feature_flags/ops/top_level_group_creation_enabled.yml @@ -0,0 +1,8 @@ +--- +name: top_level_group_creation_enabled +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56360 +rollout_issue_url: +milestone: '13.10' +type: ops +group: group::access +default_enabled: true diff --git a/ee/lib/ee/api/groups.rb b/ee/lib/ee/api/groups.rb index af4d5ef022edc7e51d89067708cbc1a80b24cd23..7c071ec238025ea28bb4e698288ac0be6224ec24 100644 --- a/ee/lib/ee/api/groups.rb +++ b/ee/lib/ee/api/groups.rb @@ -49,6 +49,11 @@ def update_group(group) super end + override :authorize_group_creation! + def authorize_group_creation! + authorize! :create_group_via_api + end + def check_audit_events_available!(group) forbidden! unless group.feature_available?(:audit_events) end diff --git a/ee/spec/policies/global_policy_spec.rb b/ee/spec/policies/global_policy_spec.rb index cd7c31a75abbb4ebec463a559ae0a99a9709422d..eee4da5a734df42b9a8c37e7da08e80e1e416ee3 100644 --- a/ee/spec/policies/global_policy_spec.rb +++ b/ee/spec/policies/global_policy_spec.rb @@ -266,4 +266,48 @@ it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end + + describe 'create_group_via_api' do + let(:policy) { :create_group_via_api } + + context 'on .com' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + end + + context 'when feature is enabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: true) + end + + it { is_expected.to be_allowed(policy) } + end + + context 'when feature is disabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: false) + end + + it { is_expected.to be_disallowed(policy) } + end + end + + context 'on self-managed' do + context 'when feature is enabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: true) + end + + it { is_expected.to be_allowed(policy) } + end + + context 'when feature is disabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: false) + end + + it { is_expected.to be_allowed(policy) } + end + end + end end diff --git a/ee/spec/requests/api/groups_spec.rb b/ee/spec/requests/api/groups_spec.rb index 40a1b77a175eb863f1ae8ce851f8c689fa31aa36..af46cbbce6b11b4123ca958cb41438a88eca10d1 100644 --- a/ee/spec/requests/api/groups_spec.rb +++ b/ee/spec/requests/api/groups_spec.rb @@ -414,6 +414,100 @@ end end end + + context 'when creating group on .com' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + end + + context 'when top_level_group_creation_enabled feature flag is disabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: false) + end + + it 'does not create a top-level group' do + group = attributes_for_group_api + + expect do + post api("/groups", admin), params: group + end.not_to change { Group.count } + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'creates a subgroup' do + parent = create(:group) + parent.add_owner(admin) + + expect do + post api("/groups", admin), params: { parent_id: parent.id, name: 'foo', path: 'foo' } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + end + end + + context 'when top_level_group_creation_enabled feature flag is enabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: true) + end + + it 'creates a top-level group' do + group = attributes_for_group_api + + expect do + post api("/groups", admin), params: group + end.to change { Group.count } + + expect(response).to have_gitlab_http_status(:created) + end + end + end + + context 'when creating group on self-managed' do + context 'when top_level_group_creation_enabled feature flag is disabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: false) + end + + it 'creates a top-level group' do + group = attributes_for_group_api + + expect do + post api("/groups", admin), params: group + end.to change { Group.count } + + expect(response).to have_gitlab_http_status(:created) + end + + it 'creates a subgroup' do + parent = create(:group) + parent.add_owner(admin) + + expect do + post api("/groups", admin), params: { parent_id: parent.id, name: 'foo', path: 'foo' } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + end + end + + context 'when top_level_group_creation_enabled feature flag is enabled' do + before do + stub_feature_flags(top_level_group_creation_enabled: true) + end + + it 'creates a top-level group' do + group = attributes_for_group_api + + expect do + post api("/groups", admin), params: group + end.to change { Group.count } + + expect(response).to have_gitlab_http_status(:created) + end + end + end end describe 'POST /groups/:id/ldap_sync' do diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a8b1cdab0212d9a95bfd677272cfefe67b67668d..26fa00d618669bf9ed8d2a56dfe87ee4df44c34f 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -137,6 +137,10 @@ def handle_similarity_order(group, projects) end end # rubocop: enable CodeReuse/ActiveRecord + + def authorize_group_creation! + authorize! :create_group + end end resource :groups do @@ -169,7 +173,7 @@ def handle_similarity_order(group, projects) if parent_group authorize! :create_subgroup, parent_group else - authorize! :create_group + authorize_group_creation! end group = create_group