Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Негативное тестирование “REST” API и защита от ...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Dmitry Efimov
February 08, 2018
Programming
350
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Негативное тестирование “REST” API и защита от него
Dmitry Efimov
February 08, 2018
More Decks by Dmitry Efimov
See All by Dmitry Efimov
Автоматизируем синхронизацию HTTP API и документации
tuwilof
0
52
Автоматизация Document-Driven Development для проектов с большим API
tuwilof
0
52
Инструменты для обнаружения рассинхронизации реализации “REST” API от документации
tuwilof
0
58
Валидация “REST” API по документации APIB
tuwilof
0
51
Ликбез по JSON
tuwilof
0
94
API Blueprint
tuwilof
0
89
Документирование API
tuwilof
0
200
Почему и как заменить все id на UUID
tuwilof
0
110
Authentication in rails. Monolith vs SPA
tuwilof
0
220
Other Decks in Programming
See All in Programming
Next.js: The final boss of Web Performance
guaca
0
280
Jetpack Compose Mechanisms
skydoves
3
820
個人開発者よ、Honoを使え(おじさんver)
tanahiro2010
2
260
Performance Optimization With LLMs
xleitix
0
150
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
110
3回つくってわかった、 定期実行ジョブ基盤の本質情報
takai
0
520
Agents on Rails - Rails at Scale 2026
irinanazarova
0
570
Railsバージョンを4倍にした話
optim
0
460
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1.2k
作れる、でも消せるか?──安全に「枯らして消す」機能削除術 / Drain, Then Delete
chobishiba
2
800
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
750
Rでドレミ/Do-Re-Mi_with_R
florets1
0
130
Featured
See All Featured
Technical Leadership for Architectural Decision Making
baasie
3
600
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
It's Worth the Effort
3n
188
29k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Google's AI Overviews - The New Search
badams
0
1.6k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
480
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
Rails Girls Zürich Keynote
gr2m
96
14k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
600
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
410
Git: the NoSQL Database
bkeepers
PRO
433
67k
Transcript
Негативное тестирование “REST” API и защита от него
создадим новое 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
1 негативный кейс: не существующий запрос
сразу в бой. запускам и выполняем нереализованный запрос. rails s
curl -X GET http://localhost:3000/user
в консоле приложения 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'
в 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 } }
в браузере
что произошло?
DefaultMiddlewareStack
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 кастомные мидлвары контроллеры
как это работает?
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 инициализация кастомные мидлвары контроллеры
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 выполнение, т.е. обработка запроса/ответа/выкинутой ошибки кастомные мидлвары контроллеры
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 обработчик данного кейса кастомные мидлвары контроллеры
2 негативный кейс: contend-type запроса не соответствуют телу
создадим контроллер с экшеном и передадим тело которые нельзя спарсить
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’
в консоле приложения 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'
в curl … < HTTP/1.1 400 Bad Request < Content-Type:
text/html; charset=UTF-8 … { "status":400, "error":"Bad Request" }
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 обработчик данного кейса кастомные мидлвары контроллеры
3 негативный кейс: contend-type json, его можно спарсить, но по
rfc он невалидный
там все криво спарсится, но отработает везде без ошибок, по
тихому
дальше уже все негативные кейсы ложатся на плечи контролеров и
т.д.
4 негативный кейс: что делать когда мы ожидаем получить имя
пользователя строкой, но приходит число?
5 негативный кейс: что делать когда content-type соответствует телу, но
мы ожидаем другой?
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”
нужен некий источник истины понятный человеку и машине
None