From a8e8e239f5928da35064a5da54f2e5a0bb9c05f6 Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Mon, 10 Feb 2025 22:53:29 -0500 Subject: [PATCH 01/10] Make Users API rate limits configurable - 7 configurable Users API endpoints - Full test coverage - Updated PO files Changelog: added --- app/helpers/application_settings_helper.rb | 7 ++ app/models/application_setting.rb | 18 ++++- .../application_setting_implementation.rb | 7 ++ .../application_setting_rate_limits.json | 35 +++++++++ .../_users_api_limits.html.haml | 62 ++++++++++++---- .../application_settings/network.html.haml | 10 +-- lib/gitlab/application_rate_limiter.rb | 14 ++-- locale/gitlab.pot | 2 +- spec/features/admin/admin_settings_spec.rb | 74 +++++++++++++++++++ .../application_settings_helper_spec.rb | 4 +- spec/models/application_setting_spec.rb | 14 ++++ .../network.html.haml_spec.rb | 20 +++++ 12 files changed, 233 insertions(+), 34 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index d3717509c91219..42a7c5db6cf686 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -555,6 +555,13 @@ def visible_attributes :user_contributed_projects_api_limit, :user_projects_api_limit, :user_starred_projects_api_limit, + :users_api_limit_followers, + :users_api_limit_following, + :users_api_limit_status, + :users_api_limit_keys, + :users_api_limit_specific_key, + :users_api_limit_gpg_keys, + :users_api_limit_specific_gpg_key, :gitlab_dedicated_instance, :gitlab_environment_toolkit_instance, :ci_max_includes, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 3edcbc91a3d11a..023e6d84fefe46 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -668,7 +668,14 @@ def self.kroki_formats_attributes :user_contributed_projects_api_limit, :user_projects_api_limit, :user_starred_projects_api_limit, - :users_get_by_id_limit + :users_get_by_id_limit, + :users_api_limit_followers, + :users_api_limit_following, + :users_api_limit_status, + :users_api_limit_keys, + :users_api_limit_specific_key, + :users_api_limit_gpg_keys, + :users_api_limit_specific_gpg_key end attribute :resource_usage_limits, ::Gitlab::Database::Type::IndifferentJsonb.new, default: -> { {} } @@ -692,7 +699,14 @@ def self.kroki_formats_attributes projects_api_limit: [:integer, { default: 2000 }], user_contributed_projects_api_limit: [:integer, { default: 100 }], user_projects_api_limit: [:integer, { default: 300 }], - user_starred_projects_api_limit: [:integer, { default: 100 }] + user_starred_projects_api_limit: [:integer, { default: 100 }], + users_api_limit_followers: [:integer, { default: 100 }], + users_api_limit_following: [:integer, { default: 100 }], + users_api_limit_status: [:integer, { default: 240 }], + users_api_limit_keys: [:integer, { default: 120 }], + users_api_limit_specific_key: [:integer, { default: 120 }], + users_api_limit_gpg_keys: [:integer, { default: 120 }], + users_api_limit_specific_gpg_key: [:integer, { default: 120 }] jsonb_accessor :service_ping_settings, gitlab_environment_toolkit_instance: [:boolean, { default: false }] diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 4c838cffa211db..752085cb8df719 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -299,6 +299,13 @@ def defaults # rubocop:disable Metrics/AbcSize user_contributed_projects_api_limit: 100, user_projects_api_limit: 300, user_starred_projects_api_limit: 100, + users_api_limit_followers: 100, + users_api_limit_following: 100, + users_api_limit_status: 240, + users_api_limit_keys: 120, + users_api_limit_specific_key: 120, + users_api_limit_gpg_keys: 120, + users_api_limit_specific_gpg_key: 120, nuget_skip_metadata_url_validation: false, ai_action_api_rate_limit: 160, code_suggestions_api_rate_limit: 60, diff --git a/app/validators/json_schemas/application_setting_rate_limits.json b/app/validators/json_schemas/application_setting_rate_limits.json index e97eee3185df3d..f7376826c7cb30 100644 --- a/app/validators/json_schemas/application_setting_rate_limits.json +++ b/app/validators/json_schemas/application_setting_rate_limits.json @@ -93,6 +93,41 @@ "type": "integer", "minimum": 0, "description": "Maximum number of downstream pipelines that can be triggered per minute (for a given project, user, and commit)." + }, + "users_api_limit_followers": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:id/followers API." + }, + "users_api_limit_following": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:id/following API." + }, + "users_api_limit_status": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:user_id/status API." + }, + "users_api_limit_keys": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:user_id/keys API." + }, + "users_api_limit_specific_key": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:id/keys/:key_id API." + }, + "users_api_limit_gpg_keys": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:id/gpg_keys API." + }, + "users_api_limit_specific_gpg_key": { + "type": "integer", + "minimum": 0, + "description": "Number of requests allowed to the GET /users/:id/gpg_keys/:key_id API." } } } diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index ddeb4bd77102bf..6e5d3d96e35f3c 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -1,14 +1,48 @@ -= gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f| - = form_errors(@application_setting) - - %fieldset - .form-group - = f.label :users_get_by_id_limit, _('Maximum requests per 10 minutes per user'), class: 'label-bold' - = f.number_field :users_get_by_id_limit, class: 'form-control gl-form-input' - .form-group - = f.label :users_get_by_id_limit_allowlist_raw, _('Users to exclude from the rate limit'), class: 'label-bold' - = f.text_area :users_get_by_id_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'users-api-limit-users-allowlist-field-description' } - .form-text.gl-text-subtle{ id: 'users-api-limit-users-allowlist-field-description' } - = _('List of users who are allowed to exceed the rate limit. Example: username1, username2') - - = f.submit _('Save changes'), pajamas_button: true += render ::Layouts::SettingsBlockComponent.new(_('Users API rate limits'), + id: 'js-users-api-limits-settings', + testid: 'users-api-limits-settings', + expanded: expanded_by_default?) do |c| + - c.with_description do + = _('Set the per-user and per-IP address rate limits for the requests to Users API.') + = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_users_api.md'), target: '_blank', rel: 'noopener noreferrer' + - c.with_body do + = gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f| + = form_errors(@application_setting) + + %fieldset + = _('Set to 0 to disable the limits.') + + %fieldset + .form-group + = f.label :users_api_limit_followers, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_followers, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_following, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_following, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' + + %fieldset + .form-group + = f.label :users_get_by_id_limit, _('Maximum requests per 10 minutes per user'), class: 'label-bold' + = f.number_field :users_get_by_id_limit, class: 'form-control gl-form-input' + .form-group + = f.label :users_get_by_id_limit_allowlist_raw, _('Users to exclude from the rate limit'), class: 'label-bold' + = f.text_area :users_get_by_id_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'users-api-limit-users-allowlist-field-description' } + .form-text.gl-text-subtle{ id: 'users-api-limit-users-allowlist-field-description' } + = _('List of users who are allowed to exceed the rate limit. Example: username1, username2') + + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 30d4feff27274f..81b9d7c8093f29 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -119,15 +119,7 @@ - c.with_body do = render 'note_limits' -= render ::Layouts::SettingsBlockComponent.new(_('Users API rate limits'), - id: 'js-users-api-limits-settings', - testid: 'users-api-limits-settings', - expanded: expanded_by_default?) do |c| - - c.with_description do - = _('Set the per-user rate limit for getting a user by ID via the API.') - = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_users_api.md'), target: '_blank', rel: 'noopener noreferrer' - - c.with_body do - = render 'users_api_limits' += render 'users_api_limits' = render 'groups_api_limits' diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index bad6ba9032bc0f..a5e429e259d9ab 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -53,13 +53,13 @@ def rate_limits # rubocop:disable Metrics/AbcSize web_hook_event_resend: { threshold: 5, interval: 1.minute }, users_get_by_id: { threshold: -> { application_settings.users_get_by_id_limit }, interval: 10.minutes }, username_exists: { threshold: 20, interval: 1.minute }, - user_followers: { threshold: 100, interval: 1.minute }, - user_following: { threshold: 100, interval: 1.minute }, - user_status: { threshold: 240, interval: 1.minute }, - user_keys: { threshold: 120, interval: 1.minute }, - user_specific_key: { threshold: 120, interval: 1.minute }, - user_gpg_keys: { threshold: 120, interval: 1.minute }, - user_specific_gpg_key: { threshold: 120, interval: 1.minute }, + user_followers: { threshold: -> { application_settings.users_api_limit_followers }, interval: 1.minute }, + user_following: { threshold: -> { application_settings.users_api_limit_following }, interval: 1.minute }, + user_status: { threshold: -> { application_settings.users_api_limit_status }, interval: 1.minute }, + user_keys: { threshold: -> { application_settings.users_api_limit_keys }, interval: 1.minute }, + user_specific_key: { threshold: -> { application_settings.users_api_limit_specific_key }, interval: 1.minute }, + user_gpg_keys: { threshold: -> { application_settings.users_api_limit_gpg_keys }, interval: 1.minute }, + user_specific_gpg_key: { threshold: -> { application_settings.users_api_limit_specific_gpg_key }, interval: 1.minute }, user_sign_up: { threshold: 20, interval: 1.minute }, user_sign_in: { threshold: 5, interval: 10.minutes }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index abd32541cc5656..4c267e11ae5e51 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -54062,7 +54062,7 @@ msgstr "" msgid "Set the per-user and per-IP address rate limits for the requests to Projects API." msgstr "" -msgid "Set the per-user rate limit for getting a user by ID via the API." +msgid "Set the per-user and per-IP address rate limits for the requests to Users API." msgstr "" msgid "Set the per-user rate limit for notes created by web or API requests." diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index cadb6c1a9ce084..066208485f2551 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -896,6 +896,80 @@ end end + describe 'users API rate limits' do + let_it_be(:network_settings_section) { 'users-api-limits-settings' } + + context 'for GET /users/:id/followers API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_followers } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:id/following API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_following } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:user_id/status API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_status } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:user_id/keys API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_keys } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:id/keys/:key_id API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_specific_key } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:id/gpg_keys API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_gpg_keys } + + it_behaves_like 'API rate limit setting' + end + + context 'for GET /users/:id/gpg_keys/:key_id API requests' do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute') + end + + let(:application_setting_key) { :users_api_limit_specific_gpg_key } + + it_behaves_like 'API rate limit setting' + end + end + describe 'organizations API rate limits' do let_it_be(:network_settings_section) { 'organizations-api-limits-settings' } diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index b78d019a0bda75..57d388f0d50c30 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ApplicationSettingsHelper do +RSpec.describe ApplicationSettingsHelper, feature_category: :shared do include Devise::Test::ControllerHelpers let_it_be(:current_user) { build_stubbed(:admin) } @@ -77,6 +77,8 @@ members_delete_limit downstream_pipeline_trigger_limit_per_project_user_sha group_api_limit group_projects_api_limit groups_api_limit project_api_limit projects_api_limit user_contributed_projects_api_limit user_projects_api_limit user_starred_projects_api_limit + users_api_limit_followers users_api_limit_following users_api_limit_status users_api_limit_keys + users_api_limit_specific_key users_api_limit_gpg_keys users_api_limit_specific_gpg_key group_shared_groups_api_limit group_invited_groups_api_limit project_invited_groups_api_limit diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 52c2780657acc6..da223d242da945 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -49,6 +49,13 @@ it { expect(setting.user_contributed_projects_api_limit).to eq(100) } it { expect(setting.user_projects_api_limit).to eq(300) } it { expect(setting.user_starred_projects_api_limit).to eq(100) } + it { expect(setting.users_api_limit_followers).to eq(100) } + it { expect(setting.users_api_limit_following).to eq(100) } + it { expect(setting.users_api_limit_status).to eq(240) } + it { expect(setting.users_api_limit_keys).to eq(120) } + it { expect(setting.users_api_limit_specific_key).to eq(120) } + it { expect(setting.users_api_limit_gpg_keys).to eq(120) } + it { expect(setting.users_api_limit_specific_gpg_key).to eq(120) } it { expect(setting.disable_password_authentication_for_users_with_sso_identities).to be(false) } it { expect(setting.root_moved_permanently_redirection).to be(false) } it { expect(setting.resource_usage_limits).to eq({}) } @@ -293,6 +300,13 @@ def many_usernames(num = 100) user_projects_api_limit user_starred_projects_api_limit users_get_by_id_limit + users_api_limit_followers + users_api_limit_following + users_api_limit_status + users_api_limit_keys + users_api_limit_specific_key + users_api_limit_gpg_keys + users_api_limit_specific_gpg_key ] end diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 25995fb6929cc7..8098df0c6b7c7e 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -21,6 +21,26 @@ end end + context 'for Users API rate limits' do + it 'renders the reset disclaimer' do + render + + expect(rendered).to have_content('Set to 0 to disable the limits.') + end + + it 'renders the users rate limit fields' do + render + + expect(rendered).to have_field('application_setting_users_api_limit_followers') + expect(rendered).to have_field('application_setting_users_api_limit_following') + expect(rendered).to have_field('application_setting_users_api_limit_status') + expect(rendered).to have_field('application_setting_users_api_limit_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_key') + expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') + end + end + context 'for Projects API rate limits' do it 'renders the project rate limit fields' do render -- GitLab From 2013c517fb48bf48b41acc4fe4bc7121d4a2b8f6 Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 11 Feb 2025 16:37:25 -0500 Subject: [PATCH 02/10] Add API docs --- .../settings/rate_limit_on_users_api.md | 35 +++++++++++++------ doc/api/settings.md | 14 ++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/doc/administration/settings/rate_limit_on_users_api.md b/doc/administration/settings/rate_limit_on_users_api.md index 306004aa434250..622b6fe7169f8b 100644 --- a/doc/administration/settings/rate_limit_on_users_api.md +++ b/doc/administration/settings/rate_limit_on_users_api.md @@ -12,25 +12,40 @@ title: Rate limits on Users API {{< /details >}} -You can configure the per user rate limit for requests to [Users API](../../api/users.md). +> - Rate limit for Users API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 with a [flag](../feature_flags.md) named `rate_limiting_user_endpoints` with configurable limits in [17.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054). Disabled by default. + +You can configure the rate-limit per minute, per each user or IP address, for requests to the following [Users API](../../api/users.md). + +| Limit | Default | +|-----------------------------------------------------------------|---------| +| [GET /users/:id/followers](../../api/user_follow_unfollow.md#list-all-accounts-that-follow-a-user) | 100 | +| [GET /users/:id/following](../../api/user_follow_unfollow.md#list-all-accounts-followed-by-a-user) | 100 | +| [GET /users/:id/status](../../api/users.md#get-the-status-of-a-user) | 240 | +| [GET /users/:id/keys](../../api/user_keys.md#list-all-ssh-keys-for-a-user) | 120 | +| [GET /users/:id/gpg_keys](../../api/user_keys.md#list-all-gpg-keys-for-a-user) | 120 | +| [GET /users/:id/gpg_keys/:key_id](../../api/user_keys.md#get-a-gpg-key-for-a-user) | 120 | To change the rate limit: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Settings > Network**. 1. Expand **Users API rate limit**. -1. In the **Maximum requests per 10 minutes** text box, enter the new value. -1. Optional. In the **Users to exclude from the rate limit** box, list users allowed to exceed the limit. +1. Change the value of any rate limit. The rate limits are per minute, per user for authenticated requests and per IP address for unauthenticated requests. Set to `0` to disable a rate limit. 1. Select **Save changes**. -This limit is: +The rate limits: + +- Apply per user if the user is authenticated. +- Apply per IP address if the user is unauthenticated. +- Can be set to 0 to disable rate limiting. +- Are behind the `rate_limiting_user_endpoints`. -- Applied independently per user. -- Not applied per IP address. +Logs: -The default value is `300`. +- Requests over the rate limit are logged into the `auth.log` file. +- Modified rate-limit configurations are logged into the `audit_json.log` file. -Requests over the rate limit are logged into the `auth.log` file. +Example(s): -For example, if you set a limit of 300, requests to the `GET /users/:id` API endpoint -exceeding a rate of 300 per 10 minutes are blocked. Access to the endpoint is allowed after ten minutes have elapsed. +If you set a limit of 400 for `GET /users/:id/followers`, requests to the API endpoint that +exceed a rate of 400 within a minute are blocked. Access to the endpoint is restored after one minute have elapsed. diff --git a/doc/api/settings.md b/doc/api/settings.md index 6b99a35bb91eb8..0883804c6d9958 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -328,6 +328,13 @@ Example response: "jira_connect_proxy_url": "http://gitlab.example.com", "user_defaults_to_private_profile": true, "projects_api_rate_limit_unauthenticated": 400, + "users_api_limit_followers": 100, + "users_api_limit_following": 100, + "users_api_limit_status": 240, + "users_api_limit_keys": 120, + "users_api_limit_specific_key": 120, + "users_api_limit_gpg_keys": 120, + "users_api_limit_specific_gpg_key": 120, "silent_mode_enabled": false, "security_policy_global_group_approvers_enabled": true, "security_approval_policies_limit": 5, @@ -620,6 +627,13 @@ to configure other related settings. These requirements are | `project_export_enabled` | boolean | no | Enable project export. | | `project_jobs_api_rate_limit` | integer | no | Maximum authenticated requests to `/project/:id/jobs` per minute. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129319) in GitLab 16.5. Default: 600. | | `projects_api_rate_limit_unauthenticated` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112283) in GitLab 15.10. Max number of requests per 10 minutes per IP address for unauthenticated requests to the [list all projects API](projects.md#list-all-projects). Default: 400. To disable throttling set to 0.| +| `users_api_limit_followers` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 100. To disable throttling, set to 0.| +| `users_api_limit_following` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 100. To disable throttling, set to 0. | +| `users_api_limit_status` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 240. To disable throttling, set to 0. | +| `users_api_limit_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_specific_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_gpg_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_specific_gpg_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | | `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. | | `protected_ci_variables` | boolean | no | CI/CD variables are protected by default. | | `disable_overriding_approvers_per_merge_request` | boolean | no | Prevent editing approval rules in projects and merge requests | -- GitLab From 05825fbaaee64130bf8161ed9b4a23812e169808 Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 11 Feb 2025 16:59:37 -0500 Subject: [PATCH 03/10] Add :rate_limiting_user_endpoints feature flag to UI --- .../_users_api_limits.html.haml | 49 ++++++++++--------- .../network.html.haml_spec.rb | 40 ++++++++++----- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index 6e5d3d96e35f3c..18695456cbb09d 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -9,31 +9,32 @@ = gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) - %fieldset - = _('Set to 0 to disable the limits.') + - if Feature.enabled?(:rate_limiting_user_endpoints) + %fieldset + = _('Set to 0 to disable the limits.') - %fieldset - .form-group - = f.label :users_api_limit_followers, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_followers, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_following, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_following, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' + %fieldset + .form-group + = f.label :users_api_limit_followers, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_followers, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_following, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_following, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' %fieldset .form-group diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 8098df0c6b7c7e..b921498bca861a 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -22,22 +22,38 @@ end context 'for Users API rate limits' do - it 'renders the reset disclaimer' do - render - - expect(rendered).to have_content('Set to 0 to disable the limits.') + context 'when the :rate_limiting_user_endpoints FF is enabled' do + before do + stub_feature_flags(rate_limiting_user_endpoints: true) + end + + it 'renders the reset disclaimer' do + render + + expect(rendered).to have_content('Set to 0 to disable the limits.') + end + + it 'renders the users rate limit fields' do + render + + expect(rendered).to have_field('application_setting_users_api_limit_followers') + expect(rendered).to have_field('application_setting_users_api_limit_following') + expect(rendered).to have_field('application_setting_users_api_limit_status') + expect(rendered).to have_field('application_setting_users_api_limit_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_key') + expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') + + expect(rendered).to have_field('application_setting_users_get_by_id_limit') + expect(rendered).to have_field('application_setting_users_get_by_id_limit_allowlist_raw') + end end - it 'renders the users rate limit fields' do + it 'renders the users rate-limit fields' do render - expect(rendered).to have_field('application_setting_users_api_limit_followers') - expect(rendered).to have_field('application_setting_users_api_limit_following') - expect(rendered).to have_field('application_setting_users_api_limit_status') - expect(rendered).to have_field('application_setting_users_api_limit_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_key') - expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') + expect(rendered).to have_field('application_setting_users_get_by_id_limit') + expect(rendered).to have_field('application_setting_users_get_by_id_limit_allowlist_raw') end end -- GitLab From 1fd6fb12c6ab15aadb4133553ded092a7b32d41f Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 11 Feb 2025 17:08:03 -0500 Subject: [PATCH 04/10] Add an instance actor to the FF --- .../admin/application_settings/_users_api_limits.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index 18695456cbb09d..415ef45dd59bb1 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -9,7 +9,7 @@ = gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) - - if Feature.enabled?(:rate_limiting_user_endpoints) + - if Feature.enabled?(:rate_limiting_user_endpoints, :instance) %fieldset = _('Set to 0 to disable the limits.') -- GitLab From 301be0e2a4d8411b0280be7dd11fa7cba50e65fd Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 11 Feb 2025 20:24:23 -0500 Subject: [PATCH 05/10] Update rate_limits docs --- doc/security/rate_limits.md | 57 +------------------------------------ 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md index 3d8415e565fafa..3498961195d9a8 100644 --- a/doc/security/rate_limits.md +++ b/doc/security/rate_limits.md @@ -61,6 +61,7 @@ You can set these rate limits in the **Admin** area of your instance: - [Incident management rate limits](../administration/settings/incident_management_rate_limits.md) - [Projects API rate limits](../administration/settings/rate_limit_on_projects_api.md) - [Groups API rate limits](../administration/settings/rate_limit_on_groups_api.md) +- [Users API rate limits](../administration/settings/rate_limit_on_users_api.md) - [Organizations API rate limits](../administration/settings/rate_limit_on_organizations_api.md) You can set these rate limits using the Rails console: @@ -96,20 +97,6 @@ For configuration information, see ## Non-configurable limits -{{< history >}} - -- Rate limit on the `:user_id/status`, `:id/following`, `:id/followers`, `:user_id/keys`, `id/keys/:key_id`, `:id/gpg_keys`, and `:id/gpg_keys/:key_id` endpoints [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 [with a flag](../administration/feature_flags.md) named `rate_limiting_user_endpoints`. Disabled by default. - -{{< /history >}} - -{{< alert type="flag" >}} - -The availability of multiple endpoints in this feature is controlled by a feature flag. -For more information, see the history. -These endpoints are available for testing, but not ready for production use. - -{{< /alert >}} - ### Repository archives A rate limit for [downloading repository archives](../api/repositories.md#get-file-archive) is @@ -131,48 +118,6 @@ discover usernames or email addresses in use. The **rate limit** is 20 calls per minute per IP address. -### User status - -There is a rate limit per IP address on the `:user_id/status` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 240 calls per minute per IP address. - -### User following - -There is a rate limit per IP address on the `:id/following` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 100 calls per minute per IP address. - -### User followers - -There is a rate limit per IP address on the `:id/followers` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 100 calls per minute per IP address. - -### User keys - -There is a rate limit per IP address on the `:user_id/keys` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 120 calls per minute per IP address. - -### User specific key - -There is a rate limit per IP address on the `id/keys/:key_id` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 120 calls per minute per IP address. - -### User GPG keys - -There is a rate limit per IP address on the `:id/gpg_keys` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 120 calls per minute per IP address. - -### User specific GPG keys - -There is a rate limit per IP address on the `:id/gpg_keys/:key_id` endpoint. This is to mitigate attempts to misuse the endpoint. - -The **rate limit** is 120 calls per minute per IP address. - ### Update username There is a rate limit on how frequently a username can be changed. This is enforced to mitigate misuse of the feature. For example, to mass discover -- GitLab From b02e501175de3e2fc3e99558c6c075ef119c73a3 Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 11 Feb 2025 20:29:15 -0500 Subject: [PATCH 06/10] Revise the rate_limit_on_users_api docs --- doc/administration/settings/rate_limit_on_users_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/settings/rate_limit_on_users_api.md b/doc/administration/settings/rate_limit_on_users_api.md index 622b6fe7169f8b..60655c9c64c8ed 100644 --- a/doc/administration/settings/rate_limit_on_users_api.md +++ b/doc/administration/settings/rate_limit_on_users_api.md @@ -12,7 +12,7 @@ title: Rate limits on Users API {{< /details >}} -> - Rate limit for Users API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 with a [flag](../feature_flags.md) named `rate_limiting_user_endpoints` with configurable limits in [17.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054). Disabled by default. +> - Rate limits for Users API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 with a [flag](../feature_flags.md) named `rate_limiting_user_endpoints` with configurable limits in [17.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054). Disabled by default. You can configure the rate-limit per minute, per each user or IP address, for requests to the following [Users API](../../api/users.md). @@ -48,4 +48,4 @@ Logs: Example(s): If you set a limit of 400 for `GET /users/:id/followers`, requests to the API endpoint that -exceed a rate of 400 within a minute are blocked. Access to the endpoint is restored after one minute have elapsed. +exceed a rate of 400 within a minute are blocked. Access to the endpoint is restored after one minute has elapsed. -- GitLab From 1c3c945b2d92dd480d7a5cc66409077ef454d11c Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Thu, 13 Feb 2025 15:47:24 -0500 Subject: [PATCH 07/10] Remove feature flag on Users API configs UI - This gives customers the ability to pre-configure their - rate limits in 17.10 before, its enabled by default in 18.0 --- .../_users_api_limits.html.haml | 49 +++++++++---------- .../network.html.haml_spec.rb | 39 +++++---------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index 415ef45dd59bb1..6e5d3d96e35f3c 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -9,32 +9,31 @@ = gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) - - if Feature.enabled?(:rate_limiting_user_endpoints, :instance) - %fieldset - = _('Set to 0 to disable the limits.') + %fieldset + = _('Set to 0 to disable the limits.') - %fieldset - .form-group - = f.label :users_api_limit_followers, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_followers, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_following, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_following, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' - .form-group - = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' + %fieldset + .form-group + = f.label :users_api_limit_followers, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_followers, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_following, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/following', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_following, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' + .form-group + = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' %fieldset .form-group diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index b921498bca861a..3e7e28f18e1b71 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -22,36 +22,23 @@ end context 'for Users API rate limits' do - context 'when the :rate_limiting_user_endpoints FF is enabled' do - before do - stub_feature_flags(rate_limiting_user_endpoints: true) - end - - it 'renders the reset disclaimer' do - render - - expect(rendered).to have_content('Set to 0 to disable the limits.') - end - - it 'renders the users rate limit fields' do - render - - expect(rendered).to have_field('application_setting_users_api_limit_followers') - expect(rendered).to have_field('application_setting_users_api_limit_following') - expect(rendered).to have_field('application_setting_users_api_limit_status') - expect(rendered).to have_field('application_setting_users_api_limit_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_key') - expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') - - expect(rendered).to have_field('application_setting_users_get_by_id_limit') - expect(rendered).to have_field('application_setting_users_get_by_id_limit_allowlist_raw') - end + it 'renders the reset disclaimer' do + render + + expect(rendered).to have_content('Set to 0 to disable the limits.') end - it 'renders the users rate-limit fields' do + it 'renders the users rate limit fields' do render + expect(rendered).to have_field('application_setting_users_api_limit_followers') + expect(rendered).to have_field('application_setting_users_api_limit_following') + expect(rendered).to have_field('application_setting_users_api_limit_status') + expect(rendered).to have_field('application_setting_users_api_limit_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_key') + expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') + expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') + expect(rendered).to have_field('application_setting_users_get_by_id_limit') expect(rendered).to have_field('application_setting_users_get_by_id_limit_allowlist_raw') end -- GitLab From b81dd2c24657bd5710c2478ea3fc12229a977d94 Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Thu, 13 Feb 2025 21:06:11 -0500 Subject: [PATCH 08/10] Change jsonB column names - Drop the `specific` word from the users_api keys --- app/helpers/application_settings_helper.rb | 4 ++-- app/models/application_setting.rb | 8 ++++---- app/models/application_setting_implementation.rb | 4 ++-- .../json_schemas/application_setting_rate_limits.json | 4 ++-- .../application_settings/_users_api_limits.html.haml | 8 ++++---- doc/api/settings.md | 8 ++++---- lib/gitlab/application_rate_limiter.rb | 4 ++-- spec/features/admin/admin_settings_spec.rb | 4 ++-- spec/helpers/application_settings_helper_spec.rb | 2 +- spec/models/application_setting_spec.rb | 8 ++++---- .../admin/application_settings/network.html.haml_spec.rb | 4 ++-- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 42a7c5db6cf686..832bca9d20f5c2 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -559,9 +559,9 @@ def visible_attributes :users_api_limit_following, :users_api_limit_status, :users_api_limit_keys, - :users_api_limit_specific_key, + :users_api_limit_key, :users_api_limit_gpg_keys, - :users_api_limit_specific_gpg_key, + :users_api_limit_gpg_key, :gitlab_dedicated_instance, :gitlab_environment_toolkit_instance, :ci_max_includes, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 023e6d84fefe46..fe9a51904d0eeb 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -673,9 +673,9 @@ def self.kroki_formats_attributes :users_api_limit_following, :users_api_limit_status, :users_api_limit_keys, - :users_api_limit_specific_key, + :users_api_limit_key, :users_api_limit_gpg_keys, - :users_api_limit_specific_gpg_key + :users_api_limit_gpg_key end attribute :resource_usage_limits, ::Gitlab::Database::Type::IndifferentJsonb.new, default: -> { {} } @@ -704,9 +704,9 @@ def self.kroki_formats_attributes users_api_limit_following: [:integer, { default: 100 }], users_api_limit_status: [:integer, { default: 240 }], users_api_limit_keys: [:integer, { default: 120 }], - users_api_limit_specific_key: [:integer, { default: 120 }], + users_api_limit_key: [:integer, { default: 120 }], users_api_limit_gpg_keys: [:integer, { default: 120 }], - users_api_limit_specific_gpg_key: [:integer, { default: 120 }] + users_api_limit_gpg_key: [:integer, { default: 120 }] jsonb_accessor :service_ping_settings, gitlab_environment_toolkit_instance: [:boolean, { default: false }] diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 752085cb8df719..c1bc24c23aa655 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -303,9 +303,9 @@ def defaults # rubocop:disable Metrics/AbcSize users_api_limit_following: 100, users_api_limit_status: 240, users_api_limit_keys: 120, - users_api_limit_specific_key: 120, + users_api_limit_key: 120, users_api_limit_gpg_keys: 120, - users_api_limit_specific_gpg_key: 120, + users_api_limit_gpg_key: 120, nuget_skip_metadata_url_validation: false, ai_action_api_rate_limit: 160, code_suggestions_api_rate_limit: 60, diff --git a/app/validators/json_schemas/application_setting_rate_limits.json b/app/validators/json_schemas/application_setting_rate_limits.json index f7376826c7cb30..74306a8dc9529d 100644 --- a/app/validators/json_schemas/application_setting_rate_limits.json +++ b/app/validators/json_schemas/application_setting_rate_limits.json @@ -114,7 +114,7 @@ "minimum": 0, "description": "Number of requests allowed to the GET /users/:user_id/keys API." }, - "users_api_limit_specific_key": { + "users_api_limit_key": { "type": "integer", "minimum": 0, "description": "Number of requests allowed to the GET /users/:id/keys/:key_id API." @@ -124,7 +124,7 @@ "minimum": 0, "description": "Number of requests allowed to the GET /users/:id/gpg_keys API." }, - "users_api_limit_specific_gpg_key": { + "users_api_limit_gpg_key": { "type": "integer", "minimum": 0, "description": "Number of requests allowed to the GET /users/:id/gpg_keys/:key_id API." diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index 6e5d3d96e35f3c..daac09af4d1891 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -26,14 +26,14 @@ = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' .form-group - = f.label :users_api_limit_specific_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_key, min: 0, class: 'form-control gl-form-input' + = f.label :users_api_limit_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_key, min: 0, class: 'form-control gl-form-input' .form-group = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' .form-group - = f.label :users_api_limit_specific_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_specific_gpg_key, min: 0, class: 'form-control gl-form-input' + = f.label :users_api_limit_gpg_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_gpg_key, min: 0, class: 'form-control gl-form-input' %fieldset .form-group diff --git a/doc/api/settings.md b/doc/api/settings.md index 0883804c6d9958..fe5cae7fcc1c4a 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -332,9 +332,9 @@ Example response: "users_api_limit_following": 100, "users_api_limit_status": 240, "users_api_limit_keys": 120, - "users_api_limit_specific_key": 120, + "users_api_limit_key": 120, "users_api_limit_gpg_keys": 120, - "users_api_limit_specific_gpg_key": 120, + "users_api_limit_gpg_key": 120, "silent_mode_enabled": false, "security_policy_global_group_approvers_enabled": true, "security_approval_policies_limit": 5, @@ -631,9 +631,9 @@ to configure other related settings. These requirements are | `users_api_limit_following` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 100. To disable throttling, set to 0. | | `users_api_limit_status` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 240. To disable throttling, set to 0. | | `users_api_limit_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | -| `users_api_limit_specific_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | | `users_api_limit_gpg_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | -| `users_api_limit_specific_gpg_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_gpg_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | | `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. | | `protected_ci_variables` | boolean | no | CI/CD variables are protected by default. | | `disable_overriding_approvers_per_merge_request` | boolean | no | Prevent editing approval rules in projects and merge requests | diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index a5e429e259d9ab..22a42b989291a3 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -57,9 +57,9 @@ def rate_limits # rubocop:disable Metrics/AbcSize user_following: { threshold: -> { application_settings.users_api_limit_following }, interval: 1.minute }, user_status: { threshold: -> { application_settings.users_api_limit_status }, interval: 1.minute }, user_keys: { threshold: -> { application_settings.users_api_limit_keys }, interval: 1.minute }, - user_specific_key: { threshold: -> { application_settings.users_api_limit_specific_key }, interval: 1.minute }, + user_specific_key: { threshold: -> { application_settings.users_api_limit_key }, interval: 1.minute }, user_gpg_keys: { threshold: -> { application_settings.users_api_limit_gpg_keys }, interval: 1.minute }, - user_specific_gpg_key: { threshold: -> { application_settings.users_api_limit_specific_gpg_key }, interval: 1.minute }, + user_specific_gpg_key: { threshold: -> { application_settings.users_api_limit_gpg_key }, interval: 1.minute }, user_sign_up: { threshold: 20, interval: 1.minute }, user_sign_in: { threshold: 5, interval: 10.minutes }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 066208485f2551..e32f09f0fe1c5d 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -944,7 +944,7 @@ format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute') end - let(:application_setting_key) { :users_api_limit_specific_key } + let(:application_setting_key) { :users_api_limit_key } it_behaves_like 'API rate limit setting' end @@ -964,7 +964,7 @@ format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys/:key_id', timeframe: 'minute') end - let(:application_setting_key) { :users_api_limit_specific_gpg_key } + let(:application_setting_key) { :users_api_limit_gpg_key } it_behaves_like 'API rate limit setting' end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 57d388f0d50c30..ed13c5b9bedba0 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -78,7 +78,7 @@ group_api_limit group_projects_api_limit groups_api_limit project_api_limit projects_api_limit user_contributed_projects_api_limit user_projects_api_limit user_starred_projects_api_limit users_api_limit_followers users_api_limit_following users_api_limit_status users_api_limit_keys - users_api_limit_specific_key users_api_limit_gpg_keys users_api_limit_specific_gpg_key + users_api_limit_key users_api_limit_gpg_keys users_api_limit_gpg_key group_shared_groups_api_limit group_invited_groups_api_limit project_invited_groups_api_limit diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index da223d242da945..811e844ac397f3 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -53,9 +53,9 @@ it { expect(setting.users_api_limit_following).to eq(100) } it { expect(setting.users_api_limit_status).to eq(240) } it { expect(setting.users_api_limit_keys).to eq(120) } - it { expect(setting.users_api_limit_specific_key).to eq(120) } + it { expect(setting.users_api_limit_key).to eq(120) } it { expect(setting.users_api_limit_gpg_keys).to eq(120) } - it { expect(setting.users_api_limit_specific_gpg_key).to eq(120) } + it { expect(setting.users_api_limit_gpg_key).to eq(120) } it { expect(setting.disable_password_authentication_for_users_with_sso_identities).to be(false) } it { expect(setting.root_moved_permanently_redirection).to be(false) } it { expect(setting.resource_usage_limits).to eq({}) } @@ -304,9 +304,9 @@ def many_usernames(num = 100) users_api_limit_following users_api_limit_status users_api_limit_keys - users_api_limit_specific_key + users_api_limit_key users_api_limit_gpg_keys - users_api_limit_specific_gpg_key + users_api_limit_gpg_key ] end diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 3e7e28f18e1b71..1cb5ca325ffabe 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -35,9 +35,9 @@ expect(rendered).to have_field('application_setting_users_api_limit_following') expect(rendered).to have_field('application_setting_users_api_limit_status') expect(rendered).to have_field('application_setting_users_api_limit_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_key') + expect(rendered).to have_field('application_setting_users_api_limit_key') expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') - expect(rendered).to have_field('application_setting_users_api_limit_specific_gpg_key') + expect(rendered).to have_field('application_setting_users_api_limit_gpg_key') expect(rendered).to have_field('application_setting_users_get_by_id_limit') expect(rendered).to have_field('application_setting_users_get_by_id_limit_allowlist_raw') -- GitLab From 3783e7f2e9fb2ff4cfb94dca08c7e6d2d114bd8e Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Mon, 17 Feb 2025 00:22:54 -0500 Subject: [PATCH 09/10] Rename API attributes and revise docs - Remove `specific` - Add ssh - Address technical writer review - Update tests --- app/helpers/application_settings_helper.rb | 4 +-- app/models/application_setting.rb | 8 ++--- .../application_setting_implementation.rb | 4 +-- .../application_setting_rate_limits.json | 4 +-- .../_users_api_limits.html.haml | 14 ++++---- .../settings/rate_limit_on_users_api.md | 32 +++++++++++-------- doc/api/settings.md | 18 +++++------ lib/api/users.rb | 6 ++-- lib/gitlab/application_rate_limiter.rb | 6 ++-- locale/gitlab.pot | 9 ++++++ spec/features/admin/admin_settings_spec.rb | 6 ++-- .../application_settings_helper_spec.rb | 4 +-- spec/models/application_setting_spec.rb | 8 ++--- spec/requests/api/users_spec.rb | 14 ++++---- .../network.html.haml_spec.rb | 6 ++-- 15 files changed, 79 insertions(+), 64 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 832bca9d20f5c2..8933caaa234fc9 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -558,8 +558,8 @@ def visible_attributes :users_api_limit_followers, :users_api_limit_following, :users_api_limit_status, - :users_api_limit_keys, - :users_api_limit_key, + :users_api_limit_ssh_keys, + :users_api_limit_ssh_key, :users_api_limit_gpg_keys, :users_api_limit_gpg_key, :gitlab_dedicated_instance, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index fe9a51904d0eeb..7fe08e84f08177 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -672,8 +672,8 @@ def self.kroki_formats_attributes :users_api_limit_followers, :users_api_limit_following, :users_api_limit_status, - :users_api_limit_keys, - :users_api_limit_key, + :users_api_limit_ssh_keys, + :users_api_limit_ssh_key, :users_api_limit_gpg_keys, :users_api_limit_gpg_key end @@ -703,8 +703,8 @@ def self.kroki_formats_attributes users_api_limit_followers: [:integer, { default: 100 }], users_api_limit_following: [:integer, { default: 100 }], users_api_limit_status: [:integer, { default: 240 }], - users_api_limit_keys: [:integer, { default: 120 }], - users_api_limit_key: [:integer, { default: 120 }], + users_api_limit_ssh_keys: [:integer, { default: 120 }], + users_api_limit_ssh_key: [:integer, { default: 120 }], users_api_limit_gpg_keys: [:integer, { default: 120 }], users_api_limit_gpg_key: [:integer, { default: 120 }] diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index c1bc24c23aa655..374f590e841868 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -302,8 +302,8 @@ def defaults # rubocop:disable Metrics/AbcSize users_api_limit_followers: 100, users_api_limit_following: 100, users_api_limit_status: 240, - users_api_limit_keys: 120, - users_api_limit_key: 120, + users_api_limit_ssh_keys: 120, + users_api_limit_ssh_key: 120, users_api_limit_gpg_keys: 120, users_api_limit_gpg_key: 120, nuget_skip_metadata_url_validation: false, diff --git a/app/validators/json_schemas/application_setting_rate_limits.json b/app/validators/json_schemas/application_setting_rate_limits.json index 74306a8dc9529d..4325aeeed5ff44 100644 --- a/app/validators/json_schemas/application_setting_rate_limits.json +++ b/app/validators/json_schemas/application_setting_rate_limits.json @@ -109,12 +109,12 @@ "minimum": 0, "description": "Number of requests allowed to the GET /users/:user_id/status API." }, - "users_api_limit_keys": { + "users_api_limit_ssh_keys": { "type": "integer", "minimum": 0, "description": "Number of requests allowed to the GET /users/:user_id/keys API." }, - "users_api_limit_key": { + "users_api_limit_ssh_key": { "type": "integer", "minimum": 0, "description": "Number of requests allowed to the GET /users/:id/keys/:key_id API." diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index daac09af4d1891..2515af8edc3b46 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -10,7 +10,7 @@ = form_errors(@application_setting) %fieldset - = _('Set to 0 to disable the limits.') + = _('Set to 0 to disable rate limits.') %fieldset .form-group @@ -23,11 +23,11 @@ = f.label :users_api_limit_status, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/status', timeframe: 'minute'), class: 'label-bold' = f.number_field :users_api_limit_status, min: 0, class: 'form-control gl-form-input' .form-group - = f.label :users_api_limit_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_keys, min: 0, class: 'form-control gl-form-input' + = f.label :users_api_limit_ssh_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_ssh_keys, min: 0, class: 'form-control gl-form-input' .form-group - = f.label :users_api_limit_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' - = f.number_field :users_api_limit_key, min: 0, class: 'form-control gl-form-input' + = f.label :users_api_limit_ssh_key, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute'), class: 'label-bold' + = f.number_field :users_api_limit_ssh_key, min: 0, class: 'form-control gl-form-input' .form-group = f.label :users_api_limit_gpg_keys, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/gpg_keys', timeframe: 'minute'), class: 'label-bold' = f.number_field :users_api_limit_gpg_keys, min: 0, class: 'form-control gl-form-input' @@ -40,9 +40,9 @@ = f.label :users_get_by_id_limit, _('Maximum requests per 10 minutes per user'), class: 'label-bold' = f.number_field :users_get_by_id_limit, class: 'form-control gl-form-input' .form-group - = f.label :users_get_by_id_limit_allowlist_raw, _('Users to exclude from the rate limit'), class: 'label-bold' + = f.label :users_get_by_id_limit_allowlist_raw, _('Excluded users'), class: 'label-bold' = f.text_area :users_get_by_id_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'users-api-limit-users-allowlist-field-description' } .form-text.gl-text-subtle{ id: 'users-api-limit-users-allowlist-field-description' } - = _('List of users who are allowed to exceed the rate limit. Example: username1, username2') + = _('List of users allowed to exceed the rate limit. Example: username1, username2') = f.submit _('Save changes'), pajamas_button: true diff --git a/doc/administration/settings/rate_limit_on_users_api.md b/doc/administration/settings/rate_limit_on_users_api.md index 60655c9c64c8ed..90246ab7aebd4c 100644 --- a/doc/administration/settings/rate_limit_on_users_api.md +++ b/doc/administration/settings/rate_limit_on_users_api.md @@ -12,9 +12,15 @@ title: Rate limits on Users API {{< /details >}} -> - Rate limits for Users API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 with a [flag](../feature_flags.md) named `rate_limiting_user_endpoints` with configurable limits in [17.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054). Disabled by default. +> - Rate limits for Users API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/452349) in GitLab 17.1 with a [flag](../feature_flags.md) named `rate_limiting_user_endpoints`. Disabled by default. +> - [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) customizable rate limits in GitLab 17.10. -You can configure the rate-limit per minute, per each user or IP address, for requests to the following [Users API](../../api/users.md). +You can configure the per minute rate limit per IP address and per user for requests to the following [Users API](../../api/users.md). + +FLAG: +The availability of this feature is controlled by a feature flag. +For more information, see the history. +This feature is available for testing, but not ready for production use. | Limit | Default | |-----------------------------------------------------------------|---------| @@ -30,22 +36,22 @@ To change the rate limit: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Settings > Network**. 1. Expand **Users API rate limit**. -1. Change the value of any rate limit. The rate limits are per minute, per user for authenticated requests and per IP address for unauthenticated requests. Set to `0` to disable a rate limit. +1. Set values for any available rate limit. The rate limits are per minute, per user for authenticated requests and per IP address for unauthenticated requests. Enter `0` to disable a rate limit. 1. Select **Save changes**. -The rate limits: +Each rate limit: -- Apply per user if the user is authenticated. -- Apply per IP address if the user is unauthenticated. -- Can be set to 0 to disable rate limiting. -- Are behind the `rate_limiting_user_endpoints`. +- Applies per user if the request is authenticated. +- Applies per IP address if the request is unauthenticated. +- Can be set to `0` to disable rate limits. Logs: -- Requests over the rate limit are logged into the `auth.log` file. -- Modified rate-limit configurations are logged into the `audit_json.log` file. +- Requests that exceed the rate limit are logged to the `auth.log` file. +- Rate limit modifications are logged to the `audit_json.log` file. -Example(s): +Example: -If you set a limit of 400 for `GET /users/:id/followers`, requests to the API endpoint that -exceed a rate of 400 within a minute are blocked. Access to the endpoint is restored after one minute has elapsed. +If you set a rate limit of 150 for `GET /users/:id/followers` and send 155 requests in a minute, the +final five requests are blocked. After a minute, you could continue sending requests until you +exceed the rate limit again. diff --git a/doc/api/settings.md b/doc/api/settings.md index fe5cae7fcc1c4a..8707ced48db026 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -331,8 +331,8 @@ Example response: "users_api_limit_followers": 100, "users_api_limit_following": 100, "users_api_limit_status": 240, - "users_api_limit_keys": 120, - "users_api_limit_key": 120, + "users_api_limit_ssh_keys": 120, + "users_api_limit_ssh_key": 120, "users_api_limit_gpg_keys": 120, "users_api_limit_gpg_key": 120, "silent_mode_enabled": false, @@ -627,13 +627,13 @@ to configure other related settings. These requirements are | `project_export_enabled` | boolean | no | Enable project export. | | `project_jobs_api_rate_limit` | integer | no | Maximum authenticated requests to `/project/:id/jobs` per minute. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129319) in GitLab 16.5. Default: 600. | | `projects_api_rate_limit_unauthenticated` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112283) in GitLab 15.10. Max number of requests per 10 minutes per IP address for unauthenticated requests to the [list all projects API](projects.md#list-all-projects). Default: 400. To disable throttling set to 0.| -| `users_api_limit_followers` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 100. To disable throttling, set to 0.| -| `users_api_limit_following` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 100. To disable throttling, set to 0. | -| `users_api_limit_status` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 240. To disable throttling, set to 0. | -| `users_api_limit_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | -| `users_api_limit_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | -| `users_api_limit_gpg_keys` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | -| `users_api_limit_gpg_key` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. Max number of requests per minute, per user or IP address to its [users API](users.md) endpoint. Default: 120. To disable throttling, set to 0. | +| `users_api_limit_following` | integer | no | Max number of requests per minute, per user or IP address. Default: 100. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_followers` | integer | no | Max number of requests per minute, per user or IP address. Default: 100. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_status` | integer | no | Max number of requests per minute, per user or IP address. Default: 240. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_keys` | integer | no | Max number of requests per minute, per user or IP address. Default: 120. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_key` | integer | no | Max number of requests per minute, per user or IP address. Default: 120. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_gpg_keys` | integer | no | Max number of requests per minute, per user or IP address. Default: 120. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | +| `users_api_limit_gpg_key` | integer | no | Max number of requests per minute, per user or IP address. Default: 120. Set to `0` to disable limits. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181054) in GitLab 17.10. | | `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. | | `protected_ci_variables` | boolean | no | CI/CD variables are protected by default. | | `disable_overriding_approvers_per_merge_request` | boolean | no | Prevent editing approval rules in projects and merge requests | diff --git a/lib/api/users.rb b/lib/api/users.rb index 11bdd067b0a0ff..75bb425cf928a7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -533,7 +533,7 @@ def reorder_users(users) end get ':user_id/keys', requirements: API::USER_REQUIREMENTS, feature_category: :system_access do if Feature.enabled?(:rate_limiting_user_endpoints, ::Feature.current_request) - check_rate_limit_by_user_or_ip!(:user_keys) + check_rate_limit_by_user_or_ip!(:user_ssh_keys) end user = find_user(params[:user_id]) @@ -552,7 +552,7 @@ def reorder_users(users) end get ':id/keys/:key_id', requirements: API::USER_REQUIREMENTS, feature_category: :system_access do if Feature.enabled?(:rate_limiting_user_endpoints, ::Feature.current_request) - check_rate_limit_by_user_or_ip!(:user_specific_key) + check_rate_limit_by_user_or_ip!(:user_ssh_key) end user = find_user(params[:id]) @@ -645,7 +645,7 @@ def reorder_users(users) # rubocop: disable CodeReuse/ActiveRecord get ':id/gpg_keys/:key_id', feature_category: :system_access do if Feature.enabled?(:rate_limiting_user_endpoints, ::Feature.current_request) - check_rate_limit_by_user_or_ip!(:user_specific_gpg_key) + check_rate_limit_by_user_or_ip!(:user_gpg_key) end user = User.find_by(id: params[:id]) diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index 22a42b989291a3..a66255dbac62cf 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -56,10 +56,10 @@ def rate_limits # rubocop:disable Metrics/AbcSize user_followers: { threshold: -> { application_settings.users_api_limit_followers }, interval: 1.minute }, user_following: { threshold: -> { application_settings.users_api_limit_following }, interval: 1.minute }, user_status: { threshold: -> { application_settings.users_api_limit_status }, interval: 1.minute }, - user_keys: { threshold: -> { application_settings.users_api_limit_keys }, interval: 1.minute }, - user_specific_key: { threshold: -> { application_settings.users_api_limit_key }, interval: 1.minute }, + user_ssh_keys: { threshold: -> { application_settings.users_api_limit_ssh_keys }, interval: 1.minute }, + user_ssh_key: { threshold: -> { application_settings.users_api_limit_ssh_key }, interval: 1.minute }, user_gpg_keys: { threshold: -> { application_settings.users_api_limit_gpg_keys }, interval: 1.minute }, - user_specific_gpg_key: { threshold: -> { application_settings.users_api_limit_gpg_key }, interval: 1.minute }, + user_gpg_key: { threshold: -> { application_settings.users_api_limit_gpg_key }, interval: 1.minute }, user_sign_up: { threshold: 20, interval: 1.minute }, user_sign_in: { threshold: 5, interval: 10.minutes }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4c267e11ae5e51..b06bacfcfc2a07 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -23727,6 +23727,9 @@ msgstr "" msgid "Exceptions" msgstr "" +msgid "Excluded users" +msgstr "" + msgid "Excluding %d project with no DORA metrics." msgid_plural "Excluding %d projects with no DORA metrics." msgstr[0] "" @@ -34046,6 +34049,9 @@ msgstr "" msgid "List of suitable GCP locations" msgstr "" +msgid "List of users allowed to exceed the rate limit. Example: username1, username2" +msgstr "" + msgid "List of users who are allowed to exceed the rate limit. Example: username1, username2" msgstr "" @@ -54089,6 +54095,9 @@ msgstr "" msgid "Set to 0 for no size limit." msgstr "" +msgid "Set to 0 to disable rate limits." +msgstr "" + msgid "Set to 0 to disable the limit." msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index e32f09f0fe1c5d..d572661a427684 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -860,7 +860,7 @@ within_testid('users-api-limits-settings') do fill_in 'Maximum requests per 10 minutes per user', with: 0 - fill_in 'Users to exclude from the rate limit', with: 'someone, someone_else' + fill_in 'Excluded users', with: 'someone, someone_else' click_button 'Save changes' end @@ -934,7 +934,7 @@ format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/keys', timeframe: 'minute') end - let(:application_setting_key) { :users_api_limit_keys } + let(:application_setting_key) { :users_api_limit_ssh_keys } it_behaves_like 'API rate limit setting' end @@ -944,7 +944,7 @@ format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/keys/:key_id', timeframe: 'minute') end - let(:application_setting_key) { :users_api_limit_key } + let(:application_setting_key) { :users_api_limit_ssh_key } it_behaves_like 'API rate limit setting' end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index ed13c5b9bedba0..af71fe131fc98c 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -77,8 +77,8 @@ members_delete_limit downstream_pipeline_trigger_limit_per_project_user_sha group_api_limit group_projects_api_limit groups_api_limit project_api_limit projects_api_limit user_contributed_projects_api_limit user_projects_api_limit user_starred_projects_api_limit - users_api_limit_followers users_api_limit_following users_api_limit_status users_api_limit_keys - users_api_limit_key users_api_limit_gpg_keys users_api_limit_gpg_key + users_api_limit_followers users_api_limit_following users_api_limit_status users_api_limit_ssh_keys + users_api_limit_ssh_key users_api_limit_gpg_keys users_api_limit_gpg_key group_shared_groups_api_limit group_invited_groups_api_limit project_invited_groups_api_limit diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 811e844ac397f3..81820d1cec0220 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -52,8 +52,8 @@ it { expect(setting.users_api_limit_followers).to eq(100) } it { expect(setting.users_api_limit_following).to eq(100) } it { expect(setting.users_api_limit_status).to eq(240) } - it { expect(setting.users_api_limit_keys).to eq(120) } - it { expect(setting.users_api_limit_key).to eq(120) } + it { expect(setting.users_api_limit_ssh_keys).to eq(120) } + it { expect(setting.users_api_limit_ssh_key).to eq(120) } it { expect(setting.users_api_limit_gpg_keys).to eq(120) } it { expect(setting.users_api_limit_gpg_key).to eq(120) } it { expect(setting.disable_password_authentication_for_users_with_sso_identities).to be(false) } @@ -303,8 +303,8 @@ def many_usernames(num = 100) users_api_limit_followers users_api_limit_following users_api_limit_status - users_api_limit_keys - users_api_limit_key + users_api_limit_ssh_keys + users_api_limit_ssh_key users_api_limit_gpg_keys users_api_limit_gpg_key ] diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c856ff124b03d3..8c8f1902b9bca0 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2595,7 +2595,7 @@ def update_password(user, admin, password = User.random_password) end context 'when user is authenticated' do - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_keys do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_ssh_keys do def request get api(path, current_user) end @@ -2605,7 +2605,7 @@ def request context 'when user is unauthenticated' do let(:current_user) { nil } - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_keys do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_ssh_keys do def request get api(path, current_user) end @@ -2643,7 +2643,7 @@ def request context 'when the rate limit has been reached' do it 'returns status 429 Too Many Requests', :aggregate_failures do ip = '1.2.3.4' - expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:user_keys, scope: ip).and_return(true) + expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:user_ssh_keys, scope: ip).and_return(true) get api(path), env: { REMOTE_ADDR: ip } @@ -2690,7 +2690,7 @@ def request end context 'when user is authenticated' do - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_specific_key do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_ssh_key do def request get api(path, current_user) end @@ -2700,7 +2700,7 @@ def request context 'when user is unauthenticated' do let(:current_user) { nil } - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_specific_key do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_ssh_key do def request get api(path, current_user) end @@ -2871,7 +2871,7 @@ def request end context 'when user is authenticated' do - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_specific_gpg_key do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_gpg_key do def request get api(path, current_user) end @@ -2881,7 +2881,7 @@ def request context 'when user is unauthenticated' do let(:current_user) { nil } - it_behaves_like 'rate limited endpoint', rate_limit_key: :user_specific_gpg_key do + it_behaves_like 'rate limited endpoint', rate_limit_key: :user_gpg_key do def request get api(path, current_user) end diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 1cb5ca325ffabe..be298123b480ad 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -28,14 +28,14 @@ expect(rendered).to have_content('Set to 0 to disable the limits.') end - it 'renders the users rate limit fields' do + it 'renders the users rate limit fields', :aggregate_failures do render expect(rendered).to have_field('application_setting_users_api_limit_followers') expect(rendered).to have_field('application_setting_users_api_limit_following') expect(rendered).to have_field('application_setting_users_api_limit_status') - expect(rendered).to have_field('application_setting_users_api_limit_keys') - expect(rendered).to have_field('application_setting_users_api_limit_key') + expect(rendered).to have_field('application_setting_users_api_limit_ssh_keys') + expect(rendered).to have_field('application_setting_users_api_limit_ssh_key') expect(rendered).to have_field('application_setting_users_api_limit_gpg_keys') expect(rendered).to have_field('application_setting_users_api_limit_gpg_key') -- GitLab From c1c20840d1ff0f4e8b4fc9525d4059d7fd88aa3f Mon Sep 17 00:00:00 2001 From: habdul-razak Date: Tue, 18 Feb 2025 15:32:30 -0500 Subject: [PATCH 10/10] Rename GET /users/:id API --- .../_users_api_limits.html.haml | 4 +- locale/gitlab.pot | 3 -- spec/features/admin/admin_settings_spec.rb | 37 ++++++++++++------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/views/admin/application_settings/_users_api_limits.html.haml b/app/views/admin/application_settings/_users_api_limits.html.haml index 2515af8edc3b46..5f6dea377bb1cc 100644 --- a/app/views/admin/application_settings/_users_api_limits.html.haml +++ b/app/views/admin/application_settings/_users_api_limits.html.haml @@ -37,8 +37,8 @@ %fieldset .form-group - = f.label :users_get_by_id_limit, _('Maximum requests per 10 minutes per user'), class: 'label-bold' - = f.number_field :users_get_by_id_limit, class: 'form-control gl-form-input' + = f.label :users_get_by_id_limit, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user'), api_name: 'GET /users/:id', timeframe: '10 minutes'), class: 'label-bold' + = f.number_field :users_get_by_id_limit, min: 0, class: 'form-control gl-form-input' .form-group = f.label :users_get_by_id_limit_allowlist_raw, _('Excluded users'), class: 'label-bold' = f.text_area :users_get_by_id_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'users-api-limit-users-allowlist-field-description' } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b06bacfcfc2a07..c3d01817fa5d98 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34847,9 +34847,6 @@ msgstr "" msgid "Maximum push size (MiB)" msgstr "" -msgid "Maximum requests per 10 minutes per user" -msgstr "" - msgid "Maximum requests per minute" msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index d572661a427684..c8a8b8a652e2ab 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -855,20 +855,6 @@ expect(current_settings.pipeline_limit_per_project_user_sha).to eq(10) end - it 'changes Users API rate limits settings' do - visit network_admin_application_settings_path - - within_testid('users-api-limits-settings') do - fill_in 'Maximum requests per 10 minutes per user', with: 0 - fill_in 'Excluded users', with: 'someone, someone_else' - click_button 'Save changes' - end - - expect(page).to have_content 'Application settings saved successfully' - expect(current_settings.users_get_by_id_limit).to eq(0) - expect(current_settings.users_get_by_id_limit_allowlist).to eq(%w[someone someone_else]) - end - it 'changes gitlab shell operation limits settings' do visit network_admin_application_settings_path @@ -899,6 +885,29 @@ describe 'users API rate limits' do let_it_be(:network_settings_section) { 'users-api-limits-settings' } + context 'for GET /users:id API requests', :aggregate_failures do + let(:rate_limit_field) do + format(_('Maximum requests to the %{api_name} API per %{timeframe} per user'), api_name: 'GET /users/:id', timeframe: '10 minutes') + end + + let(:application_setting_key) { :users_get_by_id_limit } + + it 'changes Users API rate limits settings', :aggregate_failures do + visit network_admin_application_settings_path + + new_rate_limit = 0 + within_testid('users-api-limits-settings') do + fill_in rate_limit_field, with: new_rate_limit + fill_in 'Excluded users', with: 'someone, someone_else' + click_button 'Save changes' + end + + expect(page).to have_content 'Application settings saved successfully' + expect(current_settings[application_setting_key]).to eq(new_rate_limit) + expect(current_settings.users_get_by_id_limit_allowlist).to eq(%w[someone someone_else]) + end + end + context 'for GET /users/:id/followers API requests' do let(:rate_limit_field) do format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:id/followers', timeframe: 'minute') -- GitLab