contrast-agent 4.9.1 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec_parallel +6 -0
  4. data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
  5. data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
  6. data/lib/contrast/agent/assess/contrast_event.rb +0 -1
  7. data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
  8. data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
  9. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
  10. data/lib/contrast/agent/assess/policy/preshift.rb +8 -5
  11. data/lib/contrast/agent/assess/policy/propagation_method.rb +100 -57
  12. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +0 -2
  13. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  14. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
  15. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
  16. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
  17. data/lib/contrast/agent/assess/policy/source_method.rb +13 -17
  18. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
  19. data/lib/contrast/agent/assess/policy/trigger_method.rb +59 -83
  20. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  21. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
  22. data/lib/contrast/agent/disable_reaction.rb +1 -1
  23. data/lib/contrast/agent/exclusion_matcher.rb +0 -4
  24. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  25. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +5 -4
  26. data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
  27. data/lib/contrast/agent/middleware.rb +1 -0
  28. data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
  29. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +18 -12
  30. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  31. data/lib/contrast/agent/patching/policy/patch.rb +5 -0
  32. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  33. data/lib/contrast/agent/patching/policy/patcher.rb +8 -8
  34. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  35. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  36. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  37. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  38. data/lib/contrast/agent/reaction_processor.rb +1 -1
  39. data/lib/contrast/agent/request.rb +5 -2
  40. data/lib/contrast/agent/request_context.rb +19 -22
  41. data/lib/contrast/agent/static_analysis.rb +1 -1
  42. data/lib/contrast/agent/tracepoint_hook.rb +6 -1
  43. data/lib/contrast/agent/version.rb +1 -1
  44. data/lib/contrast/api/communication/messaging_queue.rb +12 -6
  45. data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
  46. data/lib/contrast/api/communication/socket_client.rb +4 -4
  47. data/lib/contrast/api/decorators/agent_startup.rb +4 -4
  48. data/lib/contrast/api/decorators/application_startup.rb +6 -5
  49. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  50. data/lib/contrast/components/agent.rb +5 -2
  51. data/lib/contrast/components/assess.rb +6 -3
  52. data/lib/contrast/components/base.rb +2 -2
  53. data/lib/contrast/components/config.rb +1 -0
  54. data/lib/contrast/components/contrast_service.rb +4 -2
  55. data/lib/contrast/components/logger.rb +13 -8
  56. data/lib/contrast/components/scope.rb +9 -28
  57. data/lib/contrast/config/base_configuration.rb +14 -6
  58. data/lib/contrast/configuration.rb +19 -15
  59. data/lib/contrast/extension/assess/array.rb +1 -11
  60. data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
  61. data/lib/contrast/extension/assess/fiber.rb +0 -11
  62. data/lib/contrast/extension/assess/hash.rb +0 -10
  63. data/lib/contrast/extension/assess/kernel.rb +1 -10
  64. data/lib/contrast/extension/assess/marshal.rb +3 -11
  65. data/lib/contrast/extension/assess/regexp.rb +0 -11
  66. data/lib/contrast/extension/assess/string.rb +1 -26
  67. data/lib/contrast/extension/extension.rb +61 -0
  68. data/lib/contrast/extension/protect/kernel.rb +0 -10
  69. data/lib/contrast/framework/grape/support.rb +174 -0
  70. data/lib/contrast/framework/manager.rb +42 -6
  71. data/lib/contrast/framework/rack/support.rb +1 -1
  72. data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
  73. data/lib/contrast/framework/rails/patch/support.rb +6 -3
  74. data/lib/contrast/framework/rails/railtie.rb +1 -1
  75. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
  76. data/lib/contrast/framework/rails/support.rb +60 -13
  77. data/lib/contrast/framework/sinatra/support.rb +1 -1
  78. data/lib/contrast/logger/log.rb +89 -15
  79. data/lib/contrast/utils/io_util.rb +1 -1
  80. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  81. data/lib/contrast/utils/tag_util.rb +2 -1
  82. data/resources/assess/policy.json +197 -2
  83. data/resources/deadzone/policy.json +10 -0
  84. data/ruby-agent.gemspec +10 -1
  85. metadata +78 -12
  86. data/lib/contrast/utils/inventory_util.rb +0 -113
@@ -22,16 +22,6 @@ module Contrast
22
22
 
23
23
  context.reset_activity
24
24
  end
25
-
26
- def instrument
27
- @_instrument ||= begin
28
- require 'cs__protect_kernel/cs__protect_kernel'
29
- true
30
- end
31
- rescue StandardError, LoadError => e
32
- logger.error('Error loading kernel protect patch', e)
33
- false
34
- end
35
25
  end
36
26
  end
37
27
  end
@@ -0,0 +1,174 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/framework/base_support'
5
+ require 'contrast/components/logger'
6
+
7
+ module Contrast
8
+ module Framework
9
+ module Grape
10
+ # Used when Grape is present to define framework specific behaviour
11
+ class Support
12
+ extend Contrast::Framework::BaseSupport
13
+ class << self
14
+ include Contrast::Components::Logger::InstanceMethods
15
+ def detection_class
16
+ 'Grape::API'
17
+ end
18
+
19
+ def version
20
+ ::Grape::VERSION
21
+ end
22
+
23
+ def application_name
24
+ app_class&.cs__name
25
+ end
26
+
27
+ def application_root
28
+ app_instance&.root
29
+ end
30
+
31
+ def server_type
32
+ 'grape'
33
+ end
34
+
35
+ # Given an object, determine if it is a Grape controller.
36
+ # Which could include cases of ::Grape::API subclass or actual class
37
+ #
38
+ # @param app [Object] suspected Grape app.
39
+ # @return [Boolean]
40
+ def grape_controller? app
41
+ # Grape is loaded?
42
+ return false unless grape_defined?
43
+
44
+ # App is a subclass of or actually is ::Grape::API.
45
+ return false unless app.cs__respond_to?(:<=) && app <= ::Grape::API
46
+
47
+ true
48
+ end
49
+
50
+ # Find all classes that subclass ::Grape::API, Gather their routes
51
+ #
52
+ # @return [Array<Contrast::Api::Dtm::RouteCoverage>, Array]- founded routes as Dtms
53
+ def collect_routes
54
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless grape_defined?
55
+
56
+ # Each Grape controller has endpoints and each endpoints has routes
57
+ # and that's why we need to go through each one and create separate RouteCoverage object
58
+ routes = []
59
+ grape_controllers.each do |c|
60
+ c&.endpoints&.each do |endpoint|
61
+ endpoint&.routes&.map do |r|
62
+ pattern = r.pattern.pattern
63
+ temp = Contrast::Api::Dtm::RouteCoverage.from_grape_controller(c, r.request_method, pattern, r.path)
64
+ routes << temp
65
+ end
66
+ end
67
+ end
68
+ routes
69
+ end
70
+
71
+ # Given the current request return a RouteCoverage dtm.
72
+ #
73
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
74
+ # @param controller [::Grape::API] optionally use this controller instead of global ::Grape::API.
75
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil] a Dtm describing the route
76
+ # matched to the request if a match was found.
77
+ def current_route request, controller = ::Grape::API, full_route = nil
78
+ return unless grape_controller?(controller)
79
+
80
+ method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
81
+
82
+ # Find final controller - actually we gotta match the route to the scanned application
83
+ # Initially Grape compiles all routes on startup, so we can use the url from the request
84
+ # and create the observed route
85
+ # Class < Grape::API, Grape::Router::Route
86
+ final_controller, route_pattern = _route_recurse(method, _cleaned_route(request), grape_controllers)
87
+ return unless final_controller
88
+
89
+ full_route ||= request.env[::Rack::PATH_INFO]
90
+
91
+ Contrast::Api::Dtm::RouteCoverage.from_grape_controller(final_controller, method, route_pattern, full_route)
92
+ end
93
+
94
+ # Search object space for grape controllers--any class that subclasses ::Grape::API.
95
+ #
96
+ # @return [Array<::Grape::API>] grape controllers
97
+ def grape_controllers
98
+ ObjectSpace.each_object(Class).select { |klass| klass < ::Grape::API }
99
+ end
100
+
101
+ # Grape Request inherits the same as the Sinatra, so we can easily call it as it's called in Sinatra
102
+ def retrieve_request env
103
+ ::Grape::Request.new(env)
104
+ end
105
+
106
+ private
107
+
108
+ # Determine if, at the time of our Framework Support determination, Grape has been defined.
109
+ #
110
+ # @return [Boolean]
111
+ def grape_defined?
112
+ @_grape_defined = !!(defined?(::Grape) && defined?(::Grape::API)) if @_grape_defined.nil?
113
+ @_grape_defined
114
+ end
115
+
116
+ # @param method [::Rack::REQUEST_METHOD] GET, POST, PUT, etc...
117
+ # @param route [String] the relative route passed from Rack.
118
+ # @param controllers [Array<::Grape::API>] All Grape controllers found
119
+ # @return [Array[::Grape::API]], nil] Either the controller that
120
+ # will handle the route along with the route pattern or nil if no match.
121
+ def _route_recurse method, route, controllers = grape_controllers
122
+ # return if there aren't any controllers
123
+ return unless controllers&.any?
124
+
125
+ # Here we can go through the all detected controllers
126
+ # and find the one that's routes include the current one
127
+ # Grape controller actually has endpoints and each endpoint
128
+ # has routes and that's why we need to do it that way
129
+ controller = controllers.pop
130
+ return _route_recurse method, route, controllers unless controller
131
+
132
+ contr_routes = controller.endpoints&.map(&:routes)&.flatten || []
133
+ route_pattern = contr_routes&.find do |r|
134
+ r.pattern.to_regexp.match(route) # ::Mustermann::Grape match
135
+ end
136
+ return controller, route_pattern unless route_pattern.nil?
137
+
138
+ _route_recurse method, route, controllers
139
+ end
140
+
141
+ # Get route and do some cleaning
142
+ #
143
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
144
+ # @return [String] the extracted and cleaned relative route.
145
+ def _cleaned_route request
146
+ route = request.env[::Rack::PATH_INFO]
147
+ return '/' if route.empty?
148
+
149
+ route.end_with?('/') ? route[0..-2] : route
150
+ end
151
+
152
+ def app_class
153
+ return unless grape_defined?
154
+
155
+ app_instance.cs__class
156
+ end
157
+
158
+ # Search the object space for the controller handling this request which will be
159
+ # the class inheriting from ::Grape::API with @app=nil
160
+ #
161
+ # @return [::Grape::API] the current controller as routed by Rack.
162
+ def app_instance
163
+ return unless grape_defined?
164
+
165
+ @_app_instance ||= begin
166
+ grape_layers = ObjectSpace.each_object(::Grape::API).to_a
167
+ grape_layers.find { |layer| layer.app.nil? }
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -2,9 +2,11 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/extension/module'
5
6
  require 'contrast/framework/platform_version'
6
7
  require 'contrast/framework/rack/support'
7
8
  require 'contrast/framework/rails/support'
9
+ require 'contrast/framework/grape/support'
8
10
  require 'contrast/framework/sinatra/support'
9
11
  require 'contrast/utils/class_util'
10
12
 
@@ -19,7 +21,7 @@ module Contrast
19
21
  # do not exist
20
22
  SUPPORTED_FRAMEWORKS = [
21
23
  Contrast::Framework::Rails::Support, Contrast::Framework::Sinatra::Support,
22
- Contrast::Framework::Rack::Support
24
+ Contrast::Framework::Grape::Support, Contrast::Framework::Rack::Support
23
25
  ].cs__freeze
24
26
 
25
27
  def initialize
@@ -77,15 +79,24 @@ module Contrast
77
79
  found || ::Rack::Directory.new('').root
78
80
  end
79
81
 
80
- # If we have 0 or n > 1 frameworks, we need to use the default rack request
82
+ # Build a request from the provided env, based on the framework(s) we're currently supporting.
81
83
  #
82
84
  # @param env [Hash] the various variables stored by this and other Middlewares to know the state and values
83
85
  # of this particular Request
84
86
  # @return [::Rack::Request] either a rack request or subclass thereof.
85
87
  def retrieve_request env
88
+ # If we're mounted on Rails, use Rails.
89
+ if @_frameworks.include?(Contrast::Framework::Rails::Support)
90
+ return Contrast::Framework::Rails::Support.retrieve_request(env)
91
+ end
92
+
93
+ # If we know the framework, use it.
86
94
  return @_frameworks[0].retrieve_request(env) if @_frameworks.length == 1
87
95
 
96
+ # Fall back on a regular Rack::Request
88
97
  ::Rack::Request.new(env)
98
+ rescue StandardError => e
99
+ logger.warn('Unable to retrieve_request', e)
89
100
  end
90
101
 
91
102
  # @param env [Hash] the various variables stored by this and other Middlewares to know the state
@@ -109,6 +120,34 @@ module Contrast
109
120
  @_frameworks.lazy.map { |framework_support| framework_support.current_route(request) }.reject(&:nil?).first
110
121
  end
111
122
 
123
+ # Sometimes the framework we want to instrument is loaded after our agent code. To catch that case, we'll detect
124
+ # if the loaded_module is the marker class for any of our supported frameworks. If it is, and we don't already
125
+ # have support enabled, we'll enable it now. We'll also need to catch up on any other startup actions that we've
126
+ # missed. Most likely, this is only necessary for those applications which have applications mounted on them.
127
+ #
128
+ # @param mod [Module] the module or class that was just loaded
129
+ def register_late_framework mod
130
+ return unless mod
131
+
132
+ module_name = mod.cs__name
133
+ # Otherwise, check if the provided module_name requires us to register a new support
134
+ SUPPORTED_FRAMEWORKS.each do |framework|
135
+ next if @_frameworks.include?(framework)
136
+ next unless module_name == framework.detection_class
137
+
138
+ @_frameworks << framework
139
+ # Report the registered routes of that framework now that we know we need to find them
140
+ app_update_msg = Contrast::Api::Dtm::ApplicationUpdate.build
141
+ Contrast::Agent.messaging_queue.send_event_eventually(app_update_msg)
142
+ logger.info('Framework detected after initialization. Enabling support.',
143
+ framework: framework.detection_class,
144
+ frameworks: @_frameworks)
145
+ break
146
+ end
147
+ rescue StandardError => e
148
+ logger.warn('Unable to register a late framework', e, module: mod.cs__name)
149
+ end
150
+
112
151
  private
113
152
 
114
153
  def enable_framework_support? klass
@@ -124,10 +163,7 @@ module Contrast
124
163
  # @param method_name [Symbol] the method to call on each FrameworkSupport class
125
164
  # @return [Array]
126
165
  def data_for_all_frameworks method_name
127
- data = @_frameworks.flat_map do |framework|
128
- framework.send(method_name)
129
- end
130
- data.compact
166
+ @_frameworks.flat_map { |framework| framework.send(method_name) }.compact
131
167
  end
132
168
 
133
169
  # This returns a single object from the first framework to successfully respond
@@ -14,7 +14,7 @@ module Contrast
14
14
  extend Contrast::Framework::Rack::Patch::Support
15
15
  class << self
16
16
  def detection_class
17
- 'don\'t let me be detected'
17
+ 'rack -- don\'t let me be detected'
18
18
  end
19
19
  end
20
20
  end
@@ -11,7 +11,6 @@ module Contrast
11
11
  module AssessConfiguration
12
12
  include Contrast::Components::Logger::InstanceMethods
13
13
 
14
-
15
14
  CS__SESSION_TIMEOUT_NAME = 'session-timeout'
16
15
  SAFE_SESSION_TIMEOUT = (30 * 60 * 1000)
17
16
  CS__SECURE_RULE_NAME = 'secure-flag-missing'
@@ -29,12 +29,14 @@ module Contrast
29
29
  Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
30
30
  'ActionController::Live::Buffer',
31
31
  'contrast/framework/rails/patch/action_controller_live_buffer',
32
- instrumenting_module: 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
32
+ instrumenting_module:
33
+ 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
33
34
  Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
34
35
  'Rails::Application::Configuration',
35
36
  'contrast/framework/rails/patch/rails_application_configuration',
36
37
  method_to_instrument: :session_store,
37
- instrumenting_module: 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
38
+ instrumenting_module:
39
+ 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
38
40
  ])
39
41
  if RUBY_VERSION < '2.6.0'
40
42
  patches.merge([
@@ -61,7 +63,8 @@ module Contrast
61
63
  'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
62
64
  'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
63
65
  method_to_instrument: :inherited,
64
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
66
+ instrumenting_module:
67
+ 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
65
68
  ])
66
69
  end
67
70
  patches
@@ -12,7 +12,7 @@ module Contrast
12
12
  include Contrast::Components::Logger::InstanceMethods
13
13
 
14
14
  initializer 'Contrast Ruby Agent Initializer' do |app|
15
- log_rails = defined?(Rails) && defined?(Rails.logger)
15
+ log_rails = defined?(Rails) && defined?(Rails.logger)
16
16
 
17
17
  Rails.logger.debug("In railtie ::#{ app.middleware.inspect }") if log_rails
18
18
 
@@ -18,6 +18,7 @@ module Contrast
18
18
  # being phased out with support for those language versions.
19
19
  class ActiveRecordNamed
20
20
  include Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Components::Logger::InstanceMethods
21
22
 
22
23
  class << self
23
24
  def rewrite mod, method_name, body
@@ -13,6 +13,8 @@ module Contrast
13
13
  class Support
14
14
  extend Contrast::Framework::BaseSupport
15
15
  extend Contrast::Framework::Rails::Patch::Support
16
+ include Contrast::Components::Logger::InstanceMethods
17
+ extend Contrast::Components::Logger::InstanceMethods
16
18
 
17
19
  class << self
18
20
  RAILS_MODULE_NAME_VERSION = Gem::Version.new('6.0.0')
@@ -49,31 +51,36 @@ module Contrast
49
51
  # Find the current route, based on the provided Request wrapper
50
52
  #
51
53
  # @param request[Contrast::Agent::Request]
52
- # @return [Contrast::Api::Dtm::RouteCoverage]
54
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
53
55
  def current_route request
54
56
  return unless ::Rails.cs__respond_to?(:application)
55
57
 
58
+ # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
56
59
  match, _params, route, path = get_full_route(request.rack_request)
60
+ unless route
61
+ logger.warn('Unable to determine the current route of this request')
62
+ return
63
+ end
57
64
 
58
65
  original_url = request.rack_request.path_info
59
-
66
+ mounted_app = route&.app&.app
60
67
  # Route is either the final rails route, or a router that points to a Sinatra controller.
61
- if Contrast::Framework::Sinatra::Support.sinatra_controller?(route.app.app)
62
- # Create a request copied from current request, but with the base path removed from path_info.
63
- new_req = ::ActionDispatch::Request.new(request.env)
64
- new_req.path_info = new_req.path_info.gsub((path << match).join, '')
65
-
66
- return Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
68
+ if mounted_app && Contrast::Framework::Sinatra::Support.sinatra_controller?(mounted_app)
69
+ return mounted_sinatra_route(request, match, path, route, original_url)
70
+ end
71
+ if mounted_app && Contrast::Framework::Grape::Support.grape_controller?(mounted_app)
72
+ return mounted_grape_route(request, match, path, route, original_url)
67
73
  end
68
74
 
69
75
  Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route, original_url)
70
- rescue StandardError => _e
76
+ rescue StandardError => e
77
+ logger.warn('Unable to determine the current route of this request', e)
71
78
  nil
72
79
  end
73
80
 
74
81
  # Copy a request for modification.
75
82
  #
76
- # @param [::ActionDispatch::Request] original env.
83
+ # @param env [::ActionDispatch::Request] original env.
77
84
  # @return [::ActionDispatch::Request] a copy of original env with rails env merged.
78
85
  def retrieve_request env
79
86
  rails_env = ::Rails.application.env_config.merge(env)
@@ -91,18 +98,21 @@ module Contrast
91
98
 
92
99
  # Determine if route is a Rails engine route.
93
100
  #
94
- # @param [Object] app or route that points to a ::Rails::Engine
101
+ # @param route [Object] app or route that points to a ::Rails::Engine
95
102
  # @return [bool] whether the router is an engine or not.
96
103
  def engine_route? route
104
+ return false unless route&.app&.app
105
+
97
106
  route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) && route.app.app < ::Rails::Engine
98
107
  end
99
108
 
100
109
  # Recursively get final route traversing engines as required.
101
110
  #
102
111
  # @param request [::Rack::Request] the rack request as will be handed to rails controller.
103
- # @param top_router [::ActionDispatch::Journer::Router] the current router relative to the previous.
112
+ # @param top_router [::ActionDispatch::Journey::Router] the current router relative to the previous.
104
113
  # @param path [Array<String>] the chunks of path that have been seen.
105
- # @return [Array<array>] the final set of rails route classes.
114
+ # @return [Array<Object>] the final set of rails route classes.
115
+ # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
106
116
  def get_full_route request, top_router = ::Rails.application.routes.router, path = []
107
117
  return if (route_matches = top_router.send(:find_routes, request)).empty?
108
118
 
@@ -132,6 +142,43 @@ module Contrast
132
142
  end
133
143
  route_list
134
144
  end
145
+
146
+ # @param request[Contrast::Agent::Request]
147
+ # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
148
+ # @param path [Array<String>] the path of this request, built out from each nested
149
+ # ActionDispatch::Journey::Path::Pattern::MatchData
150
+ # @param route [::ActionDispatch::Journey::Route]
151
+ # @param original_url [String] the full url of this request, including the mount
152
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
153
+ def mounted_sinatra_route request, match, path, route, original_url
154
+ new_req = unmounted_route(request, match, path)
155
+ Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
156
+ end
157
+
158
+ # @param request[Contrast::Agent::Request]
159
+ # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
160
+ # @param path [Array<String>] the path of this request, built out from each nested
161
+ # ActionDispatch::Journey::Path::Pattern::MatchData
162
+ # @param route [::ActionDispatch::Journey::Route]
163
+ # @param original_url [String] the full url of this request, including the mount
164
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
165
+ def mounted_grape_route request, match, path, route, original_url
166
+ new_req = unmounted_route(request, match, path)
167
+ Contrast::Framework::Grape::Support.current_route(new_req, route.app.app, original_url)
168
+ end
169
+
170
+ # Create a request copied from current request, but with the base path removed from path_info, as that's
171
+ # the mount.
172
+ #
173
+ # @param request[Contrast::Agent::Request]
174
+ # @param match []
175
+ # @param path [String] the path of this request
176
+ # @return [::ActionDispatch::Request]
177
+ def unmounted_route request, match, path
178
+ new_req = ::ActionDispatch::Request.new(request.env)
179
+ new_req.path_info = new_req.path_info.gsub((path << match).join, '')
180
+ new_req
181
+ end
135
182
  end
136
183
  end
137
184
  end