2019-12-04 20:38:33 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
#
|
|
|
|
# Requires let variables:
|
|
|
|
# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
|
2019-12-21 20:55:43 +05:30
|
|
|
# * request_method
|
|
|
|
# * request_args
|
|
|
|
# * other_user_request_args
|
2019-12-04 20:38:33 +05:30
|
|
|
# * requests_per_period
|
|
|
|
# * period_in_seconds
|
|
|
|
# * period
|
|
|
|
shared_examples_for 'rate-limited token-authenticated requests' do
|
2019-12-21 20:55:43 +05:30
|
|
|
let(:throttle_types) do
|
|
|
|
{
|
|
|
|
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
|
|
|
|
"throttle_authenticated_api" => "throttle_authenticated_api",
|
|
|
|
"throttle_authenticated_web" => "throttle_authenticated_web"
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
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
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# the last straw
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { make_request(request_args) }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'allows requests after throttling and then waiting for the next period' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { make_request(request_args) }
|
2019-12-04 20:38:33 +05:30
|
|
|
|
|
|
|
Timecop.travel(period.from_now) do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { make_request(request_args) }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts requests from different users separately, even from the same IP' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# would be over the limit if this wasn't a different user
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(other_user_request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts all requests from the same user, even via different IPs' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
|
2019-12-04 20:38:33 +05:30
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { make_request(request_args) }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'logs RackAttack info into structured logs' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
arguments = {
|
|
|
|
message: 'Rack_Attack',
|
|
|
|
env: :throttle,
|
|
|
|
remote_ip: '127.0.0.1',
|
2019-12-21 20:55:43 +05:30
|
|
|
request_method: request_method,
|
|
|
|
path: request_args.first,
|
2019-12-04 20:38:33 +05:30
|
|
|
user_id: user.id,
|
2019-12-21 20:55:43 +05:30
|
|
|
username: user.username,
|
|
|
|
throttle_type: throttle_types[throttle_setting_prefix]
|
2019-12-04 20:38:33 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { make_request(request_args) }
|
2019-12-04 20:38:33 +05:30
|
|
|
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
|
2019-12-21 20:55:43 +05:30
|
|
|
make_request(request_args)
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-12-21 20:55:43 +05:30
|
|
|
|
|
|
|
def make_request(args)
|
|
|
|
if request_method == 'POST'
|
|
|
|
post(*args)
|
|
|
|
else
|
|
|
|
get(*args)
|
|
|
|
end
|
|
|
|
end
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# Requires let variables:
|
|
|
|
# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
|
|
|
|
# * user
|
|
|
|
# * url_that_requires_authentication
|
2019-12-21 20:55:43 +05:30
|
|
|
# * request_method
|
2019-12-04 20:38:33 +05:30
|
|
|
# * requests_per_period
|
|
|
|
# * period_in_seconds
|
|
|
|
# * period
|
|
|
|
shared_examples_for 'rate-limited web authenticated requests' do
|
2019-12-21 20:55:43 +05:30
|
|
|
let(:throttle_types) do
|
|
|
|
{
|
|
|
|
"throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
|
|
|
|
"throttle_authenticated_web" => "throttle_authenticated_web"
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
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
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# the last straw
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { request_authenticated_web_url }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'allows requests after throttling and then waiting for the next period' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { request_authenticated_web_url }
|
2019-12-04 20:38:33 +05:30
|
|
|
|
|
|
|
Timecop.travel(period.from_now) do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { request_authenticated_web_url }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts requests from different users separately, even from the same IP' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# would be over the limit if this wasn't a different user
|
|
|
|
login_as(create(:user))
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts all requests from the same user, even via different IPs' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
|
2019-12-04 20:38:33 +05:30
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
expect_rejection { request_authenticated_web_url }
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'logs RackAttack info into structured logs' do
|
|
|
|
requests_per_period.times do
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
arguments = {
|
|
|
|
message: 'Rack_Attack',
|
|
|
|
env: :throttle,
|
|
|
|
remote_ip: '127.0.0.1',
|
2019-12-21 20:55:43 +05:30
|
|
|
request_method: request_method,
|
|
|
|
path: url_that_requires_authentication,
|
2019-12-04 20:38:33 +05:30
|
|
|
user_id: user.id,
|
2019-12-21 20:55:43 +05:30
|
|
|
username: user.username,
|
|
|
|
throttle_type: throttle_types[throttle_setting_prefix]
|
2019-12-04 20:38:33 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
|
|
|
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
2019-12-04 20:38:33 +05:30
|
|
|
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
|
2019-12-21 20:55:43 +05:30
|
|
|
request_authenticated_web_url
|
|
|
|
expect(response).not_to have_http_status 429
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-12-21 20:55:43 +05:30
|
|
|
|
|
|
|
def request_authenticated_web_url
|
|
|
|
if request_method == 'POST'
|
|
|
|
post url_that_requires_authentication
|
|
|
|
else
|
|
|
|
get url_that_requires_authentication
|
|
|
|
end
|
|
|
|
end
|
2019-12-04 20:38:33 +05:30
|
|
|
end
|