Module: Datadog::AppSec::APISecurity::RouteExtractor
- Defined in:
- lib/datadog/appsec/api_security/route_extractor.rb
Overview
This is a helper module to extract the route pattern from the Rack::Request.
Constant Summary collapse
- SINATRA_ROUTE_KEY =
'sinatra.route'- SINATRA_ROUTE_SEPARATOR =
' '- GRAPE_ROUTE_KEY =
'grape.routing_args'- RAILS_ROUTE_URI_PATTERN_KEY =
'action_dispatch.route_uri_pattern'- RAILS_ROUTE_KEY =
Rails 8.1.1+
'action_dispatch.route'- RAILS_ROUTES_KEY =
'action_dispatch.routes'- RAILS_PATH_PARAMS_KEY =
'action_dispatch.request.path_parameters'- RAILS_FORMAT_SUFFIX =
'(.:format)'
Class Method Summary collapse
-
.route_pattern(request) ⇒ Object
HACK: We rely on the fact that each contrib will modify ‘request.env` and store information sufficient to compute the canonical route (ex: `/users/:id`).
Class Method Details
.route_pattern(request) ⇒ Object
HACK: We rely on the fact that each contrib will modify ‘request.env`
and store information sufficient to compute the canonical
route (ex: `/users/:id`).
When contribs like Sinatra or Grape are used, they could be mounted
into the Rails app, hence you can see the use of the `script_name`
that will contain the path prefix of the mounted app.
Rack
does not support named arguments, so we have to use `path`
Sinatra
uses `sinatra.route` with a string like "GET /users/:id"
Grape
uses `grape.routing_args` with a hash with a `:route_info` key
that contains a `Grape::Router::Route` object that contains
`Grape::Router::Pattern` object with an `origin` method
Rails < 7.1 (slow path)
uses `action_dispatch.routes` to store `ActionDispatch::Routing::RouteSet`
which can recognize requests
Rails > 7.1 (fast path)
uses `action_dispatch.route_uri_pattern` with a string like
"/users/:id(.:format)"
Rails > 8.1.1 (fast path)
uses `action_dispatch.route` to store the ActionDispatch::Journey::Route
that matched when the request was routed
WARNING: This method works only after the request has been routed.
WARNING: In Rails > 7.1 when a route was not found,
`action_dispatch.route_uri_pattern` will not be set.
In Rails < 7.1 it also will not be set even if a route was found,
but in this case `action_dispatch.request.path_parameters` won't be empty.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/datadog/appsec/api_security/route_extractor.rb', line 51 def self.route_pattern(request) if request.env.key?(GRAPE_ROUTE_KEY) pattern = request.env[GRAPE_ROUTE_KEY][:route_info]&.pattern&.origin "#{request.script_name}#{pattern}" elsif request.env.key?(SINATRA_ROUTE_KEY) pattern = request.env[SINATRA_ROUTE_KEY].split(SINATRA_ROUTE_SEPARATOR, 2)[1] "#{request.script_name}#{pattern}" elsif request.env.key?(RAILS_ROUTE_KEY) request.env[RAILS_ROUTE_KEY].path.spec.to_s.delete_suffix(RAILS_FORMAT_SUFFIX) elsif request.env.key?(RAILS_ROUTE_URI_PATTERN_KEY) request.env[RAILS_ROUTE_URI_PATTERN_KEY].delete_suffix(RAILS_FORMAT_SUFFIX) elsif request.env.key?(RAILS_ROUTES_KEY) && !request.env.fetch(RAILS_PATH_PARAMS_KEY, {}).empty? # NOTE: In Rails < 7.1 this `request` argument will be a Rack::Request, # it does not have all the methods that ActionDispatch::Request has. # Before trying to use the router to recognize the route, we need to # create a new ActionDispatch::Request from the request env # # NOTE: Rails mutates HEAD request by changing the method to GET # and uses it for route recognition to check if the route is defined request = request.env[RAILS_ROUTES_KEY].request_class.new(request.env) pattern = request.env[RAILS_ROUTES_KEY].router .recognize(request) { |route, _| break route.path.spec.to_s } # NOTE: If rails is unable to recognize request it returns empty Array pattern = nil if pattern&.empty? # NOTE: If rails can't recognize the request, we are going to fallback # to generic request path (pattern || request.path).delete_suffix(RAILS_FORMAT_SUFFIX) else Tracing::Contrib::Rack::RouteInference.read_or_infer(request.env) end rescue => e AppSec.telemetry&.report(e, description: 'AppSec: Could not extract route pattern') nil end |