From 4db0ab558f6952019f8ee322a71852c49c8215f2 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Tue, 15 Jun 2021 11:21:12 +0500 Subject: [PATCH 01/12] Plug in shunter gem --- app/controllers/epp/domains_controller.rb | 8 ++++++++ config/application.yml.sample | 1 + 2 files changed, 9 insertions(+) diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index d2e146c0a..49ea43ae0 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -6,6 +6,9 @@ module Epp before_action :set_paper_trail_whodunnit before_action :parse_schemas_prefix_and_version + THROTTLED_ACTIONS = %i[info renew update transfer delete].freeze + include Shunter::Integration::Throttle + def info authorize! :info, @domain @@ -131,6 +134,11 @@ module Epp private + def throttled_user + authorize!(:throttled_user, @domain) unless current_user + current_user + end + def validate_info @prefix = 'info > info >' requires('name') diff --git a/config/application.yml.sample b/config/application.yml.sample index 559ce2e9b..5b01624cf 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -197,6 +197,7 @@ test: cdns_scanner_output_file: 'test/fixtures/files/cdns_output.txt' dnssec_resolver_ips: 8.8.8.8, 8.8.4.4 legal_documents_dir: 'test/fixtures/files' + shunter_default_adapter: "Shunter::Adapters::Memory" openssl_config_path: 'test/fixtures/files/test_ca/openssl.cnf' crl_dir: 'test/fixtures/files/test_ca/crl' From 83413213d9111d2e36eb41fac2c125c3f4185f7c Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Fri, 18 Jun 2021 17:46:17 +0500 Subject: [PATCH 02/12] Add shunter gem support & tests --- app/controllers/epp/base_controller.rb | 15 +++++ app/controllers/epp/contacts_controller.rb | 3 + app/controllers/epp/domains_controller.rb | 5 -- config/application.yml.sample | 1 + test/integration/epp/domain/info/base_test.rb | 60 +++++++++++++++++++ 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/app/controllers/epp/base_controller.rb b/app/controllers/epp/base_controller.rb index 12efbd1d3..520b97c4f 100644 --- a/app/controllers/epp/base_controller.rb +++ b/app/controllers/epp/base_controller.rb @@ -21,12 +21,22 @@ module Epp rescue_from StandardError, with: :respond_with_command_failed_error rescue_from AuthorizationError, with: :respond_with_authorization_error rescue_from ActiveRecord::RecordNotFound, with: :respond_with_object_does_not_exist_error + rescue_from Shunter::ThrottleError, with: :respond_with_session_limit_exceeded_error + before_action :set_paper_trail_whodunnit skip_before_action :validate_against_schema protected + def respond_with_session_limit_exceeded_error(exception) + epp_errors.add(:epp_errors, + code: '2502', + message: 'Session limit exceeded, try again later') + handle_errors + log_exception(exception) + end + def respond_with_command_failed_error(exception) epp_errors.add(:epp_errors, code: '2400', @@ -51,6 +61,11 @@ module Epp private + def throttled_user + authorize!(:throttled_user, @domain) unless current_user + current_user + end + def wrap_exceptions yield rescue CanCan::AccessDenied diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index 10250563c..5509507d3 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -5,6 +5,9 @@ module Epp before_action :find_contact, only: [:info, :update, :delete] before_action :find_password, only: [:info, :update, :delete] + THROTTLED_ACTIONS = %i[info renew update transfer delete].freeze + include Shunter::Integration::Throttle + def info authorize! :info, @contact, @password render_epp_response 'epp/contacts/info' diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index 49ea43ae0..a05b56531 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -134,11 +134,6 @@ module Epp private - def throttled_user - authorize!(:throttled_user, @domain) unless current_user - current_user - end - def validate_info @prefix = 'info > info >' requires('name') diff --git a/config/application.yml.sample b/config/application.yml.sample index 5b01624cf..b8b9b12d0 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -198,6 +198,7 @@ test: dnssec_resolver_ips: 8.8.8.8, 8.8.4.4 legal_documents_dir: 'test/fixtures/files' shunter_default_adapter: "Shunter::Adapters::Memory" + shunter_enabled: "false" openssl_config_path: 'test/fixtures/files/test_ca/openssl.cnf' crl_dir: 'test/fixtures/files/test_ca/crl' diff --git a/test/integration/epp/domain/info/base_test.rb b/test/integration/epp/domain/info/base_test.rb index da3be1d38..99de33f29 100644 --- a/test/integration/epp/domain/info/base_test.rb +++ b/test/integration/epp/domain/info/base_test.rb @@ -1,6 +1,11 @@ require 'test_helper' class EppDomainInfoBaseTest < EppTestCase + setup do + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! + end + def test_returns_valid_response assert_equal 'john-001', contacts(:john).code domains(:shop).update_columns(statuses: [DomainStatus::OK], @@ -180,6 +185,61 @@ class EppDomainInfoBaseTest < EppTestCase assert_correct_against_schema response_xml end + def test_returns_valid_response_if_not_throttled + domain = domains(:shop) + + request_xml = <<-XML + + + + + + #{domain.name} + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + domain = domains(:shop) + + request_xml = <<-XML + + + + + + #{domain.name} + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_returns_valid_response_if_release_prohibited domain = domains(:shop) domain.update_columns(statuses: [DomainStatus::SERVER_RELEASE_PROHIBITED], From f17ef17d16514501b4d046644ca1b38475943e04 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Sat, 27 Nov 2021 14:48:10 +0500 Subject: [PATCH 03/12] Move throttling feature from gem back to the app --- app/lib/shunter.rb | 27 ++++++++ app/lib/shunter/adapters/memory.rb | 31 +++++++++ app/lib/shunter/adapters/redis.rb | 29 +++++++++ app/lib/shunter/base.rb | 63 +++++++++++++++++++ app/lib/shunter/integration/throttle.rb | 44 +++++++++++++ config/environments/test.rb | 2 + test/integration/epp/domain/info/base_test.rb | 2 +- test/lib/shunter/base_test.rb | 33 ++++++++++ test/test_helper.rb | 1 - 9 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 app/lib/shunter.rb create mode 100644 app/lib/shunter/adapters/memory.rb create mode 100644 app/lib/shunter/adapters/redis.rb create mode 100644 app/lib/shunter/base.rb create mode 100644 app/lib/shunter/integration/throttle.rb create mode 100644 test/lib/shunter/base_test.rb diff --git a/app/lib/shunter.rb b/app/lib/shunter.rb new file mode 100644 index 000000000..045f7fdd0 --- /dev/null +++ b/app/lib/shunter.rb @@ -0,0 +1,27 @@ +module Shunter + module_function + + class ThrottleError < StandardError; end + + BASE_LOGGER = ::Logger.new($stdout) + ONE_MINUTE = 60 + ONE_HUNDRED_REQUESTS = 100 + + BASE_CONNECTION = ENV['shunter_redis_connection'] || { host: 'redis', port: 6379 } + + def default_timespan + ENV['shunter_default_timespan'] || ONE_MINUTE + end + + def default_threshold + ENV['shunter_default_threshold'] || ONE_HUNDRED_REQUESTS + end + + def default_adapter + ENV['shunter_default_adapter'] || 'Shunter::Adapters::Redis' + end + + def feature_enabled? + ActiveModel::Type::Boolean.new.cast(ENV['shunter_enabled'] || 'false') + end +end diff --git a/app/lib/shunter/adapters/memory.rb b/app/lib/shunter/adapters/memory.rb new file mode 100644 index 000000000..eb0b25b27 --- /dev/null +++ b/app/lib/shunter/adapters/memory.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Shunter + module Adapters + class Memory + attr_reader :store + + def initialize(_options = {}) + @@store ||= {} + end + + def find_counter(key) + @@store[key] + end + + def write_counter(key) + @@store[key] = 1 + end + + def increment_counter(key) + @@store[key] += 1 + end + + def clear! + @@store = {} + end + + def expire_counter(_key, _timespan); end + end + end +end diff --git a/app/lib/shunter/adapters/redis.rb b/app/lib/shunter/adapters/redis.rb new file mode 100644 index 000000000..adf27b359 --- /dev/null +++ b/app/lib/shunter/adapters/redis.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Shunter + module Adapters + class Redis + attr_reader :redis + + def initialize(options) + @redis = ::Redis.new(options) + end + + def find_counter(key) + @redis.get(key) + end + + def write_counter(key) + @redis.set(key, 1) + end + + def increment_counter(key) + @redis.incr(key) + end + + def expire_counter(key, timespan) + @redis.expire(key, timespan) + end + end + end +end diff --git a/app/lib/shunter/base.rb b/app/lib/shunter/base.rb new file mode 100644 index 000000000..f3f6867f0 --- /dev/null +++ b/app/lib/shunter/base.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Shunter + class Base + attr_accessor :user_id, :adapter + + def initialize(options = {}) + @user_id = options[:user_id] + adapter_klass = Shunter.default_adapter.constantize + @adapter = adapter_klass.new(options[:conn_options]) + end + + def user_key + "counting_#{@user_id}" + end + + def blocked_user_key + "blocked_#{@user_id}" + end + + def throttle + return false if blocked? + + valid_counter? + end + + def blocked? + adapter.find_counter(blocked_user_key).present? + end + + def valid_counter? + if adapter.find_counter(user_key) + number_of_requests = adapter.increment_counter(user_key) + if number_of_requests > allowed_requests.to_i + init_counter(blocked_user_key) + return false + end + else + init_counter(user_key) + end + true + end + + private + + def init_counter(key) + adapter.write_counter(key) + adapter.expire_counter(key, timespan) + end + + def allowed_requests + Shunter.default_threshold + end + + def timespan + Shunter.default_timespan + end + + def logger + Shunter::BASE_LOGGER + end + end +end diff --git a/app/lib/shunter/integration/throttle.rb b/app/lib/shunter/integration/throttle.rb new file mode 100644 index 000000000..0b4517112 --- /dev/null +++ b/app/lib/shunter/integration/throttle.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'active_support/concern' + +module Shunter + module Integration + module Throttle + extend ActiveSupport::Concern + + included do |base| + actions = base.const_defined?('THROTTLED_ACTIONS') && base.const_get('THROTTLED_ACTIONS') + return if actions.blank? + + around_action :throttle, only: actions + + def throttle + unless throttled_user.present? && Shunter.feature_enabled? + yield if block_given? + return + end + + user_id = throttled_user.id + + shunter = Shunter::Base.new(conn_options: connection_options, user_id: user_id) + if shunter.throttle + logger.info "Request from #{throttled_user.class}/#{throttled_user.id} is coming through throttling" + yield if block_given? + else + logger.info "Too many requests from #{throttled_user.class}/#{throttled_user.id}." + raise Shunter::ThrottleError + end + end + end + + def connection_options + Shunter::BASE_CONNECTION + end + + def logger + Shunter::BASE_LOGGER + end + end + end +end diff --git a/config/environments/test.rb b/config/environments/test.rb index 028c61b47..6e680b9c0 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -41,4 +41,6 @@ Rails.application.configure do # If set to :null_store, Setting.x returns nil after first spec runs (database is emptied) config.cache_store = :memory_store + + config.log_level = :fatal end diff --git a/test/integration/epp/domain/info/base_test.rb b/test/integration/epp/domain/info/base_test.rb index 99de33f29..25c65d476 100644 --- a/test/integration/epp/domain/info/base_test.rb +++ b/test/integration/epp/domain/info/base_test.rb @@ -193,7 +193,7 @@ class EppDomainInfoBaseTest < EppTestCase - + #{domain.name} diff --git a/test/lib/shunter/base_test.rb b/test/lib/shunter/base_test.rb new file mode 100644 index 000000000..c251724ed --- /dev/null +++ b/test/lib/shunter/base_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "test_helper" +require "action_controller" +require "pry" + +class BaseTest < Minitest::Test + ENV["shunter_enabled"] = 'true' + + def test_throttling_works_on_inclusion + ENV["shunter_default_adapter"] = "Shunter::Adapters::Memory" + ENV["shunter_default_threshold"] = "100" + adapter = ENV["shunter_default_adapter"].constantize.new + adapter.clear! + + TestKlass.new.throttle do + TestKlass.new.test + end + end + + class TestKlass < ::ActionController::Base + THROTTLED_ACTIONS = %i[test].freeze + include Shunter::Integration::Throttle + + def test + "test" + end + + def throttled_user + @throttled_user ||= OpenStruct.new(id: 1) + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0cd407f84..cce9afa97 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -18,7 +18,6 @@ require 'capybara/minitest' require 'webmock/minitest' require 'support/assertions/epp_assertions' require 'sidekiq/testing' -require 'spy/integration' Sidekiq::Testing.fake! From 16d4e4b4e680343a36e8ea38ec009b9a982b6b84 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Sat, 27 Nov 2021 16:59:00 +0500 Subject: [PATCH 04/12] Fix throttling condition --- app/lib/shunter/integration/throttle.rb | 2 +- test/test_helper.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/shunter/integration/throttle.rb b/app/lib/shunter/integration/throttle.rb index 0b4517112..91fb033ac 100644 --- a/app/lib/shunter/integration/throttle.rb +++ b/app/lib/shunter/integration/throttle.rb @@ -14,7 +14,7 @@ module Shunter around_action :throttle, only: actions def throttle - unless throttled_user.present? && Shunter.feature_enabled? + if throttled_user.blank? || !Shunter.feature_enabled? yield if block_given? return end diff --git a/test/test_helper.rb b/test/test_helper.rb index cce9afa97..0cd407f84 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -18,6 +18,7 @@ require 'capybara/minitest' require 'webmock/minitest' require 'support/assertions/epp_assertions' require 'sidekiq/testing' +require 'spy/integration' Sidekiq::Testing.fake! From dbe1678430c127e5c3dc333cb14efb836da34f13 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Sun, 5 Dec 2021 14:06:13 +0500 Subject: [PATCH 05/12] Add current limits to the error message --- app/controllers/epp/base_controller.rb | 2 +- app/lib/shunter.rb | 4 ++++ app/models/epp/domain.rb | 3 +++ app/models/epp/response/result/code.rb | 2 +- test/integration/epp/domain/info/base_test.rb | 1 + test/models/epp/response/result/code_test.rb | 2 +- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/controllers/epp/base_controller.rb b/app/controllers/epp/base_controller.rb index 520b97c4f..3400a07e5 100644 --- a/app/controllers/epp/base_controller.rb +++ b/app/controllers/epp/base_controller.rb @@ -32,7 +32,7 @@ module Epp def respond_with_session_limit_exceeded_error(exception) epp_errors.add(:epp_errors, code: '2502', - message: 'Session limit exceeded, try again later') + msg: Shunter.default_error_message) handle_errors log_exception(exception) end diff --git a/app/lib/shunter.rb b/app/lib/shunter.rb index 045f7fdd0..3339a8fcd 100644 --- a/app/lib/shunter.rb +++ b/app/lib/shunter.rb @@ -9,6 +9,10 @@ module Shunter BASE_CONNECTION = ENV['shunter_redis_connection'] || { host: 'redis', port: 6379 } + def default_error_message + "Session limit exceeded. Current limit is #{default_threshold} in #{default_timespan} seconds" + end + def default_timespan ENV['shunter_default_timespan'] || ONE_MINUTE end diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index c6589b63d..bb2bc84a1 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -105,6 +105,9 @@ class Epp::Domain < Domain max: Setting.ns_max_count } ], + '2502' => [ # Rate limit exceeded + %i[base session_limit_exceeded], + ], ] } end diff --git a/app/models/epp/response/result/code.rb b/app/models/epp/response/result/code.rb index f2b1ccd3b..916683840 100644 --- a/app/models/epp/response/result/code.rb +++ b/app/models/epp/response/result/code.rb @@ -62,7 +62,7 @@ module Epp 2308 => 'Data management policy violation', 2400 => 'Command failed', 2501 => 'Authentication error; server closing connection', - 2502 => 'Session limit exceeded; server closing connection', + 2502 => Shunter.default_error_message, }.freeze private_constant :DEFAULT_DESCRIPTIONS diff --git a/test/integration/epp/domain/info/base_test.rb b/test/integration/epp/domain/info/base_test.rb index 25c65d476..56f9cd775 100644 --- a/test/integration/epp/domain/info/base_test.rb +++ b/test/integration/epp/domain/info/base_test.rb @@ -236,6 +236,7 @@ class EppDomainInfoBaseTest < EppTestCase response_xml = Nokogiri::XML(response.body) assert_epp_response :session_limit_exceeded_server_closing_connection assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) ENV["shunter_default_threshold"] = '10000' ENV["shunter_enabled"] = 'false' end diff --git a/test/models/epp/response/result/code_test.rb b/test/models/epp/response/result/code_test.rb index a78c92d3a..ca41db458 100644 --- a/test/models/epp/response/result/code_test.rb +++ b/test/models/epp/response/result/code_test.rb @@ -85,7 +85,7 @@ class EppResponseResultCodeTest < ActiveSupport::TestCase 2308 => 'Data management policy violation', 2400 => 'Command failed', 2501 => 'Authentication error; server closing connection', - 2502 => 'Session limit exceeded; server closing connection' + 2502 => Shunter.default_error_message } assert_equal descriptions, Epp::Response::Result::Code.default_descriptions end From 1a8d8b52e74b9fd3b8bae15801e3dac89b92a59a Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Sun, 12 Dec 2021 13:41:55 +0500 Subject: [PATCH 06/12] Changed redis setup --- app/controllers/epp/base_controller.rb | 2 +- app/lib/shunter.rb | 7 ++++++- config/application.yml.sample | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/epp/base_controller.rb b/app/controllers/epp/base_controller.rb index 3400a07e5..024a39406 100644 --- a/app/controllers/epp/base_controller.rb +++ b/app/controllers/epp/base_controller.rb @@ -20,8 +20,8 @@ module Epp rescue_from StandardError, with: :respond_with_command_failed_error rescue_from AuthorizationError, with: :respond_with_authorization_error - rescue_from ActiveRecord::RecordNotFound, with: :respond_with_object_does_not_exist_error rescue_from Shunter::ThrottleError, with: :respond_with_session_limit_exceeded_error + rescue_from ActiveRecord::RecordNotFound, with: :respond_with_object_does_not_exist_error before_action :set_paper_trail_whodunnit diff --git a/app/lib/shunter.rb b/app/lib/shunter.rb index 3339a8fcd..b06de6b9f 100644 --- a/app/lib/shunter.rb +++ b/app/lib/shunter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Shunter module_function @@ -7,7 +9,10 @@ module Shunter ONE_MINUTE = 60 ONE_HUNDRED_REQUESTS = 100 - BASE_CONNECTION = ENV['shunter_redis_connection'] || { host: 'redis', port: 6379 } + BASE_CONNECTION = { + host: ENV['shunter_redis_host'] || 'redis', + port: (ENV['shunter_redis_port'] || '6379').to_i, + }.freeze def default_error_message "Session limit exceeded. Current limit is #{default_threshold} in #{default_timespan} seconds" diff --git a/config/application.yml.sample b/config/application.yml.sample index b8b9b12d0..dffeea5be 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -199,6 +199,8 @@ test: legal_documents_dir: 'test/fixtures/files' shunter_default_adapter: "Shunter::Adapters::Memory" shunter_enabled: "false" + shunter_redis_host: "redis" + shunter_redis_port: "6379" openssl_config_path: 'test/fixtures/files/test_ca/openssl.cnf' crl_dir: 'test/fixtures/files/test_ca/crl' From fb60466194178361a14850b3f82c1c7f09e7d28e Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Tue, 2 Aug 2022 06:14:21 -0300 Subject: [PATCH 07/12] Add rate limiting to all EPP actions --- app/controllers/epp/contacts_controller.rb | 2 +- app/controllers/epp/domains_controller.rb | 2 +- app/controllers/epp/polls_controller.rb | 3 + app/controllers/epp/sessions_controller.rb | 3 + .../epp/contact/check/base_test.rb | 56 ++++++++++++ .../epp/contact/create/base_test.rb | 83 ++++++++++++++++++ .../integration/epp/contact/info/base_test.rb | 59 +++++++++++++ .../epp/contact/update/base_test.rb | 73 ++++++++++++++++ .../integration/epp/domain/check/base_test.rb | 57 +++++++++++++ .../epp/domain/create/base_test.rb | 85 +++++++++++++++++++ test/integration/epp/domain/info/base_test.rb | 1 + .../epp/domain/update/base_test.rb | 85 +++++++++++++++++++ test/integration/epp/login_test.rb | 82 ++++++++++++++++++ test/integration/epp/poll_test.rb | 40 +++++++++ 14 files changed, 629 insertions(+), 2 deletions(-) diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index 5509507d3..db96a186d 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -5,7 +5,7 @@ module Epp before_action :find_contact, only: [:info, :update, :delete] before_action :find_password, only: [:info, :update, :delete] - THROTTLED_ACTIONS = %i[info renew update transfer delete].freeze + THROTTLED_ACTIONS = %i[info check create renew update transfer delete].freeze include Shunter::Integration::Throttle def info diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index a05b56531..abf360c55 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -6,7 +6,7 @@ module Epp before_action :set_paper_trail_whodunnit before_action :parse_schemas_prefix_and_version - THROTTLED_ACTIONS = %i[info renew update transfer delete].freeze + THROTTLED_ACTIONS = %i[info create check renew update transfer delete].freeze include Shunter::Integration::Throttle def info diff --git a/app/controllers/epp/polls_controller.rb b/app/controllers/epp/polls_controller.rb index a674bcd45..7fe6d2636 100644 --- a/app/controllers/epp/polls_controller.rb +++ b/app/controllers/epp/polls_controller.rb @@ -1,5 +1,8 @@ module Epp class PollsController < BaseController + THROTTLED_ACTIONS = %i[poll].freeze + include Shunter::Integration::Throttle + def poll authorize! :manage, :poll req_poll if params[:parsed_frame].css('poll').first['op'] == 'req' diff --git a/app/controllers/epp/sessions_controller.rb b/app/controllers/epp/sessions_controller.rb index 8d8b56e62..715b6d8f7 100644 --- a/app/controllers/epp/sessions_controller.rb +++ b/app/controllers/epp/sessions_controller.rb @@ -3,6 +3,9 @@ module Epp skip_authorization_check only: [:hello, :login, :logout] before_action :set_paper_trail_whodunnit + THROTTLED_ACTIONS = %i[login].freeze + include Shunter::Integration::Throttle + def hello render_epp_response('greeting') end diff --git a/test/integration/epp/contact/check/base_test.rb b/test/integration/epp/contact/check/base_test.rb index 6ad027fc6..367436fc5 100644 --- a/test/integration/epp/contact/check/base_test.rb +++ b/test/integration/epp/contact/check/base_test.rb @@ -3,6 +3,9 @@ require 'test_helper' class EppContactCheckBaseTest < EppTestCase setup do @contact = contacts(:john) + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_valid_response @@ -157,6 +160,59 @@ class EppContactCheckBaseTest < EppTestCase # assert_equal 'in use', response_xml.at_xpath('//contact:reason', contact: xml_schema).text end + def test_returns_valid_response_if_not_throttled + request_xml = <<-XML + + + + + + john-001 + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + request_xml = <<-XML + + + + + + john-001 + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def xml_schema diff --git a/test/integration/epp/contact/create/base_test.rb b/test/integration/epp/contact/create/base_test.rb index ba94fcd6f..1f16d531f 100644 --- a/test/integration/epp/contact/create/base_test.rb +++ b/test/integration/epp/contact/create/base_test.rb @@ -1,6 +1,11 @@ require 'test_helper' class EppContactCreateBaseTest < EppTestCase + setup do + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! + end + def test_creates_new_contact_with_required_attributes name = 'new' email = 'new@registrar.test' @@ -362,4 +367,82 @@ class EppContactCreateBaseTest < EppTestCase assert_equal country_code, contact.country_code assert_equal state, contact.state end + + def test_returns_valid_response_if_not_throttled + name = 'new' + email = 'new@registrar.test' + phone = '+1.2' + + request_xml = <<-XML + + + + + + + #{name} + + #{phone} + #{email} + + + + + any + + + + + XML + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + name = 'new' + email = 'new@registrar.test' + phone = '+1.2' + + request_xml = <<-XML + + + + + + + #{name} + + #{phone} + #{email} + + + + + any + + + + + XML + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/epp/contact/info/base_test.rb b/test/integration/epp/contact/info/base_test.rb index dc8fcd5f7..09d0b0308 100644 --- a/test/integration/epp/contact/info/base_test.rb +++ b/test/integration/epp/contact/info/base_test.rb @@ -3,6 +3,9 @@ require 'test_helper' class EppContactInfoBaseTest < EppTestCase setup do @contact = contacts(:john) + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_valid_response @@ -129,6 +132,62 @@ class EppContactInfoBaseTest < EppTestCase assert_equal 'No access', response_xml.at_xpath('//contact:name', contact: xml_schema).text end + def test_returns_valid_response_if_not_throttled + @contact.update_columns(code: @contact.code.upcase) + + request_xml = <<-XML + + + + + + john-001 + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + @contact.update_columns(code: @contact.code.upcase) + + request_xml = <<-XML + + + + + + john-001 + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def xml_schema diff --git a/test/integration/epp/contact/update/base_test.rb b/test/integration/epp/contact/update/base_test.rb index 0c55c5223..6999e3f2d 100644 --- a/test/integration/epp/contact/update/base_test.rb +++ b/test/integration/epp/contact/update/base_test.rb @@ -6,6 +6,9 @@ class EppContactUpdateBaseTest < EppTestCase setup do @contact = contacts(:john) ActionMailer::Base.deliveries.clear + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_updates_contact @@ -470,6 +473,76 @@ class EppContactUpdateBaseTest < EppTestCase assert_equal '+123.4', @contact.phone end + def test_returns_valid_response_if_not_throttled + @contact.update_columns(code: @contact.code.upcase) + + request_xml = <<-XML + + + + + + john-001 + + + new name + + +123.4 + new-email@inbox.test + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + @contact.update_columns(code: @contact.code.upcase) + + request_xml = <<-XML + + + + + + john-001 + + + new name + + +123.4 + new-email@inbox.test + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def make_contact_free_of_domains_where_it_acts_as_a_registrant(contact) diff --git a/test/integration/epp/domain/check/base_test.rb b/test/integration/epp/domain/check/base_test.rb index 7a1c20c88..536d6525a 100644 --- a/test/integration/epp/domain/check/base_test.rb +++ b/test/integration/epp/domain/check/base_test.rb @@ -1,6 +1,11 @@ require 'test_helper' class EppDomainCheckBaseTest < EppTestCase + setup do + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! + end + def test_returns_valid_response request_xml = <<-XML @@ -193,4 +198,56 @@ class EppDomainCheckBaseTest < EppTestCase assert_correct_against_schema response_xml assert_equal 3, response_xml.xpath('//domain:cd', 'domain' => "#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}").size end + + def test_returns_valid_response_if_not_throttled + request_xml = <<-XML + + + + + + some.test + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + request_xml = <<-XML + + + + + + some.test + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/epp/domain/create/base_test.rb b/test/integration/epp/domain/create/base_test.rb index 4932c6989..180d165b3 100644 --- a/test/integration/epp/domain/create/base_test.rb +++ b/test/integration/epp/domain/create/base_test.rb @@ -1,6 +1,10 @@ require 'test_helper' class EppDomainCreateBaseTest < EppTestCase + setup do + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! + end def test_illegal_chars_in_dns_key name = "new.#{dns_zones(:one).origin}" @@ -852,4 +856,85 @@ class EppDomainCreateBaseTest < EppTestCase assert_correct_against_schema response_xml assert_epp_response :completed_successfully end + + def test_returns_valid_response_if_not_throttled + now = Time.zone.parse('2010-07-05') + travel_to now + disputed_domain = disputes(:active) + password = disputed_domain.password + + request_xml = <<-XML + + + + + + #{disputed_domain.domain_name} + #{contacts(:john).code} + + + + + #{'test' * 2000} + + #{password} + + + + + + XML + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + now = Time.zone.parse('2010-07-05') + travel_to now + disputed_domain = disputes(:active) + password = disputed_domain.password + + request_xml = <<-XML + + + + + + #{disputed_domain.domain_name} + #{contacts(:john).code} + + + + + #{'test' * 2000} + + #{password} + + + + + + XML + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/epp/domain/info/base_test.rb b/test/integration/epp/domain/info/base_test.rb index 56f9cd775..15939e212 100644 --- a/test/integration/epp/domain/info/base_test.rb +++ b/test/integration/epp/domain/info/base_test.rb @@ -234,6 +234,7 @@ class EppDomainInfoBaseTest < EppTestCase headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } response_xml = Nokogiri::XML(response.body) + # binding.pry assert_epp_response :session_limit_exceeded_server_closing_connection assert_correct_against_schema response_xml assert response.body.include?(Shunter.default_error_message) diff --git a/test/integration/epp/domain/update/base_test.rb b/test/integration/epp/domain/update/base_test.rb index 10c92ebc5..dbcbe3cb2 100644 --- a/test/integration/epp/domain/update/base_test.rb +++ b/test/integration/epp/domain/update/base_test.rb @@ -10,6 +10,9 @@ class EppDomainUpdateBaseTest < EppTestCase @original_registrant_change_verification = Setting.request_confirmation_on_registrant_change_enabled ActionMailer::Base.deliveries.clear + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end teardown do @@ -882,6 +885,88 @@ class EppDomainUpdateBaseTest < EppTestCase assert_epp_response :object_does_not_exist end + def test_returns_valid_response_if_not_throttled + ENV['obj_and_extensions_prohibited'] = 'true' + @domain = domains(:shop) + @domain.statuses << DomainStatus::SERVER_EXTENSION_UPDATE_PROHIBITED + @domain.save + + request_xml = <<-XML + + + + + + shop.test + + + + #{nameservers(:shop_ns1).hostname} + + + #{nameservers(:shop_ns2).hostname} + + + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + ENV['obj_and_extensions_prohibited'] = 'true' + @domain = domains(:shop) + @domain.statuses << DomainStatus::SERVER_EXTENSION_UPDATE_PROHIBITED + @domain.save + + request_xml = <<-XML + + + + + + shop.test + + + + #{nameservers(:shop_ns1).hostname} + + + #{nameservers(:shop_ns2).hostname} + + + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def assert_verification_and_notification_emails diff --git a/test/integration/epp/login_test.rb b/test/integration/epp/login_test.rb index 80d7251d6..0e07da9c6 100644 --- a/test/integration/epp/login_test.rb +++ b/test/integration/epp/login_test.rb @@ -3,6 +3,11 @@ require 'test_helper' class EppLoginTest < EppTestCase setup do @original_sessions_per_registrar_setting = EppSession.sessions_per_registrar + + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end teardown do @@ -181,6 +186,83 @@ class EppLoginTest < EppTestCase assert_epp_response :session_limit_exceeded_server_closing_connection end + def test_returns_valid_response_if_not_throttled + ENV["shunter_enabled"] = 'true' + user = users(:api_bestnames) + new_session_id = 'new-session-id' + + request_xml = <<-XML + + + + + #{user.username} + #{user.plain_text_password} + + 1.0 + en + + + #{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')} + #{Xsd::Schema.filename(for_prefix: 'contact-ee', for_version: '1.1')} + urn:ietf:params:xml:ns:host-1.0 + urn:ietf:params:xml:ns:keyrelay-1.0 + + + + + XML + + post '/epp/session/login', params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + user = users(:api_bestnames) + new_session_id = 'new-session-id' + + request_xml = <<-XML + + + + + #{user.username} + #{user.plain_text_password} + + 1.0 + en + + + #{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')} + #{Xsd::Schema.filename(for_prefix: 'contact-ee', for_version: '1.1')} + urn:ietf:params:xml:ns:host-1.0 + urn:ietf:params:xml:ns:keyrelay-1.0 + + + + + XML + + post '/epp/session/login', params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } + + post '/epp/session/login', params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def eliminate_effect_of_existing_epp_sessions diff --git a/test/integration/epp/poll_test.rb b/test/integration/epp/poll_test.rb index 7b114f7c0..0f7dc3765 100644 --- a/test/integration/epp/poll_test.rb +++ b/test/integration/epp/poll_test.rb @@ -2,6 +2,8 @@ require 'test_helper' class EppPollTest < EppTestCase setup do + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! @notification = notifications(:complete) end @@ -149,6 +151,44 @@ class EppPollTest < EppTestCase assert_epp_response :authorization_error end + def test_returns_valid_response_if_not_throttled + notification = notifications(:greeting) + + request_xml = <<-XML + + + + + + + XML + + post epp_poll_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_correct_against_schema response_xml + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + post epp_poll_path, params: { frame: request_req_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + post epp_poll_path, params: { frame: request_req_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :session_limit_exceeded_server_closing_connection + assert_correct_against_schema response_xml + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def request_req_xml From 2a58bf38496bde937122bb40cfc7b45f549d4509 Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Tue, 2 Aug 2022 06:14:47 -0300 Subject: [PATCH 08/12] Add rate limiting to all repp actions --- .../repp/v1/accounts_controller.rb | 3 ++ app/controllers/repp/v1/base_controller.rb | 9 ++++ .../repp/v1/contacts_controller.rb | 3 ++ .../v1/domains/admin_contacts_controller.rb | 3 ++ .../repp/v1/domains/contacts_controller.rb | 3 ++ .../repp/v1/domains/dnssec_controller.rb | 3 ++ .../repp/v1/domains/nameservers_controller.rb | 3 ++ .../repp/v1/domains/renews_controller.rb | 3 ++ .../repp/v1/domains/statuses_controller.rb | 3 ++ .../repp/v1/domains/transfers_controller.rb | 3 ++ app/controllers/repp/v1/domains_controller.rb | 3 ++ .../repp/v1/invoices_controller.rb | 3 ++ .../repp/v1/registrar/auth_controller.rb | 3 ++ .../v1/registrar/nameservers_controller.rb | 3 ++ .../v1/registrar/notifications_controller.rb | 3 ++ .../repp/v1/registrar/summary_controller.rb | 3 ++ .../api/domain_admin_contacts_test.rb | 23 ++++++++ .../repp/v1/accounts/activities_list_test.rb | 18 +++++++ .../repp/v1/accounts/balance_test.rb | 20 ++++++- .../repp/v1/accounts/details_test.rb | 20 ++++++- .../repp/v1/accounts/switch_user_test.rb | 28 +++++++++- .../update_auto_reload_balance_test.rb | 46 +++++++++++++++- .../repp/v1/accounts/update_details_test.rb | 27 +++++++++- .../repp/v1/contacts/check_test.rb | 19 +++++++ .../repp/v1/contacts/create_test.rb | 31 +++++++++++ .../repp/v1/contacts/delete_test.rb | 18 +++++++ .../integration/repp/v1/contacts/list_test.rb | 18 +++++++ .../repp/v1/contacts/search_test.rb | 19 ++++++- .../integration/repp/v1/contacts/show_test.rb | 20 +++++++ .../repp/v1/contacts/update_test.rb | 24 +++++++++ .../repp/v1/domains/bulk_renew_test.rb | 27 ++++++++++ .../repp/v1/domains/contacts_test.rb | 20 ++++++- .../repp/v1/domains/dnssec_test.rb | 18 +++++++ test/integration/repp/v1/domains/list_test.rb | 18 +++++++ .../repp/v1/domains/nameservers_test.rb | 18 +++++++ .../repp/v1/domains/statuses_test.rb | 17 ++++++ .../repp/v1/domains/transfer_info_test.rb | 21 ++++++++ .../repp/v1/domains/transfer_test.rb | 19 +++++++ .../repp/v1/invoices/add_credit_test.rb | 39 +++++++++++++- .../repp/v1/invoices/cancel_test.rb | 2 +- .../repp/v1/invoices/download_test.rb | 22 +++++++- .../integration/repp/v1/invoices/list_test.rb | 20 ++++++- .../integration/repp/v1/invoices/send_test.rb | 31 ++++++++++- .../integration/repp/v1/invoices/show_test.rb | 22 +++++++- .../repp/v1/registrar/auth/check_info_test.rb | 20 ++++++- .../v1/registrar/auth/tara_callback_test.rb | 2 +- .../repp/v1/registrar/notifications_test.rb | 52 +++++++++++++++++++ .../repp/v1/registrar/summary_test.rb | 20 ++++++- 48 files changed, 757 insertions(+), 16 deletions(-) diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb index 8395db42c..f3dd56b6b 100644 --- a/app/controllers/repp/v1/accounts_controller.rb +++ b/app/controllers/repp/v1/accounts_controller.rb @@ -3,6 +3,9 @@ module Repp class AccountsController < BaseController # rubocop:disable Metrics/ClassLength load_and_authorize_resource + THROTTLED_ACTIONS = %i[index balance details update_auto_reload_balance disable_auto_reload_balance switch_user update].freeze + include Shunter::Integration::Throttle + api :get, '/repp/v1/accounts' desc 'Get all activities' def index diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb index d84c8e37b..fde6a35f1 100644 --- a/app/controllers/repp/v1/base_controller.rb +++ b/app/controllers/repp/v1/base_controller.rb @@ -27,6 +27,10 @@ module Repp @response = { code: 2201, message: 'Authorization error' } logger.error e.to_s render(json: @response, status: :unauthorized) + rescue Shunter::ThrottleError => e + @response = { code: 2502, message: Shunter.default_error_message } + logger.error e.to_s + render(json: @response, status: :bad_request) ensure create_repp_log end @@ -167,6 +171,11 @@ module Repp data[:abilities] = Ability.new(current_user).permissions data end + + def throttled_user + authorize!(:throttled_user, @domain) unless current_user + current_user + end end end end diff --git a/app/controllers/repp/v1/contacts_controller.rb b/app/controllers/repp/v1/contacts_controller.rb index 5d8f20ee0..510525ac5 100644 --- a/app/controllers/repp/v1/contacts_controller.rb +++ b/app/controllers/repp/v1/contacts_controller.rb @@ -5,6 +5,9 @@ module Repp before_action :find_contact, only: %i[show update destroy] skip_around_action :log_request, only: :search + THROTTLED_ACTIONS = %i[index check search create show update destroy].freeze + include Shunter::Integration::Throttle + api :get, '/repp/v1/contacts' desc 'Get all existing contacts' def index diff --git a/app/controllers/repp/v1/domains/admin_contacts_controller.rb b/app/controllers/repp/v1/domains/admin_contacts_controller.rb index 5db865199..ce06296f5 100644 --- a/app/controllers/repp/v1/domains/admin_contacts_controller.rb +++ b/app/controllers/repp/v1/domains/admin_contacts_controller.rb @@ -2,6 +2,9 @@ module Repp module V1 module Domains class AdminContactsController < BaseContactsController + THROTTLED_ACTIONS = %i[update].freeze + include Shunter::Integration::Throttle + def update super diff --git a/app/controllers/repp/v1/domains/contacts_controller.rb b/app/controllers/repp/v1/domains/contacts_controller.rb index b41b3a378..a90a2d27a 100644 --- a/app/controllers/repp/v1/domains/contacts_controller.rb +++ b/app/controllers/repp/v1/domains/contacts_controller.rb @@ -4,6 +4,9 @@ module Repp class ContactsController < BaseContactsController before_action :set_domain, only: %i[index create destroy] + THROTTLED_ACTIONS = %i[index create destroy update].freeze + include Shunter::Integration::Throttle + def_param_group :contacts_apidoc do param :contacts, Array, required: true, desc: 'Array of new linked contacts' do param :code, String, required: true, desc: 'Contact code' diff --git a/app/controllers/repp/v1/domains/dnssec_controller.rb b/app/controllers/repp/v1/domains/dnssec_controller.rb index fcfaa991a..0acf2e6e9 100644 --- a/app/controllers/repp/v1/domains/dnssec_controller.rb +++ b/app/controllers/repp/v1/domains/dnssec_controller.rb @@ -4,6 +4,9 @@ module Repp class DnssecController < BaseController before_action :set_domain, only: %i[index create destroy] + THROTTLED_ACTIONS = %i[index create destroy].freeze + include Shunter::Integration::Throttle + def_param_group :dns_keys_apidoc do param :flags, String, required: true, desc: '256 (KSK) or 257 (ZSK)' param :protocol, String, required: true, desc: 'Key protocol (3)' diff --git a/app/controllers/repp/v1/domains/nameservers_controller.rb b/app/controllers/repp/v1/domains/nameservers_controller.rb index 6f76f9e99..8ee1cba35 100644 --- a/app/controllers/repp/v1/domains/nameservers_controller.rb +++ b/app/controllers/repp/v1/domains/nameservers_controller.rb @@ -5,6 +5,9 @@ module Repp before_action :set_domain, only: %i[index create destroy] before_action :set_nameserver, only: %i[destroy] + THROTTLED_ACTIONS = %i[index create destroy].freeze + include Shunter::Integration::Throttle + api :GET, '/repp/v1/domains/:domain_name/nameservers' desc "Get domain's nameservers" def index diff --git a/app/controllers/repp/v1/domains/renews_controller.rb b/app/controllers/repp/v1/domains/renews_controller.rb index b47710c7e..c91130119 100644 --- a/app/controllers/repp/v1/domains/renews_controller.rb +++ b/app/controllers/repp/v1/domains/renews_controller.rb @@ -6,6 +6,9 @@ module Repp before_action :select_renewable_domains, only: [:bulk_renew] before_action :set_domain, only: [:create] + THROTTLED_ACTIONS = %i[create bulk_renew].freeze + include Shunter::Integration::Throttle + api :POST, 'repp/v1/domains/:domain_name/renew' desc 'Renew domain' param :renews, Hash, required: true, desc: 'Renew parameters' do diff --git a/app/controllers/repp/v1/domains/statuses_controller.rb b/app/controllers/repp/v1/domains/statuses_controller.rb index d46725c46..b07ce3956 100644 --- a/app/controllers/repp/v1/domains/statuses_controller.rb +++ b/app/controllers/repp/v1/domains/statuses_controller.rb @@ -5,6 +5,9 @@ module Repp before_action :set_domain, only: %i[update destroy] before_action :verify_status + THROTTLED_ACTIONS = %i[update destroy].freeze + include Shunter::Integration::Throttle + api :DELETE, '/repp/v1/domains/:domain_name/statuses/:status' param :domain_name, String, desc: 'Domain name' desc 'Remove status from specific domain' diff --git a/app/controllers/repp/v1/domains/transfers_controller.rb b/app/controllers/repp/v1/domains/transfers_controller.rb index e9474d94d..69e685571 100644 --- a/app/controllers/repp/v1/domains/transfers_controller.rb +++ b/app/controllers/repp/v1/domains/transfers_controller.rb @@ -4,6 +4,9 @@ module Repp class TransfersController < BaseController before_action :set_domain, only: [:create] + THROTTLED_ACTIONS = %i[create].freeze + include Shunter::Integration::Throttle + api :POST, 'repp/v1/domains/:domain_name/transfer' desc 'Transfer a specific domain' param :transfer, Hash, required: true, desc: 'Renew parameters' do diff --git a/app/controllers/repp/v1/domains_controller.rb b/app/controllers/repp/v1/domains_controller.rb index 6990b0a86..c72c83882 100644 --- a/app/controllers/repp/v1/domains_controller.rb +++ b/app/controllers/repp/v1/domains_controller.rb @@ -8,6 +8,9 @@ module Repp before_action :forward_registrar_id, only: %i[create update destroy] before_action :set_domain, only: %i[update] + THROTTLED_ACTIONS = %i[transfer_info transfer index create show update destroy].freeze + include Shunter::Integration::Throttle + api :GET, '/repp/v1/domains' desc 'Get all existing domains' def index diff --git a/app/controllers/repp/v1/invoices_controller.rb b/app/controllers/repp/v1/invoices_controller.rb index 2d4340b3e..fe2c1c50a 100644 --- a/app/controllers/repp/v1/invoices_controller.rb +++ b/app/controllers/repp/v1/invoices_controller.rb @@ -4,6 +4,9 @@ module Repp class InvoicesController < BaseController # rubocop:disable Metrics/ClassLength load_and_authorize_resource + THROTTLED_ACTIONS = %i[download add_credit send_to_recipient cancel index show].freeze + include Shunter::Integration::Throttle + # rubocop:disable Metrics/MethodLength api :get, '/repp/v1/invoices' desc 'Get all invoices' diff --git a/app/controllers/repp/v1/registrar/auth_controller.rb b/app/controllers/repp/v1/registrar/auth_controller.rb index 46c21459e..c5897bcea 100644 --- a/app/controllers/repp/v1/registrar/auth_controller.rb +++ b/app/controllers/repp/v1/registrar/auth_controller.rb @@ -6,6 +6,9 @@ module Repp skip_before_action :check_ip_restriction, only: :tara_callback skip_before_action :validate_client_certs, only: :tara_callback + THROTTLED_ACTIONS = %i[index].freeze + include Shunter::Integration::Throttle + api :GET, 'repp/v1/registrar/auth' desc 'check user auth info and return data' def index diff --git a/app/controllers/repp/v1/registrar/nameservers_controller.rb b/app/controllers/repp/v1/registrar/nameservers_controller.rb index b3c6d8412..1014ddc0d 100644 --- a/app/controllers/repp/v1/registrar/nameservers_controller.rb +++ b/app/controllers/repp/v1/registrar/nameservers_controller.rb @@ -4,6 +4,9 @@ module Repp class NameserversController < BaseController before_action :verify_nameserver_existance, only: %i[update] + THROTTLED_ACTIONS = %i[put].freeze + include Shunter::Integration::Throttle + api :PUT, 'repp/v1/registrar/nameservers' desc 'bulk nameserver change' param :data, Hash, required: true, desc: 'Object holding nameserver changes' do diff --git a/app/controllers/repp/v1/registrar/notifications_controller.rb b/app/controllers/repp/v1/registrar/notifications_controller.rb index 6b1d342cc..eb3d158ad 100644 --- a/app/controllers/repp/v1/registrar/notifications_controller.rb +++ b/app/controllers/repp/v1/registrar/notifications_controller.rb @@ -4,6 +4,9 @@ module Repp class NotificationsController < BaseController before_action :set_notification, only: %i[update show] + THROTTLED_ACTIONS = %i[all_notifications index show update].freeze + include Shunter::Integration::Throttle + api :GET, '/repp/v1/registrar/notifications' desc 'Get the latest unread poll message' def index diff --git a/app/controllers/repp/v1/registrar/summary_controller.rb b/app/controllers/repp/v1/registrar/summary_controller.rb index a0e266e93..eaa3b0f57 100644 --- a/app/controllers/repp/v1/registrar/summary_controller.rb +++ b/app/controllers/repp/v1/registrar/summary_controller.rb @@ -2,6 +2,9 @@ module Repp module V1 module Registrar class SummaryController < BaseController + THROTTLED_ACTIONS = %i[index].freeze + include Shunter::Integration::Throttle + api :GET, 'repp/v1/registrar/summary' desc 'check user summary info and return data' diff --git a/test/integration/api/domain_admin_contacts_test.rb b/test/integration/api/domain_admin_contacts_test.rb index 6aa412c23..8064483bc 100644 --- a/test/integration/api/domain_admin_contacts_test.rb +++ b/test/integration/api/domain_admin_contacts_test.rb @@ -10,6 +10,8 @@ class APIDomainAdminContactsTest < ApplicationIntegrationTest @admin_new.update(ident: @admin_current.ident, ident_type: @admin_current.ident_type, ident_country_code: @admin_current.ident_country_code) + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_replace_all_admin_contacts_when_ident_data_doesnt_match @@ -148,6 +150,27 @@ class APIDomainAdminContactsTest < ApplicationIntegrationTest JSON.parse(response.body, symbolize_names: true) end + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + domain = domains(:airport) + domain.admin_contacts = [@admin_current] + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def http_auth_key diff --git a/test/integration/repp/v1/accounts/activities_list_test.rb b/test/integration/repp/v1/accounts/activities_list_test.rb index 2038815a1..5cc5c47a4 100644 --- a/test/integration/repp/v1/accounts/activities_list_test.rb +++ b/test/integration/repp/v1/accounts/activities_list_test.rb @@ -7,6 +7,9 @@ class ReppV1AccountsActivitiesListTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_account_activities @@ -67,4 +70,19 @@ class ReppV1AccountsActivitiesListTest < ActionDispatch::IntegrationTest assert_equal @user.registrar.cash_account.activities.count, json[:data][:activities].length assert_equal json[:data][:activities][0][:description], activity.description end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get repp_v1_accounts_path, headers: @auth_headers + get repp_v1_accounts_path, headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/accounts/balance_test.rb b/test/integration/repp/v1/accounts/balance_test.rb index 3fd25f3e7..de41da26d 100644 --- a/test/integration/repp/v1/accounts/balance_test.rb +++ b/test/integration/repp/v1/accounts/balance_test.rb @@ -8,9 +8,12 @@ class ReppV1BalanceTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end - + def test_can_query_balance get '/repp/v1/accounts/balance', headers: @auth_headers @@ -49,5 +52,20 @@ class ReppV1BalanceTest < ActionDispatch::IntegrationTest assert trans[:created_at].to_date.to_s(:db) >= started_from assert trans[:created_at].to_date.to_s(:db) >= end_to end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/accounts/balance', headers: @auth_headers + get '/repp/v1/accounts/balance', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end end diff --git a/test/integration/repp/v1/accounts/details_test.rb b/test/integration/repp/v1/accounts/details_test.rb index 30acb5eb6..b643c60e2 100644 --- a/test/integration/repp/v1/accounts/details_test.rb +++ b/test/integration/repp/v1/accounts/details_test.rb @@ -7,6 +7,9 @@ class ReppV1AccountsDetailsTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_account_details @@ -19,4 +22,19 @@ class ReppV1AccountsDetailsTest < ActionDispatch::IntegrationTest assert_equal @user.registrar.billing_email, json[:data][:account][:billing_email] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/accounts/details', headers: @auth_headers + get '/repp/v1/accounts/details', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/accounts/switch_user_test.rb b/test/integration/repp/v1/accounts/switch_user_test.rb index a860fb162..2299f2bf5 100644 --- a/test/integration/repp/v1/accounts/switch_user_test.rb +++ b/test/integration/repp/v1/accounts/switch_user_test.rb @@ -7,6 +7,9 @@ class ReppV1AccountsSwitchUserTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_switches_to_linked_api_user @@ -48,4 +51,27 @@ class ReppV1AccountsSwitchUserTest < ActionDispatch::IntegrationTest assert_response :bad_request assert_equal 'Cannot switch to unlinked user', json[:message] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + new_user = users(:api_goodnames) + new_user.update(identity_code: '1234') + request_body = { + account: { + new_user_id: new_user.id, + }, + } + + put '/repp/v1/accounts/switch_user', headers: @auth_headers, params: request_body + put '/repp/v1/accounts/switch_user', headers: @auth_headers, params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/accounts/update_auto_reload_balance_test.rb b/test/integration/repp/v1/accounts/update_auto_reload_balance_test.rb index 11a8d08ba..253cd6b0b 100644 --- a/test/integration/repp/v1/accounts/update_auto_reload_balance_test.rb +++ b/test/integration/repp/v1/accounts/update_auto_reload_balance_test.rb @@ -7,6 +7,9 @@ class ReppV1AccountsUpdateAutoReloadBalanceTest < ActionDispatch::IntegrationTes token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_updates_auto_reload_balance @@ -66,4 +69,45 @@ class ReppV1AccountsUpdateAutoReloadBalanceTest < ActionDispatch::IntegrationTes assert_nil @user.registrar.settings['balance_auto_reload'] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + amount = 100 + threshold = 10 + request_body = { + type: { + amount: amount, + threshold: threshold, + }, + } + + post '/repp/v1/accounts/update_auto_reload_balance', headers: @auth_headers, + params: request_body + post '/repp/v1/accounts/update_auto_reload_balance', headers: @auth_headers, + params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/accounts/disable_auto_reload_balance', headers: @auth_headers + get '/repp/v1/accounts/disable_auto_reload_balance', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/accounts/update_details_test.rb b/test/integration/repp/v1/accounts/update_details_test.rb index c1275ddeb..a9293605d 100644 --- a/test/integration/repp/v1/accounts/update_details_test.rb +++ b/test/integration/repp/v1/accounts/update_details_test.rb @@ -7,6 +7,9 @@ class ReppV1AccountsUpdateDetailsTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_updates_details @@ -27,4 +30,26 @@ class ReppV1AccountsUpdateDetailsTest < ActionDispatch::IntegrationTest assert_equal(request_body[:account][:billing_email], @user.registrar.billing_email) assert_equal(request_body[:account][:iban], @user.registrar.iban) end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + request_body = { + account: { + billing_email: 'donaldtrump@yandex.ru', + iban: 'GB331111111111111111', + }, + } + + put '/repp/v1/accounts', headers: @auth_headers, params: request_body + put '/repp/v1/accounts', headers: @auth_headers, params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/contacts/check_test.rb b/test/integration/repp/v1/contacts/check_test.rb index 6fc716638..1da629320 100644 --- a/test/integration/repp/v1/contacts/check_test.rb +++ b/test/integration/repp/v1/contacts/check_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsCheckTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_code_based_check_returns_true_for_available_contact @@ -27,4 +30,20 @@ class ReppV1ContactsCheckTest < ActionDispatch::IntegrationTest assert_equal contact.code, json[:data][:contact][:code] assert_equal false, json[:data][:contact][:available] end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + contact = contacts(:jack) + get "/repp/v1/contacts/check/#{contact.code}", headers: @auth_headers + get "/repp/v1/contacts/check/#{contact.code}", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/contacts/create_test.rb b/test/integration/repp/v1/contacts/create_test.rb index af1ca0fbf..3d0c2f646 100644 --- a/test/integration/repp/v1/contacts/create_test.rb +++ b/test/integration/repp/v1/contacts/create_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_creates_new_contact @@ -153,4 +156,32 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.legal_documents.any? end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + request_body = { + contact: { + name: 'Donald Trump', + phone: '+372.51111112', + email: 'donald@trumptower.com', + ident: { + ident_type: 'priv', + ident_country_code: 'EE', + ident: '39708290069', + }, + }, + } + + post '/repp/v1/contacts', headers: @auth_headers, params: request_body + post '/repp/v1/contacts', headers: @auth_headers, params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/contacts/delete_test.rb b/test/integration/repp/v1/contacts/delete_test.rb index 07438d8af..e585e0962 100644 --- a/test/integration/repp/v1/contacts/delete_test.rb +++ b/test/integration/repp/v1/contacts/delete_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsDeleteTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_deletes_unassociated_contact @@ -44,4 +47,19 @@ class ReppV1ContactsDeleteTest < ActionDispatch::IntegrationTest assert_response :not_found end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + delete "/repp/v1/contacts/#{contacts(:invalid_email).code}", headers: @auth_headers + delete "/repp/v1/contacts/#{contacts(:john).code}", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/contacts/list_test.rb b/test/integration/repp/v1/contacts/list_test.rb index 979d3ea40..56b909459 100644 --- a/test/integration/repp/v1/contacts/list_test.rb +++ b/test/integration/repp/v1/contacts/list_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_registrar_contacts @@ -79,4 +82,19 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest assert_equal @user.registrar.contacts.count, json[:data][:contacts].length assert_equal json[:data][:contacts][0][:code], contact.code end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get repp_v1_contacts_path, headers: @auth_headers + get repp_v1_contacts_path, headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/contacts/search_test.rb b/test/integration/repp/v1/contacts/search_test.rb index ceeefc7be..949e9718d 100644 --- a/test/integration/repp/v1/contacts/search_test.rb +++ b/test/integration/repp/v1/contacts/search_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsSearchTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_searches_all_contacts_by_id @@ -40,4 +43,18 @@ class ReppV1ContactsSearchTest < ActionDispatch::IntegrationTest assert json[:data].is_a? Array assert_equal json[:data].length, 0 end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/contacts/search', headers: @auth_headers, params: { query: '000' } + get '/repp/v1/contacts/search', headers: @auth_headers, params: { query: 'j' } + json = JSON.parse(response.body, symbolize_names: true) + + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/contacts/show_test.rb b/test/integration/repp/v1/contacts/show_test.rb index 496935ab6..8a79ccfdd 100644 --- a/test/integration/repp/v1/contacts/show_test.rb +++ b/test/integration/repp/v1/contacts/show_test.rb @@ -7,6 +7,9 @@ class ReppV1ContactsShowTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_error_when_not_found @@ -42,4 +45,21 @@ class ReppV1ContactsShowTest < ActionDispatch::IntegrationTest assert_equal 2303, json[:code] assert_equal 'Object does not exist', json[:message] end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + contact = @user.registrar.contacts.first + + get repp_v1_contact_path(id: contact.code), headers: @auth_headers + get repp_v1_contact_path(id: contact.code), headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/contacts/update_test.rb b/test/integration/repp/v1/contacts/update_test.rb index e75ce4188..d51602c32 100644 --- a/test/integration/repp/v1/contacts/update_test.rb +++ b/test/integration/repp/v1/contacts/update_test.rb @@ -8,6 +8,9 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_updates_contact @@ -118,4 +121,25 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest assert_equal 2308, json[:code] assert_equal 'Ident update is not allowed. Consider creating new contact object', json[:message] end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + request_body = { + "contact": { + "email": "donaldtrump@yandex.ru" + } + } + + put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body + put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/domains/bulk_renew_test.rb b/test/integration/repp/v1/domains/bulk_renew_test.rb index 510d09f62..56439a908 100644 --- a/test/integration/repp/v1/domains/bulk_renew_test.rb +++ b/test/integration/repp/v1/domains/bulk_renew_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsBulkRenewTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_renews_domains @@ -129,6 +132,30 @@ class ReppV1DomainsBulkRenewTest < ActionDispatch::IntegrationTest assert_equal 'Invalid renew period', json[:message] end + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + payload = { + "domains": [ + 'shop.test', + 'airport.test', + 'library.test' + ], + "renew_period": "1y" + } + + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + private def set_status_for_domain(domain, statuses) diff --git a/test/integration/repp/v1/domains/contacts_test.rb b/test/integration/repp/v1/domains/contacts_test.rb index 17f8f1f6b..87b916811 100644 --- a/test/integration/repp/v1/domains/contacts_test.rb +++ b/test/integration/repp/v1/domains/contacts_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_shows_existing_domain_contacts @@ -22,6 +25,21 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest assert_equal @domain.tech_contacts.length, json[:data][:tech_contacts].length end + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers + get "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_can_add_new_admin_contacts new_contact = contacts(:john) refute @domain.admin_contacts.find_by(code: new_contact.code).present? @@ -71,7 +89,7 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest def test_can_remove_tech_contacts Spy.on_instance_method(Actions::DomainUpdate, :validate_email).and_return(true) - + contact = contacts(:john) payload = { contacts: [ { code: contact.code, type: 'tech' } ] } post "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers, params: payload diff --git a/test/integration/repp/v1/domains/dnssec_test.rb b/test/integration/repp/v1/domains/dnssec_test.rb index 349639ad4..46e239fbf 100644 --- a/test/integration/repp/v1/domains/dnssec_test.rb +++ b/test/integration/repp/v1/domains/dnssec_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsDnssecTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_shows_dnssec_keys_associated_with_domain @@ -120,4 +123,19 @@ class ReppV1DomainsDnssecTest < ActionDispatch::IntegrationTest assert @domain.dnskeys.empty? end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get "/repp/v1/domains/#{@domain.name}/dnssec", headers: @auth_headers + get "/repp/v1/domains/#{@domain.name}/dnssec", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/domains/list_test.rb b/test/integration/repp/v1/domains/list_test.rb index 205017a6c..3699927db 100644 --- a/test/integration/repp/v1/domains/list_test.rb +++ b/test/integration/repp/v1/domains/list_test.rb @@ -7,6 +7,9 @@ class ReppV1DomainsListTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_registrar_domains @@ -92,4 +95,19 @@ class ReppV1DomainsListTest < ActionDispatch::IntegrationTest assert_equal @user.registrar.domains.count, json[:data][:domains].length assert_equal json[:data][:domains][0][:name], domain.name end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get repp_v1_domains_path, headers: @auth_headers + get repp_v1_domains_path, headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/domains/nameservers_test.rb b/test/integration/repp/v1/domains/nameservers_test.rb index 780e889c1..3ff85260e 100644 --- a/test/integration/repp/v1/domains/nameservers_test.rb +++ b/test/integration/repp/v1/domains/nameservers_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsNameserversTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_can_add_new_nameserver @@ -30,6 +33,21 @@ class ReppV1DomainsNameserversTest < ActionDispatch::IntegrationTest assert_equal payload[:nameservers][0][:ipv6], @domain.nameservers.last.ipv6 end + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get "/repp/v1/domains/#{@domain.name}/nameservers", headers: @auth_headers + get "/repp/v1/domains/#{@domain.name}/nameservers", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_can_remove_existing_nameserver payload = { nameservers: [ diff --git a/test/integration/repp/v1/domains/statuses_test.rb b/test/integration/repp/v1/domains/statuses_test.rb index 271752ae3..ee2cb445b 100644 --- a/test/integration/repp/v1/domains/statuses_test.rb +++ b/test/integration/repp/v1/domains/statuses_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsStatusesTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_client_hold_can_be_added @@ -79,4 +82,18 @@ class ReppV1DomainsStatusesTest < ActionDispatch::IntegrationTest assert_equal 2306, json[:code] end + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + put repp_v1_domain_status_path(domain_id: @domain.name, id: DomainStatus::CLIENT_HOLD), headers: @auth_headers + put repp_v1_domain_status_path(domain_id: @domain.name, id: DomainStatus::CLIENT_HOLD), headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/domains/transfer_info_test.rb b/test/integration/repp/v1/domains/transfer_info_test.rb index a3b8fe874..64fd8ed10 100644 --- a/test/integration/repp/v1/domains/transfer_info_test.rb +++ b/test/integration/repp/v1/domains/transfer_info_test.rb @@ -7,6 +7,9 @@ class ReppV1DomainsTransferInfoTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @domain = domains(:shop) @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_can_query_domain_info @@ -51,4 +54,22 @@ class ReppV1DomainsTransferInfoTest < ActionDispatch::IntegrationTest assert_response :ok assert_equal 1000, json[:code] end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + headers = @auth_headers + headers['Auth-Code'] = @domain.transfer_code + + get "/repp/v1/domains/#{@domain.name}/transfer_info", headers: headers + get "/repp/v1/domains/#{@domain.name}/transfer_info", headers: headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/domains/transfer_test.rb b/test/integration/repp/v1/domains/transfer_test.rb index 5854de195..fdcbe41d7 100644 --- a/test/integration/repp/v1/domains/transfer_test.rb +++ b/test/integration/repp/v1/domains/transfer_test.rb @@ -8,6 +8,9 @@ class ReppV1DomainsTransferTest < ActionDispatch::IntegrationTest @domain = domains(:hospital) @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_transfers_scoped_domain @@ -152,4 +155,20 @@ class ReppV1DomainsTransferTest < ActionDispatch::IntegrationTest assert_not @domain.registrar == @user.registrar end + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + payload = { transfer: { transfer_code: @domain.transfer_code } } + post "/repp/v1/domains/#{@domain.name}/transfer", headers: @auth_headers, params: payload + post "/repp/v1/domains/#{@domain.name}/transfer", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end end diff --git a/test/integration/repp/v1/invoices/add_credit_test.rb b/test/integration/repp/v1/invoices/add_credit_test.rb index fe250de7d..a6dd541f4 100644 --- a/test/integration/repp/v1/invoices/add_credit_test.rb +++ b/test/integration/repp/v1/invoices/add_credit_test.rb @@ -24,6 +24,8 @@ class ReppV1InvoicesAddCreditTest < ActionDispatch::IntegrationTest message: 'success' } stub_request(:post, "https://eis_billing_system:3000/api/v1/e_invoice/e_invoice").to_return(status: 200, body: msg2.to_json, headers: {}) + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end teardown do @@ -101,4 +103,39 @@ class ReppV1InvoicesAddCreditTest < ActionDispatch::IntegrationTest assert_response :bad_request assert_equal "Amount is too small. Minimum deposit is #{Setting.minimum_deposit} EUR", json[:message] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + request_body = { + invoice: { + amount: 100, + description: 'Add credit', + }, + } + Setting.registry_vat_prc = 0.1 + ENV['billing_system_integrated'] = 'true' + + if Feature.billing_system_integrated? + invoice_n = Invoice.order(number: :desc).last.number + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator') + .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/e_invoice/e_invoice') + .to_return(status: 200, body: '', headers: {}) + end + + post '/repp/v1/invoices/add_credit', headers: @auth_headers, + params: request_body + post '/repp/v1/invoices/add_credit', headers: @auth_headers, + params: request_body + + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/invoices/cancel_test.rb b/test/integration/repp/v1/invoices/cancel_test.rb index 1041d1d1f..0d7d3a585 100644 --- a/test/integration/repp/v1/invoices/cancel_test.rb +++ b/test/integration/repp/v1/invoices/cancel_test.rb @@ -41,4 +41,4 @@ class ReppV1InvoicesCancelTest < ActionDispatch::IntegrationTest invoice.reload assert_not invoice.cancelled? end -end \ No newline at end of file +end diff --git a/test/integration/repp/v1/invoices/download_test.rb b/test/integration/repp/v1/invoices/download_test.rb index cbb9de585..fe2e9233d 100644 --- a/test/integration/repp/v1/invoices/download_test.rb +++ b/test/integration/repp/v1/invoices/download_test.rb @@ -7,6 +7,9 @@ class ReppV1InvoicesDownloadTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_invoice_as_pdf @@ -19,4 +22,21 @@ class ReppV1InvoicesDownloadTest < ActionDispatch::IntegrationTest assert_equal "attachment; filename=\"Invoice-2.pdf\"; filename*=UTF-8''Invoice-2.pdf", response.headers['Content-Disposition'] assert_not_empty response.body end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + invoice = @user.registrar.invoices.first + + get "/repp/v1/invoices/#{invoice.id}/download", headers: @auth_headers + get "/repp/v1/invoices/#{invoice.id}/download", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/invoices/list_test.rb b/test/integration/repp/v1/invoices/list_test.rb index 5dfe2d53f..35fa7256a 100644 --- a/test/integration/repp/v1/invoices/list_test.rb +++ b/test/integration/repp/v1/invoices/list_test.rb @@ -7,6 +7,9 @@ class ReppV1InvoicesListTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_registrar_invoices @@ -82,4 +85,19 @@ class ReppV1InvoicesListTest < ActionDispatch::IntegrationTest assert_equal (@user.registrar.invoices.count - offset), json[:data][:invoices].length end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get repp_v1_invoices_path, headers: @auth_headers + get repp_v1_invoices_path, headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/invoices/send_test.rb b/test/integration/repp/v1/invoices/send_test.rb index 77fe9997f..565ac27ff 100644 --- a/test/integration/repp/v1/invoices/send_test.rb +++ b/test/integration/repp/v1/invoices/send_test.rb @@ -7,6 +7,9 @@ class ReppV1InvoicesSendTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_sends_invoice_to_recipient @@ -36,4 +39,30 @@ class ReppV1InvoicesSendTest < ActionDispatch::IntegrationTest assert_equal 'Invoice no. 1', email.subject assert email.attachments['invoice-1.pdf'] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + invoice = invoices(:one) + recipient = 'donaldtrump@yandex.ru' + request_body = { + invoice: { + id: invoice.id, + recipient: recipient, + }, + } + post "/repp/v1/invoices/#{invoice.id}/send_to_recipient", headers: @auth_headers, + params: request_body + post "/repp/v1/invoices/#{invoice.id}/send_to_recipient", headers: @auth_headers, + params: request_body + + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/invoices/show_test.rb b/test/integration/repp/v1/invoices/show_test.rb index 74feb42ac..4f26f4b11 100644 --- a/test/integration/repp/v1/invoices/show_test.rb +++ b/test/integration/repp/v1/invoices/show_test.rb @@ -7,6 +7,9 @@ class ReppV1InvoicesShowTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_error_when_not_found @@ -30,4 +33,21 @@ class ReppV1InvoicesShowTest < ActionDispatch::IntegrationTest assert_equal invoice.id, json[:data][:invoice][:id] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + invoice = @user.registrar.invoices.first + + get repp_v1_invoice_path(id: invoice.id), headers: @auth_headers + get repp_v1_invoice_path(id: invoice.id), headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/registrar/auth/check_info_test.rb b/test/integration/repp/v1/registrar/auth/check_info_test.rb index 03563d273..154e8e258 100644 --- a/test/integration/repp/v1/registrar/auth/check_info_test.rb +++ b/test/integration/repp/v1/registrar/auth/check_info_test.rb @@ -7,6 +7,9 @@ class ReppV1RegistrarAuthCheckInfoTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_returns_valid_user_auth_values @@ -35,4 +38,19 @@ class ReppV1RegistrarAuthCheckInfoTest < ActionDispatch::IntegrationTest assert_response :unauthorized assert_equal json[:message], 'Invalid authorization information' end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/registrar/auth', headers: @auth_headers + get '/repp/v1/registrar/auth', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end diff --git a/test/integration/repp/v1/registrar/auth/tara_callback_test.rb b/test/integration/repp/v1/registrar/auth/tara_callback_test.rb index e39e24078..918fce9cc 100644 --- a/test/integration/repp/v1/registrar/auth/tara_callback_test.rb +++ b/test/integration/repp/v1/registrar/auth/tara_callback_test.rb @@ -43,4 +43,4 @@ class ReppV1RegistrarAuthTaraCallbackTest < ActionDispatch::IntegrationTest assert_response :unauthorized assert_equal 'No such user', json[:message] end -end \ No newline at end of file +end diff --git a/test/integration/repp/v1/registrar/notifications_test.rb b/test/integration/repp/v1/registrar/notifications_test.rb index 2677d393b..6eb6f9898 100644 --- a/test/integration/repp/v1/registrar/notifications_test.rb +++ b/test/integration/repp/v1/registrar/notifications_test.rb @@ -7,6 +7,9 @@ class ReppV1RegistrarNotificationsTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_all_unreaded_poll_messages @@ -20,6 +23,22 @@ class ReppV1RegistrarNotificationsTest < ActionDispatch::IntegrationTest assert_equal json[:data].last[:text], notification.last.text end + def test_all_notifications_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + notification = @user.registrar.notifications.where(read: false).order(created_at: :desc).all + get "/repp/v1/registrar/notifications/all_notifications", headers: @auth_headers + get "/repp/v1/registrar/notifications/all_notifications", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_gets_latest_unread_poll_message notification = @user.registrar.notifications.where(read: false).order(created_at: :desc).first get "/repp/v1/registrar/notifications", headers: @auth_headers @@ -31,6 +50,22 @@ class ReppV1RegistrarNotificationsTest < ActionDispatch::IntegrationTest assert_equal notification.text, json[:data][:text] end + def test_index_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + notification = @user.registrar.notifications.where(read: false).order(created_at: :desc).first + get "/repp/v1/registrar/notifications", headers: @auth_headers + get "/repp/v1/registrar/notifications", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_can_read_specific_notification_by_id notification = @user.registrar.notifications.order(created_at: :desc).second @@ -43,6 +78,23 @@ class ReppV1RegistrarNotificationsTest < ActionDispatch::IntegrationTest assert_equal notification.text, json[:data][:text] end + def test_show_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + notification = @user.registrar.notifications.order(created_at: :desc).second + + get "/repp/v1/registrar/notifications/#{notification.id}", headers: @auth_headers + get "/repp/v1/registrar/notifications/#{notification.id}", headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end + def test_can_mark_notification_as_read @auth_headers['Content-Type'] = 'application/json' notification = @user.registrar.notifications.where(read: false).order(created_at: :desc).first diff --git a/test/integration/repp/v1/registrar/summary_test.rb b/test/integration/repp/v1/registrar/summary_test.rb index 97797990a..bd58d8e97 100644 --- a/test/integration/repp/v1/registrar/summary_test.rb +++ b/test/integration/repp/v1/registrar/summary_test.rb @@ -7,6 +7,9 @@ class ReppV1RegistrarSummaryTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + + adapter = ENV["shunter_default_adapter"].constantize.new + adapter&.clear! end def test_checks_user_summary_info @@ -40,4 +43,19 @@ class ReppV1RegistrarSummaryTest < ActionDispatch::IntegrationTest assert_nil json[:data][:notification] assert_nil json[:data][:notifications_count] end -end \ No newline at end of file + + def test_returns_error_response_if_throttled + ENV["shunter_default_threshold"] = '1' + ENV["shunter_enabled"] = 'true' + + get '/repp/v1/registrar/summary', headers: @auth_headers + get '/repp/v1/registrar/summary', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal json[:code], 2502 + assert response.body.include?(Shunter.default_error_message) + ENV["shunter_default_threshold"] = '10000' + ENV["shunter_enabled"] = 'false' + end +end From 7a7cd6be084458ce911f4202bc48c9347af04a52 Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Tue, 2 Aug 2022 06:47:12 -0300 Subject: [PATCH 09/12] Fix test naming error --- app/controllers/repp/v1/accounts_controller.rb | 4 +++- test/lib/shunter/{base_test.rb => shunter_base_test.rb} | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) rename test/lib/shunter/{base_test.rb => shunter_base_test.rb} (94%) diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb index f3dd56b6b..a405646ca 100644 --- a/app/controllers/repp/v1/accounts_controller.rb +++ b/app/controllers/repp/v1/accounts_controller.rb @@ -3,7 +3,9 @@ module Repp class AccountsController < BaseController # rubocop:disable Metrics/ClassLength load_and_authorize_resource - THROTTLED_ACTIONS = %i[index balance details update_auto_reload_balance disable_auto_reload_balance switch_user update].freeze + THROTTLED_ACTIONS = %i[ + index balance details update_auto_reload_balance disable_auto_reload_balance switch_user update + ].freeze include Shunter::Integration::Throttle api :get, '/repp/v1/accounts' diff --git a/test/lib/shunter/base_test.rb b/test/lib/shunter/shunter_base_test.rb similarity index 94% rename from test/lib/shunter/base_test.rb rename to test/lib/shunter/shunter_base_test.rb index c251724ed..5645f8cec 100644 --- a/test/lib/shunter/base_test.rb +++ b/test/lib/shunter/shunter_base_test.rb @@ -4,7 +4,7 @@ require "test_helper" require "action_controller" require "pry" -class BaseTest < Minitest::Test +class ShunterBaseTest < Minitest::Test ENV["shunter_enabled"] = 'true' def test_throttling_works_on_inclusion From e65d55765a1b1b558ed1f413c27c1c33131041ed Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Sat, 6 Aug 2022 18:19:01 -0300 Subject: [PATCH 10/12] Fix tests --- app/controllers/epp/base_controller.rb | 2 +- test/integration/epp/login_test.rb | 82 ------------------- .../repp/v1/contacts/search_test.rb | 8 +- 3 files changed, 5 insertions(+), 87 deletions(-) diff --git a/app/controllers/epp/base_controller.rb b/app/controllers/epp/base_controller.rb index 024a39406..a481a8f9a 100644 --- a/app/controllers/epp/base_controller.rb +++ b/app/controllers/epp/base_controller.rb @@ -62,7 +62,7 @@ module Epp private def throttled_user - authorize!(:throttled_user, @domain) unless current_user + authorize!(:throttled_user, @domain) unless current_user || instance_of?(Epp::SessionsController) current_user end diff --git a/test/integration/epp/login_test.rb b/test/integration/epp/login_test.rb index 0e07da9c6..80d7251d6 100644 --- a/test/integration/epp/login_test.rb +++ b/test/integration/epp/login_test.rb @@ -3,11 +3,6 @@ require 'test_helper' class EppLoginTest < EppTestCase setup do @original_sessions_per_registrar_setting = EppSession.sessions_per_registrar - - ENV["shunter_default_threshold"] = '10000' - ENV["shunter_enabled"] = 'false' - adapter = ENV["shunter_default_adapter"].constantize.new - adapter&.clear! end teardown do @@ -186,83 +181,6 @@ class EppLoginTest < EppTestCase assert_epp_response :session_limit_exceeded_server_closing_connection end - def test_returns_valid_response_if_not_throttled - ENV["shunter_enabled"] = 'true' - user = users(:api_bestnames) - new_session_id = 'new-session-id' - - request_xml = <<-XML - - - - - #{user.username} - #{user.plain_text_password} - - 1.0 - en - - - #{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')} - #{Xsd::Schema.filename(for_prefix: 'contact-ee', for_version: '1.1')} - urn:ietf:params:xml:ns:host-1.0 - urn:ietf:params:xml:ns:keyrelay-1.0 - - - - - XML - - post '/epp/session/login', params: { frame: request_xml }, - headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } - - response_xml = Nokogiri::XML(response.body) - assert_epp_response :completed_successfully - assert_correct_against_schema response_xml - end - - def test_returns_error_response_if_throttled - ENV["shunter_default_threshold"] = '1' - ENV["shunter_enabled"] = 'true' - user = users(:api_bestnames) - new_session_id = 'new-session-id' - - request_xml = <<-XML - - - - - #{user.username} - #{user.plain_text_password} - - 1.0 - en - - - #{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')} - #{Xsd::Schema.filename(for_prefix: 'contact-ee', for_version: '1.1')} - urn:ietf:params:xml:ns:host-1.0 - urn:ietf:params:xml:ns:keyrelay-1.0 - - - - - XML - - post '/epp/session/login', params: { frame: request_xml }, - headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } - - post '/epp/session/login', params: { frame: request_xml }, - headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" } - - response_xml = Nokogiri::XML(response.body) - assert_epp_response :session_limit_exceeded_server_closing_connection - assert_correct_against_schema response_xml - assert response.body.include?(Shunter.default_error_message) - ENV["shunter_default_threshold"] = '10000' - ENV["shunter_enabled"] = 'false' - end - private def eliminate_effect_of_existing_epp_sessions diff --git a/test/integration/repp/v1/contacts/search_test.rb b/test/integration/repp/v1/contacts/search_test.rb index 949e9718d..dfbed9cee 100644 --- a/test/integration/repp/v1/contacts/search_test.rb +++ b/test/integration/repp/v1/contacts/search_test.rb @@ -49,11 +49,11 @@ class ReppV1ContactsSearchTest < ActionDispatch::IntegrationTest ENV["shunter_enabled"] = 'true' get '/repp/v1/contacts/search', headers: @auth_headers, params: { query: '000' } - get '/repp/v1/contacts/search', headers: @auth_headers, params: { query: 'j' } - json = JSON.parse(response.body, symbolize_names: true) - assert_equal json[:code], 2502 - assert response.body.include?(Shunter.default_error_message) + assert_raise Shunter::ThrottleError do + get '/repp/v1/contacts/search', headers: @auth_headers, params: { query: '000' } + end + ENV["shunter_default_threshold"] = '10000' ENV["shunter_enabled"] = 'false' end From 88090523fff30a8669492901f7b943d294dc9538 Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Sun, 7 Aug 2022 11:31:58 -0300 Subject: [PATCH 11/12] Add rate limiting to missing actions --- app/controllers/epp/sessions_controller.rb | 2 +- .../repp/v1/registrar/accreditation_info_controller.rb | 7 +++++-- app/controllers/repp/v1/registrar/auth_controller.rb | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/epp/sessions_controller.rb b/app/controllers/epp/sessions_controller.rb index 715b6d8f7..0667c9adc 100644 --- a/app/controllers/epp/sessions_controller.rb +++ b/app/controllers/epp/sessions_controller.rb @@ -3,7 +3,7 @@ module Epp skip_authorization_check only: [:hello, :login, :logout] before_action :set_paper_trail_whodunnit - THROTTLED_ACTIONS = %i[login].freeze + THROTTLED_ACTIONS = %i[login hello].freeze include Shunter::Integration::Throttle def hello diff --git a/app/controllers/repp/v1/registrar/accreditation_info_controller.rb b/app/controllers/repp/v1/registrar/accreditation_info_controller.rb index c55a561d2..dfb1fc3b0 100644 --- a/app/controllers/repp/v1/registrar/accreditation_info_controller.rb +++ b/app/controllers/repp/v1/registrar/accreditation_info_controller.rb @@ -3,8 +3,11 @@ module Repp module Registrar class AccreditationInfoController < BaseController if Feature.allow_accr_endspoints? - api :GET, 'repp/v1/registrar/accreditation/get_info' - desc 'check login user and return data' + THROTTLED_ACTIONS = %i[index].freeze + include Shunter::Integration::Throttle + + api :GET, 'repp/v1/registrar/accreditation/get_info' + desc 'check login user and return data' def index login = current_user diff --git a/app/controllers/repp/v1/registrar/auth_controller.rb b/app/controllers/repp/v1/registrar/auth_controller.rb index c5897bcea..5da1b3a38 100644 --- a/app/controllers/repp/v1/registrar/auth_controller.rb +++ b/app/controllers/repp/v1/registrar/auth_controller.rb @@ -6,7 +6,7 @@ module Repp skip_before_action :check_ip_restriction, only: :tara_callback skip_before_action :validate_client_certs, only: :tara_callback - THROTTLED_ACTIONS = %i[index].freeze + THROTTLED_ACTIONS = %i[index tara_callback].freeze include Shunter::Integration::Throttle api :GET, 'repp/v1/registrar/auth' From 0b761498fd4565614d46041cb92d07e9e8ae4ea9 Mon Sep 17 00:00:00 2001 From: Thiago Youssef Date: Sun, 7 Aug 2022 11:57:30 -0300 Subject: [PATCH 12/12] Skip action tara callback authorization --- app/controllers/repp/v1/base_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb index fde6a35f1..903c9e58b 100644 --- a/app/controllers/repp/v1/base_controller.rb +++ b/app/controllers/repp/v1/base_controller.rb @@ -173,7 +173,7 @@ module Repp end def throttled_user - authorize!(:throttled_user, @domain) unless current_user + authorize!(:throttled_user, @domain) unless current_user || action_name == 'tara_callback' current_user end end