Class: Gitlab::ApplicationRateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/application_rate_limiter.rb

Overview

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.

end

Examples:

if Gitlab::ApplicationRateLimiter.throttled?(:project_export, scope: [@project, @current_user])
 flash[:alert] = 'error!'
 redirect_to(edit_project_path(@project), status: :too_many_requests)

Class Method Summary collapse

Class Method Details

.increment(key, scope, interval = nil) ⇒ Integer

Increments the given cache key and increments the value by 1 with the expiration interval defined in `.rate_limits`.

Parameters:

  • key (Symbol)

    Key attribute registered in `.rate_limits`

  • scope (Hash)

    a customizable set of options

  • interval (Hash) (defaults to: nil)

    a customizable set of options

Options Hash (scope):

  • Array (Array<ActiveRecord>)

    of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)

Options Hash (interval):

  • Optional (Integer)

    interval value to override default one registered in `.rate_limits`

Returns:

  • (Integer)

    incremented value


64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/gitlab/application_rate_limiter.rb', line 64

def increment(key, scope, interval = nil)
  value = 0
  interval_value = interval || interval(key)

  Gitlab::Redis::Cache.with do |redis|
    cache_key = action_key(key, scope)
    value     = redis.incr(cache_key)
    redis.expire(cache_key, interval_value) if value == 1
  end

  value
end

.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object

Logs request using provided logger

Parameters:

  • request (Http::Request)
    • Web request to be logged

  • type (Symbol)

    A symbol key that represents the request

  • current_user (User)

    Current user of the request, it can be nil

  • logger (Logger) (defaults to: Gitlab::AuthLogger)

    Logger to log request to a specific log file. Defaults to Gitlab::AuthLogger


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/gitlab/application_rate_limiter.rb', line 83

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

.rate_limitsObject

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.


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/gitlab/application_rate_limiter.rb', line 20

def rate_limits
  {
    issues_create:                { threshold: -> { application_settings.issues_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 },
    show_raw_controller:          { 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 }
  }.freeze
end

.throttled?(key, scope: nil, interval: nil, threshold: nil) ⇒ Boolean

Increments the given key and returns true if the action should be throttled.

Parameters:

  • key (Symbol)

    Key attribute registered in `.rate_limits`

  • scope (Hash) (defaults to: nil)

    a customizable set of options

  • threshold (Hash) (defaults to: nil)

    a customizable set of options

  • interval (Hash) (defaults to: nil)

    a customizable set of options

Options Hash (scope:):

  • Array (Array<ActiveRecord>)

    of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)

Options Hash (interval:):

  • Optional (Integer)

    interval value to override default one registered in `.rate_limits`

Options Hash (threshold:):

  • Optional (Integer)

    threshold value to override default one registered in `.rate_limits`

Returns:

  • (Boolean)

    Whether or not a request should be throttled


47
48
49
50
51
52
53
54
# File 'lib/gitlab/application_rate_limiter.rb', line 47

def throttled?(key, scope: nil, interval: nil, threshold: nil)
  return unless rate_limits[key]

  threshold_value = threshold || threshold(key)

  threshold_value > 0 &&
    increment(key, scope, interval) > threshold_value
end