1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
# frozen_string_literal: true
module Rack
class Attack
class Cache
attr_accessor :prefix
attr_reader :last_epoch_time
def self.default_store
if Object.const_defined?(:Rails) && Rails.respond_to?(:cache)
::Rails.cache
end
end
def initialize(store: self.class.default_store)
self.store = store
@prefix = 'rack::attack'
end
attr_reader :store
def store=(store)
@store =
if (proxy = BaseProxy.lookup(store))
proxy.new(store)
else
store
end
end
def count(unprefixed_key, period)
key, expires_in = key_and_expiry(unprefixed_key, period)
do_count(key, expires_in)
end
def read(unprefixed_key)
enforce_store_presence!
enforce_store_method_presence!(:read)
store.read("#{prefix}:#{unprefixed_key}")
end
def write(unprefixed_key, value, expires_in)
store.write("#{prefix}:#{unprefixed_key}", value, expires_in: expires_in)
end
def reset_count(unprefixed_key, period)
key, _ = key_and_expiry(unprefixed_key, period)
store.delete(key)
end
def delete(unprefixed_key)
store.delete("#{prefix}:#{unprefixed_key}")
end
def reset!
if store.respond_to?(:delete_matched)
store.delete_matched("#{prefix}*")
else
raise(
Rack::Attack::IncompatibleStoreError,
"Configured store #{store.class.name} doesn't respond to #delete_matched method"
)
end
end
private
def key_and_expiry(unprefixed_key, period)
@last_epoch_time = Time.now.to_i
# Add 1 to expires_in to avoid timing error: https://github.com/rack/rack-attack/pull/85
expires_in = (period - (@last_epoch_time % period) + 1).to_i
["#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
end
def do_count(key, expires_in)
enforce_store_presence!
enforce_store_method_presence!(:increment)
result = store.increment(key, 1, expires_in: expires_in)
# NB: Some stores return nil when incrementing uninitialized values
if result.nil?
enforce_store_method_presence!(:write)
store.write(key, 1, expires_in: expires_in)
end
result || 1
end
def enforce_store_presence!
if store.nil?
raise Rack::Attack::MissingStoreError
end
end
def enforce_store_method_presence!(method_name)
if !store.respond_to?(method_name)
raise(
Rack::Attack::MisconfiguredStoreError,
"Configured store #{store.class.name} doesn't respond to ##{method_name} method"
)
end
end
end
end
end
|