From 4dac478b9e35a3ae23325c827bc73adb41996eb2 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Thu, 23 Jun 2022 14:17:19 +0800 Subject: [PATCH 1/9] Move NamespaceSetting::NAMESPACE_SETTINGS_PARAMS to a method This allows us to change the values for EE. --- app/models/namespace_setting.rb | 23 +++++++++++++++++------ app/services/groups/base_service.rb | 4 ++-- app/services/groups/create_service.rb | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index 504daf2662e12a..b9f4a56ea1ebfd 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -24,14 +24,25 @@ class NamespaceSetting < ApplicationRecord chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval - NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal, - :lock_delayed_project_removal, :resource_access_token_creation_allowed, - :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, - :setup_for_company, :jobs_to_be_done, :runner_token_expiration_interval, :enabled_git_access_protocol, - :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].freeze - self.primary_key = :namespace_id + def self.parameters + %i[ + default_branch_name + delayed_project_removal + lock_delayed_project_removal + resource_access_token_creation_allowed + prevent_sharing_groups_outside_hierarchy + new_user_signups_cap + setup_for_company + jobs_to_be_done + runner_token_expiration_interval + enabled_git_access_protocol + subgroup_runner_token_expiration_interval + project_runner_token_expiration_interval + ].freeze + end + sanitizes! :default_branch_name def prevent_sharing_groups_outside_hierarchy diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb index 06136aff50ec1d..10cff57a460535 100644 --- a/app/services/groups/base_service.rb +++ b/app/services/groups/base_service.rb @@ -13,11 +13,11 @@ def initialize(group, user, params = {}) private def handle_namespace_settings - settings_params = params.slice(*::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS) + settings_params = params.slice(*::NamespaceSetting.parameters) return if settings_params.empty? - ::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS.each do |nsp| + ::NamespaceSetting.parameters.each do |nsp| params.delete(nsp) end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 639f7c68c40c71..5e369cb8693a5c 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -13,7 +13,7 @@ def execute remove_unallowed_params set_visibility_level - @group = Group.new(params.except(*::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS)) + @group = Group.new(params.except(*::NamespaceSetting.parameters)) @group.build_namespace_settings handle_namespace_settings -- GitLab From ce5084ae6245218462489dbbb045572fcfa05c2c Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Thu, 23 Jun 2022 16:46:39 +0800 Subject: [PATCH 2/9] Add settings for rate limiting unique project downloads per namespace Add namespace settings to set how many unique projects a user can download within a set time interval before they are auto-banned. Changelog: added --- ...e_project_downloads_per_namespace_user.yml | 8 ++ ...ad_limit_settings_to_namespace_settings.rb | 10 ++ db/schema_migrations/20220613054349 | 1 + db/structure.sql | 2 + .../groups/settings/reporting_controller.rb | 41 ++++++ ee/app/models/ee/group.rb | 6 + ee/app/models/ee/namespace_setting.rb | 25 ++++ .../models/gitlab_subscriptions/features.rb | 1 + .../groups/settings/reporting/show.html.haml | 23 ++++ ee/config/routes/group.rb | 4 + .../ee/sidebars/groups/menus/settings_menu.rb | 16 +++ .../groups/settings/reporting_spec.rb | 69 ++++++++++ .../groups/menus/settings_menu_spec.rb | 17 +++ ee/spec/models/ee/group_spec.rb | 36 +++++ ee/spec/models/namespace_setting_spec.rb | 28 ++++ .../settings/reporting_controller_spec.rb | 125 ++++++++++++++++++ locale/gitlab.pot | 18 +++ 17 files changed, 430 insertions(+) create mode 100644 config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml create mode 100644 db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb create mode 100644 db/schema_migrations/20220613054349 create mode 100644 ee/app/controllers/groups/settings/reporting_controller.rb create mode 100644 ee/app/views/groups/settings/reporting/show.html.haml create mode 100644 ee/spec/features/groups/settings/reporting_spec.rb create mode 100644 ee/spec/requests/groups/settings/reporting_controller_spec.rb diff --git a/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml b/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml new file mode 100644 index 00000000000000..9e2735672fb061 --- /dev/null +++ b/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml @@ -0,0 +1,8 @@ +--- +name: limit_unique_project_downloads_per_namespace_user +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89996 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365724 +milestone: '15.2' +type: development +group: group::anti-abuse +default_enabled: false diff --git a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb new file mode 100644 index 00000000000000..de6efa0d7e5e7f --- /dev/null +++ b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddUniqueProjectDownloadLimitSettingsToNamespaceSettings < Gitlab::Database::Migration[2.0] + def change + add_column :namespace_settings, :unique_project_download_limit, :smallint, + default: 0, null: false + add_column :namespace_settings, :unique_project_download_limit_interval, :integer, + default: 0, null: false + end +end diff --git a/db/schema_migrations/20220613054349 b/db/schema_migrations/20220613054349 new file mode 100644 index 00000000000000..1c3806a80c8b88 --- /dev/null +++ b/db/schema_migrations/20220613054349 @@ -0,0 +1 @@ +4c3e4852614dd1a59d63809c40417887794bcbbcf8d3ea3a96f8846e2bd5f795 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f697bacdfc51ac..dd5651257eb8fb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17550,6 +17550,8 @@ CREATE TABLE namespace_settings ( project_runner_token_expiration_interval integer, exclude_from_free_user_cap boolean DEFAULT false NOT NULL, enabled_git_access_protocol smallint DEFAULT 0 NOT NULL, + unique_project_download_limit smallint DEFAULT 0 NOT NULL, + unique_project_download_limit_interval integer DEFAULT 0 NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); diff --git a/ee/app/controllers/groups/settings/reporting_controller.rb b/ee/app/controllers/groups/settings/reporting_controller.rb new file mode 100644 index 00000000000000..ae8db85306aaee --- /dev/null +++ b/ee/app/controllers/groups/settings/reporting_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Groups + module Settings + class ReportingController < Groups::ApplicationController + layout 'group_settings' + + before_action :check_feature_availability + before_action :authorize_admin_group! + + feature_category :insider_threat + urgency :low + + def show + end + + def update + if Groups::UpdateService.new(@group, current_user, group_params).execute + notice = "Group '#{@group.name}' was successfully updated." + + redirect_to group_settings_reporting_path(@group), notice: notice + else + render action: "show" + end + end + + private + + def group_params + params.require(:group).permit(%i[ + unique_project_download_limit + unique_project_download_limit_interval + ]) + end + + def check_feature_availability + render_404 unless group.unique_project_download_limit_enabled? + end + end + end +end diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb index 9d72dda8d51cbc..53cd6da39bb6dd 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -177,6 +177,12 @@ def enforced_sso? def repository_read_only? !!namespace_settings&.repository_read_only? end + + def unique_project_download_limit_enabled? + root? && + ::Feature.enabled?(:limit_unique_project_downloads_per_namespace_user, self) && + licensed_feature_available?(:unique_project_download_limit) + end end class_methods do diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index 92d63050df5e59..51d869efd303b1 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -5,6 +5,13 @@ module NamespaceSetting extend ActiveSupport::Concern prepended do + validates :unique_project_download_limit, + numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10_000 }, + presence: true + validates :unique_project_download_limit_interval, + numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10.days.to_i }, + presence: true + validate :user_cap_allowed, if: -> { enabling_user_cap? } before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? } @@ -46,5 +53,23 @@ def user_cap_enabled? new_user_signups_cap.present? && namespace.root? end end + + class_methods do + extend ::Gitlab::Utils::Override + + override :parameters + def parameters + super + unique_project_download_limit_attributes + end + + private + + def unique_project_download_limit_attributes + %i[ + unique_project_download_limit + unique_project_download_limit_interval + ].freeze + end + end end end diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 5f520d44559e39..f404571964d5a2 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -216,6 +216,7 @@ class Features stale_runner_cleanup_for_namespace status_page subepics + unique_project_download_limit vulnerability_auto_fix vulnerability_finding_signatures ].freeze diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml new file mode 100644 index 00000000000000..91d37f4ae892a8 --- /dev/null +++ b/ee/app/views/groups/settings/reporting/show.html.haml @@ -0,0 +1,23 @@ +- title = s_("GroupSettings|Reporting") +- breadcrumb_title title +- page_title title + +%h4.settings-title + = s_('GroupSettings|Project download rate limit') +%p + = s_('GroupSettings|Automatically ban users who download more than the specified number of projects within the specified interval.') + += form_errors(@group.namespace_settings) + += gitlab_ui_form_for @group, url: group_settings_reporting_path(@group) do |f| + .form-group + = f.label :unique_project_download_limit, s_('GroupSettings|Unique project download limit'), class: 'label-bold' + = f.number_field :unique_project_download_limit, value: @group.namespace_settings&.unique_project_download_limit, class: 'form-control gl-form-input' + .form-text.text-muted + = s_("GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned.") + + .form-group + = f.label :unique_project_download_limit_interval, s_('GroupSettings|Unique project download limit interval in seconds'), class: 'label-bold' + = f.number_field :unique_project_download_limit_interval, value: @group.namespace_settings&.unique_project_download_limit_interval, class: 'form-control gl-form-input' + + = f.submit _('Save changes'), class: 'btn gl-button btn-confirm mt-4' diff --git a/ee/config/routes/group.rb b/ee/config/routes/group.rb index 3487f2a64b38fd..20aef35be02de6 100644 --- a/ee/config/routes/group.rb +++ b/ee/config/routes/group.rb @@ -7,6 +7,10 @@ constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do draw :wiki + namespace :settings do + resource :reporting, only: [:show, :update], controller: 'reporting' + end + resources :group_members, only: [], concerns: :access_requestable do patch :override, on: :member diff --git a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb index 3b59a60eed7d06..9d29292ba2839c 100644 --- a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb @@ -18,6 +18,7 @@ def configure_menu_items add_item(saml_group_links_menu_item) add_item(usage_quotas_menu_item) add_item(billing_menu_item) + add_item(reporting_menu_item) true end @@ -133,6 +134,21 @@ def administration_nav_item_disabled? ::Feature.disabled?(:group_administration_nav_item, context.group) end end + + def reporting_menu_item + id = :reporting + + unless context.group.unique_project_download_limit_enabled? + return ::Sidebars::NilMenuItem.new(item_id: id) + end + + ::Sidebars::MenuItem.new( + title: s_('GroupSettings|Reporting'), + link: group_settings_reporting_path(context.group), + active_routes: { path: 'reporting#show' }, + item_id: id + ) + end end end end diff --git a/ee/spec/features/groups/settings/reporting_spec.rb b/ee/spec/features/groups/settings/reporting_spec.rb new file mode 100644 index 00000000000000..b074e2fab34709 --- /dev/null +++ b/ee/spec/features/groups/settings/reporting_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Group reporting settings' do + let_it_be(:user) { create(:user) } + + let(:group) { create(:group) } + let(:feature_flag_enabled) { true } + let(:licensed_feature_available) { true } + let(:current_limit) { 1 } + let(:current_interval) { 9 } + + before do + stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled) + stub_licensed_features(unique_project_download_limit: licensed_feature_available) + + sign_in(user) + + group.add_owner(user) + + group.namespace_settings.update!( + unique_project_download_limit: current_limit, + unique_project_download_limit_interval: current_interval + ) + + visit group_settings_reporting_path(group) + end + + it 'displays the side bar menu item' do + page.within('.shortcuts-settings') do + expect(page).to have_link 'Reporting', href: group_settings_reporting_path(group) + end + end + + it 'updates the settings' do + limit_label = s_('GroupSettings|Unique project download limit') + interval_label = s_('GroupSettings|Unique project download limit interval in seconds') + + expect(page).to have_field(limit_label, with: current_limit) + expect(page).to have_field(interval_label, with: current_interval) + + new_limit = 5 + new_interval = 300 + + fill_in(limit_label, with: new_limit) + fill_in(interval_label, with: new_interval) + + click_button 'Save changes' + + group.reload + + expect(group.namespace_settings.unique_project_download_limit).to eq new_limit + expect(group.namespace_settings.unique_project_download_limit_interval).to eq new_interval + + expect(page).to have_field(limit_label, with: new_limit) + expect(page).to have_field(interval_label, with: new_interval) + end + + it 'displays validation errors' do + fill_in s_('GroupSettings|Unique project download limit'), with: -1 + fill_in s_('GroupSettings|Unique project download limit interval in seconds'), with: -1 + + click_button 'Save changes' + + expect(page).to have_content('Unique project download limit must be greater than or equal to 0') + expect(page).to have_content('Unique project download limit interval must be greater than or equal to 0') + end +end diff --git a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb index d28d1f508456ec..1779b7d48aac36 100644 --- a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb +++ b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb @@ -183,5 +183,22 @@ specify { is_expected.to be_nil } end end + + describe 'Reporting menu' do + let(:item_id) { :reporting } + let(:feature_enabled) { true } + + before do + allow(group).to receive(:unique_project_download_limit_enabled?) { feature_enabled } + end + + it { is_expected.to be_present } + + context 'when feature is not enabled' do + let(:feature_enabled) { false } + + it { is_expected.to be_nil } + end + end end end diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb index 52a0c42ec17e60..12be8c6c50d41e 100644 --- a/ee/spec/models/ee/group_spec.rb +++ b/ee/spec/models/ee/group_spec.rb @@ -2542,4 +2542,40 @@ def webhook_headers it { is_expected.to contain_exactly(cluster_agent_for_project, cluster_agent_for_project_in_subgroup) } end + + describe '#unique_project_download_limit_enabled?' do + let_it_be(:group) { create(:group) } + + let(:feature_flag_enabled) { true } + let(:licensed_feature_available) { true } + + before do + stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled) + stub_licensed_features(unique_project_download_limit: licensed_feature_available) + end + + subject { group.unique_project_download_limit_enabled? } + + it { is_expected.to eq true } + + context 'when feature flag is disabled' do + let(:feature_flag_enabled) { false } + + it { is_expected.to eq false } + end + + context 'when licensed feature is not available' do + let(:licensed_feature_available) { false } + + it { is_expected.to eq false } + end + + context 'when sub-group' do + let(:subgroup) { create(:group, parent: group) } + + subject { subgroup.unique_project_download_limit_enabled? } + + it { is_expected.to eq false } + end + end end diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 0076c55437f8f1..e91f9c254d1c8c 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -6,6 +6,25 @@ let(:group) { create(:group) } let(:setting) { group.namespace_settings } + describe 'validations' do + subject(:settings) { group.namespace_settings } + + it { is_expected.to validate_presence_of(:unique_project_download_limit) } + it { is_expected.to validate_presence_of(:unique_project_download_limit_interval) } + it { + is_expected.to validate_numericality_of(:unique_project_download_limit) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than_or_equal_to(10_000) + } + it { + is_expected.to validate_numericality_of(:unique_project_download_limit_interval) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than_or_equal_to(10.days.to_i) + } + end + describe '#prevent_forking_outside_group?' do context 'with feature available' do before do @@ -220,4 +239,13 @@ end end end + + describe '.parameters' do + it 'includes attributes used for limiting unique project downloads' do + expect(described_class.parameters).to include(*%i[ + unique_project_download_limit + unique_project_download_limit_interval + ]) + end + end end diff --git a/ee/spec/requests/groups/settings/reporting_controller_spec.rb b/ee/spec/requests/groups/settings/reporting_controller_spec.rb new file mode 100644 index 00000000000000..ed0326f8971a44 --- /dev/null +++ b/ee/spec/requests/groups/settings/reporting_controller_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::Settings::ReportingController, type: :request do + let_it_be(:user) { create(:user) } + + let(:group) { create(:group) } + let(:feature_flag_enabled) { true } + let(:licensed_feature_available) { true } + + before do + stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled) + stub_licensed_features(unique_project_download_limit: licensed_feature_available) + + sign_in(user) + end + + shared_examples 'renders 404' do + it 'renders 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples '404 when feature is unavailable' do + before do + subject + end + + context 'when feature flag is disabled' do + let(:feature_flag_enabled) { false } + + it_behaves_like 'renders 404' + end + + context 'when feature flag is disabled' do + let(:licensed_feature_available) { false } + + it_behaves_like 'renders 404' + end + + context 'when subgroup' do + let(:group) { create(:group, parent: create(:group)) } + + it_behaves_like 'renders 404' + end + end + + describe 'GET /groups/:group_id/-/settings/reporting' do + subject(:request) { get group_settings_reporting_path(group) } + + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'renders show with 200 status code' do + request + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:show) + end + + it_behaves_like '404 when feature is unavailable' + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'PATCH #update' do + let(:params) { { group: { unique_project_download_limit: 10 } } } + + subject(:request) do + patch(group_settings_reporting_path(group), params: params) + end + + context 'when user is not an owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is an owner' do + before do + group.add_owner(user) + end + + it_behaves_like '404 when feature is unavailable' + + it 'redirects back to show page' do + request + + expect(response).to redirect_to(group_settings_reporting_path(group)) + expect(flash[:notice]).to include("Group '#{group.name}' was successfully updated.") + end + + context 'update failed' do + let(:params) { { group: { unique_project_download_limit: -1 } } } + + it 're-renders show template' do + request + + expect(response).not_to have_gitlab_http_status(:redirect) + expect(response).to render_template(:show) + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index dc5e4ca25df294..db3624c2be0983 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18443,6 +18443,9 @@ msgstr "" msgid "GroupSettings|Auto DevOps pipeline was updated for the group" msgstr "" +msgid "GroupSettings|Automatically ban users who download more than the specified number of projects within the specified interval." +msgstr "" + msgid "GroupSettings|Available only on the top-level group. Applies to all subgroups. Groups already shared with a group outside %{group} are still shared unless removed manually." msgstr "" @@ -18521,9 +18524,15 @@ msgstr "" msgid "GroupSettings|Prevent forking setting was not saved" msgstr "" +msgid "GroupSettings|Project download rate limit" +msgstr "" + msgid "GroupSettings|Projects in %{group} cannot be shared with other groups" msgstr "" +msgid "GroupSettings|Reporting" +msgstr "" + msgid "GroupSettings|Select a subgroup to use as the source for custom project templates for this group." msgstr "" @@ -18545,6 +18554,9 @@ msgstr "" msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found." msgstr "" +msgid "GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned." +msgstr "" + msgid "GroupSettings|The projects in this subgroup can be selected as templates for new projects created in the group. %{link_start}Learn more.%{link_end}" msgstr "" @@ -18566,6 +18578,12 @@ msgstr "" msgid "GroupSettings|Transfer group" msgstr "" +msgid "GroupSettings|Unique project download limit" +msgstr "" + +msgid "GroupSettings|Unique project download limit interval in seconds" +msgstr "" + msgid "GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group" msgstr "" -- GitLab From f7f27bc1172c12896ce46b74c5081be1e1fc03b0 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Tue, 28 Jun 2022 13:39:40 +0800 Subject: [PATCH 3/9] Use lock retries --- ...que_project_download_limit_settings_to_namespace_settings.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb index de6efa0d7e5e7f..8cc965b7b30f86 100644 --- a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb +++ b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class AddUniqueProjectDownloadLimitSettingsToNamespaceSettings < Gitlab::Database::Migration[2.0] + enable_lock_retries! + def change add_column :namespace_settings, :unique_project_download_limit, :smallint, default: 0, null: false -- GitLab From 2e6367b28c52577014927b49ab23031a0a94c2d0 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Tue, 28 Jun 2022 13:40:42 +0800 Subject: [PATCH 4/9] Use gl class --- ee/app/views/groups/settings/reporting/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml index 91d37f4ae892a8..2eb736e71787e0 100644 --- a/ee/app/views/groups/settings/reporting/show.html.haml +++ b/ee/app/views/groups/settings/reporting/show.html.haml @@ -20,4 +20,4 @@ = f.label :unique_project_download_limit_interval, s_('GroupSettings|Unique project download limit interval in seconds'), class: 'label-bold' = f.number_field :unique_project_download_limit_interval, value: @group.namespace_settings&.unique_project_download_limit_interval, class: 'form-control gl-form-input' - = f.submit _('Save changes'), class: 'btn gl-button btn-confirm mt-4' + = f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-4' -- GitLab From a9da252548e2c05f682d30742f670bbe5c0bc3a6 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Tue, 28 Jun 2022 16:36:03 +0800 Subject: [PATCH 5/9] Update attribute labels --- config/locales/en.yml | 3 +++ .../views/groups/settings/reporting/show.html.haml | 4 ++-- ee/spec/features/groups/settings/reporting_spec.rb | 12 ++++++------ locale/gitlab.pot | 12 ++++++------ 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 233dca33bb88a5..52c6cb12f71389 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -10,6 +10,9 @@ en: target: Target issue group: path: Group URL + namespace_setting: + unique_project_download_limit: "Number of projects" + unique_project_download_limit_interval: "Interval (seconds)" member: user: "The member's email address" invite_email: "The member's email address" diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml index 2eb736e71787e0..4341483ae3de43 100644 --- a/ee/app/views/groups/settings/reporting/show.html.haml +++ b/ee/app/views/groups/settings/reporting/show.html.haml @@ -11,13 +11,13 @@ = gitlab_ui_form_for @group, url: group_settings_reporting_path(@group) do |f| .form-group - = f.label :unique_project_download_limit, s_('GroupSettings|Unique project download limit'), class: 'label-bold' + = f.label :unique_project_download_limit, s_('GroupSettings|Number of projects'), class: 'label-bold' = f.number_field :unique_project_download_limit, value: @group.namespace_settings&.unique_project_download_limit, class: 'form-control gl-form-input' .form-text.text-muted = s_("GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned.") .form-group - = f.label :unique_project_download_limit_interval, s_('GroupSettings|Unique project download limit interval in seconds'), class: 'label-bold' + = f.label :unique_project_download_limit_interval, s_('GroupSettings|Interval (seconds)'), class: 'label-bold' = f.number_field :unique_project_download_limit_interval, value: @group.namespace_settings&.unique_project_download_limit_interval, class: 'form-control gl-form-input' = f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-4' diff --git a/ee/spec/features/groups/settings/reporting_spec.rb b/ee/spec/features/groups/settings/reporting_spec.rb index b074e2fab34709..ba9e7d3876c5e6 100644 --- a/ee/spec/features/groups/settings/reporting_spec.rb +++ b/ee/spec/features/groups/settings/reporting_spec.rb @@ -34,8 +34,8 @@ end it 'updates the settings' do - limit_label = s_('GroupSettings|Unique project download limit') - interval_label = s_('GroupSettings|Unique project download limit interval in seconds') + limit_label = s_('GroupSettings|Number of projects') + interval_label = s_('GroupSettings|Interval (seconds)') expect(page).to have_field(limit_label, with: current_limit) expect(page).to have_field(interval_label, with: current_interval) @@ -58,12 +58,12 @@ end it 'displays validation errors' do - fill_in s_('GroupSettings|Unique project download limit'), with: -1 - fill_in s_('GroupSettings|Unique project download limit interval in seconds'), with: -1 + fill_in s_('GroupSettings|Number of projects'), with: -1 + fill_in s_('GroupSettings|Interval (seconds)'), with: -1 click_button 'Save changes' - expect(page).to have_content('Unique project download limit must be greater than or equal to 0') - expect(page).to have_content('Unique project download limit interval must be greater than or equal to 0') + expect(page).to have_content('Number of projects must be greater than or equal to 0') + expect(page).to have_content('Interval (seconds) must be greater than or equal to 0') end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index db3624c2be0983..b3dbd7d7b75dc4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18503,9 +18503,15 @@ msgstr "" msgid "GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility." msgstr "" +msgid "GroupSettings|Interval (seconds)" +msgstr "" + msgid "GroupSettings|Members cannot invite groups outside of %{group} and its subgroups" msgstr "" +msgid "GroupSettings|Number of projects" +msgstr "" + msgid "GroupSettings|Organizations and contacts can be created and associated with issues." msgstr "" @@ -18578,12 +18584,6 @@ msgstr "" msgid "GroupSettings|Transfer group" msgstr "" -msgid "GroupSettings|Unique project download limit" -msgstr "" - -msgid "GroupSettings|Unique project download limit interval in seconds" -msgstr "" - msgid "GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group" msgstr "" -- GitLab From f3ad7f456a69d35569770c276613992585bc97e3 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Wed, 29 Jun 2022 10:32:32 +0800 Subject: [PATCH 6/9] Address review feedback --- app/models/namespace_setting.rb | 32 ++++++++++--------- app/services/groups/base_service.rb | 4 +-- app/services/groups/create_service.rb | 2 +- config/locales/en.yml | 2 +- ...ad_limit_settings_to_namespace_settings.rb | 2 +- db/structure.sql | 2 +- .../groups/settings/reporting_controller.rb | 4 +-- ee/app/models/ee/namespace_setting.rb | 20 +++++------- .../groups/settings/reporting/show.html.haml | 8 +++-- .../ee/sidebars/groups/menus/settings_menu.rb | 6 ++-- .../groups/settings/reporting_spec.rb | 4 +-- ee/spec/models/namespace_setting_spec.rb | 8 ++--- .../settings/reporting_controller_spec.rb | 2 +- locale/gitlab.pot | 8 ++++- 14 files changed, 54 insertions(+), 50 deletions(-) diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index b9f4a56ea1ebfd..595e34821af7d1 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -24,23 +24,25 @@ class NamespaceSetting < ApplicationRecord chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval + NAMESPACE_SETTINGS_PARAMS = %i[ + default_branch_name + delayed_project_removal + lock_delayed_project_removal + resource_access_token_creation_allowed + prevent_sharing_groups_outside_hierarchy + new_user_signups_cap + setup_for_company + jobs_to_be_done + runner_token_expiration_interval + enabled_git_access_protocol + subgroup_runner_token_expiration_interval + project_runner_token_expiration_interval + ].freeze + self.primary_key = :namespace_id - def self.parameters - %i[ - default_branch_name - delayed_project_removal - lock_delayed_project_removal - resource_access_token_creation_allowed - prevent_sharing_groups_outside_hierarchy - new_user_signups_cap - setup_for_company - jobs_to_be_done - runner_token_expiration_interval - enabled_git_access_protocol - subgroup_runner_token_expiration_interval - project_runner_token_expiration_interval - ].freeze + def self.allowed_namespace_settings_params + NAMESPACE_SETTINGS_PARAMS end sanitizes! :default_branch_name diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb index 10cff57a460535..9705f3a560d922 100644 --- a/app/services/groups/base_service.rb +++ b/app/services/groups/base_service.rb @@ -13,11 +13,11 @@ def initialize(group, user, params = {}) private def handle_namespace_settings - settings_params = params.slice(*::NamespaceSetting.parameters) + settings_params = params.slice(*::NamespaceSetting.allowed_namespace_settings_params) return if settings_params.empty? - ::NamespaceSetting.parameters.each do |nsp| + ::NamespaceSetting.allowed_namespace_settings_params.each do |nsp| params.delete(nsp) end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 5e369cb8693a5c..35716f7742a82c 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -13,7 +13,7 @@ def execute remove_unallowed_params set_visibility_level - @group = Group.new(params.except(*::NamespaceSetting.parameters)) + @group = Group.new(params.except(*::NamespaceSetting.allowed_namespace_settings_params)) @group.build_namespace_settings handle_namespace_settings diff --git a/config/locales/en.yml b/config/locales/en.yml index 52c6cb12f71389..56df8f93113a48 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -12,7 +12,7 @@ en: path: Group URL namespace_setting: unique_project_download_limit: "Number of projects" - unique_project_download_limit_interval: "Interval (seconds)" + unique_project_download_limit_interval_in_seconds: "Interval (seconds)" member: user: "The member's email address" invite_email: "The member's email address" diff --git a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb index 8cc965b7b30f86..7e821cb17a2f6d 100644 --- a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb +++ b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb @@ -6,7 +6,7 @@ class AddUniqueProjectDownloadLimitSettingsToNamespaceSettings < Gitlab::Databas def change add_column :namespace_settings, :unique_project_download_limit, :smallint, default: 0, null: false - add_column :namespace_settings, :unique_project_download_limit_interval, :integer, + add_column :namespace_settings, :unique_project_download_limit_interval_in_seconds, :integer, default: 0, null: false end end diff --git a/db/structure.sql b/db/structure.sql index dd5651257eb8fb..2013a434bab1e6 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17551,7 +17551,7 @@ CREATE TABLE namespace_settings ( exclude_from_free_user_cap boolean DEFAULT false NOT NULL, enabled_git_access_protocol smallint DEFAULT 0 NOT NULL, unique_project_download_limit smallint DEFAULT 0 NOT NULL, - unique_project_download_limit_interval integer DEFAULT 0 NOT NULL, + unique_project_download_limit_interval_in_seconds integer DEFAULT 0 NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); diff --git a/ee/app/controllers/groups/settings/reporting_controller.rb b/ee/app/controllers/groups/settings/reporting_controller.rb index ae8db85306aaee..705c4067deeadc 100644 --- a/ee/app/controllers/groups/settings/reporting_controller.rb +++ b/ee/app/controllers/groups/settings/reporting_controller.rb @@ -16,7 +16,7 @@ def show def update if Groups::UpdateService.new(@group, current_user, group_params).execute - notice = "Group '#{@group.name}' was successfully updated." + notice = _('Group "%{group_name}" was successfully updated.' % { group_name: @group.name }) redirect_to group_settings_reporting_path(@group), notice: notice else @@ -29,7 +29,7 @@ def update def group_params params.require(:group).permit(%i[ unique_project_download_limit - unique_project_download_limit_interval + unique_project_download_limit_interval_in_seconds ]) end diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index 51d869efd303b1..d63e58c88106b1 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -8,7 +8,7 @@ module NamespaceSetting validates :unique_project_download_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10_000 }, presence: true - validates :unique_project_download_limit_interval, + validates :unique_project_download_limit_interval_in_seconds, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10.days.to_i }, presence: true @@ -57,18 +57,14 @@ def user_cap_enabled? class_methods do extend ::Gitlab::Utils::Override - override :parameters - def parameters - super + unique_project_download_limit_attributes - end - - private + EE_NAMESPACE_SETTINGS_PARAMS = %i[ + unique_project_download_limit + unique_project_download_limit_interval_in_seconds + ].freeze - def unique_project_download_limit_attributes - %i[ - unique_project_download_limit - unique_project_download_limit_interval - ].freeze + override :allowed_namespace_settings_params + def allowed_namespace_settings_params + super + EE_NAMESPACE_SETTINGS_PARAMS end end end diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml index 4341483ae3de43..732218727fd39f 100644 --- a/ee/app/views/groups/settings/reporting/show.html.haml +++ b/ee/app/views/groups/settings/reporting/show.html.haml @@ -14,10 +14,12 @@ = f.label :unique_project_download_limit, s_('GroupSettings|Number of projects'), class: 'label-bold' = f.number_field :unique_project_download_limit, value: @group.namespace_settings&.unique_project_download_limit, class: 'form-control gl-form-input' .form-text.text-muted - = s_("GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned.") + = s_("GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned. Set to 0 to disable limiting.") .form-group - = f.label :unique_project_download_limit_interval, s_('GroupSettings|Interval (seconds)'), class: 'label-bold' - = f.number_field :unique_project_download_limit_interval, value: @group.namespace_settings&.unique_project_download_limit_interval, class: 'form-control gl-form-input' + = f.label :unique_project_download_limit_interval_in_seconds, s_('GroupSettings|Interval (seconds)'), class: 'label-bold' + = f.number_field :unique_project_download_limit_interval_in_seconds, value: @group.namespace_settings&.unique_project_download_limit_interval_in_seconds, class: 'form-control gl-form-input' + .form-text.text-muted + = s_("GroupSettings|Set to 0 to disable limiting.") = f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-4' diff --git a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb index 9d29292ba2839c..347f5aefb3bb4b 100644 --- a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb @@ -136,17 +136,15 @@ def administration_nav_item_disabled? end def reporting_menu_item - id = :reporting - unless context.group.unique_project_download_limit_enabled? - return ::Sidebars::NilMenuItem.new(item_id: id) + return ::Sidebars::NilMenuItem.new(item_id: :reporting) end ::Sidebars::MenuItem.new( title: s_('GroupSettings|Reporting'), link: group_settings_reporting_path(context.group), active_routes: { path: 'reporting#show' }, - item_id: id + item_id: :reporting ) end end diff --git a/ee/spec/features/groups/settings/reporting_spec.rb b/ee/spec/features/groups/settings/reporting_spec.rb index ba9e7d3876c5e6..36009d0cd2a200 100644 --- a/ee/spec/features/groups/settings/reporting_spec.rb +++ b/ee/spec/features/groups/settings/reporting_spec.rb @@ -21,7 +21,7 @@ group.namespace_settings.update!( unique_project_download_limit: current_limit, - unique_project_download_limit_interval: current_interval + unique_project_download_limit_interval_in_seconds: current_interval ) visit group_settings_reporting_path(group) @@ -51,7 +51,7 @@ group.reload expect(group.namespace_settings.unique_project_download_limit).to eq new_limit - expect(group.namespace_settings.unique_project_download_limit_interval).to eq new_interval + expect(group.namespace_settings.unique_project_download_limit_interval_in_seconds).to eq new_interval expect(page).to have_field(limit_label, with: new_limit) expect(page).to have_field(interval_label, with: new_interval) diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index e91f9c254d1c8c..6ed39a95fc55d8 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -10,7 +10,7 @@ subject(:settings) { group.namespace_settings } it { is_expected.to validate_presence_of(:unique_project_download_limit) } - it { is_expected.to validate_presence_of(:unique_project_download_limit_interval) } + it { is_expected.to validate_presence_of(:unique_project_download_limit_interval_in_seconds) } it { is_expected.to validate_numericality_of(:unique_project_download_limit) .only_integer @@ -18,7 +18,7 @@ .is_less_than_or_equal_to(10_000) } it { - is_expected.to validate_numericality_of(:unique_project_download_limit_interval) + is_expected.to validate_numericality_of(:unique_project_download_limit_interval_in_seconds) .only_integer .is_greater_than_or_equal_to(0) .is_less_than_or_equal_to(10.days.to_i) @@ -242,9 +242,9 @@ describe '.parameters' do it 'includes attributes used for limiting unique project downloads' do - expect(described_class.parameters).to include(*%i[ + expect(described_class.allowed_namespace_settings_params).to include(*%i[ unique_project_download_limit - unique_project_download_limit_interval + unique_project_download_limit_interval_in_seconds ]) end end diff --git a/ee/spec/requests/groups/settings/reporting_controller_spec.rb b/ee/spec/requests/groups/settings/reporting_controller_spec.rb index ed0326f8971a44..2c8db608b1ee6d 100644 --- a/ee/spec/requests/groups/settings/reporting_controller_spec.rb +++ b/ee/spec/requests/groups/settings/reporting_controller_spec.rb @@ -107,7 +107,7 @@ request expect(response).to redirect_to(group_settings_reporting_path(group)) - expect(flash[:notice]).to include("Group '#{group.name}' was successfully updated.") + expect(flash[:notice]).to include("Group \"#{group.name}\" was successfully updated.") end context 'update failed' do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b3dbd7d7b75dc4..7d62c7129b4b4f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18005,6 +18005,9 @@ msgstr "" msgid "Group" msgstr "" +msgid "Group \"%{group_name}\" was successfully updated." +msgstr "" + msgid "Group %{group_name} couldn't be exported." msgstr "" @@ -18557,10 +18560,13 @@ msgstr "" msgid "GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group." msgstr "" +msgid "GroupSettings|Set to 0 to disable limiting." +msgstr "" + msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found." msgstr "" -msgid "GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned." +msgid "GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned. Set to 0 to disable limiting." msgstr "" msgid "GroupSettings|The projects in this subgroup can be selected as templates for new projects created in the group. %{link_start}Learn more.%{link_end}" -- GitLab From fb25662acc6d4ea1fd73edabdcf43622e9bf1201 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Thu, 30 Jun 2022 10:09:05 +0800 Subject: [PATCH 7/9] Use single quotes --- ee/app/views/groups/settings/reporting/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml index 732218727fd39f..2e207d24c642ad 100644 --- a/ee/app/views/groups/settings/reporting/show.html.haml +++ b/ee/app/views/groups/settings/reporting/show.html.haml @@ -20,6 +20,6 @@ = f.label :unique_project_download_limit_interval_in_seconds, s_('GroupSettings|Interval (seconds)'), class: 'label-bold' = f.number_field :unique_project_download_limit_interval_in_seconds, value: @group.namespace_settings&.unique_project_download_limit_interval_in_seconds, class: 'form-control gl-form-input' .form-text.text-muted - = s_("GroupSettings|Set to 0 to disable limiting.") + = s_('GroupSettings|Set to 0 to disable limiting.') = f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-4' -- GitLab From fd4db21dff05f7dab3ed5e53fa442eb82bc59c57 Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Thu, 30 Jun 2022 10:19:56 +0800 Subject: [PATCH 8/9] Update spec description --- ee/spec/models/namespace_setting_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 6ed39a95fc55d8..1d415b8dd0d181 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -240,7 +240,7 @@ end end - describe '.parameters' do + describe '.allowed_namespace_settings_params' do it 'includes attributes used for limiting unique project downloads' do expect(described_class.allowed_namespace_settings_params).to include(*%i[ unique_project_download_limit -- GitLab From 86363cfac5bd3b5f0057d6b45732ebf96f05388d Mon Sep 17 00:00:00 2001 From: eugielimpin Date: Thu, 30 Jun 2022 16:59:06 +0800 Subject: [PATCH 9/9] Move feature flag under EE --- .../limit_unique_project_downloads_per_namespace_user.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {config => ee/config}/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml (100%) diff --git a/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml b/ee/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml similarity index 100% rename from config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml rename to ee/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml -- GitLab