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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rspec_parallel +6 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
- data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
- data/lib/contrast/agent/assess/contrast_event.rb +0 -1
- data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
- data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
- data/lib/contrast/agent/assess/policy/preshift.rb +8 -5
- data/lib/contrast/agent/assess/policy/propagation_method.rb +100 -57
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +0 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
- data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
- data/lib/contrast/agent/assess/policy/source_method.rb +13 -17
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +59 -83
- data/lib/contrast/agent/assess/property/evented.rb +2 -1
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
- data/lib/contrast/agent/disable_reaction.rb +1 -1
- data/lib/contrast/agent/exclusion_matcher.rb +0 -4
- data/lib/contrast/agent/inventory/database_config.rb +117 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +5 -4
- data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
- data/lib/contrast/agent/middleware.rb +1 -0
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +18 -12
- data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
- data/lib/contrast/agent/patching/policy/patch.rb +5 -0
- data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
- data/lib/contrast/agent/patching/policy/patcher.rb +8 -8
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
- data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/request.rb +5 -2
- data/lib/contrast/agent/request_context.rb +19 -22
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/tracepoint_hook.rb +6 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +12 -6
- data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
- data/lib/contrast/api/communication/socket_client.rb +4 -4
- data/lib/contrast/api/decorators/agent_startup.rb +4 -4
- data/lib/contrast/api/decorators/application_startup.rb +6 -5
- data/lib/contrast/api/decorators/route_coverage.rb +24 -1
- data/lib/contrast/components/agent.rb +5 -2
- data/lib/contrast/components/assess.rb +6 -3
- data/lib/contrast/components/base.rb +2 -2
- data/lib/contrast/components/config.rb +1 -0
- data/lib/contrast/components/contrast_service.rb +4 -2
- data/lib/contrast/components/logger.rb +13 -8
- data/lib/contrast/components/scope.rb +9 -28
- data/lib/contrast/config/base_configuration.rb +14 -6
- data/lib/contrast/configuration.rb +19 -15
- data/lib/contrast/extension/assess/array.rb +1 -11
- data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
- data/lib/contrast/extension/assess/fiber.rb +0 -11
- data/lib/contrast/extension/assess/hash.rb +0 -10
- data/lib/contrast/extension/assess/kernel.rb +1 -10
- data/lib/contrast/extension/assess/marshal.rb +3 -11
- data/lib/contrast/extension/assess/regexp.rb +0 -11
- data/lib/contrast/extension/assess/string.rb +1 -26
- data/lib/contrast/extension/extension.rb +61 -0
- data/lib/contrast/extension/protect/kernel.rb +0 -10
- data/lib/contrast/framework/grape/support.rb +174 -0
- data/lib/contrast/framework/manager.rb +42 -6
- data/lib/contrast/framework/rack/support.rb +1 -1
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
- data/lib/contrast/framework/rails/patch/support.rb +6 -3
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
- data/lib/contrast/framework/rails/support.rb +60 -13
- data/lib/contrast/framework/sinatra/support.rb +1 -1
- data/lib/contrast/logger/log.rb +89 -15
- data/lib/contrast/utils/io_util.rb +1 -1
- data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
- data/lib/contrast/utils/tag_util.rb +2 -1
- data/resources/assess/policy.json +197 -2
- data/resources/deadzone/policy.json +10 -0
- data/ruby-agent.gemspec +10 -1
- metadata +78 -12
- 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
|
-
#
|
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
|
-
|
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
|
@@ -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:
|
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:
|
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:
|
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
|
-
|
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?(
|
62
|
-
|
63
|
-
|
64
|
-
|
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 =>
|
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::
|
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<
|
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
|