diff options
Diffstat (limited to 'app/controllers/concerns/page_limiter.rb')
-rw-r--r-- | app/controllers/concerns/page_limiter.rb | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/app/controllers/concerns/page_limiter.rb b/app/controllers/concerns/page_limiter.rb new file mode 100644 index 00000000000..5b078d80fca --- /dev/null +++ b/app/controllers/concerns/page_limiter.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# Include this in your controller and call `limit_pages` in order +# to configure the limiter. +# +# Examples: +# class MyController < ApplicationController +# include PageLimiter +# +# before_action only: [:index] do +# limit_pages(500) +# end +# +# # You can override the default response +# rescue_from PageOutOfBoundsError, with: :page_out_of_bounds +# +# def page_out_of_bounds(error) +# # Page limit number is available as error.message +# head :ok +# end +# + +module PageLimiter + extend ActiveSupport::Concern + + PageLimiterError = Class.new(StandardError) + PageLimitNotANumberError = Class.new(PageLimiterError) + PageLimitNotSensibleError = Class.new(PageLimiterError) + PageOutOfBoundsError = Class.new(PageLimiterError) + + included do + rescue_from PageOutOfBoundsError, with: :default_page_out_of_bounds_response + end + + def limit_pages(max_page_number) + check_page_number!(max_page_number) + end + + private + + # If the page exceeds the defined maximum, raise a PageOutOfBoundsError + # If the page doesn't exceed the limit, it does nothing. + def check_page_number!(max_page_number) + raise PageLimitNotANumberError unless max_page_number.is_a?(Integer) + raise PageLimitNotSensibleError unless max_page_number > 0 + + if params[:page].present? && params[:page].to_i > max_page_number + record_page_limit_interception + raise PageOutOfBoundsError.new(max_page_number) + end + end + + # By default just return a HTTP status code and an empty response + def default_page_out_of_bounds_response + head :bad_request + end + + # Record the page limit being hit in Prometheus + def record_page_limit_interception + dd = DeviceDetector.new(request.user_agent) + + Gitlab::Metrics.counter(:gitlab_page_out_of_bounds, + controller: params[:controller], + action: params[:action], + bot: dd.bot? + ) + end +end |