diff options
Diffstat (limited to 'spec/support/shared_examples/requests/rack_attack_shared_examples.rb')
-rw-r--r-- | spec/support/shared_examples/requests/rack_attack_shared_examples.rb | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index b294467d482..c6c6c44dce8 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -580,3 +580,88 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do end end end + +# Requires let variables: +# * throttle_setting_prefix: "throttle_authenticated", "throttle_unauthenticated" +RSpec.shared_examples 'rate-limited frontend API requests' do + let(:requests_per_period) { 1 } + let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) } + let(:csrf_session) { { _csrf_token: csrf_token } } + let(:personal_access_token) { nil } + + let(:api_path) { '/projects' } + + # These don't actually exist, so a 404 is the expected response. + let(:files_api_path) { '/projects/1/repository/files/ref/path' } + let(:packages_api_path) { '/projects/1/packages/foo' } + let(:deprecated_api_path) { '/groups/1?with_projects=true' } + + def get_api(path: api_path, csrf: false) + headers = csrf ? { 'X-CSRF-Token' => csrf_token } : nil + get api(path, personal_access_token: personal_access_token), headers: headers + end + + def expect_not_found(&block) + yield + + expect(response).to have_gitlab_http_status(:not_found) + end + + before do + stub_application_setting( + "#{throttle_setting_prefix}_enabled" => true, + "#{throttle_setting_prefix}_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_api_enabled" => true, + "#{throttle_setting_prefix}_api_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_web_enabled" => true, + "#{throttle_setting_prefix}_web_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_files_api_enabled" => true, + "#{throttle_setting_prefix}_packages_api_enabled" => true, + "#{throttle_setting_prefix}_deprecated_api_enabled" => true + ) + + stub_session(csrf_session) + end + + context 'with a CSRF token' do + it 'uses the rate limit for web requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: files_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: packages_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: deprecated_api_path } + + # API rate limit is not triggered yet + expect_ok { get_api } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + + context 'without a CSRF session' do + let(:csrf_session) { nil } + + it 'always uses the rate limit for API requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + end + end + end + + context 'without a CSRF token' do + it 'uses the rate limit for API requests' do + requests_per_period.times { get_api } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + + # Web and custom API rate limits are not triggered yet + expect_ok { get_api csrf: true } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + end +end |