gitlab-org--gitlab-foss/spec/requests/rack_attack_global_spec.rb

373 lines
13 KiB
Ruby
Raw Normal View History

2017-10-11 19:39:58 -04:00
require 'spec_helper'
2017-10-13 20:13:11 -04:00
describe 'Rack Attack global throttles' do
2017-10-11 19:39:58 -04:00
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
# Start with really high limits and override them with low limits to ensure
# the right settings are being exercised
let(:settings_to_set) do
{
throttle_unauthenticated_requests_per_period: 100,
throttle_unauthenticated_period_in_seconds: 1,
throttle_authenticated_api_requests_per_period: 100,
throttle_authenticated_api_period_in_seconds: 1,
throttle_authenticated_web_requests_per_period: 100,
throttle_authenticated_web_period_in_seconds: 1
}
end
let(:requests_per_period) { 1 }
let(:period_in_seconds) { 10000 }
let(:period) { period_in_seconds.seconds }
2017-10-31 14:36:33 -04:00
let(:url_that_does_not_require_authentication) { '/users/sign_in' }
let(:url_that_requires_authentication) { '/dashboard/snippets' }
let(:url_api_internal) { '/api/v4/internal/check' }
2017-10-31 14:36:33 -04:00
let(:api_partial_url) { '/todos' }
2017-10-30 20:05:26 -04:00
around do |example|
# Instead of test environment's :null_store so the throttles can increment
2017-10-11 19:39:58 -04:00
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
# Make time-dependent tests deterministic
2017-10-11 19:39:58 -04:00
Timecop.freeze { example.run }
2017-10-30 20:05:26 -04:00
Rack::Attack.cache.store = Rails.cache
2017-10-11 19:39:58 -04:00
end
2017-10-13 20:05:18 -04:00
# Requires let variables:
# * throttle_setting_prefix (e.g. "throttle_authenticated_api" or "throttle_authenticated_web")
# * get_args
# * other_user_get_args
shared_examples_for 'rate-limited token-authenticated requests' do
2017-10-11 19:39:58 -04:00
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
2017-10-11 19:39:58 -04:00
end
context 'when the throttle is enabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
stub_application_setting(settings_to_set)
2017-10-11 19:39:58 -04:00
end
it 'rejects requests over the rate limit' do
# At first, allow requests under the rate limit.
requests_per_period.times do
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
# the last straw
2017-10-13 20:13:11 -04:00
expect_rejection { get(*get_args) }
2017-10-11 19:39:58 -04:00
end
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-13 20:13:11 -04:00
expect_rejection { get(*get_args) }
2017-10-11 19:39:58 -04:00
2017-11-07 05:58:10 -05:00
Timecop.travel(period.from_now) do
2017-10-11 19:39:58 -04:00
requests_per_period.times do
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-13 20:13:11 -04:00
expect_rejection { get(*get_args) }
2017-10-11 19:39:58 -04:00
end
end
2017-10-13 20:05:18 -04:00
it 'counts requests from different users separately, even from the same IP' do
2017-10-11 19:39:58 -04:00
requests_per_period.times do
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-13 20:05:18 -04:00
expect(response).to have_http_status 200
end
# would be over the limit if this wasn't a different user
2017-10-13 20:13:11 -04:00
get(*other_user_get_args)
2017-10-13 20:05:18 -04:00
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
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
2017-10-13 20:13:11 -04:00
expect_rejection { get(*get_args) }
2017-10-11 19:39:58 -04:00
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)
2017-10-11 19:39:58 -04:00
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
2017-10-13 20:13:11 -04:00
get(*get_args)
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
end
end
end
2017-10-13 20:05:18 -04:00
describe 'unauthenticated requests' do
2017-10-11 19:39:58 -04:00
before do
# Set low limits
settings_to_set[:throttle_unauthenticated_requests_per_period] = requests_per_period
settings_to_set[:throttle_unauthenticated_period_in_seconds] = period_in_seconds
2017-10-11 19:39:58 -04:00
end
context 'when the throttle is enabled' do
before do
settings_to_set[:throttle_unauthenticated_enabled] = true
stub_application_setting(settings_to_set)
2017-10-11 19:39:58 -04:00
end
it 'rejects requests over the rate limit' do
# At first, allow requests under the rate limit.
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
# the last straw
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_does_not_require_authentication }
2017-10-11 19:39:58 -04:00
end
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_does_not_require_authentication }
2017-10-11 19:39:58 -04:00
2017-11-07 05:58:10 -05:00
Timecop.travel(period.from_now) do
2017-10-11 19:39:58 -04:00
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_does_not_require_authentication }
2017-10-11 19:39:58 -04:00
end
end
2017-10-13 20:05:18 -04:00
it 'counts requests from different IPs separately' do
2017-10-11 19:39:58 -04:00
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
2017-10-13 20:05:18 -04:00
# would be over limit for the same IP
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-13 20:05:18 -04:00
expect(response).to have_http_status 200
2017-10-11 19:39:58 -04:00
end
context 'when the request is to the api internal endpoints' do
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
get url_api_internal, params: { secret_token: Gitlab::Shell.secret_token }
expect(response).to have_http_status 200
end
end
end
2017-10-11 19:39:58 -04:00
end
context 'when the throttle is disabled' do
before do
settings_to_set[:throttle_unauthenticated_enabled] = false
stub_application_setting(settings_to_set)
2017-10-11 19:39:58 -04:00
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
2017-10-31 14:36:33 -04:00
get url_that_does_not_require_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
end
end
end
2017-10-13 20:05:18 -04:00
describe 'API requests authenticated with personal access token', :api do
let(:user) { create(:user) }
let(:token) { create(:personal_access_token, user: user) }
let(:other_user) { create(:user) }
let(:other_user_token) { create(:personal_access_token, user: other_user) }
let(:throttle_setting_prefix) { 'throttle_authenticated_api' }
context 'with the token in the query string' do
2017-10-31 14:36:33 -04:00
let(:get_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
2017-10-13 20:05:18 -04:00
it_behaves_like 'rate-limited token-authenticated requests'
end
context 'with the token in the headers' do
2017-10-31 14:36:33 -04:00
let(:get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
2017-10-13 20:05:18 -04:00
it_behaves_like 'rate-limited token-authenticated requests'
end
end
describe 'API requests authenticated with OAuth token', :api do
let(:user) { create(:user) }
let(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") }
let(:other_user) { create(:user) }
let(:other_user_application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: other_user) }
let(:other_user_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: other_user.id, scopes: "api") }
let(:throttle_setting_prefix) { 'throttle_authenticated_api' }
context 'with the token in the query string' do
2017-10-31 14:36:33 -04:00
let(:get_args) { [api(api_partial_url, oauth_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, oauth_access_token: other_user_token)] }
2017-10-13 20:05:18 -04:00
it_behaves_like 'rate-limited token-authenticated requests'
end
context 'with the token in the headers' do
2017-10-31 14:36:33 -04:00
let(:get_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
2017-10-13 20:05:18 -04:00
it_behaves_like 'rate-limited token-authenticated requests'
end
end
describe '"web" (non-API) requests authenticated with RSS token' do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
context 'with the token in the query string' do
let(:get_args) { [rss_url(user), params: nil] }
let(:other_user_get_args) { [rss_url(other_user), params: nil] }
2017-10-13 20:05:18 -04:00
2017-11-10 13:17:55 -05:00
it_behaves_like 'rate-limited token-authenticated requests'
2017-10-13 20:05:18 -04:00
end
end
describe 'web requests authenticated with regular login' do
2017-10-11 19:39:58 -04:00
let(:user) { create(:user) }
before do
login_as(user)
# Set low limits
settings_to_set[:throttle_authenticated_web_requests_per_period] = requests_per_period
settings_to_set[:throttle_authenticated_web_period_in_seconds] = period_in_seconds
2017-10-11 19:39:58 -04:00
end
context 'when the throttle is enabled' do
before do
settings_to_set[:throttle_authenticated_web_enabled] = true
stub_application_setting(settings_to_set)
2017-10-11 19:39:58 -04:00
end
it 'rejects requests over the rate limit' do
# At first, allow requests under the rate limit.
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
# the last straw
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_requires_authentication }
2017-10-11 19:39:58 -04:00
end
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_requires_authentication }
2017-10-11 19:39:58 -04:00
2017-11-07 05:58:10 -05:00
Timecop.travel(period.from_now) do
2017-10-11 19:39:58 -04:00
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_requires_authentication }
2017-10-11 19:39:58 -04:00
end
end
it 'counts requests from different users separately, even from the same IP' do
requests_per_period.times do
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
# would be over the limit if this wasn't a different user
login_as(create(:user))
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
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
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
2017-10-31 14:36:33 -04:00
expect_rejection { get url_that_requires_authentication }
2017-10-11 19:39:58 -04:00
end
end
context 'when the throttle is disabled' do
before do
settings_to_set[:throttle_authenticated_web_enabled] = false
stub_application_setting(settings_to_set)
2017-10-11 19:39:58 -04:00
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
2017-10-31 14:36:33 -04:00
get url_that_requires_authentication
2017-10-11 19:39:58 -04:00
expect(response).to have_http_status 200
end
end
end
end
2017-10-13 20:05:18 -04:00
2017-10-31 14:36:33 -04:00
def api_get_args_with_token_headers(partial_url, token_headers)
["/api/#{API::API.version}#{partial_url}", params: nil, headers: token_headers]
2017-10-31 14:36:33 -04:00
end
def rss_url(user)
"/dashboard/projects.atom?feed_token=#{user.feed_token}"
2017-10-31 14:36:33 -04:00
end
2017-10-13 20:05:18 -04:00
def private_token_headers(user)
{ 'HTTP_PRIVATE_TOKEN' => user.private_token }
end
def personal_access_token_headers(personal_access_token)
{ 'HTTP_PRIVATE_TOKEN' => personal_access_token.token }
end
def oauth_token_headers(oauth_access_token)
{ 'AUTHORIZATION' => "Bearer #{oauth_access_token.token}" }
end
def expect_rejection(&block)
yield
2017-10-13 20:05:18 -04:00
expect(response).to have_http_status(429)
end
2017-10-11 19:39:58 -04:00
end