222 lines
6.3 KiB
Ruby
222 lines
6.3 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
#
|
||
|
# Requires let variables:
|
||
|
# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
|
||
|
# * get_args
|
||
|
# * other_user_get_args
|
||
|
# * requests_per_period
|
||
|
# * period_in_seconds
|
||
|
# * period
|
||
|
shared_examples_for 'rate-limited token-authenticated requests' do
|
||
|
before do
|
||
|
# Set low limits
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
|
||
|
end
|
||
|
|
||
|
context 'when the throttle is enabled' do
|
||
|
before do
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
|
||
|
stub_application_setting(settings_to_set)
|
||
|
end
|
||
|
|
||
|
it 'rejects requests over the rate limit' do
|
||
|
# At first, allow requests under the rate limit.
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
# the last straw
|
||
|
expect_rejection { get(*get_args) }
|
||
|
end
|
||
|
|
||
|
it 'allows requests after throttling and then waiting for the next period' do
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_rejection { get(*get_args) }
|
||
|
|
||
|
Timecop.travel(period.from_now) do
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_rejection { get(*get_args) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'counts requests from different users separately, even from the same IP' do
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
# would be over the limit if this wasn't a different user
|
||
|
get(*other_user_get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
it 'counts all requests from the same user, even via different IPs' do
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
|
||
|
|
||
|
expect_rejection { get(*get_args) }
|
||
|
end
|
||
|
|
||
|
it 'logs RackAttack info into structured logs' do
|
||
|
requests_per_period.times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
arguments = {
|
||
|
message: 'Rack_Attack',
|
||
|
env: :throttle,
|
||
|
remote_ip: '127.0.0.1',
|
||
|
request_method: 'GET',
|
||
|
path: get_args.first,
|
||
|
user_id: user.id,
|
||
|
username: user.username
|
||
|
}
|
||
|
|
||
|
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
||
|
|
||
|
expect_rejection { get(*get_args) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when the throttle is disabled' do
|
||
|
before do
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
|
||
|
stub_application_setting(settings_to_set)
|
||
|
end
|
||
|
|
||
|
it 'allows requests over the rate limit' do
|
||
|
(1 + requests_per_period).times do
|
||
|
get(*get_args)
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Requires let variables:
|
||
|
# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
|
||
|
# * user
|
||
|
# * url_that_requires_authentication
|
||
|
# * requests_per_period
|
||
|
# * period_in_seconds
|
||
|
# * period
|
||
|
shared_examples_for 'rate-limited web authenticated requests' do
|
||
|
before do
|
||
|
login_as(user)
|
||
|
|
||
|
# Set low limits
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
|
||
|
end
|
||
|
|
||
|
context 'when the throttle is enabled' do
|
||
|
before do
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
|
||
|
stub_application_setting(settings_to_set)
|
||
|
end
|
||
|
|
||
|
it 'rejects requests over the rate limit' do
|
||
|
# At first, allow requests under the rate limit.
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
# the last straw
|
||
|
expect_rejection { get url_that_requires_authentication }
|
||
|
end
|
||
|
|
||
|
it 'allows requests after throttling and then waiting for the next period' do
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_rejection { get url_that_requires_authentication }
|
||
|
|
||
|
Timecop.travel(period.from_now) do
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_rejection { get url_that_requires_authentication }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'counts requests from different users separately, even from the same IP' do
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
# would be over the limit if this wasn't a different user
|
||
|
login_as(create(:user))
|
||
|
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
it 'counts all requests from the same user, even via different IPs' do
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
|
||
|
|
||
|
expect_rejection { get url_that_requires_authentication }
|
||
|
end
|
||
|
|
||
|
it 'logs RackAttack info into structured logs' do
|
||
|
requests_per_period.times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
|
||
|
arguments = {
|
||
|
message: 'Rack_Attack',
|
||
|
env: :throttle,
|
||
|
remote_ip: '127.0.0.1',
|
||
|
request_method: 'GET',
|
||
|
path: '/dashboard/snippets',
|
||
|
user_id: user.id,
|
||
|
username: user.username
|
||
|
}
|
||
|
|
||
|
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
||
|
|
||
|
get url_that_requires_authentication
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when the throttle is disabled' do
|
||
|
before do
|
||
|
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
|
||
|
stub_application_setting(settings_to_set)
|
||
|
end
|
||
|
|
||
|
it 'allows requests over the rate limit' do
|
||
|
(1 + requests_per_period).times do
|
||
|
get url_that_requires_authentication
|
||
|
expect(response).to have_http_status 200
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|