Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Негативное тестирование “REST” API и защита от него

Негативное тестирование “REST” API и защита от него

Dmitry Efimov

February 08, 2018
Tweet

More Decks by Dmitry Efimov

Other Decks in Programming

Transcript

  1. создадим новое API-приложение на Rails ruby -v ruby 2.5.0p0 (2017-12-25

    revision 61468) [x86_64-darwin17] rails -v Rails 5.1.4 rails new backend --api -d postgresql --skip-sprockets --skip- javascript --skip-turbolinks --skip-test-unit --skip-spring
  2. в консоле приложения Started GET "/user" for 127.0.0.1 at 2018-02-08

    01:09:22 +0400 ActionController::RoutingError (No route matches [GET] "/user"): actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:63:in `call' actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call' railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app' railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call' activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged' activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged' activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged' railties (5.1.4) lib/rails/rack/logger.rb:24:in `call' actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call' actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call' rack (2.0.4) lib/rack/runtime.rb:22:in `call' activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call' actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call' actionpack (5.1.4) lib/action_dispatch/middleware/static.rb:125:in `call' rack (2.0.4) lib/rack/sendfile.rb:111:in `call' railties (5.1.4) lib/rails/engine.rb:522:in `call' puma (3.11.2) lib/puma/configuration.rb:225:in `call' puma (3.11.2) lib/puma/server.rb:624:in `handle_request' puma (3.11.2) lib/puma/server.rb:438:in `process_client' puma (3.11.2) lib/puma/server.rb:302:in `block in run' puma (3.11.2) lib/puma/thread_pool.rb:120:in `block in spawn_thread'
  3. в curl … < HTTP/1.1 404 Not Found < Content-Type:

    application/json; charset=UTF-8 … { "status":404, "error":"Not Found", “exception":"#\u003cActionController::RoutingError: No route matches [GET] \"/user\"\u003e", "traces":{ огромный backtrace } }
  4. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end кастомные мидлвары контроллеры
  5. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end инициализация кастомные мидлвары контроллеры
  6. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end выполнение, т.е. обработка запроса/ответа/выкинутой ошибки кастомные мидлвары контроллеры
  7. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app #ловит эксепшен выброшенный ниже middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end обработчик данного кейса кастомные мидлвары контроллеры
  8. создадим контроллер с экшеном и передадим тело которые нельзя спарсить

    rake routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index curl -v -X GET http://localhost:3000/users -d q=1 -H 'Content- Type: application/json’
  9. в консоле приложения ActionDispatch::Http::Parameters::ParseError (765: unexpected token at 'q=1'): /Users/dmitry/github/rails/actionpack/lib/action_dispatch/http/parameters.rb:115:in

    `rescue in parse_formatted_parameters' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/http/parameters.rb:109:in `parse_formatted_parameters' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/http/request.rb:361:in `block in POST' rack (2.0.4) lib/rack/request.rb:57:in `fetch' rack (2.0.4) lib/rack/request.rb:57:in `fetch_header' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/http/request.rb:360:in `POST' /Users/dmitry/github/rails/actionpack/lib/action_controller/metal/params_wrapper.rb:286:in `_wrapper_enabled?' /Users/dmitry/github/rails/actionpack/lib/action_controller/metal/params_wrapper.rb:235:in `process_action' /Users/dmitry/github/rails/activerecord/lib/active_record/railties/controller_runtime.rb:22:in `process_action' /Users/dmitry/github/rails/actionpack/lib/abstract_controller/base.rb:124:in `process' /Users/dmitry/github/rails/actionpack/lib/action_controller/metal.rb:189:in `dispatch' /Users/dmitry/github/rails/actionpack/lib/action_controller/metal.rb:253:in `dispatch' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/routing/route_set.rb:49:in `dispatch' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/routing/route_set.rb:31:in `serve' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/journey/router.rb:50:in `block in serve' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/journey/router.rb:33:in `each' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/journey/router.rb:33:in `serve' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/routing/route_set.rb:834:in `call' rack (2.0.4) lib/rack/etag.rb:25:in `call' rack (2.0.4) lib/rack/conditional_get.rb:25:in `call' rack (2.0.4) lib/rack/head.rb:12:in `call' /Users/dmitry/github/rails/activerecord/lib/active_record/migration.rb:556:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:26:in `block in call' /Users/dmitry/github/rails/activesupport/lib/active_support/callbacks.rb:97:in `run_callbacks' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:24:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/executor.rb:12:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/show_exceptions.rb:31:in `call' /Users/dmitry/github/rails/railties/lib/rails/rack/logger.rb:36:in `call_app' /Users/dmitry/github/rails/railties/lib/rails/rack/logger.rb:24:in `block in call' /Users/dmitry/github/rails/activesupport/lib/active_support/tagged_logging.rb:69:in `block in tagged' /Users/dmitry/github/rails/activesupport/lib/active_support/tagged_logging.rb:26:in `tagged' /Users/dmitry/github/rails/activesupport/lib/active_support/tagged_logging.rb:69:in `tagged' /Users/dmitry/github/rails/railties/lib/rails/rack/logger.rb:24:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/remote_ip.rb:79:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/request_id.rb:25:in `call' rack (2.0.4) lib/rack/runtime.rb:22:in `call' /Users/dmitry/github/rails/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/executor.rb:12:in `call' /Users/dmitry/github/rails/actionpack/lib/action_dispatch/middleware/static.rb:125:in `call' rack (2.0.4) lib/rack/sendfile.rb:111:in `call' /Users/dmitry/github/rails/railties/lib/rails/engine.rb:522:in `call' puma (3.11.2) lib/puma/configuration.rb:225:in `call' puma (3.11.2) lib/puma/server.rb:624:in `handle_request' puma (3.11.2) lib/puma/server.rb:438:in `process_client' puma (3.11.2) lib/puma/server.rb:302:in `block in run' puma (3.11.2) lib/puma/thread_pool.rb:120:in `block in spawn_thread'
  10. в curl … < HTTP/1.1 400 Bad Request < Content-Type:

    text/html; charset=UTF-8 … { "status":400, "error":"Bad Request" }
  11. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app #ловит эксепшен выброшенный ниже middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end обработчик данного кейса кастомные мидлвары контроллеры
  12. 4 негативный кейс: что делать когда мы ожидаем получить имя

    пользователя строкой, но приходит число?
  13. def build_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options

    end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" end end кастомные мидлвары контроллеры свой “firewall”