cartodb-4.42/services/platform-limits/lib/abstract_limit.rb
2024-04-06 05:25:13 +00:00

205 lines
5.9 KiB
Ruby

module CartoDB
module PlatformLimits
# Abstract limit class, providing scaffolding and common logic.
#
# Each limit should specify here the usage and expected contents of 'context' parameter used in most methods.
class AbstractLimit
TYPE_USER = 'u'
TYPE_IP = 'i'
def self.classname
self.name.gsub(/.*::/, '').to_sym
end
# Class constructor
# @param options Hash
# {
# :user => ::User|nil
# :ip => Integer|nil
# :initial_value => mixed|nil
# :max_value => mixed|nil
# :ttl => Integer|nil
# }
# @throws ArgumentError
def initialize(options={})
@user = options.fetch(:user, nil)
@ip = options.fetch(:ip, nil)
raise ArgumentError.new('options must be a Hash') unless options.is_a?(Hash)
raise ArgumentError.new('Must supply at least a user or an IP address') if @user.nil? && @ip.nil?
unless @user.nil?
raise ArgumentError.new('Supplied user object must have :username') unless @user.respond_to?(:username)
end
@initial_value = options.fetch(:initial_value, nil)
@max_value = options.fetch(:max_value, nil)
@time_frame = options.fetch(:ttl, nil)
end
# Checks if user is over limit, increasing the internal counter (where proceeds)
# @param context mixed|nil
# @return bool
def is_over_limit!(context=nil)
increase(context)
is_over_limit(context)
end
# Checks if user is over limit without increasing the internal counter
# @param context mixed|nil
# @return bool
def is_over_limit?(context=nil)
is_over_limit(context)
end
# Checks if user is within the allowed limit, increasing the internal counter (where proceeds)
# @param context mixed|nil
# @return bool
def is_within_limit!(context=nil)
!is_over_limit!(context)
end
# Checks if user is within the allowed limit without increasing the internal counter
# @param context mixed|nil
# @return bool
def is_within_limit?(context=nil)
!is_over_limit?(context)
end
# Peeks the current value of the limit without increasing the internal counter
# @param context mixed|nil
# @return mixed
def peek(context=nil)
get(context)
end
# Increments the limit
# @param context mixed|nil
# @param amount integer
def increment!(context=nil, amount=1)
increase(context, amount)
end
# Decrements the limit
# @param context mixed|nil
# @param amount integer
def decrement!(context=nil, amount=1)
decrease(context, amount)
end
# Gets (where proceeds) the remaining count for the limit
# Useful for a "X-Rate-Limit-Remaining" header
# @param context mixed|nil
# @return mixed|nil
def remaining_limit?(context=nil)
max = get_maximum(context)
current = get(context)
(max.nil? || current.nil?) ? nil : max - current
end
# Gets (where proceeds) the maximum value until hitting the limit
# Useful for a "X-Rate-Limit-Limit" header
# @param context mixed|nil
# @return mixed|nil
def maximum_limit?(context=nil)
get_maximum(context)
end
# Gets (where proceeds) the timestamp when the limit will expire/reset
# Useful for a "X-Rate-Limit-Reset" header
# @param context mixed|nil
# @return integer|nil
def time_period?(context=nil)
get_time_period(context)
end
# Returns the key for this limit instance
# e.g. limits:Importer:InputFileSize:u:cartodb_user
# e.g. limits:Security::LoginAttempts:ui:cartodb_user127.0.0.1
# @return string
def key
"limits:#{subkey}:#{user_ip_key_fragment}"
end
# TODO: Revisit this methods, might not be needed hidden and just provided with default logic and overriden
protected
attr_accessor :user, :ip, :initial_value, :max_value, :time_frame, :current_value
# Builds the subkey part that identifies current limit
# Must have format 'xxx:yyy' Where xxx is the limit group and yyy the limit name.
# e.g. Importer:InputFileSize
def subkey
raise "Implement at child classes"
end
# @param context mixed
# @return bool
def is_over_limit(context)
raise "Implement at child classes"
end
# Gets current value of the limit
# @param context mixed
# @return mixed
def get(context)
raise "Implement at child classes"
end
# Gets the maximum limit value
# @param context mixed
# @return mixed
def get_maximum(context)
raise "Implement at child classes"
end
# Gets when the limit expires
# @param context mixed
# @return integer|nil Timestamp
def get_time_period(context)
raise "Implement at child classes"
end
# Increases the limit
# @param context mixed
# @param amount integer
def increase(context, amount=1)
raise "Implement at child classes"
end
# Decreases the limit
# @param context mixed
# @param amount integer
def decrease(context, amount=1)
raise "Implement at child classes"
end
# Resets the limit
def expire
raise "Implement at child classes"
end
private
# Builds the key fragment that indicates if limit is based on user, ip or both (and their values)
# @return string
def user_ip_key_fragment
type_fragment = ''
value_fragment = ''
unless @user.nil?
type_fragment << TYPE_USER
value_fragment << @user.username
end
unless @ip.nil?
type_fragment << TYPE_IP
value_fragment << @ip.to_s
end
"#{type_fragment}:#{value_fragment}"
end
end
end
end