sqreen-alt 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +20 -0
- data/lib/sqreen-alt.rb +1 -0
- data/lib/sqreen.rb +68 -0
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +288 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/call_countable.rb +67 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +100 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +235 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +168 -0
- data/lib/sqreen/context.rb +26 -0
- data/lib/sqreen/deliveries/batch.rb +84 -0
- data/lib/sqreen/deliveries/simple.rb +39 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +61 -0
- data/lib/sqreen/events/remote_exception.rb +54 -0
- data/lib/sqreen/events/request_record.rb +62 -0
- data/lib/sqreen/exception.rb +34 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +446 -0
- data/lib/sqreen/frameworks/rails.rb +148 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/request_recorder.rb +69 -0
- data/lib/sqreen/frameworks/sinatra.rb +57 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +542 -0
- data/lib/sqreen/log.rb +119 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +45 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +96 -0
- data/lib/sqreen/middleware.rb +34 -0
- data/lib/sqreen/payload_creator.rb +137 -0
- data/lib/sqreen/performance_notifications.rb +86 -0
- data/lib/sqreen/performance_notifications/log.rb +36 -0
- data/lib/sqreen/performance_notifications/metrics.rb +36 -0
- data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
- data/lib/sqreen/remote_command.rb +93 -0
- data/lib/sqreen/rule_attributes.rb +26 -0
- data/lib/sqreen/rule_callback.rb +108 -0
- data/lib/sqreen/rules.rb +126 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
- data/lib/sqreen/rules_signature.rb +151 -0
- data/lib/sqreen/runner.rb +365 -0
- data/lib/sqreen/runtime_infos.rb +138 -0
- data/lib/sqreen/safe_json.rb +60 -0
- data/lib/sqreen/sdk.rb +22 -0
- data/lib/sqreen/serializer.rb +46 -0
- data/lib/sqreen/session.rb +317 -0
- data/lib/sqreen/shared_storage.rb +31 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +5 -0
- metadata +148 -0
@@ -0,0 +1,29 @@
|
|
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/rules_callbacks/regexp_rule'
|
5
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
6
|
+
|
7
|
+
require 'sqreen/rules_callbacks/record_request_context'
|
8
|
+
require 'sqreen/rules_callbacks/rails_parameters'
|
9
|
+
|
10
|
+
require 'sqreen/rules_callbacks/headers_insert'
|
11
|
+
require 'sqreen/rules_callbacks/blacklist_ips'
|
12
|
+
|
13
|
+
require 'sqreen/rules_callbacks/inspect_rule'
|
14
|
+
|
15
|
+
require 'sqreen/rules_callbacks/shell_env'
|
16
|
+
|
17
|
+
require 'sqreen/rules_callbacks/url_matches'
|
18
|
+
require 'sqreen/rules_callbacks/user_agent_matches'
|
19
|
+
require 'sqreen/rules_callbacks/crawler_user_agent_matches'
|
20
|
+
|
21
|
+
require 'sqreen/rules_callbacks/reflected_xss'
|
22
|
+
require 'sqreen/rules_callbacks/execjs'
|
23
|
+
|
24
|
+
require 'sqreen/rules_callbacks/binding_accessor_metrics'
|
25
|
+
require 'sqreen/rules_callbacks/binding_accessor_matcher'
|
26
|
+
require 'sqreen/rules_callbacks/count_http_codes'
|
27
|
+
require 'sqreen/rules_callbacks/crawler_user_agent_matches_metrics'
|
28
|
+
|
29
|
+
require 'sqreen/rules_callbacks/custom_error'
|
@@ -0,0 +1,77 @@
|
|
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/rule_callback'
|
5
|
+
require 'sqreen/binding_accessor'
|
6
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Callback that match on a list or matcher+binding accessor
|
11
|
+
class BindingAccessorMatcherCB < RuleCB
|
12
|
+
attr_reader :rules
|
13
|
+
|
14
|
+
# matcher on one elem
|
15
|
+
class MatcherElem
|
16
|
+
def initialize(expr)
|
17
|
+
prepare([expr])
|
18
|
+
end
|
19
|
+
include Matcher
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(klass, method, rule_hash)
|
23
|
+
super(klass, method, rule_hash)
|
24
|
+
@rules = []
|
25
|
+
if @data.empty? || @data['values'].nil? || @data['values'].empty?
|
26
|
+
msg = "no rules in data (had #{@data.keys})"
|
27
|
+
raise Sqreen::Exception, msg
|
28
|
+
end
|
29
|
+
prepare_rules(@data['values'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def prepare_rules(rules)
|
33
|
+
accessors = Hash.new do |hash, key|
|
34
|
+
hash[key] = BindingAccessor.new(key, true)
|
35
|
+
end
|
36
|
+
@rules = rules.map do |r|
|
37
|
+
if r['binding_accessor'].empty?
|
38
|
+
raise Sqreen::Exception, "no accessors #{r['id']}"
|
39
|
+
end
|
40
|
+
[
|
41
|
+
r['id'],
|
42
|
+
r['binding_accessor'].map { |expression| accessors[expression] },
|
43
|
+
MatcherElem.new(r['matcher']),
|
44
|
+
r['matcher']['value'],
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pre(inst, *args, &_block)
|
50
|
+
resol_cache = Hash.new do |hash, accessor|
|
51
|
+
hash[accessor] = accessor.resolve(binding, framework, inst, args)
|
52
|
+
end
|
53
|
+
@rules.each do |id, accessors, matcher, matcher_ref|
|
54
|
+
accessors.each do |accessor|
|
55
|
+
val = resol_cache[accessor]
|
56
|
+
val = [val] if val.is_a?(String)
|
57
|
+
next unless val.respond_to?(:each)
|
58
|
+
next if val.respond_to?(:seek)
|
59
|
+
val.each do |v|
|
60
|
+
next if !v.is_a?(String) || (!matcher.min_size.nil? && v.size < matcher.min_size)
|
61
|
+
next if matcher.match(v).nil?
|
62
|
+
infos = {
|
63
|
+
'id' => id,
|
64
|
+
'binding_accessor' => accessor.expression,
|
65
|
+
'matcher' => matcher_ref,
|
66
|
+
'found' => v,
|
67
|
+
}
|
68
|
+
record_event(infos)
|
69
|
+
return advise_action(:raise, :infos => infos)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,79 @@
|
|
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/rule_callback'
|
5
|
+
require 'sqreen/binding_accessor'
|
6
|
+
require 'sqreen/events/remote_exception'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Publish metrics about data taken from the binding accessor
|
11
|
+
class BindingAccessorMetrics < RuleCB
|
12
|
+
# Exception thrown when no expression are present
|
13
|
+
class NoExpressions < Sqreen::Exception
|
14
|
+
def initialize(expr)
|
15
|
+
super("No valid expressions defined in #{expr.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(klass, method, rule_hash)
|
20
|
+
super(klass, method, rule_hash)
|
21
|
+
@expr = {}
|
22
|
+
build_expressions(rule_hash[Attrs::CALLBACKS])
|
23
|
+
end
|
24
|
+
|
25
|
+
PRE_CB = 'pre'.freeze
|
26
|
+
POST_CB = 'post'.freeze
|
27
|
+
FAILING_CB = 'failing'.freeze
|
28
|
+
|
29
|
+
def pre?
|
30
|
+
@expr[PRE_CB]
|
31
|
+
end
|
32
|
+
|
33
|
+
def post?
|
34
|
+
@expr[POST_CB]
|
35
|
+
end
|
36
|
+
|
37
|
+
def failing?
|
38
|
+
@expr[FAILING_CB]
|
39
|
+
end
|
40
|
+
|
41
|
+
def pre(inst, *args, &_block)
|
42
|
+
return unless pre?
|
43
|
+
|
44
|
+
add_metrics(PRE_CB, inst, args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def post(rv, inst, *args, &_block)
|
48
|
+
return unless post?
|
49
|
+
|
50
|
+
add_metrics(POST_CB, inst, args, rv)
|
51
|
+
end
|
52
|
+
|
53
|
+
def failing(exception, inst, *args, &_block)
|
54
|
+
return unless failing?
|
55
|
+
|
56
|
+
add_metrics(FAILING_CB, inst, args, exception)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def add_metrics(name, inst, args, rv = nil)
|
62
|
+
category, key, value, = @expr[name].map do |accessor|
|
63
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
64
|
+
end
|
65
|
+
record_observation(category, key, value)
|
66
|
+
advise_action(nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_expressions(callbacks)
|
70
|
+
raise NoExpressions, callbacks if callbacks.nil? || callbacks.empty?
|
71
|
+
[PRE_CB, POST_CB, FAILING_CB].each do |c|
|
72
|
+
next if callbacks[c].nil? || callbacks[c].size < 3
|
73
|
+
@expr[c] = callbacks[c].map { |req| BindingAccessor.new(req, true) }
|
74
|
+
end
|
75
|
+
raise NoExpressions, callbacks if @expr.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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 'ipaddr'
|
5
|
+
|
6
|
+
require 'sqreen/rule_callback'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Looks for a blacklisted ip and block
|
11
|
+
class BlacklistIPsCB < RuleCB
|
12
|
+
def initialize(klass, method, rule_hash)
|
13
|
+
super(klass, method, rule_hash)
|
14
|
+
@ips = Hash[@data['values'].map { |v| [v, IPAddr.new(v)] }]
|
15
|
+
raise ArgumentError.new("no ips given") if @ips.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def pre(_inst, *_args, &_block)
|
19
|
+
return unless framework
|
20
|
+
ip = framework.client_ip
|
21
|
+
return unless ip
|
22
|
+
found = find_blacklisted_ip(ip)
|
23
|
+
return unless found
|
24
|
+
Sqreen.log.debug { "Found blacklisted IP #{ip} - found: #{found}" }
|
25
|
+
record_observation('blacklisted', found, 1)
|
26
|
+
advise_action(:raise)
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# Is this a blacklisted ip?
|
32
|
+
# return the ip blacklisted range that match ip
|
33
|
+
def find_blacklisted_ip(rip)
|
34
|
+
ret = (@ips || {}).find do |_, ip|
|
35
|
+
ip.include?(rip)
|
36
|
+
end
|
37
|
+
return nil unless ret
|
38
|
+
ret.first
|
39
|
+
rescue
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
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/rule_attributes'
|
5
|
+
require 'sqreen/rule_callback'
|
6
|
+
require 'sqreen/safe_json'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Save request context for handling further down
|
11
|
+
class CountHTTPCodes < RuleCB
|
12
|
+
METRIC_CATEGORY = 'http_code'.freeze
|
13
|
+
def post(rv, _inst, *_args, &_block)
|
14
|
+
return unless rv.is_a?(Array) && !rv.empty?
|
15
|
+
record_observation(METRIC_CATEGORY, rv[0], 1)
|
16
|
+
advise_action(nil)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Count 1 for each things located by the binding accessor
|
21
|
+
class BindingAccessorCounter < RuleCB
|
22
|
+
def initialize(klass, method, rule_hash)
|
23
|
+
super(klass, method, rule_hash)
|
24
|
+
@accessors = @data['values'].map do |expr|
|
25
|
+
BindingAccessor.new(expr, true)
|
26
|
+
end
|
27
|
+
@metric_category = rule_hash[Attrs::METRICS].first['name']
|
28
|
+
end
|
29
|
+
|
30
|
+
def post(rv, inst, *args, &_block)
|
31
|
+
return unless rv.is_a?(Array) && !rv.empty?
|
32
|
+
key = @accessors.map do |accessor|
|
33
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
34
|
+
end
|
35
|
+
record_observation(@metric_category, SafeJSON.dump(key), 1)
|
36
|
+
advise_action(nil)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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/rules_callbacks/matcher_rule'
|
5
|
+
require 'sqreen/frameworks'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# FIXME: Factor with UserAgentMatchesCB
|
10
|
+
# Look for crawlers
|
11
|
+
class CrawlerUserAgentMatchesCB < MatcherRuleCB
|
12
|
+
def pre(_inst, *_args, &_block)
|
13
|
+
ua = framework.client_user_agent
|
14
|
+
return unless ua
|
15
|
+
found = match(ua)
|
16
|
+
return unless found
|
17
|
+
Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
|
18
|
+
infos = { :found => found }
|
19
|
+
record_event(infos)
|
20
|
+
advise_action(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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/rules_callbacks/matcher_rule'
|
5
|
+
require 'sqreen/frameworks'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Look for crawlers and post them in metrics
|
10
|
+
class CrawlerUserAgentMatchesMetricsCB < MatcherRuleCB
|
11
|
+
CRAWLER_CATEGORY = 'crawler'.freeze
|
12
|
+
|
13
|
+
def pre(_inst, *_args, &_block)
|
14
|
+
ua = framework.client_user_agent
|
15
|
+
return unless ua
|
16
|
+
found = match(ua)
|
17
|
+
return unless found
|
18
|
+
Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
|
19
|
+
record_observation(CRAWLER_CATEGORY, ua, 1)
|
20
|
+
advise_action(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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/rule_callback'
|
5
|
+
require 'sqreen/exception'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Display sqreen presence
|
10
|
+
class CustomErrorCB < RuleCB
|
11
|
+
attr_reader :status_code, :redirect_url
|
12
|
+
def initialize(klass, method, rule_hash)
|
13
|
+
@redirect_url = nil
|
14
|
+
@status_code = nil
|
15
|
+
super(klass, method, rule_hash)
|
16
|
+
if @data.nil? || @data['values'].empty?
|
17
|
+
raise Sqreen::Exception, 'No data'
|
18
|
+
end
|
19
|
+
configure_custom_error(@data['values'][0])
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_custom_error(custom_error)
|
23
|
+
case custom_error['type']
|
24
|
+
when 'custom_error_page' then
|
25
|
+
@status_code = custom_error['status_code'].to_i
|
26
|
+
when 'redirection' then
|
27
|
+
@redirect_url = custom_error['redirection_url']
|
28
|
+
@status_code = custom_error.fetch('status_code', 303).to_i
|
29
|
+
else
|
30
|
+
raise Sqreen::Exception, "No custom error #{custom_error['type']}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def failing(except, _inst, *_args, &_block)
|
35
|
+
oexcept = nil
|
36
|
+
if except.respond_to?(:original_exception)
|
37
|
+
oexcept = except.original_exception
|
38
|
+
end
|
39
|
+
if !except.is_a?(Sqreen::AttackBlocked) &&
|
40
|
+
!oexcept.is_a?(Sqreen::AttackBlocked)
|
41
|
+
return advise_action(nil)
|
42
|
+
end
|
43
|
+
if @redirect_url
|
44
|
+
advise_action(:override, :new_return_value => respond_redirect)
|
45
|
+
else
|
46
|
+
advise_action(:override, :new_return_value => respond_page)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_redirect
|
51
|
+
[@status_code, { 'Location' => @redirect_url }, ['']]
|
52
|
+
end
|
53
|
+
|
54
|
+
def respond_page
|
55
|
+
page = open(File.join(File.dirname(__FILE__), '../attack_detected.html'))
|
56
|
+
headers = {
|
57
|
+
'Content-Type' => 'text/html',
|
58
|
+
'Content-Length' => page.size.to_s,
|
59
|
+
}
|
60
|
+
[@status_code, headers, page]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,241 @@
|
|
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
|
+
if defined?(::JRUBY_VERSION)
|
5
|
+
require 'rhino'
|
6
|
+
SQREEN_V8_THREAD = false
|
7
|
+
else
|
8
|
+
begin
|
9
|
+
require 'mini_racer'
|
10
|
+
SQREEN_V8_THREAD = true
|
11
|
+
rescue LoadError
|
12
|
+
require 'therubyracer'
|
13
|
+
SQREEN_V8_THREAD = false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'execjs'
|
18
|
+
|
19
|
+
require 'sqreen/rule_attributes'
|
20
|
+
require 'sqreen/rule_callback'
|
21
|
+
require 'sqreen/condition_evaluator'
|
22
|
+
require 'sqreen/binding_accessor'
|
23
|
+
require 'sqreen/events/remote_exception'
|
24
|
+
|
25
|
+
module Sqreen
|
26
|
+
module Rules
|
27
|
+
# Exec js callbacks
|
28
|
+
class ExecJSCB < RuleCB
|
29
|
+
attr_accessor :restrict_max_depth
|
30
|
+
def initialize(klass, method, rule_hash)
|
31
|
+
super(klass, method, rule_hash)
|
32
|
+
callbacks = @rule[Attrs::CALLBACKS]
|
33
|
+
@conditions = @rule.fetch(Attrs::CONDITIONS, {})
|
34
|
+
|
35
|
+
if callbacks['pre'].nil? &&
|
36
|
+
callbacks['post'].nil? &&
|
37
|
+
callbacks['failing'].nil?
|
38
|
+
raise(Sqreen::Exception, 'no JS CB provided')
|
39
|
+
end
|
40
|
+
|
41
|
+
build_runnable(callbacks)
|
42
|
+
if !SQREEN_V8_THREAD
|
43
|
+
@compiled = ExecJS.compile(@source)
|
44
|
+
end
|
45
|
+
@restrict_max_depth = 20
|
46
|
+
end
|
47
|
+
|
48
|
+
def pre?
|
49
|
+
@js_pre
|
50
|
+
end
|
51
|
+
|
52
|
+
def post?
|
53
|
+
@js_post
|
54
|
+
end
|
55
|
+
|
56
|
+
def failing?
|
57
|
+
@js_failing
|
58
|
+
end
|
59
|
+
|
60
|
+
def pre(inst, *args, &_block)
|
61
|
+
return unless pre?
|
62
|
+
|
63
|
+
call_callback('pre', inst, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
def post(rv, inst, *args, &_block)
|
67
|
+
return unless post?
|
68
|
+
|
69
|
+
call_callback('post', inst, args, rv)
|
70
|
+
end
|
71
|
+
|
72
|
+
def failing(rv, inst, *args, &_block)
|
73
|
+
return unless failing?
|
74
|
+
|
75
|
+
call_callback('failing', inst, args, rv)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
|
79
|
+
new_obj = {}
|
80
|
+
insert = []
|
81
|
+
to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
|
82
|
+
until to_do.empty?
|
83
|
+
where, key, value, deepness = to_do.pop
|
84
|
+
safe_key = key.kind_of?(Integer) ? key : key.to_s
|
85
|
+
if value.is_a?(Hash) && deepness < max_depth
|
86
|
+
val = {}
|
87
|
+
insert << [where, safe_key, val]
|
88
|
+
to_do += value.map { |k, v| [val, k, v, deepness + 1] }
|
89
|
+
elsif value.is_a?(Array) && deepness < max_depth
|
90
|
+
val = []
|
91
|
+
insert << [where, safe_key, val]
|
92
|
+
i = -1
|
93
|
+
to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
|
94
|
+
elsif deepness >= max_depth # if we are after max_depth don't try to filter
|
95
|
+
insert << [where, safe_key, value]
|
96
|
+
else
|
97
|
+
v = value.to_s
|
98
|
+
if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
|
99
|
+
case where
|
100
|
+
when Array
|
101
|
+
where << value
|
102
|
+
else
|
103
|
+
where[safe_key] = value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
insert.reverse.each do |wh, ikey, ival|
|
109
|
+
case wh
|
110
|
+
when Array
|
111
|
+
wh << ival unless ival.respond_to?(:empty?) && ival.empty?
|
112
|
+
else
|
113
|
+
wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
new_obj
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def record_and_continue?(ret)
|
122
|
+
case ret
|
123
|
+
when NilClass
|
124
|
+
return false
|
125
|
+
when Hash
|
126
|
+
ret.keys.each do |k|
|
127
|
+
ret[(begin
|
128
|
+
k.to_sym
|
129
|
+
rescue
|
130
|
+
k
|
131
|
+
end)] = ret[k] end
|
132
|
+
record_event(ret[:record]) unless ret[:record].nil?
|
133
|
+
unless ret['observations'].nil?
|
134
|
+
ret['observations'].each do |obs|
|
135
|
+
obs[3] = Time.parse(obs[3]) if obs.size >= 3 && obs[3].is_a?(String)
|
136
|
+
record_observation(*obs)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
return !ret[:call].nil?
|
140
|
+
else
|
141
|
+
raise Sqreen::Exception, "Invalid return type #{ret.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def call_callback(name, inst, args, rv = nil)
|
146
|
+
if SQREEN_V8_THREAD
|
147
|
+
Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"] ||= ExecJS.compile(@source)
|
148
|
+
end
|
149
|
+
ret = nil
|
150
|
+
args_override = nil
|
151
|
+
arguments = nil
|
152
|
+
loop do
|
153
|
+
arguments = (args_override || @argument_requirements[name]).map do |accessor|
|
154
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
155
|
+
end
|
156
|
+
arguments = restrict(name, arguments) if @conditions.key?(name)
|
157
|
+
Sqreen.log.debug { [name, arguments].inspect }
|
158
|
+
if SQREEN_V8_THREAD
|
159
|
+
ret = Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"].call(name, *arguments)
|
160
|
+
else
|
161
|
+
ret = @compiled.call(name, *arguments)
|
162
|
+
end
|
163
|
+
unless record_and_continue?(ret)
|
164
|
+
return nil if ret.nil?
|
165
|
+
return advise_action(ret[:status], ret)
|
166
|
+
end
|
167
|
+
name = ret[:call]
|
168
|
+
rv = ret[:data]
|
169
|
+
args_override = ret[:args]
|
170
|
+
args_override = build_accessor(args_override) if args_override
|
171
|
+
end
|
172
|
+
rescue => e
|
173
|
+
Sqreen.log.warn "we catch a JScb exception: #{e.inspect}"
|
174
|
+
Sqreen.log.debug e.backtrace
|
175
|
+
record_exception(e, :cb => name, :args => arguments)
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
def each_hash_val_include(condition, depth = 10)
|
180
|
+
return if depth <= 0
|
181
|
+
condition.each do |key, values|
|
182
|
+
if key == ConditionEvaluator::HASH_INC_OPERATOR
|
183
|
+
yield values
|
184
|
+
else
|
185
|
+
values.map do |v|
|
186
|
+
each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def restrict(cbname, arguments)
|
193
|
+
condition = @conditions[cbname]
|
194
|
+
return arguments if condition.nil? or @argument_requirements[cbname].nil?
|
195
|
+
|
196
|
+
each_hash_val_include(condition) do |needle, haystack, min_length|
|
197
|
+
# We could actually run the binding accessor expression here.
|
198
|
+
needed_idx = @argument_requirements[cbname].map(&:expression).index(needle)
|
199
|
+
next unless needed_idx
|
200
|
+
|
201
|
+
haystack_idx = @argument_requirements[cbname].map(&:expression).index(haystack)
|
202
|
+
next unless haystack_idx
|
203
|
+
|
204
|
+
arguments[haystack_idx] = ExecJSCB.hash_val_included(
|
205
|
+
arguments[needed_idx],
|
206
|
+
arguments[haystack_idx],
|
207
|
+
min_length.to_i,
|
208
|
+
@restrict_max_depth
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
arguments
|
213
|
+
end
|
214
|
+
|
215
|
+
def build_accessor(reqs)
|
216
|
+
reqs.map do |req|
|
217
|
+
BindingAccessor.new(req, true)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def build_runnable(callbacks)
|
222
|
+
@argument_requirements = {}
|
223
|
+
@source = ''
|
224
|
+
@js_pre = !callbacks['pre'].nil?
|
225
|
+
@js_post = !callbacks['post'].nil?
|
226
|
+
@js_failing = !callbacks['failing'].nil?
|
227
|
+
callbacks.each do |name, args_or_func|
|
228
|
+
@source << "var #{name} = "
|
229
|
+
if args_or_func.is_a?(Array)
|
230
|
+
@source << args_or_func.pop
|
231
|
+
@argument_requirements[name] = build_accessor(args_or_func)
|
232
|
+
else
|
233
|
+
@source << args_or_func
|
234
|
+
@argument_requirements[name] = []
|
235
|
+
end
|
236
|
+
@source << ";\n"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|