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
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Негативное тестирование “REST” API и защита от ...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
51
Автоматизация Document-Driven Development для проектов с большим API
tuwilof
0
52
Инструменты для обнаружения рассинхронизации реализации “REST” API от документации
tuwilof
0
58
Валидация “REST” API по документации APIB
tuwilof
0
48
Ликбез по JSON
tuwilof
0
93
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
Press start. Python's next generation.
willingc
PRO
3
320
The Good Stuff, Not the Slop: Engineering High-Quality Android Apps with Modern AI Tooling
danybony
1
210
XHTMLが残したもの
yosuke_furukawa
PRO
2
460
AIエージェント時代のコードレビューを設計する
nogu66
6
2.6k
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
1
160
RSSとCodexを使ってX投稿自動化してみた
ochtum
0
110
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
120
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
150
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
1
470
Claude Codeを組織的に動かして月400PRを実現した話
happy_ryo
0
260
Laravelのアプリケーションをどこにデプロイするか #ツナギメオフライン.9
akase244
0
120
ALB ログから Trace を気合で繋げる技術
fohte
7
880
Featured
See All Featured
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
240
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
410
New Earth Scene 8
popppiees
3
2.5k
Context Engineering - Making Every Token Count
addyosmani
9
1.1k
30 Presentation Tips
portentint
PRO
1
390
How GitHub (no longer) Works
holman
316
150k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
Prompt Engineering for Job Search
mfonobong
0
450
Game over? The fight for quality and originality in the time of robots
wayneb77
1
270
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.7k
Designing for humans not robots
tammielis
254
26k
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