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_KEY =
'action_dispatch.route_uri_pattern'
RAILS_ROUTES_KEY =
'action_dispatch.routes'
RAILS_FORMAT_SUFFIX =
'(.:format)'

Class Method Summary collapse

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)"

WARNING: This method works only after the request has been routed.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/datadog/appsec/api_security/route_extractor.rb', line 39

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].delete_suffix(RAILS_FORMAT_SUFFIX)
  elsif request.env.key?(RAILS_ROUTES_KEY)
    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
    request.path
  end
end