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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
# frozen_string_literal: true
require_relative 'spec_helper'
describe 'Rack::Attack.throttle' do
before do
@period = 60 # Use a long period; failures due to cache key rotation less likely
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip }
end
it('should have a throttle') { Rack::Attack.throttles.key?('ip/sec') }
it_allows_ok_requests
describe 'a single request' do
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
it 'should set the counter for one request' do
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
_(Rack::Attack.cache.store.read(key)).must_equal 1
end
it 'should populate throttle data' do
data = {
count: 1,
limit: 1,
period: @period,
epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
discriminator: "1.2.3.4"
}
_(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
end
end
describe "with 2 requests" do
before do
2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
end
it 'should block the last request' do
_(last_response.status).must_equal 429
end
it 'should tag the env' do
_(last_request.env['rack.attack.matched']).must_equal 'ip/sec'
_(last_request.env['rack.attack.match_type']).must_equal :throttle
_(last_request.env['rack.attack.match_data']).must_equal(
count: 2,
limit: 1,
period: @period,
epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
discriminator: "1.2.3.4"
)
_(last_request.env['rack.attack.match_discriminator']).must_equal('1.2.3.4')
end
end
end
describe 'Rack::Attack.throttle with limit as proc' do
before do
@period = 60 # Use a long period; failures due to cache key rotation less likely
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: @period) { |req| req.ip }
end
it_allows_ok_requests
describe 'a single request' do
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
it 'should set the counter for one request' do
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
_(Rack::Attack.cache.store.read(key)).must_equal 1
end
it 'should populate throttle data' do
data = {
count: 1,
limit: 1,
period: @period,
epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
discriminator: "1.2.3.4"
}
_(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
end
end
end
describe 'Rack::Attack.throttle with period as proc' do
before do
@period = 60 # Use a long period; failures due to cache key rotation less likely
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: lambda { |_req| @period }) { |req| req.ip }
end
it_allows_ok_requests
describe 'a single request' do
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
it 'should set the counter for one request' do
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
_(Rack::Attack.cache.store.read(key)).must_equal 1
end
it 'should populate throttle data' do
data = {
count: 1,
limit: 1,
period: @period,
epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
discriminator: "1.2.3.4"
}
_(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
end
end
end
describe 'Rack::Attack.throttle with block retuning nil' do
before do
@period = 60
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |_| nil }
end
it_allows_ok_requests
describe 'a single request' do
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
it 'should not set the counter' do
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
assert_nil Rack::Attack.cache.store.read(key)
end
it 'should not populate throttle data' do
assert_nil last_request.env['rack.attack.throttle_data']
end
end
end
describe 'Rack::Attack.throttle with throttle_discriminator_normalizer' do
before do
@period = 60
@emails = [
"person@example.com",
"PERSON@example.com ",
" person@example.com\r\n ",
]
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle('logins/email', limit: 4, period: @period) do |req|
if req.path == '/login' && req.post?
req.params['email']
end
end
end
it 'should not differentiate requests when throttle_discriminator_normalizer is enabled' do
post_logins
key = "rack::attack:#{Time.now.to_i / @period}:logins/email:person@example.com"
_(Rack::Attack.cache.store.read(key)).must_equal 3
end
it 'should differentiate requests when throttle_discriminator_normalizer is disabled' do
begin
prev = Rack::Attack.throttle_discriminator_normalizer
Rack::Attack.throttle_discriminator_normalizer = nil
post_logins
@emails.each do |email|
key = "rack::attack:#{Time.now.to_i / @period}:logins/email:#{email}"
_(Rack::Attack.cache.store.read(key)).must_equal 1
end
ensure
Rack::Attack.throttle_discriminator_normalizer = prev
end
end
def post_logins
@emails.each do |email|
post '/login', email: email
end
end
end
|