contrast-agent 4.13.1 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
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