diff options
Diffstat (limited to 'lib/gitlab/web_hooks/rate_limiter.rb')
-rw-r--r-- | lib/gitlab/web_hooks/rate_limiter.rb | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/lib/gitlab/web_hooks/rate_limiter.rb b/lib/gitlab/web_hooks/rate_limiter.rb new file mode 100644 index 00000000000..73d59f6f786 --- /dev/null +++ b/lib/gitlab/web_hooks/rate_limiter.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Gitlab + module WebHooks + class RateLimiter + include Gitlab::Utils::StrongMemoize + + LIMIT_NAME = :web_hook_calls + NO_LIMIT = 0 + # SystemHooks (instance admin hooks) and ServiceHooks (integration hooks) + # are not rate-limited. + EXCLUDED_HOOK_TYPES = %w(SystemHook ServiceHook).freeze + + def initialize(hook) + @hook = hook + @parent = hook.parent + end + + # Increments the rate-limit counter. + # Returns true if the hook should be rate-limited. + def rate_limit! + return false if no_limit? + + ::Gitlab::ApplicationRateLimiter.throttled?( + limit_name, + scope: [root_namespace], + threshold: limit + ) + end + + # Returns true if the hook is currently over its rate-limit. + # It does not increment the rate-limit counter. + def rate_limited? + return false if no_limit? + + Gitlab::ApplicationRateLimiter.peek( + limit_name, + scope: [root_namespace], + threshold: limit + ) + end + + def limit + strong_memoize(:limit) do + next NO_LIMIT if hook.class.name.in?(EXCLUDED_HOOK_TYPES) + + root_namespace.actual_limits.limit_for(limit_name) || NO_LIMIT + end + end + + private + + attr_reader :hook, :parent + + def no_limit? + limit == NO_LIMIT + end + + def root_namespace + @root_namespace ||= parent.root_ancestor + end + + def limit_name + LIMIT_NAME + end + end + end +end + +Gitlab::WebHooks::RateLimiter.prepend_mod |