sqreen 0.7.01461158029-java

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +40 -0
  5. data/lib/sqreen.rb +67 -0
  6. data/lib/sqreen/binding_accessor.rb +184 -0
  7. data/lib/sqreen/ca.crt +72 -0
  8. data/lib/sqreen/callback_tree.rb +78 -0
  9. data/lib/sqreen/callbacks.rb +120 -0
  10. data/lib/sqreen/capped_queue.rb +23 -0
  11. data/lib/sqreen/condition_evaluator.rb +169 -0
  12. data/lib/sqreen/conditionable.rb +50 -0
  13. data/lib/sqreen/configuration.rb +151 -0
  14. data/lib/sqreen/context.rb +22 -0
  15. data/lib/sqreen/deliveries/batch.rb +80 -0
  16. data/lib/sqreen/deliveries/simple.rb +36 -0
  17. data/lib/sqreen/detect.rb +14 -0
  18. data/lib/sqreen/detect/shell_injection.rb +61 -0
  19. data/lib/sqreen/detect/sql_injection.rb +115 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +60 -0
  22. data/lib/sqreen/events/remote_exception.rb +53 -0
  23. data/lib/sqreen/exception.rb +31 -0
  24. data/lib/sqreen/frameworks.rb +40 -0
  25. data/lib/sqreen/frameworks/generic.rb +243 -0
  26. data/lib/sqreen/frameworks/rails.rb +155 -0
  27. data/lib/sqreen/frameworks/rails3.rb +36 -0
  28. data/lib/sqreen/frameworks/sinatra.rb +34 -0
  29. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  30. data/lib/sqreen/instrumentation.rb +504 -0
  31. data/lib/sqreen/log.rb +116 -0
  32. data/lib/sqreen/metrics.rb +6 -0
  33. data/lib/sqreen/metrics/average.rb +39 -0
  34. data/lib/sqreen/metrics/base.rb +41 -0
  35. data/lib/sqreen/metrics/collect.rb +22 -0
  36. data/lib/sqreen/metrics/sum.rb +20 -0
  37. data/lib/sqreen/metrics_store.rb +94 -0
  38. data/lib/sqreen/parsers/sql.rb +98 -0
  39. data/lib/sqreen/parsers/sql_tokenizer.rb +266 -0
  40. data/lib/sqreen/parsers/unix.rb +110 -0
  41. data/lib/sqreen/payload_creator.rb +132 -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 +82 -0
  47. data/lib/sqreen/rule_attributes.rb +25 -0
  48. data/lib/sqreen/rule_callback.rb +97 -0
  49. data/lib/sqreen/rules.rb +116 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  52. data/lib/sqreen/rules_callbacks/count_http_codes.rb +18 -0
  53. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  54. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +25 -0
  55. data/lib/sqreen/rules_callbacks/execjs.rb +136 -0
  56. data/lib/sqreen/rules_callbacks/headers_insert.rb +20 -0
  57. data/lib/sqreen/rules_callbacks/inspect_rule.rb +20 -0
  58. data/lib/sqreen/rules_callbacks/matcher_rule.rb +103 -0
  59. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  60. data/lib/sqreen/rules_callbacks/record_request_context.rb +23 -0
  61. data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -0
  62. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  63. data/lib/sqreen/rules_callbacks/shell.rb +33 -0
  64. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  65. data/lib/sqreen/rules_callbacks/sql.rb +41 -0
  66. data/lib/sqreen/rules_callbacks/system_shell.rb +25 -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 +142 -0
  70. data/lib/sqreen/runner.rb +312 -0
  71. data/lib/sqreen/runtime_infos.rb +127 -0
  72. data/lib/sqreen/session.rb +340 -0
  73. data/lib/sqreen/stats.rb +18 -0
  74. data/lib/sqreen/version.rb +6 -0
  75. metadata +143 -0
@@ -0,0 +1,78 @@
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
+
6
+ module Sqreen
7
+ class CBTree
8
+ include Enumerable
9
+ # Callbacks tree:
10
+ # class
11
+ # methods
12
+ # position
13
+
14
+ def initialize
15
+ @by_class = {}
16
+ end
17
+
18
+ def add(cb)
19
+ @by_class[cb.klass] = {} unless @by_class[cb.klass]
20
+
21
+ cb_klass = @by_class[cb.klass]
22
+ unless cb_klass[cb.method]
23
+ cb_klass[cb.method] = { :pre => [], :post => [], :failing => [] }
24
+ end
25
+
26
+ methods = cb_klass[cb.method]
27
+
28
+ methods[:pre] << cb if cb.pre?
29
+ methods[:post] << cb if cb.post?
30
+ methods[:failing] << cb if cb.failing?
31
+ end
32
+
33
+ def remove(cb)
34
+ types = @by_class[cb.klass][cb.method]
35
+
36
+ types[:pre].delete cb if cb.pre?
37
+ types[:post].delete cb if cb.post?
38
+ types[:failing].delete cb if cb.failing?
39
+ end
40
+
41
+ def get(klass, method, type = nil)
42
+ k = @by_class[klass]
43
+ unless k
44
+ log(format('Error: no cb registered for class %s (%s)', klass.inspect, klass.class))
45
+ log(inspect)
46
+ return []
47
+ end
48
+ cbs = k[method]
49
+ unless cbs
50
+ log(format('Error: no cbs registered for method %s.%s', klass, method))
51
+ log(inspect)
52
+ return []
53
+ end
54
+
55
+ if type.nil?
56
+ res = Set.new
57
+ cbs.values.each do |v|
58
+ res += v
59
+ end
60
+ return res.to_a
61
+ else
62
+ return cbs[type]
63
+ end
64
+ end
65
+
66
+ def each
67
+ @by_class.each do |_klass, values|
68
+ values.each do |_method, cbs|
69
+ cbs.values.each do |cb_ary|
70
+ cb_ary.each do |cb|
71
+ yield cb
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,120 @@
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 'set'
5
+
6
+ module Sqreen
7
+ module SharedStorage
8
+ @@shared = {}
9
+
10
+ def self::get(key, default = nil)
11
+ h = @@shared[Thread.current]
12
+ return h.fetch(key, default) if h
13
+ default
14
+ end
15
+
16
+ def self::set(key, obj)
17
+ main_key = Thread.current
18
+ @@shared[main_key] = {} unless @@shared.key? main_key
19
+ @@shared[main_key][key] = obj
20
+ end
21
+
22
+ def self.clear
23
+ @@shared.delete(Thread.current)
24
+ end
25
+
26
+ def self.inc(value)
27
+ set(value, get(value, 0) + 1)
28
+ end
29
+
30
+ def self.dec(value)
31
+ set(value, get(value, 0) - 1)
32
+ end
33
+ end
34
+
35
+ class CB
36
+ # Callback class.
37
+ #
38
+ # Three methods can be defined:
39
+ # - pre(*args, &block)
40
+ # To be called prior to the hooked method.
41
+ # - post(return_value, *args, &block)
42
+ # To be called after the hooked method. The return_value argument is
43
+ # the value returned by the hooked method.
44
+ # - failing(exception, ...)
45
+ # To be called when the method raise
46
+ # The method pre, post and exception may return nil or a Hash.
47
+ # - nil: the original method is called and the callback has no further
48
+ # effect
49
+ # - { :status => :skip }: we skip the original method call
50
+ # - { :status => :raise}:
51
+ #
52
+ # - nil: the original return value is returned, as if coallback had no
53
+ # effect
54
+ # - { :status => :raise}:
55
+ # - { :status => :override }:
56
+ #
57
+ # - nil: reraise
58
+ # - { :status => :reraise }: reraise
59
+ # - { :status => :override }: eat exception
60
+ # - { :retry => :retry }: try the block again
61
+
62
+ attr_reader :klass, :method
63
+
64
+ def initialize(klass, method)
65
+ @method = method
66
+ @klass = klass
67
+
68
+ @has_pre = respond_to? :pre
69
+ @has_post = respond_to? :post
70
+ @has_failing = respond_to? :failing
71
+
72
+ raise(Sqreen::Exception, 'No callback provided') unless @has_pre || @has_post || @has_failing
73
+ end
74
+
75
+ def pre?
76
+ @has_pre
77
+ end
78
+
79
+ def post?
80
+ @has_post
81
+ end
82
+
83
+ def failing?
84
+ @has_failing
85
+ end
86
+
87
+ def to_s
88
+ format('#<%s: %s.%s>', self.class, @klass, @method)
89
+ end
90
+ end
91
+ # target_method, position, callback, callback class
92
+
93
+ class DefaultCB < CB
94
+ def pre(_inst, *args, &_block)
95
+ Sqreen.log.debug "<< #{@klass} #{@method} #{Thread.current}"
96
+ Sqreen.log.debug args.join ' '
97
+ # log params
98
+ end
99
+
100
+ def post(_rv, _inst, *_args, &_block)
101
+ # log "#{rv}"
102
+ Sqreen.log.debug ">> #{@klass} #{@method} #{Thread.current}"
103
+ end
104
+ end
105
+
106
+ class RunWhenCalledCB < CB
107
+ def initialize(klass, method, &block)
108
+ super(klass, method)
109
+
110
+ raise 'missing block' unless block_given?
111
+ @block = block
112
+ end
113
+
114
+ def pre(_inst, *_args, &_block)
115
+ # FIXME: implement this removal
116
+ @remove_me = true
117
+ @block.call
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,23 @@
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
+ # A simple size limited queue.
6
+ # When trying to enqueue more than the capacity
7
+ # the older elements will get thrown
8
+ class CappedQueue < Queue
9
+ attr_reader :capacity
10
+
11
+ def initialize(capacity)
12
+ @capacity = capacity
13
+ super()
14
+ end
15
+
16
+ alias original_push push
17
+
18
+ def push(value)
19
+ pop until size < @capacity
20
+ original_push(value)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,169 @@
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/binding_accessor'
5
+ require 'sqreen/exception'
6
+
7
+ # Evaluate a condition, resolving literals using BindingAccessor.
8
+ #
9
+ # { "%and" => ["true", "true"] } -> true
10
+ # { "%or" => ["true", "false"] } -> true
11
+ # { "%and" => ["false", "true"] } -> false
12
+ #
13
+ # { "%equal" => ["coucou", "#.args[0]"] } -> "coucou" == args[0]
14
+ # { "%hash_val_include" => ["toto is a small guy", "#.request_params"] } ->
15
+ # true if one value of request params in included
16
+ # in the sentence 'toto is a small guy'.
17
+ #
18
+ # Combine expressions:
19
+ # { "%or" =>
20
+ # [
21
+ # { "%hash_val_include" => ["AAA", "#.request_params"] },
22
+ # { "%hash_val_include" => ["BBB", "#.request_params"] },
23
+ # ]
24
+ # }
25
+ # will return true if one of the request_params include either AAA or BBB.
26
+ #
27
+ class ConditionEvaluator
28
+ # Predicate: Is value deeply included in hash
29
+ # @params value [Object] object to find
30
+ # @params hash [Hash] Hash to search into
31
+ # @params min_value_size [Fixnum] to compare against
32
+ def self.hash_val_include?(value, hash, min_value_size, rem = 10)
33
+ return false if rem <= 0
34
+ hash.any? do |_k, hval|
35
+ case hval
36
+ when Hash
37
+ ConditionEvaluator.hash_val_include?(value, hval, min_value_size, rem - 1)
38
+ when NilClass
39
+ false
40
+ else
41
+ return false if hval.respond_to?(:empty?) && hval.empty?
42
+ v = hval.to_s
43
+ return false if v.size < min_value_size
44
+ value.to_s.include?(v)
45
+ end
46
+ end
47
+ end
48
+
49
+ # Initialize evaluator
50
+ # @param cond [Hash] condition Hash
51
+ def initialize(cond)
52
+ unless cond == true || cond == false
53
+ unless cond.respond_to? :each
54
+ raise(Sqreen::Exception, "cond should be a Hash (was #{cond.class})")
55
+ end
56
+ end
57
+ @raw = cond
58
+ @compiled = compile_expr(cond, 10)
59
+ end
60
+
61
+ # Evaluate the condition
62
+ # @params *args: BindingAccessor evaluate arguments
63
+ def evaluate(*args)
64
+ evaluate_expr(@compiled, 10, *args)
65
+ end
66
+
67
+ protected
68
+
69
+ def compile_expr(exp, rem)
70
+ return exp if exp == true || exp == false
71
+ raise(Sqreen::Exception, 'too deep call detected') if rem <= 0
72
+ h = {}
73
+ exp.each do |op, values|
74
+ unless op.is_a? String
75
+ raise Sqreen::Exception, "op should be a String (was #{op.class})"
76
+ end
77
+ unless values.is_a?(Array)
78
+ raise Sqreen::Exception, "values should be an Array (was #{values.class})"
79
+ end
80
+ h[op] = values.map do |v|
81
+ case v
82
+ when Hash
83
+ compile_expr(v, rem - 1)
84
+ when 'true'
85
+ true
86
+ when 'false'
87
+ false
88
+ else
89
+ BindingAccessor.new(v.to_s)
90
+ end
91
+ end
92
+ end
93
+ h
94
+ end
95
+
96
+ EQ_OPERATOR = '%equal'.freeze
97
+ NEQ_OPERATOR = '%not_equal'.freeze
98
+ GTE_OPERATOR = '%gte'.freeze
99
+ LTE_OPERATOR = '%lte'.freeze
100
+ GT_OPERATOR = '%gt'.freeze
101
+ LT_OPERATOR = '%lt'.freeze
102
+ HASH_INC_OPERATOR = '%hash_val_include'.freeze
103
+ INC_OPERATOR = '%include'.freeze
104
+ OR_OPERATOR = '%or'.freeze
105
+ AND_OPERATOR = '%and'.freeze
106
+
107
+ OPERATORS_ARITY = {
108
+ HASH_INC_OPERATOR => 3,
109
+ EQ_OPERATOR => 2,
110
+ NEQ_OPERATOR => 2,
111
+ INC_OPERATOR => 2,
112
+ GTE_OPERATOR => 2,
113
+ LTE_OPERATOR => 2,
114
+ GT_OPERATOR => 2,
115
+ LT_OPERATOR => 2,
116
+ }.freeze
117
+
118
+ def evaluate_expr(exp, rem, *args)
119
+ return exp if exp == true || exp == false
120
+ raise(Sqreen::Exception, 'too deep call detected') if rem <= 0
121
+ bool = nil
122
+ exp.each do |op, values|
123
+ res = values.map do |v|
124
+ case v
125
+ when Hash
126
+ evaluate_expr(v, rem - 1, *args)
127
+ when true, false
128
+ v
129
+ else
130
+ v.resolve(*args)
131
+ end
132
+ end
133
+
134
+ arity = OPERATORS_ARITY[op]
135
+ if !arity.nil? && res.size != arity
136
+ raise(Sqreen::Exception, "bad res #{res} (op #{op} wanted #{arity})")
137
+ end
138
+ bool = case op
139
+ when OR_OPERATOR
140
+ res.any?
141
+ when AND_OPERATOR
142
+ res.all?
143
+ when EQ_OPERATOR
144
+ res[0] == res[1]
145
+ when NEQ_OPERATOR
146
+ res[0] != res[1]
147
+ when GT_OPERATOR
148
+ res[0] > res[1]
149
+ when GTE_OPERATOR
150
+ res[0] >= res[1]
151
+ when LT_OPERATOR
152
+ res[0] < res[1]
153
+ when LTE_OPERATOR
154
+ res[0] <= res[1]
155
+ when INC_OPERATOR
156
+ unless res[0].respond_to?(:include?)
157
+ raise(Sqreen::Exception, "no include on res #{res[0].inspect}")
158
+ end
159
+ res[0].include?(res[1])
160
+ when HASH_INC_OPERATOR
161
+ ConditionEvaluator.hash_val_include?(res[0], res[1], res[2])
162
+ else
163
+ # FIXME: this should be check in compile
164
+ raise(Sqreen::Exception, "unknown op #{op})")
165
+ end
166
+ end
167
+ bool
168
+ end
169
+ end
@@ -0,0 +1,50 @@
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/condition_evaluator'
5
+
6
+ module Sqreen
7
+ # A module that will dynamically had preconditions to the pre/post/failing
8
+ # callbacks
9
+ module Conditionable
10
+ # Hook the necessary callback function
11
+ #
12
+ # @param conditions [Hash] hash of callback names => conditions
13
+ def condition_callbacks(conditions)
14
+ base = self.class
15
+ %w(pre post failing).each do |cb|
16
+ next unless conditions.key?(cb)
17
+ conds = conditions[cb]
18
+ next if conds.respond_to?(:empty?) && conds.empty?
19
+ next unless base.method_defined?(cb)
20
+ send("#{cb}_conditions=", ConditionEvaluator.new(conds))
21
+ next if base.instance_variable_get("@conditional_hooked_#{cb}")
22
+ base.send(:alias_method, "#{cb}_without_conditions", cb)
23
+ base.send(:alias_method, cb, "#{cb}_with_conditions")
24
+ base.instance_variable_set("@conditional_hooked_#{cb}", true)
25
+ end
26
+ end
27
+
28
+ def pre_with_conditions(inst, *args, &block)
29
+ eargs = [binding, framework, inst, args, @data, nil]
30
+ return nil if !pre_conditions.nil? && !pre_conditions.evaluate(*eargs)
31
+ pre_without_conditions(inst, *args, &block)
32
+ end
33
+
34
+ def post_with_conditions(rv, inst, *args, &block)
35
+ eargs = [binding, framework, inst, args, @data, rv]
36
+ return nil if !post_conditions.nil? && !post_conditions.evaluate(*eargs)
37
+ post_without_conditions(rv, inst, *args, &block)
38
+ end
39
+
40
+ def failing_with_conditions(rv, inst, *args, &block)
41
+ eargs = [binding, framework, inst, args, @data, rv]
42
+ return nil if !failing_conditions.nil? && !failing_conditions.evaluate(*eargs)
43
+ failing_without_conditions(rv, inst, *args, &block)
44
+ end
45
+
46
+ protected
47
+
48
+ attr_accessor :pre_conditions, :post_conditions, :failing_conditions
49
+ end
50
+ end
@@ -0,0 +1,151 @@
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 'yaml'
5
+ require 'sqreen/performance_notifications/newrelic'
6
+
7
+ module Sqreen
8
+ @config = nil
9
+
10
+ def self::config_init(framework = nil)
11
+ @config = Configuration.new(framework)
12
+ @config.load!
13
+ if @config && config_get(:report_perf_newrelic)
14
+ Sqreen::PerformanceNotifications::NewRelic.enable
15
+ end
16
+ @config
17
+ end
18
+
19
+ def self::config_get(name)
20
+ raise 'No configuration defined' if @config.nil?
21
+ @config.get(name)
22
+ end
23
+
24
+ CONFIG_FILE_BY_ENV = 'SQREEN_CONFIG_FILE'.freeze
25
+
26
+ CONFIG_DESCRIPTION = [
27
+ { :env => :SQREEN_URL, :name => :url,
28
+ :default => 'https://back.sqreen.io' },
29
+ { :env => :SQREEN_TOKEN, :name => :token,
30
+ :default => nil },
31
+ { :env => :SQREEN_RULES, :name => :local_rules,
32
+ :default => nil },
33
+ { :env => :SQREEN_RULES_SIGNATURE, :name => :rules_verify_signature,
34
+ :default => true },
35
+ { :env => :SQREEN_LOG_LEVEL, :name => :log_level,
36
+ :default => 'WARN', :choice => %w(UNKNOWN FATAL ERROR WARN INFO DEBUG) },
37
+ { :env => :SQREEN_LOG_LOCATION, :name => :log_location,
38
+ :default => 'log/sqreen.log' },
39
+ { :env => :SQREEN_RUN_IN_TEST, :name => :run_in_test,
40
+ :default => false },
41
+ { :env => :SQREEN_BLOCK_ALL_RULES, :name => :block_all_rules,
42
+ :default => nil },
43
+ { :env => :SQREEN_REPORT_PERF_NR, :name => :report_perf_newrelic,
44
+ :default => false },
45
+
46
+ ].freeze
47
+
48
+ CONFIG_FILE_NAME = 'sqreen.yml'.freeze
49
+
50
+ # Class to access configurations variables
51
+ # This try to load environment by different ways.
52
+ # 1. By file:
53
+ # a. From path in environment variable SQREEN_CONFIG_FILE
54
+ # b. From path in #{Rails.root}/config/sqreen.yml
55
+ # c. From home in ~/.sqreen.yml
56
+ # 2. From the environment, which overrides whatever result we found in 1.
57
+ class Configuration
58
+ def initialize(framework = nil)
59
+ @framework = framework
60
+ @config = default_values
61
+ end
62
+
63
+ def load!
64
+ path = find_configuration_file
65
+ if path
66
+ file_config = parse_configuration_file(path)
67
+ @config.merge!(file_config)
68
+ end
69
+
70
+ env_config = from_environment
71
+ @config.merge!(env_config)
72
+ end
73
+
74
+ def get(name)
75
+ @config[name.to_sym]
76
+ end
77
+
78
+ def default_values
79
+ res = {}
80
+ Sqreen::CONFIG_DESCRIPTION.each do |param|
81
+ name = param[:name]
82
+ value = param[:default]
83
+ choices = param[:choices]
84
+ if choices && !choices.include?(value)
85
+ msg = format("Invalid value '%s' for env '%s' (allowed: %s)", value, name, choices)
86
+ raise Sqreen::Exception, msg
87
+ end
88
+ res[name] = value
89
+ end
90
+ res
91
+ end
92
+
93
+ def from_environment
94
+ res = {}
95
+ Sqreen::CONFIG_DESCRIPTION.each do |param|
96
+ name = param[:name]
97
+ value = ENV[param[:env].to_s]
98
+ next unless value
99
+ res[name] = value
100
+ end
101
+ res
102
+ end
103
+
104
+ def parse_configuration_file(path)
105
+ res = YAML.load_file path
106
+ return {} unless res.is_a?(Hash)
107
+ if @framework
108
+ env = @framework.framework_infos[:environment]
109
+ res = res[env] if env && res[env].is_a?(Hash)
110
+ end
111
+ # hash keys loaded by YAML are strings instead of symbols
112
+ res.keys.each do |key|
113
+ res[key.to_sym] = res.delete(key)
114
+ end
115
+ res
116
+ end
117
+
118
+ def find_user_home
119
+ homes = %w(HOME HOMEPATH)
120
+ homes.detect { |h| !ENV[h].nil? }
121
+ end
122
+
123
+ def find_configuration_file
124
+ config_file_from_env || local_config_file || config_file_from_home
125
+ end
126
+
127
+ protected
128
+
129
+ def config_file_from_env
130
+ path = ENV[Sqreen::CONFIG_FILE_BY_ENV]
131
+ return path if path && File.exist?(path)
132
+ end
133
+
134
+ def local_config_file
135
+ if @framework && @framework.root
136
+ path = File.join(@framework.root.to_s, 'config', CONFIG_FILE_NAME)
137
+ return path if File.exist?(path)
138
+ else
139
+ path = File.expand_path(File.join('config', 'sqreen.yml'))
140
+ return path if File.exist?(path)
141
+ end
142
+ end
143
+
144
+ def config_file_from_home
145
+ home = find_user_home
146
+ return unless home
147
+ path = File.join(ENV[home], '.' + CONFIG_FILE_NAME)
148
+ return path if File.exist?(path)
149
+ end
150
+ end
151
+ end