contrast-agent 4.13.1 → 4.14.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  4. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  5. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  6. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  7. data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
  8. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  9. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  10. data/lib/contrast/agent/assess/property/tagged.rb +51 -57
  11. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  12. data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
  13. data/lib/contrast/agent/middleware.rb +5 -75
  14. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  15. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  16. data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
  17. data/lib/contrast/agent/reporting/report.rb +21 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  20. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  21. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  22. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  23. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  26. data/lib/contrast/agent/request.rb +2 -81
  27. data/lib/contrast/agent/request_context.rb +4 -128
  28. data/lib/contrast/agent/request_context_extend.rb +138 -0
  29. data/lib/contrast/agent/response.rb +2 -73
  30. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
  31. data/lib/contrast/agent/static_analysis.rb +1 -1
  32. data/lib/contrast/agent/telemetry.rb +15 -7
  33. data/lib/contrast/agent/telemetry_event.rb +8 -9
  34. data/lib/contrast/agent/thread_watcher.rb +31 -5
  35. data/lib/contrast/agent/version.rb +1 -1
  36. data/lib/contrast/agent.rb +15 -0
  37. data/lib/contrast/api/communication/connection_status.rb +10 -7
  38. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  39. data/lib/contrast/api/communication/response_processor.rb +15 -8
  40. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  41. data/lib/contrast/api/communication/socket.rb +6 -8
  42. data/lib/contrast/api/communication/socket_client.rb +29 -12
  43. data/lib/contrast/api/communication/speedracer.rb +37 -1
  44. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  45. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  46. data/lib/contrast/api/decorators/finding.rb +45 -0
  47. data/lib/contrast/components/api.rb +56 -0
  48. data/lib/contrast/components/app_context.rb +10 -65
  49. data/lib/contrast/components/app_context_extend.rb +78 -0
  50. data/lib/contrast/components/base.rb +23 -0
  51. data/lib/contrast/components/config.rb +8 -8
  52. data/lib/contrast/components/contrast_service.rb +5 -0
  53. data/lib/contrast/components/sampling.rb +2 -2
  54. data/lib/contrast/config/agent_configuration.rb +1 -1
  55. data/lib/contrast/config/api_configuration.rb +9 -4
  56. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  57. data/lib/contrast/config/application_configuration.rb +2 -3
  58. data/lib/contrast/config/assess_configuration.rb +3 -3
  59. data/lib/contrast/config/base_configuration.rb +17 -28
  60. data/lib/contrast/config/certification_configuration.rb +15 -0
  61. data/lib/contrast/config/env_variables.rb +2 -9
  62. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  63. data/lib/contrast/config/inventory_configuration.rb +1 -5
  64. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  65. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  66. data/lib/contrast/config/ruby_configuration.rb +6 -6
  67. data/lib/contrast/config/service_configuration.rb +1 -2
  68. data/lib/contrast/config.rb +0 -1
  69. data/lib/contrast/configuration.rb +1 -2
  70. data/lib/contrast/extension/assess/array.rb +5 -7
  71. data/lib/contrast/framework/manager.rb +8 -32
  72. data/lib/contrast/framework/manager_extend.rb +50 -0
  73. data/lib/contrast/framework/rails/railtie.rb +1 -1
  74. data/lib/contrast/framework/sinatra/support.rb +2 -1
  75. data/lib/contrast/logger/log.rb +8 -103
  76. data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
  77. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  78. data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
  79. data/lib/contrast/utils/class_util.rb +18 -14
  80. data/lib/contrast/utils/findings.rb +62 -0
  81. data/lib/contrast/utils/hash_digest.rb +10 -73
  82. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  83. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  84. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  85. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  86. data/lib/contrast/utils/io_util.rb +1 -1
  87. data/lib/contrast/utils/log_utils.rb +108 -0
  88. data/lib/contrast/utils/middleware_utils.rb +87 -0
  89. data/lib/contrast/utils/net_http_base.rb +158 -0
  90. data/lib/contrast/utils/object_share.rb +1 -0
  91. data/lib/contrast/utils/request_utils.rb +88 -0
  92. data/lib/contrast/utils/response_utils.rb +97 -0
  93. data/lib/contrast/utils/substitution_utils.rb +167 -0
  94. data/lib/contrast/utils/tag_util.rb +9 -9
  95. data/lib/contrast/utils/telemetry.rb +4 -2
  96. data/lib/contrast/utils/telemetry_client.rb +90 -0
  97. data/lib/contrast/utils/telemetry_identifier.rb +17 -24
  98. data/ruby-agent.gemspec +5 -5
  99. metadata +48 -23
  100. data/lib/contrast/config/default_value.rb +0 -17
  101. data/lib/contrast/utils/requests_client.rb +0 -150
@@ -0,0 +1,56 @@
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/components/logger'
5
+ require 'contrast/agent/reporting/reporting_events/reporting_event'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Reporting
10
+ # This is the new PreflightMessage class which will include all the needed information
11
+ # for the new reporting system to report a single message, part of the main Preflight
12
+ class PreflightMessage < Contrast::Agent::Reporting::ReportingEvent
13
+ attr_accessor :data, :routes, :hash_code
14
+
15
+ def initialize
16
+ super
17
+ @event_type = :preflight_message
18
+ @app_language = Contrast::Utils::ObjectShare::RUBY
19
+ @app_name = ::Contrast::APP_CONTEXT.app_name
20
+ @app_version = ::Contrast::APP_CONTEXT.app_version
21
+ end
22
+
23
+ def to_controlled_hash **_args
24
+ validate
25
+ super.merge!({
26
+ app_language: @app_language,
27
+ app_name: @app_name,
28
+ app_version: @app_version,
29
+ data: '',
30
+ key: 0,
31
+ routes: @routes,
32
+ session_id: @agent_session_id_value
33
+ })
34
+ end
35
+
36
+ def validate
37
+ raise(ArgumentError, "#{ cs__class } did not have a proper data. Unable to continue.") unless data
38
+ unless @app_name
39
+ raise(ArgumentError, "#{ cs__class } did not have a proper application name. Unable to continue.")
40
+ end
41
+ unless @app_language
42
+ raise(ArgumentError, "#{ cs__class } did not have a proper application language. Unable to continue.")
43
+ end
44
+ unless @app_version
45
+ raise(ArgumentError, "#{ cs__class } did not have a proper application version. Unable to continue.")
46
+ end
47
+ unless @agent_session_id_value
48
+ raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
49
+ end
50
+
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
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/components/logger'
5
+ require 'contrast/utils/assess/trigger_method_utils'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Reporting
10
+ # This is the new ReportingEvent class which will include all the needed and mutual information
11
+ # for the new reporting system
12
+ # @abstract
13
+ class ReportingEvent
14
+ attr_reader :routes
15
+ attr_accessor :event_type
16
+
17
+ CODE = :TRACE
18
+
19
+ def initialize(*) # rubocop:disable Style/MethodDefParentheses
20
+ @event_type = Contrast::Utils::ObjectShare::EMPTY_STRING
21
+ @agent_session_id_value = 0
22
+ @routes = []
23
+ end
24
+
25
+ def to_controlled_hash **_args
26
+ { code: CODE, routes: routes }
27
+ end
28
+
29
+ def validate
30
+ raise(ArgumentError, "#{ self } did not have a proper routes. Unable to continue.") if routes.empty?
31
+
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,127 @@
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/components/logger'
5
+ require 'fileutils'
6
+ require 'json'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Reporting
11
+ # This class will facilitate the Audit functionality and it will be
12
+ # controlled from the configuration classes
13
+ class Audit
14
+ include Contrast::Components::Logger::InstanceMethods
15
+
16
+ attr_reader :path_for_requests, :path_for_responses
17
+
18
+ def initialize
19
+ generate_paths if enabled? && Contrast::CONTRAST_SERVICE.use_agent_communication?
20
+ end
21
+
22
+ # This method will be handling the auditing of the requests and responses we send to SpeedRacer. If the audit
23
+ # feature is enabled, we'll log to file the request and/or response protobuf objects.
24
+ #
25
+ # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
26
+ # Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
27
+ # @param response_data [Contrast::Api::Settings::AgentSettings,nil]
28
+ def audit_event event, response_data = nil
29
+ return unless ::Contrast::API.request_audit_requests || ::Contrast::API.request_audit_responses
30
+
31
+ type = event.cs__respond_to?(:file_name) ? event.file_name : event.cs__class.cs__name.to_s.downcase
32
+ if ::Contrast::API.request_audit_requests
33
+ data = event.to_s
34
+ log_data :request, type, data if data
35
+ end
36
+ return unless ::Contrast::API.request_audit_responses
37
+
38
+ data = response_data.to_s || event.http_response.try(:body) || 'There is no available response'
39
+ log_data :response, type, data
40
+ end
41
+
42
+ private
43
+
44
+ # This method will proceed with passing the data with to the writing method
45
+ # @param type [Symbol] This is the type of the file /:request, :response/
46
+ # @param data_type[String] DTM type String representation
47
+ # @param data[String] String representation if the logged data
48
+ def log_data type, data_type, data = nil
49
+ return unless enabled?
50
+ return unless Contrast::CONTRAST_SERVICE.use_agent_communication?
51
+
52
+ write_to_file type, data_type, data
53
+ end
54
+
55
+ # This method will be actually writing to the file
56
+ # @param type [Symbol] This is the type of the file /:request, :response/
57
+ # @param data_type [String] Data type /Activity/Finding../
58
+ # @param data [any] The data to be written to the file
59
+ def write_to_file type, data_type, data = nil
60
+ time = Time.now.to_i
61
+ destination = type == :request ? path_for_requests : path_for_responses
62
+ # If the feature is disabled or we have yet to create the directory structure, then we could have a nil
63
+ # destination. In that case, take no action
64
+ return unless destination
65
+
66
+ filename = "#{ time }-#{ data_type.gsub('::', '_') }-teamserver.json"
67
+ filepath = File.join(destination, filename)
68
+ # Here is use append mode, because of a slightly possibility of overwriting an existing file
69
+ File.open(filepath, 'a') do |f|
70
+ f.write({ data_type: Contrast::Utils::StringUtils.force_utf8(data) }.to_json)
71
+ end
72
+ end
73
+
74
+ # Here we will generate the directories for the requests and responses
75
+ def generate_paths
76
+ message_directories = File.expand_path(path_to_audits)
77
+ FileUtils.mkdir_p(message_directories) unless Dir.exist?(message_directories)
78
+
79
+ requests_destination = File.expand_path(File.join(message_directories, '/requests'))
80
+ responses_destination = File.expand_path(File.join(message_directories, '/responses'))
81
+
82
+ Dir.mkdir(requests_destination) if enabled_for_requests? && !Dir.exist?(requests_destination)
83
+ Dir.mkdir(responses_destination) if enabled_for_responses? && !Dir.exist?(responses_destination)
84
+
85
+ @path_for_requests ||= requests_destination if enabled_for_requests?
86
+ @path_for_responses ||= responses_destination if enabled_for_responses?
87
+ rescue StandardError => e
88
+ logger.warn('Generating the paths failed with: ', e: e)
89
+ end
90
+
91
+ # Retrieves the configuration value if the request audit is enabled
92
+ # @return [Boolean]
93
+ def enabled?
94
+ ::Contrast::API.request_audit_enable
95
+ end
96
+
97
+ # The boolean values for the requests and the responses should be taken under
98
+ # consideration only if it's in combination with enabled
99
+ # So in order for us to actually audit the requests, we need:
100
+ # - enabled? -> ture and enabled_for_requests? -> true
101
+ # The same is for the responses
102
+ #
103
+ #
104
+ # Retrieve the configuration value if the audit for requests is enabled
105
+ # @return [Boolean]
106
+ def enabled_for_requests?
107
+ ::Contrast::API.request_audit_requests
108
+ end
109
+
110
+ # Retrieve the configuration value if the audit for responses is enabled
111
+ # @return [Boolean]
112
+ def enabled_for_responses?
113
+ ::Contrast::API.request_audit_requests
114
+ end
115
+
116
+ # Retrieve the configuration value for the path of the audits
117
+ # The value will be read from the configuration yml
118
+ # but if it isn't defined any - here will be returned the default path from
119
+ # Contrast::Config::RequestAuditConfiguration::DEFAULT_PATH
120
+ # @return [String]
121
+ def path_to_audits
122
+ ::Contrast::API.request_audit_path
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,168 @@
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 'json'
5
+ require 'net/http'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/utils/net_http_base'
8
+ require 'contrast/agent/reporting/reporting_events/finding'
9
+ require 'contrast/agent/reporting/reporting_events/preflight_message'
10
+ require 'contrast/agent/reporting/reporting_events/reporting_event'
11
+
12
+ module Contrast
13
+ module Utils
14
+ # This module creates a Net::HTTP client and initiates a connection to the provided result
15
+ class ReporterClient < NetHttpBase
16
+ include ObjectShare
17
+ SERVICE_NAME = 'Reporter'
18
+ ENDPOINTS = {
19
+ application_activity: '/api/ng/activity/application',
20
+ application_create: '/api/ng/applications/create',
21
+ agent_startup: '/api/ng/servers/',
22
+ preflight: '/api/ng/preflight/',
23
+ report_vulnerability: '/api/ng/traces',
24
+ server_activity: '/api/ng/servers/',
25
+ server_update: '/api/ng/update/application'
26
+ }.cs__freeze
27
+ # EVENT_TYPES = {
28
+ # agent_startup: Contrast::Api::Dtm::AgentStartup,
29
+ # report_vulnerability: Contrast::Agent::Reporting::Finding,
30
+ # preflight: Contrast::Agent::Reporting::Preflight
31
+ # }.cs__freeze
32
+ include Contrast::Components::Logger::InstanceMethods
33
+ # This method initializes the Net::HTTP client we'll need. it will validate
34
+ # the connection and make the first request. If connection is valid and response
35
+ # is available then the open connection is returned.
36
+ #
37
+ # @return [Net::HTTP, nil] Return open connection or nil
38
+ def initialize_connection
39
+ # for this client we would use proxy and custom certificate file if available
40
+ super(SERVICE_NAME, Contrast::API.api_url, true, true)
41
+ end
42
+
43
+ # @param headers [Hash] all the headers needed for communication with TS
44
+ def initialize headers
45
+ @headers = headers
46
+ super()
47
+ end
48
+
49
+ # Send Agent Startup event
50
+ #
51
+ # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
52
+ # Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
53
+ # @param connection [Net::HTTP] open connection
54
+ # @return response [Net::HTTP::Response] response from TS
55
+ def send_agent_startup event, connection
56
+ logger.debug('Preparing to send startup messages')
57
+ request = build_request ENDPOINTS[:agent_startup], event
58
+ response = connection.request(request)
59
+ logger.debug('Successfully sent startup messages to service.') if response
60
+ response
61
+ end
62
+
63
+ # Check event type and send it to appropriate TS endpoint
64
+ #
65
+ # @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the event
66
+ # field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
67
+ # @param connection [Net::HTTP] open connection
68
+ # @param send_immediately [Boolean] flag for the logger
69
+ # @return response [Net::HTTP::Response, nil] response from TS if no response
70
+ def send_event event, connection, send_immediately = false
71
+ event_type = symbolized_event_type event.event_type
72
+ @response = send_events event, event_type, connection
73
+ log_send_event event if send_immediately
74
+ @response
75
+ end
76
+
77
+ private
78
+
79
+ # This method will build headers of the request required for TS communication
80
+ #
81
+ # @param request [Net::HTTP::Post]
82
+ def build_headers request
83
+ @app_version = @headers[:app_version]
84
+ request['API-Key'] = @headers[:api_key]
85
+ request['Application-Language'] = @headers[:app_language]
86
+ request['Application-Name'] = @headers[:app_name]
87
+ request['Application-Path'] = @headers[:app_path]
88
+ request['Application-Version'] = @app_version if @app_version
89
+ request['Authorization'] = @headers[:authorization]
90
+ request['Content-Type'] = @headers[:content_type]
91
+ request['Server-Name'] = @headers[:server_name]
92
+ request['Server-Path'] = @headers[:server_path]
93
+ request['Server-Type'] = @headers[:server_type]
94
+ request['X-Contrast-Agent'] = @headers[:agent_version]
95
+ request['X-Contrast-Header-Encoding'] = @headers[:encoding]
96
+ request
97
+ end
98
+
99
+ # build the request headers and assign endpoint
100
+ #
101
+ # @param endpoint [String] endpoint to report to
102
+ # @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent]
103
+ # One of the DTMs valid for the event field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
104
+ # @return [Net::HTTP::Post]
105
+ def build_request endpoint, event
106
+ @request = build_headers Net::HTTP::Post.new(endpoint)
107
+ @request.body = if event.cs__is_a?(Contrast::Agent::Reporting::ReportingEvent)
108
+ event.to_controlled_hash.to_json
109
+ else
110
+ event.to_json
111
+ end
112
+ @request
113
+ end
114
+
115
+ # log the event sent immediately
116
+ #
117
+ # @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the
118
+ # event field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
119
+ def log_send_event event
120
+ logger.debug('Immediately sending event.', event_id: event.__id__, event_type: event.cs__class.cs__name)
121
+ end
122
+
123
+ # Sent different events to different endpoints
124
+ #
125
+ # @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] Main reporting event, all events
126
+ # inherit it
127
+ # @param event_type [Symbol] We'll use this symbol to match the urls
128
+ # @param connection [Net::HTTP] open connection
129
+ def send_events event, event_type, connection
130
+ logger.debug("Preparing to send #{ event_type.to_s.capitalize } messages")
131
+ request = build_request ENDPOINTS[event_type], event
132
+ response = connection.request(request)
133
+ logger.debug("Successfully sent #{ event_type.to_s.capitalize } messages to service.") if response
134
+ response
135
+ end
136
+
137
+ # Eventually here we'll handle more response types and etc
138
+
139
+ # @param event [Contrast::Agent::Reporting::Preflight] the preflight we handle here
140
+ # @param response [Net::HTTP::Response,nil] The response we handle and read from
141
+ # @param connection [Net::HTTP] open connection
142
+ def handle_response event, response, connection
143
+ return unless event || response || connection
144
+
145
+ preflight_message = event.messages[0]
146
+ event_type = symbolized_event_type preflight_message.event_type
147
+ return unless event_type == :preflight_message
148
+
149
+ # for handling multiple findings
150
+ # we'll only extract the indexes without *
151
+ # findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') }
152
+ # after that we'll do some magic and return them the same way we do for corresponding_finding
153
+ corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message.hash_code)
154
+ return unless corresponding_finding
155
+
156
+ send_event corresponding_finding, connection, true
157
+ nil
158
+ end
159
+
160
+ def symbolized_event_type event_type
161
+ return unless event_type
162
+ return event_type unless event_type.cs__is_a?(String)
163
+
164
+ event_type.downcase.to_sym
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,66 @@
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/components/logger'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ # This module will be used to store everything that would be sent
10
+ # and depends on the response we receive
11
+ module ReportingStorage
12
+ class << self
13
+ include Contrast::Components::Logger::InstanceMethods
14
+
15
+ # So the collection will be used to store those events
16
+ def collection
17
+ @_collection ||= {}
18
+ end
19
+
20
+ # @param key[String] the key for the pair
21
+ # @param value[Object] the value we need to store
22
+ # @return [Object] the value we saved
23
+ def []= key, value
24
+ return unless key
25
+ return unless value
26
+
27
+ logger.debug('Saving new value', key: key)
28
+
29
+ key = key.to_s.downcase.strip
30
+ collection[key] = value
31
+
32
+ value # rubocop:disable Lint/Void
33
+ end
34
+
35
+ # @param key[String] the key for the pair
36
+ def [] key
37
+ return unless key
38
+
39
+ collection[key]
40
+ end
41
+ alias_method :get, :[]
42
+ alias_method :set, :[]=
43
+
44
+ def delete key
45
+ return unless key
46
+
47
+ logger.debug('Starting deleting value for', key: key)
48
+
49
+ deleted_value = collection.delete(key)
50
+ logger.debug('Key wasn\'t found') unless deleted_value
51
+
52
+ deleted_value
53
+ end
54
+
55
+ # @param rule_id [String] the rule_id
56
+ # @return [Hash, nil] return array with key and value of the pair
57
+ def find_by_rule_id rule_id
58
+ return unless rule_id
59
+
60
+ collection.find { |_, v| v.rule_id == rule_id }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -9,6 +9,7 @@ require 'contrast/utils/string_utils'
9
9
  require 'contrast/utils/hash_digest'
10
10
  require 'contrast/components/logger'
11
11
  require 'contrast/components/scope'
12
+ require 'contrast/utils/request_utils'
12
13
 
13
14
  module Contrast
14
15
  module Agent
@@ -17,6 +18,7 @@ module Contrast
17
18
  # data in a format that the Agent expects, caching those transformations in
18
19
  # order to avoid repeatedly creating Strings & thrashing GC.
19
20
  class Request
21
+ include Contrast::Utils::RequestUtils
20
22
  include Contrast::Components::Logger::InstanceMethods
21
23
  include Contrast::Components::Scope::InstanceMethods
22
24
 
@@ -152,87 +154,6 @@ module Contrast
152
154
 
153
155
  accepts.start_with?(*MEDIA_TYPE_MARKERS)
154
156
  end
155
-
156
- private
157
-
158
- # Return a flattened hash of params with realized paths for keys, in
159
- # addition to a separate, valueless, entry for each nest key.
160
- # See RUBY-621 for more details.
161
- # { key : { nested_key : ['x','y','z' ] } }
162
- # becomes
163
- # {
164
- # key[nested_key][0] : 'x'
165
- # key[nested_key][1] : 'y'
166
- # key[nested_key][2] : 'z'
167
- # key : ''
168
- # nested_key : ''
169
- # }
170
- def normalize_params val, prefix: nil
171
- # In non-recursive invocations, val should always be a Hash
172
- # (rather than breaking this out into two methods)
173
- case val
174
- when Tempfile
175
- # Skip if it's the auto-generated value from rails when it handles
176
- # file uploads. The file name will still be sent to SR for analysis.
177
- {}
178
- when Hash
179
- res = val.each_with_object({}) do |(k, v), hash|
180
- k = Contrast::Utils::StringUtils.force_utf8(k)
181
- nested_prefix = prefix.nil? ? k : "#{ prefix }[#{ k }]"
182
- hash[k] = Contrast::Utils::ObjectShare::EMPTY_STRING
183
- hash.merge! normalize_params(v, prefix: nested_prefix)
184
- end
185
- res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
186
- res
187
- when Enumerable
188
- idx = 0
189
- res = {}
190
- while idx < val.length
191
- res.merge! normalize_params(val[idx], prefix: "#{ prefix }[#{ idx }]")
192
- idx += 1
193
- end
194
- res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
195
- res
196
- else
197
- { prefix => Contrast::Utils::StringUtils.force_utf8(val) }
198
- end
199
- end
200
-
201
- def read_body body
202
- return body if body.is_a?(String)
203
-
204
- begin
205
- can_rewind = Contrast::Utils::DuckUtils.quacks_to?(body, :rewind)
206
- # if we are after a middleware that failed to rewind
207
- body.rewind if can_rewind
208
- body.read
209
- rescue StandardError => e
210
- logger.error('Error in attempt to read body', message: e.message)
211
- logger.trace('With Stack', e)
212
- body.to_s
213
- ensure
214
- # be a good citizen and rewind
215
- body.rewind if can_rewind
216
- end
217
- end
218
-
219
- def traverse_parsed_multipart multipart_data, current_names
220
- return current_names unless multipart_data
221
-
222
- multipart_data.each_value do |data_value|
223
- next unless data_value.is_a?(Hash)
224
-
225
- tempfile = data_value[:tempfile]
226
- if tempfile.nil?
227
- traverse_parsed_multipart(data_value, current_names)
228
- else
229
- name = data_value[:name].to_s
230
- file_name = data_value[:filename].to_s
231
- current_names[name] = file_name
232
- end
233
- end
234
- current_names
235
- end
236
157
  end
237
158
  end
238
159
  end