Commit f7eb9240 authored by Matthias Käppler's avatar Matthias Käppler

Merge branch '346140-unify-request-rate-limiting-logic' into 'master'

Unify request rate limiting logic

See merge request gitlab-org/gitlab!75024
parents bead17ae 17f70841
......@@ -23,6 +23,7 @@ class ApplicationController < ActionController::Base
include Gitlab::Utils::StrongMemoize
include ::Gitlab::EndpointAttributes
include FlocOptOut
include CheckRateLimit
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
......
......@@ -5,19 +5,27 @@
# Controller concern that checks if the rate limit for a given action is throttled by calling the
# Gitlab::ApplicationRateLimiter class. If the action is throttled for the current user, the request
# will be logged and an error message will be rendered with a Too Many Requests response status.
# See lib/api/helpers/rate_limiter.rb for API version
module CheckRateLimit
def check_rate_limit(key)
return unless rate_limiter.throttled?(key, scope: current_user, users_allowlist: rate_limit_users_allowlist)
def check_rate_limit!(key, scope:, redirect_back: false, **options)
return unless rate_limiter.throttled?(key, scope: scope, **options)
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
return yield if block_given?
message = _('This endpoint has been requested too many times. Try again later.')
if redirect_back
redirect_back_or_default(options: { alert: message })
else
render plain: message, status: :too_many_requests
end
end
private
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
def rate_limit_users_allowlist
Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist
end
end
......@@ -32,16 +32,4 @@ module Integrations::HooksExecution
flash[:alert] = "Hook execution failed: #{message}"
end
end
def create_rate_limit(key, scope)
if rate_limiter.throttled?(key, scope: [scope, current_user])
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
end
......@@ -3,7 +3,6 @@
module NotesActions
include RendersNotes
include Gitlab::Utils::StrongMemoize
include CheckRateLimit
extend ActiveSupport::Concern
# last_fetched_at is an integer number of microseconds, which is the same
......@@ -16,7 +15,11 @@ module NotesActions
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
before_action -> { check_rate_limit(:notes_create) }, only: [:create]
before_action -> {
check_rate_limit!(:notes_create,
scope: current_user,
users_allowlist: Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist)
}, only: [:create]
end
def index
......
......@@ -37,7 +37,7 @@ class GroupsController < Groups::ApplicationController
push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml)
end
before_action :export_rate_limit, only: [:export, :download_export]
before_action :check_export_rate_limit!, only: [:export, :download_export]
helper_method :captcha_required?
......@@ -314,16 +314,12 @@ class GroupsController < Groups::ApplicationController
url_for(safe_params)
end
def export_rate_limit
def check_export_rate_limit!
prefixed_action = "group_#{params[:action]}".to_sym
scope = params[:action] == :download_export ? @group : nil
if Gitlab::ApplicationRateLimiter.throttled?(prefixed_action, scope: [current_user, scope].compact)
Gitlab::ApplicationRateLimiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
end
check_rate_limit!(prefixed_action, scope: [current_user, scope].compact)
end
def ensure_export_enabled
......
......@@ -3,7 +3,7 @@
class Import::BaseController < ApplicationController
include ActionView::Helpers::SanitizeHelper
before_action :import_rate_limit, only: [:create]
before_action -> { check_rate_limit!(:project_import, scope: [current_user, :project_import], redirect_back: true) }, only: [:create]
feature_category :importers
def status
......@@ -98,18 +98,4 @@ class Import::BaseController < ApplicationController
def project_save_error(project)
project.errors.full_messages.join(', ')
end
def import_rate_limit
key = "project_import".to_sym
if rate_limiter.throttled?(key, scope: [current_user, key])
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
redirect_back_or_default(options: { alert: _('This endpoint has been requested too many times. Try again later.') })
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
end
......@@ -4,7 +4,7 @@ class Import::GitlabGroupsController < ApplicationController
include WorkhorseAuthorization
before_action :ensure_group_import_enabled
before_action :import_rate_limit, only: %i[create]
before_action :check_import_rate_limit!, only: %i[create]
feature_category :importers
......@@ -55,12 +55,9 @@ class Import::GitlabGroupsController < ApplicationController
render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true)
end
def import_rate_limit
if Gitlab::ApplicationRateLimiter.throttled?(:group_import, scope: current_user)
Gitlab::ApplicationRateLimiter.log_request(request, :group_import_request_limit, current_user)
flash[:alert] = _('This endpoint has been requested too many times. Try again later.')
redirect_to new_group_path
def check_import_rate_limit!
check_rate_limit!(:group_import, scope: current_user) do
redirect_to new_group_path, alert: _('This endpoint has been requested too many times. Try again later.')
end
end
......
......@@ -2,8 +2,10 @@
class Profiles::EmailsController < Profiles::ApplicationController
before_action :find_email, only: [:destroy, :resend_confirmation_instructions]
before_action -> { rate_limit!(:profile_add_new_email) }, only: [:create]
before_action -> { rate_limit!(:profile_resend_email_confirmation) }, only: [:resend_confirmation_instructions]
before_action -> { check_rate_limit!(:profile_add_new_email, scope: current_user, redirect_back: true) },
only: [:create]
before_action -> { check_rate_limit!(:profile_resend_email_confirmation, scope: current_user, redirect_back: true) },
only: [:resend_confirmation_instructions]
feature_category :users
......@@ -42,16 +44,6 @@ class Profiles::EmailsController < Profiles::ApplicationController
private
def rate_limit!(action)
rate_limiter = ::Gitlab::ApplicationRateLimiter
if rate_limiter.throttled?(action, scope: current_user)
rate_limiter.log_request(request, action, current_user)
redirect_back_or_default(options: { alert: _('This action has been performed too many times. Try again later.') })
end
end
def email_params
params.require(:email).permit(:email)
end
......
......@@ -6,7 +6,7 @@ class Projects::HooksController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
before_action :hook_logs, only: :edit
before_action -> { create_rate_limit(:project_testing_hook, @project) }, only: :test
before_action -> { check_rate_limit!(:project_testing_hook, scope: [@project, current_user]) }, only: :test
respond_to :html
......
......@@ -37,7 +37,9 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_download_code!, only: [:related_branches]
# Limit the amount of issues created per minute
before_action :create_rate_limit, only: [:create], if: -> { Feature.disabled?('rate_limited_service_issues_create', project, default_enabled: :yaml) }
before_action -> { check_rate_limit!(:issues_create, scope: [@project, @current_user])},
only: [:create],
if: -> { Feature.disabled?('rate_limited_service_issues_create', project, default_enabled: :yaml) }
before_action do
push_frontend_feature_flag(:tribute_autocomplete, @project)
......@@ -363,20 +365,6 @@ class Projects::IssuesController < Projects::ApplicationController
project_compare_path(project, from: project.default_branch, to: branch[:name])
end
def create_rate_limit
key = :issues_create
if rate_limiter.throttled?(key, scope: [@project, @current_user])
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
def service_desk?
action_name == 'service_desk'
end
......
......@@ -3,7 +3,7 @@
class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :schedule, except: [:index, :new, :create]
before_action :play_rate_limit, only: [:play]
before_action :check_play_rate_limit!, only: [:play]
before_action :authorize_play_pipeline_schedule!, only: [:play]
before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
......@@ -81,19 +81,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
private
def play_rate_limit
def check_play_rate_limit!
return unless current_user
if rate_limiter.throttled?(:play_pipeline_schedule, scope: [current_user, schedule])
check_rate_limit!(:play_pipeline_schedule, scope: [current_user, schedule]) do
flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
redirect_to pipeline_schedules_path(@project)
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
def schedule
@schedule ||= project.pipeline_schedules.find(params[:id])
end
......
......@@ -13,7 +13,7 @@ class Projects::RawController < Projects::ApplicationController
before_action :set_ref_and_path
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :show_rate_limit, only: [:show], unless: :external_storage_request?
before_action :check_show_rate_limit!, only: [:show], unless: :external_storage_request?
before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled?
feature_category :source_code_management
......@@ -33,23 +33,11 @@ class Projects::RawController < Projects::ApplicationController
@ref, @path = extract_ref(get_id)
end
def show_rate_limit
if rate_limiter.throttled?(:show_raw_controller, scope: [@project, @path], threshold: raw_blob_request_limit)
rate_limiter.log_request(request, :raw_blob_request_limit, current_user)
def check_show_rate_limit!
check_rate_limit!(:raw_blob, scope: [@project, @path]) do
render plain: _('You cannot access the raw file. Please wait a minute.'), status: :too_many_requests
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
def raw_blob_request_limit
Gitlab::CurrentSettings
.current_application_settings
.raw_blob_request_limit
end
end
Projects::RawController.prepend_mod
......@@ -3,16 +3,16 @@
class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath
include StaticObjectExternalStorage
include Gitlab::RateLimitHelpers
include HotlinkInterceptor
include Gitlab::RepositoryArchiveRateLimiter
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
skip_before_action :default_cache_headers, only: :archive
# Authorize
before_action :check_archive_rate_limiting!, only: :archive
before_action :require_non_empty_project, except: :create
before_action :archive_rate_limit!, only: :archive
before_action :intercept_hotlinking!, only: :archive
before_action :assign_archive_vars, only: :archive
before_action :assign_append_sha, only: :archive
......@@ -42,12 +42,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
private
def archive_rate_limit!
if archive_rate_limit_reached?(current_user, @project)
render plain: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE, status: :too_many_requests
end
end
def repo_params
@repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
end
......@@ -125,6 +119,12 @@ class Projects::RepositoriesController < Projects::ApplicationController
[path, nil]
end
end
def check_archive_rate_limiting!
check_archive_rate_limit!(current_user, @project) do
render(plain: _('This archive has been requested too many times. Try again later.'), status: :too_many_requests)
end
end
end
Projects::RepositoriesController.prepend_mod_with('Projects::RepositoriesController')
......@@ -30,7 +30,7 @@ class ProjectsController < Projects::ApplicationController
before_action :event_filter, only: [:show, :activity]
# Project Export Rate Limit
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
before_action :check_export_rate_limit!, only: [:export, :download_export, :generate_new_export]
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project, default_enabled: :yaml)
......@@ -544,20 +544,12 @@ class ProjectsController < Projects::ApplicationController
@project = @project.present(current_user: current_user)
end
def export_rate_limit
def check_export_rate_limit!
prefixed_action = "project_#{params[:action]}".to_sym
project_scope = params[:action] == 'download_export' ? @project : nil
if rate_limiter.throttled?(prefixed_action, scope: [current_user, project_scope].compact)
rate_limiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
check_rate_limit!(prefixed_action, scope: [current_user, project_scope].compact)
end
def render_edit
......
......@@ -4,7 +4,7 @@ class Admin::EmailsController < Admin::ApplicationController
include Admin::EmailsHelper
before_action :check_license_send_emails_from_admin_area_available!
before_action :check_rate_limit!, only: [:create]
before_action :check_emails_rate_limit!, only: [:create]
feature_category :not_owned
......@@ -18,7 +18,7 @@ class Admin::EmailsController < Admin::ApplicationController
private
def check_rate_limit!
def check_emails_rate_limit!
if admin_emails_are_currently_rate_limited?
redirect_to admin_email_path, alert: _('Email could not be sent')
end
......
......@@ -17,8 +17,8 @@ class Groups::EpicsController < Groups::ApplicationController
before_action :verify_group_bulk_edit_enabled!, only: [:bulk_update]
after_action :log_epic_show, only: :show
# Limit the amount of epics created per minute
before_action :create_rate_limit, only: [:create]
# Limit the amount of epics created per minute. Epics share the issue creation rate limit
before_action -> { check_rate_limit!(:issues_create, scope: current_user) }, only: [:create]
before_action do
push_frontend_feature_flag(:vue_epics_list, @group, type: :development, default_enabled: :yaml)
......@@ -133,19 +133,4 @@ class Groups::EpicsController < Groups::ApplicationController
def verify_group_bulk_edit_enabled!
render_404 unless group.licensed_feature_available?(:group_bulk_edit)
end
def create_rate_limit
# Epics share the issue creation rate limit
key = :issues_create
if rate_limiter.throttled?(key, scope: current_user)
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
end
......@@ -8,7 +8,7 @@ class Groups::HooksController < Groups::ApplicationController
before_action :authorize_admin_group!
before_action :check_group_webhooks_available!
before_action :set_hook, only: [:edit, :update, :test, :destroy]
before_action -> { create_rate_limit(:group_testing_hook, @group) }, only: :test
before_action -> { check_rate_limit!(:group_testing_hook, scope: [@group, current_user]) }, only: :test
respond_to :html
......
......@@ -18,7 +18,7 @@ module API
detail 'This feature was introduced in GitLab 12.5.'
end
get ':id/export/download' do
check_rate_limit! :group_download_export, [current_user, user_group]
check_rate_limit! :group_download_export, scope: [current_user, user_group]
if user_group.export_file_exists?
if user_group.export_archive_exists?
......@@ -35,7 +35,7 @@ module API
detail 'This feature was introduced in GitLab 12.5.'
end
post ':id/export' do
check_rate_limit! :group_export, [current_user]
check_rate_limit! :group_export, scope: current_user
export_service = ::Groups::ImportExport::ExportService.new(group: user_group, user: current_user)
......
......@@ -2,26 +2,27 @@
module API
module Helpers
# == RateLimiter
#
# Helper that checks if the rate limit for a given endpoint is throttled by calling the
# Gitlab::ApplicationRateLimiter class. If the action is throttled for the current user, the request
# will be logged and an error message will be rendered with a Too Many Requests response status.
# See app/controllers/concerns/check_rate_limit.rb for Rails controllers version
module RateLimiter
def check_rate_limit!(key, scope, users_allowlist = nil)
if rate_limiter.throttled?(key, scope: scope, users_allowlist: users_allowlist)
log_request(key)
render_exceeded_limit_error!
end
end
def check_rate_limit!(key, scope:, **options)
return unless rate_limiter.throttled?(key, scope: scope, **options)
private
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
return yield if block_given?
def render_exceeded_limit_error!
render_api_error!({ error: _('This endpoint has been requested too many times. Try again later.') }, 429)
end
def log_request(key)
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
private
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
end
end
......
......@@ -262,7 +262,7 @@ module API
post ':id/issues' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21140')
check_rate_limit! :issues_create, [current_user] if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml)
check_rate_limit!(:issues_create, scope: current_user) if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml)
authorize! :create_issue, user_project
......
......@@ -79,7 +79,7 @@ module API
post ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do
allowlist =
Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist
check_rate_limit! :notes_create, [current_user], allowlist
check_rate_limit! :notes_create, scope: current_user, users_allowlist: allowlist
noteable = find_noteable(noteable_type, params[:noteable_id])
opts = {
......
......@@ -25,7 +25,7 @@ module API
detail 'This feature was introduced in GitLab 10.6.'
end
get ':id/export/download' do
check_rate_limit! :project_download_export, [current_user, user_project]
check_rate_limit! :project_download_export, scope: [current_user, user_project]
if user_project.export_file_exists?
if user_project.export_archive_exists?
......@@ -49,7 +49,7 @@ module API
end
end
post ':id/export' do
check_rate_limit! :project_export, [current_user]
check_rate_limit! :project_export, scope: current_user
user_project.remove_exports
......
......@@ -81,7 +81,7 @@ module API
post 'import' do
require_gitlab_workhorse!
check_rate_limit! :project_import, [current_user, :project_import]
check_rate_limit! :project_import, scope: [current_user, :project_import]
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21041')
......@@ -135,7 +135,7 @@ module API
post 'remote-import' do
not_found! unless ::Feature.enabled?(:import_project_from_remote_file)
check_rate_limit! :project_import, [current_user, :project_import]
check_rate_limit! :project_import, scope: [current_user, :project_import]
response = ::Import::GitlabProjects::CreateProjectFromRemoteFileService.new(
current_user,
......
......@@ -45,7 +45,7 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
include ::Gitlab::RateLimitHelpers
include Gitlab::RepositoryArchiveRateLimiter
def handle_project_member_errors(errors)
if errors[:project_access].any?
......@@ -150,8 +150,8 @@ module API
optional :path, type: String, desc: 'Subfolder of the repository to be downloaded'
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
if archive_rate_limit_reached?(current_user, user_project)
render_api_error!({ error: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE }, 429)
check_archive_rate_limit!(current_user, user_project) do
render_api_error!({ error: _('This archive has been requested too many times. Try again later.') }, 429)
end
not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
......
......@@ -4,12 +4,7 @@ module Gitlab
# This class implements a simple rate limiter that can be used to throttle
# certain actions. Unlike Rack Attack and Rack::Throttle, which operate at
# the middleware level, this can be used at the controller or API level.
#
# @example
# if Gitlab::ApplicationRateLimiter.throttled?(:project_export, scope: [@project, @current_user])
# flash[:alert] = 'error!'
# redirect_to(edit_project_path(@project), status: :too_many_requests)
# end
# See CheckRateLimit concern for usage.
class ApplicationRateLimiter
InvalidKeyError = Class.new(StandardError)
......@@ -47,7 +42,7 @@ module Gitlab
project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
project_testing_hook: { threshold: 5, interval: 1.minute },
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
raw_blob: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
......
# frozen_string_literal: true
module Gitlab
module RateLimitHelpers
ARCHIVE_RATE_LIMIT_REACHED_MESSAGE = 'This archive has been requested too many times. Try again later.'
ARCHIVE_RATE_ANONYMOUS_THRESHOLD = 100 # Allow 100 requests/min for anonymous users
ARCHIVE_RATE_THROTTLE_KEY = :project_repositories_archive
def archive_rate_limit_reached?(user, project)
return false unless Feature.enabled?(:archive_rate_limit)
key = ARCHIVE_RATE_THROTTLE_KEY
if rate_limiter.throttled?(key, scope: [project, user], threshold: archive_rate_threshold_by_user(user))
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, user)
return true
end
false
end
def archive_rate_threshold_by_user(user)
if user
nil # Use the defaults
else
ARCHIVE_RATE_ANONYMOUS_THRESHOLD
end
end
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
end
end
# frozen_string_literal: true
module Gitlab
module RepositoryArchiveRateLimiter
def check_archive_rate_limit!(current_user, project, &block)
return unless Feature.enabled?(:archive_rate_limit)
threshold = current_user ? nil : 100
check_rate_limit!(:project_repositories_archive, scope: [project, current_user], threshold: threshold, &block)
end
end
end
......@@ -35585,9 +35585,6 @@ msgstr ""
msgid "This action cannot be undone, and will permanently delete the %{key} SSH key"
msgstr ""
msgid "This action has been performed too many times. Try again later."
msgstr ""
msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all related resources, including issues and merge requests."
msgstr ""
......@@ -35612,6 +35609,9 @@ msgstr ""
msgid "This application will be able to:"
msgstr ""
msgid "This archive has been requested too many times. Try again later."
msgstr ""
msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of %{size_limit}. %{written_count} of %{count} %{issuables} have been included. Consider re-exporting with a narrower selection of %{issuables}."
msgstr ""
......
......@@ -33,7 +33,7 @@ RSpec.describe Profiles::EmailsController do
subject
expect(response).to have_gitlab_http_status(:redirect)
expect(flash[:alert]).to eq(_('This action has been performed too many times. Try again later.'))
expect(flash[:alert]).to eq(_('This endpoint has been requested too many times. Try again later.'))
end
end
end
......
......@@ -86,7 +86,7 @@ RSpec.describe Projects::RepositoriesController do
describe 'rate limiting' do
it 'rate limits user when thresholds hit' do
expect(controller).to receive(:archive_rate_limit_reached?).and_return(true)
allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: "html"
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_rate_limiting do
let(:limiter_class) do
Class.new do
include ::Gitlab::RateLimitHelpers
attr_reader :request
def initialize(request)
@request = request
end
end
end
let(:request) { instance_double(ActionDispatch::Request, request_method: 'GET', ip: '127.0.0.1', fullpath: '/') }
let(:class_instance) { limiter_class.new(request) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
describe '#archive_rate_limit_reached?' do
context 'with a user' do
it 'rate limits the user properly' do
5.times do
expect(class_instance.archive_rate_limit_reached?(user, project)).to be_falsey
end
expect(class_instance.archive_rate_limit_reached?(user, project)).to be_truthy
end
end
context 'with an anonymous user' do
before do
stub_const('Gitlab::RateLimitHelpers::ARCHIVE_RATE_ANONYMOUS_THRESHOLD', 2)
end
it 'rate limits with higher limits' do
2.times do
expect(class_instance.archive_rate_limit_reached?(nil, project)).to be_falsey
end
expect(class_instance.archive_rate_limit_reached?(nil, project)).to be_truthy
expect(class_instance.archive_rate_limit_reached?(user, project)).to be_falsey
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::RepositoryArchiveRateLimiter do
let(:described_class) do
Class.new do
include ::Gitlab::RepositoryArchiveRateLimiter
def check_rate_limit!(**args)
end
end
end
describe "#check_archive_rate_limit!" do
let(:project) { instance_double('Project') }
let(:current_user) { instance_double('User') }
let(:check) { subject.check_archive_rate_limit!(current_user, project) }
context 'when archive_rate_limit feature flag is disabled' do
before do
stub_feature_flags(archive_rate_limit: false)
end
it 'does not check rate limit' do
expect(subject).not_to receive(:check_rate_limit!)
expect(check).to eq nil
end
end
context 'when archive_rate_limit feature flag is enabled' do
before do
stub_feature_flags(archive_rate_limit: true)
end
context 'when current user exists' do
it 'checks for project_repositories_archive rate limiting with default threshold' do
expect(subject).to receive(:check_rate_limit!)
.with(:project_repositories_archive, scope: [project, current_user], threshold: nil)
check
end
end
context 'when current user does not exist' do
let(:current_user) { nil }
it 'checks for project_repositories_archive rate limiting with threshold 100' do
expect(subject).to receive(:check_rate_limit!)
.with(:project_repositories_archive, scope: [project, current_user], threshold: 100)
check
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment