Module: Gitlab::ApplicationRateLimiter
- Defined in:
- lib/gitlab/application_rate_limiter.rb
Overview
This module 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. See CheckRateLimit concern for usage.
Constant Summary collapse
- InvalidKeyError =
Class.new(StandardError)
Class Method Summary collapse
-
.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object
Logs request using provided logger.
-
.peek(key, scope:, threshold: nil, users_allowlist: nil) ⇒ Boolean
Returns the current rate limited state without incrementing the count.
-
.rate_limits ⇒ Object
Application rate limits.
-
.throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false) ⇒ Boolean
Increments the given key and returns true if the action should be throttled.
Class Method Details
.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object
Logs request using provided logger
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/gitlab/application_rate_limiter.rb', line 102 def log_request(request, type, current_user, logger = Gitlab::AuthLogger) request_information = { message: 'Application_Rate_Limiter_Request', env: type, remote_ip: request.ip, request_method: request.request_method, path: request.fullpath } if current_user request_information.merge!({ user_id: current_user.id, username: current_user.username }) end logger.error(request_information) end |
.peek(key, scope:, threshold: nil, users_allowlist: nil) ⇒ Boolean
Returns the current rate limited state without incrementing the count.
92 93 94 |
# File 'lib/gitlab/application_rate_limiter.rb', line 92 def peek(key, scope:, threshold: nil, users_allowlist: nil) throttled?(key, peek: true, scope: scope, threshold: threshold, users_allowlist: users_allowlist) end |
.rate_limits ⇒ Object
Application rate limits
Threshold value can be either an Integer or a Proc in order to not evaluate it's value every time this method is called and only do that when it's needed.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/gitlab/application_rate_limiter.rb', line 17 def rate_limits # rubocop:disable Metrics/AbcSize { issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute }, notes_create: { threshold: -> { application_settings.notes_create_limit }, interval: 1.minute }, project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute }, project_repositories_archive: { threshold: 5, interval: 1.minute }, project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, 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 }, 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 }, group_testing_hook: { threshold: 5, interval: 1.minute }, profile_add_new_email: { threshold: 5, interval: 1.minute }, web_hook_calls: { 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_sign_up: { threshold: 20, interval: 1.minute }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, profile_update_username: { threshold: 10, interval: 1.minute }, update_environment_canary_ingress: { threshold: 1, interval: 1.minute }, auto_rollback_deployment: { threshold: 1, interval: 3.minutes }, search_rate_limit: { threshold: -> { application_settings.search_rate_limit }, interval: 1.minute }, search_rate_limit_unauthenticated: { threshold: -> { application_settings.search_rate_limit_unauthenticated }, interval: 1.minute }, gitlab_shell_operation: { threshold: 600, interval: 1.minute }, pipelines_create: { threshold: 25, interval: 1.minute } }.freeze end |
.throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false) ⇒ Boolean
Increments the given key and returns true if the action should be throttled.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/gitlab/application_rate_limiter.rb', line 59 def throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false) raise InvalidKeyError unless rate_limits[key] return false if scoped_user_in_allowlist?(scope, users_allowlist) threshold_value = threshold || threshold(key) return false if threshold_value == 0 interval_value = interval(key) # `period_key` is based on the current time and interval so when time passes to the next interval # the key changes and the rate limit count starts again from 0. # Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68 period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value) cache_key = cache_key(key, scope, period_key) value = if peek read(cache_key) else increment(cache_key, interval_value, time_elapsed_in_period) end value > threshold_value end |