sqreen-alt 1.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +20 -0
  5. data/lib/sqreen-alt.rb +1 -0
  6. data/lib/sqreen.rb +68 -0
  7. data/lib/sqreen/attack_detected.html +2 -0
  8. data/lib/sqreen/binding_accessor.rb +288 -0
  9. data/lib/sqreen/ca.crt +72 -0
  10. data/lib/sqreen/call_countable.rb +67 -0
  11. data/lib/sqreen/callback_tree.rb +78 -0
  12. data/lib/sqreen/callbacks.rb +100 -0
  13. data/lib/sqreen/capped_queue.rb +23 -0
  14. data/lib/sqreen/condition_evaluator.rb +235 -0
  15. data/lib/sqreen/conditionable.rb +50 -0
  16. data/lib/sqreen/configuration.rb +168 -0
  17. data/lib/sqreen/context.rb +26 -0
  18. data/lib/sqreen/deliveries/batch.rb +84 -0
  19. data/lib/sqreen/deliveries/simple.rb +39 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +61 -0
  22. data/lib/sqreen/events/remote_exception.rb +54 -0
  23. data/lib/sqreen/events/request_record.rb +62 -0
  24. data/lib/sqreen/exception.rb +34 -0
  25. data/lib/sqreen/frameworks.rb +40 -0
  26. data/lib/sqreen/frameworks/generic.rb +446 -0
  27. data/lib/sqreen/frameworks/rails.rb +148 -0
  28. data/lib/sqreen/frameworks/rails3.rb +36 -0
  29. data/lib/sqreen/frameworks/request_recorder.rb +69 -0
  30. data/lib/sqreen/frameworks/sinatra.rb +57 -0
  31. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  32. data/lib/sqreen/instrumentation.rb +542 -0
  33. data/lib/sqreen/log.rb +119 -0
  34. data/lib/sqreen/metrics.rb +6 -0
  35. data/lib/sqreen/metrics/average.rb +39 -0
  36. data/lib/sqreen/metrics/base.rb +45 -0
  37. data/lib/sqreen/metrics/collect.rb +22 -0
  38. data/lib/sqreen/metrics/sum.rb +20 -0
  39. data/lib/sqreen/metrics_store.rb +96 -0
  40. data/lib/sqreen/middleware.rb +34 -0
  41. data/lib/sqreen/payload_creator.rb +137 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +93 -0
  47. data/lib/sqreen/rule_attributes.rb +26 -0
  48. data/lib/sqreen/rule_callback.rb +108 -0
  49. data/lib/sqreen/rules.rb +126 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
  52. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  53. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
  54. data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
  55. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  56. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
  57. data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
  58. data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
  59. data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
  60. data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
  61. data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
  62. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  63. data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
  64. data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
  65. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  66. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +151 -0
  70. data/lib/sqreen/runner.rb +365 -0
  71. data/lib/sqreen/runtime_infos.rb +138 -0
  72. data/lib/sqreen/safe_json.rb +60 -0
  73. data/lib/sqreen/sdk.rb +22 -0
  74. data/lib/sqreen/serializer.rb +46 -0
  75. data/lib/sqreen/session.rb +317 -0
  76. data/lib/sqreen/shared_storage.rb +31 -0
  77. data/lib/sqreen/stats.rb +18 -0
  78. data/lib/sqreen/version.rb +5 -0
  79. metadata +148 -0
@@ -0,0 +1,138 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/version'
5
+ require 'sqreen/frameworks'
6
+
7
+ require 'socket'
8
+ require 'digest/sha1'
9
+
10
+ module Sqreen
11
+ module RuntimeInfos
12
+ module_function
13
+
14
+ def all(framework)
15
+ res = { :various_infos => {} }
16
+ res.merge! agent
17
+ res.merge! os
18
+ res.merge! runtime
19
+ res.merge! framework.framework_infos
20
+ res[:bundle_signature] = dependencies_signature
21
+ res[:various_infos].merge! time
22
+ res[:various_infos].merge! process
23
+ res
24
+ end
25
+
26
+ def local_infos
27
+ {
28
+ 'time' => Time.now.utc,
29
+ 'name' => hostname,
30
+ }
31
+ end
32
+
33
+ def dependencies
34
+ gem_info = Gem.loaded_specs
35
+ gem_info.map do |name, spec|
36
+ {
37
+ :name => name,
38
+ :version => spec.version.to_s,
39
+ :homepage => spec.homepage,
40
+ :source => (extract_source(spec.source) if spec.respond_to?(:source)),
41
+ }
42
+ end
43
+ end
44
+
45
+ def time
46
+ # FIXME: That should maybe be called local-time
47
+ { :time => Time.now }
48
+ end
49
+
50
+ def ssl
51
+ type = nil
52
+ version = nil
53
+ if defined? OpenSSL
54
+ type = 'OpenSSL'
55
+ version = OpenSSL::OPENSSL_VERSION if defined? OpenSSL::OPENSSL_VERSION
56
+ end
57
+ { :ssl =>
58
+ {
59
+ :type => type,
60
+ :version => version,
61
+ } }
62
+ end
63
+
64
+ def agent
65
+ {
66
+ :agent_type => :ruby,
67
+ :agent_version => ::Sqreen::VERSION,
68
+ }
69
+ end
70
+
71
+ def os
72
+ plat = if defined? ::RUBY_PLATFORM
73
+ ::RUBY_PLATFORM
74
+ elsif defined? ::PLATFORM
75
+ ::PLATFORM
76
+ else
77
+ ''
78
+ end
79
+ {
80
+ :os_type => plat,
81
+ :hostname => hostname,
82
+ }
83
+ end
84
+
85
+ def hostname
86
+ Socket.gethostname
87
+ end
88
+
89
+ def process
90
+ {
91
+ :pid => Process.pid,
92
+ :ppid => Process.ppid,
93
+ :euid => Process.euid,
94
+ :egid => Process.egid,
95
+ :uid => Process.uid,
96
+ :gid => Process.gid,
97
+ :name => $0,
98
+ }
99
+ end
100
+
101
+ def runtime
102
+ engine = if defined? ::RUBY_ENGINE
103
+ ::RUBY_ENGINE
104
+ else
105
+ 'ruby'
106
+ end
107
+ {
108
+ :runtime_type => engine,
109
+ :runtime_version => ::RUBY_DESCRIPTION,
110
+ }
111
+ end
112
+
113
+ def dependencies_signature
114
+ calculate_dependencies_signature(dependencies)
115
+ end
116
+
117
+ def calculate_dependencies_signature(pkgs)
118
+ return nil if pkgs.nil? || pkgs.empty?
119
+ sha1 = Digest::SHA1.new
120
+ pkgs.map { |pkg| [pkg[:name], pkg[:version]] }.sort.each_with_index do |p, i|
121
+ sha1 << format(i.zero? ? '%s-%s' : '|%s-%s', *p)
122
+ end
123
+ sha1.hexdigest
124
+ end
125
+
126
+ def extract_source(source)
127
+ return nil unless source
128
+ ret = { 'name' => source.class.name.split(':')[-1] }
129
+ opts = {}
130
+ opts = source.options if source.respond_to?(:options)
131
+ ret['remotes'] = opts['remotes'] if opts['remotes']
132
+ ret['uri'] = opts['uri'] if opts['uri']
133
+ # FIXME: scrub any auth data in uris
134
+ ret['path'] = opts['path'].to_s if opts['path']
135
+ ret
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'json'
5
+
6
+ require 'sqreen/log'
7
+
8
+ module Sqreen
9
+ # Safely dump datastructure in json (more resilient to encoding errors)
10
+ class SafeJSON
11
+ def self.dump(data)
12
+ JSON.generate(data)
13
+ rescue JSON::GeneratorError, Encoding::UndefinedConversionError
14
+ Sqreen.log.debug('Payload could not be encoded enforcing recode')
15
+ JSON.generate(rencode_payload(data))
16
+ end
17
+
18
+ def self.rencode_payload(obj, max_depth = 20)
19
+ max_depth -= 1
20
+ return obj if max_depth < 0
21
+ return rencode_array(obj, max_depth) if obj.is_a?(Array)
22
+ return enforce_encoding(obj) unless obj.is_a?(Hash)
23
+ nobj = {}
24
+ obj.each do |k, v|
25
+ safe_k = rencode_payload(k, max_depth)
26
+ nobj[safe_k] = case v
27
+ when Array
28
+ rencode_array(v, max_depth)
29
+ when Hash
30
+ rencode_payload(v, max_depth)
31
+ when String
32
+ enforce_encoding(v)
33
+ else # for example integers
34
+ v
35
+ end
36
+ end
37
+ nobj
38
+ end
39
+
40
+ def self.rencode_array(array, max_depth)
41
+ array.map! { |e| rencode_payload(e, max_depth - 1) }
42
+ array
43
+ end
44
+
45
+ def self.enforce_encoding(str)
46
+ return str unless str.is_a?(String)
47
+ return str if str.ascii_only?
48
+ encoded8bit = str.encoding.name == 'ASCII-8BIT'
49
+ return str if !encoded8bit && str.valid_encoding?
50
+ r = str.chars.map do |v|
51
+ if !v.valid_encoding? || (encoded8bit && !v.ascii_only?)
52
+ v.bytes.map { |c| "\\x#{c.to_s(16).upcase}" }.join
53
+ else
54
+ v
55
+ end
56
+ end.join
57
+ "SqBytes[#{r}]"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ # Sqreen Namespace
5
+ module Sqreen
6
+ # Sqreen SDK
7
+ class << self
8
+ # Authentication tracking method
9
+ def auth_track(is_logged_in, authentication_keys); end
10
+
11
+ def signup_track(authentication_keys); end
12
+
13
+ def identify(authentication_keys, traits = {})
14
+ return unless Sqreen.framework
15
+ Sqreen.framework.observe(
16
+ :sdk,
17
+ [:identify, Time.now, authentication_keys, traits],
18
+ [], false
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ module Sqreen
5
+ # Serialization functions: convert Hash -> simple ruby types
6
+ module Serializer
7
+ # Serialize a deep hash/array to more simple types
8
+ def self.serialize(obj, max_depth = 10)
9
+ if obj.is_a?(Array)
10
+ new_obj = []
11
+ i = -1
12
+ to_do = obj.map { |v| [new_obj, i += 1, v, 0] }
13
+ else
14
+ new_obj = {}
15
+ to_do = obj.map { |k, v| [new_obj, k, v, 0] }
16
+ end
17
+ until to_do.empty?
18
+ where, key, value, deepness = to_do.pop
19
+ safe_key = key.kind_of?(Integer) ? key : key.to_s
20
+ if value.is_a?(Hash) && deepness < max_depth
21
+ where[safe_key] = {}
22
+ to_do += value.map { |k, v| [where[safe_key], k, v, deepness + 1] }
23
+ elsif value.is_a?(Array) && deepness < max_depth
24
+ where[safe_key] = []
25
+ i = -1
26
+ to_do += value.map { |v| [where[safe_key], i += 1, v, deepness + 1] }
27
+ else
28
+ case value
29
+ when Symbol
30
+ where[safe_key] = value.to_s
31
+ when Rational
32
+ where[safe_key] = value.to_f
33
+ when Time
34
+ where[safe_key] = value.iso8601
35
+ when Numeric, String, TrueClass, FalseClass, NilClass
36
+ where[safe_key] = value
37
+ else
38
+ where[safe_key] = "#{value.class.name}:#{value.inspect}"
39
+ end
40
+ end
41
+ end
42
+
43
+ new_obj
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,317 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/log'
5
+ require 'sqreen/serializer'
6
+ require 'sqreen/runtime_infos'
7
+ require 'sqreen/events/remote_exception'
8
+ require 'sqreen/events/attack'
9
+ require 'sqreen/events/request_record'
10
+ require 'sqreen/exception'
11
+ require 'sqreen/safe_json'
12
+
13
+ require 'net/https'
14
+ require 'uri'
15
+ require 'openssl'
16
+ require 'zlib'
17
+
18
+ # $ curl -H"x-api-key: ${KEY}" http://127.0.0.1:5000/sqreen/v0/app-login
19
+ # {
20
+ # "session_id": "c9171007c27d4da8906312ff343ed41307f65b2f6fdf4a05a445bb7016186657",
21
+ # "status": true
22
+ # }
23
+ #
24
+ # $ curl -H"x-session-key: ${SESS}" http://127.0.0.1:5000/sqreen/v0/get-rulespack
25
+
26
+ #
27
+ # FIXME: we should be proxy capable
28
+ # FIXME: we should be multithread aware (when callbacks perform server requests?)
29
+ #
30
+
31
+ module Sqreen
32
+ class Session
33
+ RETRY_CONNECT_SECONDS = 10
34
+ RETRY_REQUEST_SECONDS = 10
35
+
36
+ MAX_DELAY = 60 * 30
37
+
38
+ RETRY_LONG = 128
39
+
40
+ MUTEX = Mutex.new
41
+ METRICS_KEY = 'metrics'.freeze
42
+
43
+ @@path_prefix = '/sqreen/v0/'
44
+
45
+ attr_accessor :request_compression
46
+
47
+ def initialize(server_url, token)
48
+ @token = token
49
+ @session_id = nil
50
+ @server_url = server_url
51
+ @request_compression = false
52
+ @connected = nil
53
+ @con = nil
54
+
55
+ uri = parse_uri(server_url)
56
+ use_ssl = (uri.scheme == 'https')
57
+
58
+ @req_nb = 0
59
+
60
+ @http = Net::HTTP.new(uri.host, uri.port)
61
+ @http.use_ssl = use_ssl
62
+ if use_ssl
63
+ cert_file = File.join(File.dirname(__FILE__), 'ca.crt')
64
+ cert_store = OpenSSL::X509::Store.new
65
+ cert_store.add_file cert_file
66
+ @http.cert_store = cert_store
67
+ end
68
+ end
69
+
70
+ def parse_uri(uri)
71
+ # This regexp is the Ruby constant URI::PATTERN::HOSTNAME augmented
72
+ # with the _ character that is frequent in Docker linked containers.
73
+ re = '(?:(?:[a-zA-Z\\d](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.)*(?:[a-zA-Z](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.?'
74
+ parser = URI::Parser.new :HOSTNAME => re
75
+ parser.parse(uri)
76
+ end
77
+
78
+ def prefix_path(path)
79
+ return '/sqreen/v1/' + path if path == 'app-login' || path == 'app-beat'
80
+ @@path_prefix + path
81
+ end
82
+
83
+ def connected?
84
+ @con && @con.started?
85
+ end
86
+
87
+ def disconnect
88
+ @http.finish if connected?
89
+ end
90
+
91
+ NET_ERRORS = [Timeout::Error,
92
+ Errno::EINVAL,
93
+ Errno::ECONNRESET,
94
+ Errno::ECONNREFUSED,
95
+ EOFError,
96
+ Net::HTTPBadResponse,
97
+ Net::HTTPHeaderSyntaxError,
98
+ SocketError,
99
+ Net::ProtocolError].freeze
100
+
101
+ def connect
102
+ return if connected?
103
+ Sqreen.log.warn "connection to #{@server_url}..."
104
+ @session_id = nil
105
+ @conn_retry = 0
106
+ begin
107
+ @con = @http.start
108
+ rescue *NET_ERRORS
109
+ Sqreen.log.debug "Cannot connect, retry in #{RETRY_CONNECT_SECONDS} seconds"
110
+ sleep RETRY_CONNECT_SECONDS
111
+ @conn_retry += 1
112
+ retry
113
+ else
114
+ Sqreen.log.warn 'connection success.'
115
+ end
116
+ end
117
+
118
+ def resilient_post(path, data, headers = {})
119
+ post(path, data, headers, RETRY_LONG)
120
+ end
121
+
122
+ def resilient_get(path, headers = {})
123
+ get(path, headers, RETRY_LONG)
124
+ end
125
+
126
+ def post(path, data, headers = {}, max_retry = 2)
127
+ do_http_request(:POST, path, data, headers, max_retry)
128
+ end
129
+
130
+ def get(path, headers = {}, max_retry = 2)
131
+ do_http_request(:GET, path, nil, headers, max_retry)
132
+ end
133
+
134
+ def resiliently(retry_request_seconds, max_retry, current_retry = 0)
135
+ return yield
136
+ rescue => e
137
+ Sqreen.log.debug(e.inspect)
138
+
139
+ current_retry += 1
140
+
141
+ raise e if current_retry >= max_retry || e.is_a?(Sqreen::NotImplementedYet)
142
+
143
+ sleep_delay = [MAX_DELAY, retry_request_seconds * current_retry].min
144
+ Sqreen.log.debug format('Sleeping %ds', sleep_delay)
145
+ sleep(sleep_delay)
146
+
147
+ retry
148
+ end
149
+
150
+ def thread_id
151
+ th = Thread.current
152
+ return '' unless th
153
+ re = th.to_s.scan(/:(0x.*)>/)
154
+ return '' unless re && !re.empty?
155
+ res = re[0]
156
+ return '' unless res && !res.empty?
157
+ res[0]
158
+ end
159
+
160
+ def do_http_request(method, path, data, headers = {}, max_retry = 2)
161
+ connect unless connected?
162
+ headers['X-Session-Key'] = @session_id if @session_id
163
+ headers['X-Sqreen-Time'] = Time.now.utc.to_f.to_s
164
+ headers['User-Agent'] = "sqreen-ruby/#{Sqreen::VERSION}"
165
+ headers['X-Sqreen-Beta'] = format('pid=%d;tid=%s;nb=%d;t=%f',
166
+ Process.pid,
167
+ thread_id,
168
+ @req_nb,
169
+ Time.now.utc.to_f)
170
+ headers['Content-Type'] = 'application/json'
171
+ if request_compression && !method.casecmp(:GET).zero?
172
+ headers['Content-Encoding'] = 'gzip'
173
+ end
174
+
175
+ @req_nb += 1
176
+
177
+ path = prefix_path(path)
178
+ Sqreen.log.debug format('%s %s (%s)', method, path, @token)
179
+
180
+ res = {}
181
+ resiliently(RETRY_REQUEST_SECONDS, max_retry) do
182
+ json = nil
183
+ MUTEX.synchronize do
184
+ json = case method.upcase
185
+ when :GET
186
+ @con.get(path, headers)
187
+ when :POST
188
+ json_data = nil
189
+ unless data.nil?
190
+ serialized = Serializer.serialize(data)
191
+ json_data = compress(SafeJSON.dump(serialized))
192
+ end
193
+ @con.post(path, json_data, headers)
194
+ else
195
+ Sqreen.log.debug format('unknown method %s', method)
196
+ raise Sqreen::NotImplementedYet
197
+ end
198
+ end
199
+
200
+ if json && json.body
201
+ res = JSON.parse(json.body)
202
+ unless res['status']
203
+ Sqreen.log.debug(format('Cannot %s %s.', method, path))
204
+ end
205
+ else
206
+ Sqreen.log.debug 'warning: empty return value'
207
+ end
208
+ end
209
+ Sqreen.log.debug format('%s %s (DONE)', method, path)
210
+ res
211
+ end
212
+
213
+ def compress(data)
214
+ return data unless request_compression
215
+ out = StringIO.new
216
+ w = Zlib::GzipWriter.new(out)
217
+ w.write(data)
218
+ w.close
219
+ out.string
220
+ end
221
+
222
+ def login(framework)
223
+ headers = { 'x-api-key' => @token }
224
+
225
+ res = resilient_post('app-login', RuntimeInfos.all(framework), headers)
226
+
227
+ if !res || !res['status']
228
+ public_error = format('Cannot login. Token may be invalid: %s', @token)
229
+ Sqreen.log.error public_error
230
+ raise(Sqreen::TokenInvalidException,
231
+ format('invalid response: %s', res.inspect))
232
+ end
233
+ Sqreen.log.info 'Login success.'
234
+ @session_id = res['session_id']
235
+ Sqreen.log.debug "received session_id #{@session_id}"
236
+ Sqreen.logged_in = true
237
+ res
238
+ end
239
+
240
+ def rules
241
+ resilient_get('rulespack')
242
+ end
243
+
244
+ def heartbeat(cmd_res = {}, metrics = [])
245
+ payload = {}
246
+ payload['metrics'] = metrics unless metrics.nil? || metrics.empty?
247
+ payload['command_results'] = cmd_res unless cmd_res.nil? || cmd_res.empty?
248
+
249
+ post('app-beat', payload.empty? ? nil : payload, {}, 5)
250
+ end
251
+
252
+ def post_metrics(metrics)
253
+ return if metrics.nil? || metrics.empty?
254
+ payload = { METRICS_KEY => metrics }
255
+ resilient_post(METRICS_KEY, payload)
256
+ end
257
+
258
+ def post_attack(attack)
259
+ resilient_post('attack', attack.to_hash)
260
+ end
261
+
262
+ def post_bundle(bundle_sig, dependencies)
263
+ resilient_post('bundle', 'bundle_signature' => bundle_sig,
264
+ 'dependencies' => dependencies)
265
+ end
266
+
267
+ def post_request_record(request_record)
268
+ resilient_post('request_record', request_record.to_hash)
269
+ end
270
+
271
+ # Post an exception to Sqreen for analysis
272
+ # @param exception [RemoteException] Exception and context to be sent over
273
+ def post_sqreen_exception(exception)
274
+ post('sqreen_exception', exception.to_hash, {}, 5)
275
+ rescue *NET_ERRORS => e
276
+ Sqreen.log.warn(format('Could not post exception (network down? %s) %s',
277
+ e.inspect,
278
+ exception.to_hash.inspect))
279
+ nil
280
+ end
281
+
282
+ BATCH_KEY = 'batch'.freeze
283
+ EVENT_TYPE_KEY = 'event_type'.freeze
284
+ def post_batch(events)
285
+ batch = events.map do |event|
286
+ h = event.to_hash
287
+ h[EVENT_TYPE_KEY] = event_kind(event)
288
+ h
289
+ end
290
+ resilient_post(BATCH_KEY, BATCH_KEY => batch)
291
+ end
292
+
293
+ # Perform agent logout
294
+ # @param retrying [Boolean] whether to try again on error
295
+ def logout(retrying = true)
296
+ # Do not try to connect if we are not connected
297
+ unless connected?
298
+ Sqreen.log.debug('Not connected: not trying to logout')
299
+ return
300
+ end
301
+ # Perform not very resilient logout not to slow down client app shutdown
302
+ get('app-logout', {}, retrying ? 2 : 1)
303
+ Sqreen.logged_in = false
304
+ disconnect
305
+ end
306
+
307
+ protected
308
+
309
+ def event_kind(event)
310
+ case event
311
+ when Sqreen::RemoteException then 'sqreen_exception'
312
+ when Sqreen::Attack then 'attack'
313
+ when Sqreen::RequestRecord then 'request_record'
314
+ end
315
+ end
316
+ end
317
+ end