sqreen-alt 1.10.0

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