sentry-raven 1.1.0 → 3.1.2

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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/.craft.yml +19 -0
  3. data/.scripts/bump-version.rb +5 -0
  4. data/CHANGELOG.md +703 -0
  5. data/Gemfile +37 -0
  6. data/Makefile +3 -0
  7. data/README.md +116 -18
  8. data/Rakefile +30 -0
  9. data/exe/raven +32 -0
  10. data/lib/raven/backtrace.rb +17 -8
  11. data/lib/raven/base.rb +45 -194
  12. data/lib/raven/breadcrumbs/active_support_logger.rb +25 -0
  13. data/lib/raven/breadcrumbs/logger.rb +3 -0
  14. data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
  15. data/lib/raven/breadcrumbs.rb +76 -0
  16. data/lib/raven/cli.rb +31 -39
  17. data/lib/raven/client.rb +45 -32
  18. data/lib/raven/configuration.rb +427 -130
  19. data/lib/raven/context.rb +33 -6
  20. data/lib/raven/core_ext/object/deep_dup.rb +57 -0
  21. data/lib/raven/core_ext/object/duplicable.rb +153 -0
  22. data/lib/raven/event.rb +194 -206
  23. data/lib/raven/helpers/deprecation_helper.rb +17 -0
  24. data/lib/raven/instance.rb +249 -0
  25. data/lib/raven/integrations/delayed_job.rb +25 -23
  26. data/lib/raven/integrations/rack-timeout.rb +22 -0
  27. data/lib/raven/integrations/rack.rb +40 -24
  28. data/lib/raven/integrations/rails/active_job.rb +52 -20
  29. data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
  30. data/lib/raven/integrations/rails/controller_transaction.rb +13 -0
  31. data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
  32. data/lib/raven/integrations/rails.rb +39 -7
  33. data/lib/raven/integrations/rake.rb +7 -2
  34. data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
  35. data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
  36. data/lib/raven/integrations/sidekiq.rb +6 -48
  37. data/lib/raven/integrations/tasks.rb +1 -1
  38. data/lib/raven/interface.rb +25 -0
  39. data/lib/raven/interfaces/exception.rb +5 -8
  40. data/lib/raven/interfaces/http.rb +5 -12
  41. data/lib/raven/interfaces/message.rb +10 -6
  42. data/lib/raven/interfaces/single_exception.rb +1 -5
  43. data/lib/raven/interfaces/stack_trace.rb +23 -30
  44. data/lib/raven/linecache.rb +35 -23
  45. data/lib/raven/logger.rb +13 -16
  46. data/lib/raven/processor/cookies.rb +27 -7
  47. data/lib/raven/processor/http_headers.rb +55 -0
  48. data/lib/raven/processor/post_data.rb +16 -3
  49. data/lib/raven/processor/removecircularreferences.rb +12 -8
  50. data/lib/raven/processor/removestacktrace.rb +17 -6
  51. data/lib/raven/processor/sanitizedata.rb +92 -37
  52. data/lib/raven/processor/utf8conversion.rb +39 -14
  53. data/lib/raven/processor.rb +5 -1
  54. data/lib/raven/transports/http.rb +31 -22
  55. data/lib/raven/transports/stdout.rb +20 -0
  56. data/lib/raven/transports.rb +6 -10
  57. data/lib/raven/utils/context_filter.rb +42 -0
  58. data/lib/raven/utils/deep_merge.rb +6 -12
  59. data/lib/raven/utils/exception_cause_chain.rb +20 -0
  60. data/lib/raven/utils/real_ip.rb +62 -0
  61. data/lib/raven/utils/request_id.rb +16 -0
  62. data/lib/raven/version.rb +2 -1
  63. data/lib/sentry-raven-without-integrations.rb +6 -1
  64. data/lib/sentry_raven_without_integrations.rb +1 -0
  65. data/sentry-raven.gemspec +28 -0
  66. metadata +44 -127
  67. data/lib/raven/error.rb +0 -4
  68. data/lib/raven/interfaces.rb +0 -34
  69. data/lib/raven/okjson.rb +0 -614
@@ -1,14 +1,18 @@
1
1
  module Raven
2
2
  class Processor::RemoveCircularReferences < Processor
3
- def process(v, visited = [])
4
- return "(...)" if visited.include?(v.__id__)
5
- visited += [v.__id__]
6
- if v.is_a?(Hash)
7
- v.each_with_object({}) { |(k, v_), memo| memo[k] = process(v_, visited) }
8
- elsif v.is_a?(Array)
9
- v.map { |v_| process(v_, visited) }
3
+ ELISION_STRING = "(...)".freeze
4
+ def process(value, visited = [])
5
+ return ELISION_STRING if visited.include?(value.__id__)
6
+
7
+ visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
8
+
9
+ case value
10
+ when Hash
11
+ !value.frozen? ? value.merge!(value) { |_, v| process v, visited } : value.merge(value) { |_, v| process v, visited }
12
+ when Array
13
+ !value.frozen? ? value.map! { |v| process v, visited } : value.map { |v| process v, visited }
10
14
  else
11
- v
15
+ value
12
16
  end
13
17
  end
14
18
  end
@@ -1,13 +1,24 @@
1
1
  module Raven
2
2
  class Processor::RemoveStacktrace < Processor
3
- def process(value)
4
- if value[:exception]
5
- value[:exception][:values].map do |single_exception|
6
- single_exception.delete(:stacktrace) if single_exception[:stacktrace]
7
- end
3
+ def process(data)
4
+ process_if_symbol_keys(data) if data[:exception]
5
+ process_if_string_keys(data) if data["exception"]
6
+
7
+ data
8
+ end
9
+
10
+ private
11
+
12
+ def process_if_symbol_keys(data)
13
+ data[:exception][:values].map do |single_exception|
14
+ single_exception.delete(:stacktrace) if single_exception[:stacktrace]
8
15
  end
16
+ end
9
17
 
10
- value
18
+ def process_if_string_keys(data)
19
+ data["exception"]["values"].map do |single_exception|
20
+ single_exception.delete("stacktrace") if single_exception["stacktrace"]
21
+ end
11
22
  end
12
23
  end
13
24
  end
@@ -1,63 +1,118 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
4
+
3
5
  module Raven
4
6
  class Processor::SanitizeData < Processor
5
- STRING_MASK = '********'.freeze
6
- INT_MASK = 0
7
7
  DEFAULT_FIELDS = %w(authorization password passwd secret ssn social(.*)?sec).freeze
8
- CREDIT_CARD_RE = /^(?:\d[ -]*?){13,16}$/
9
- REGEX_SPECIAL_CHARACTERS = %w(. $ ^ { [ ( | ) * + ?).freeze
8
+ CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b/.freeze
9
+ QUERY_STRING = ['query_string', :query_string].freeze
10
+ JSON_STARTS_WITH = ["[", "{"].freeze
10
11
 
11
- attr_accessor :sanitize_fields, :sanitize_credit_cards
12
+ attr_accessor :sanitize_fields, :sanitize_credit_cards, :sanitize_fields_excluded
12
13
 
13
14
  def initialize(client)
14
15
  super
15
16
  self.sanitize_fields = client.configuration.sanitize_fields
16
17
  self.sanitize_credit_cards = client.configuration.sanitize_credit_cards
18
+ self.sanitize_fields_excluded = client.configuration.sanitize_fields_excluded
17
19
  end
18
20
 
19
- def process(value)
20
- value.each_with_object(value) { |(k,v), memo| memo[k] = sanitize(k,v) }
21
+ def process(value, key = nil)
22
+ case value
23
+ when Hash
24
+ sanitize_hash_value(key, value)
25
+ when Array
26
+ sanitize_array_value(key, value)
27
+ when Integer
28
+ matches_regexes?(key, value.to_s) ? INT_MASK : value
29
+ when String
30
+ sanitize_string_value(key, value)
31
+ else
32
+ value
33
+ end
21
34
  end
22
35
 
23
- def sanitize(k,v)
24
- if v.is_a?(Hash)
25
- process(v)
26
- elsif v.is_a?(Array)
27
- v.map{|a| sanitize(k, a)}
28
- elsif k.to_s == 'query_string'
29
- sanitize_query_string(v)
30
- elsif v.is_a?(Integer) && matches_regexes?(k,v)
31
- INT_MASK
32
- elsif v.is_a?(String)
33
- if fields_re.match(v.to_s) && (json = parse_json_or_nil(v))
34
- #if this string is actually a json obj, convert and sanitize
35
- json.is_a?(Hash) ? process(json).to_json : v
36
- elsif matches_regexes?(k,v)
37
- STRING_MASK
38
- else
39
- v
40
- end
36
+ private
37
+
38
+ # CGI.parse takes our nice UTF-8 strings and converts them back to ASCII,
39
+ # so we have to convert them back, again.
40
+ def utf8_processor
41
+ @utf8_processor ||= Processor::UTF8Conversion.new
42
+ end
43
+
44
+ def sanitize_hash_value(key, value)
45
+ if key =~ sensitive_fields
46
+ STRING_MASK
47
+ elsif value.frozen?
48
+ value.merge(value) { |k, v| process v, k }
41
49
  else
42
- v
50
+ value.merge!(value) { |k, v| process v, k }
43
51
  end
44
52
  end
45
53
 
46
- private
54
+ def sanitize_array_value(key, value)
55
+ if value.frozen?
56
+ value.map { |v| process v, key }
57
+ else
58
+ value.map! { |v| process v, key }
59
+ end
60
+ end
61
+
62
+ def sanitize_string_value(key, value)
63
+ if value =~ sensitive_fields && (json = parse_json_or_nil(value))
64
+ # if this string is actually a json obj, convert and sanitize
65
+ process(json).to_json
66
+ elsif matches_regexes?(key, value)
67
+ STRING_MASK
68
+ elsif QUERY_STRING.include?(key)
69
+ sanitize_query_string(value)
70
+ elsif value =~ sensitive_fields
71
+ sanitize_sensitive_string_content(value)
72
+ else
73
+ value
74
+ end
75
+ end
47
76
 
48
77
  def sanitize_query_string(query_string)
49
78
  query_hash = CGI.parse(query_string)
50
- processed_query_hash = process(query_hash)
79
+ sanitized = utf8_processor.process(query_hash)
80
+ processed_query_hash = process(sanitized)
51
81
  URI.encode_www_form(processed_query_hash)
52
82
  end
53
83
 
84
+ # this scrubs some sensitive info from the string content. for example:
85
+ #
86
+ # ```
87
+ # unexpected token at '{
88
+ # "role": "admin","password": "Abc@123","foo": "bar"
89
+ # }'
90
+ # ```
91
+ #
92
+ # will become
93
+ #
94
+ # ```
95
+ # unexpected token at '{
96
+ # "role": "admin","password": *******,"foo": "bar"
97
+ # }'
98
+ # ```
99
+ #
100
+ # it's particularly useful in hash or param-parsing related errors
101
+ def sanitize_sensitive_string_content(value)
102
+ value.gsub(/(#{sensitive_fields}['":]\s?(:|=>)?\s?)(".*?"|'.*?')/, '\1' + STRING_MASK)
103
+ end
104
+
54
105
  def matches_regexes?(k, v)
55
- (sanitize_credit_cards && CREDIT_CARD_RE.match(v.to_s)) ||
56
- fields_re.match(k.to_s)
106
+ (sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
107
+ k =~ sensitive_fields
57
108
  end
58
109
 
59
- def fields_re
60
- @fields_re ||= /#{(DEFAULT_FIELDS | sanitize_fields).map do |f|
110
+ def sensitive_fields
111
+ return @sensitive_fields if instance_variable_defined?(:@sensitive_fields)
112
+
113
+ fields = DEFAULT_FIELDS | sanitize_fields
114
+ fields -= sanitize_fields_excluded
115
+ @sensitive_fields = /#{fields.map do |f|
61
116
  use_boundary?(f) ? "\\b#{f}\\b" : f
62
117
  end.join("|")}/i
63
118
  end
@@ -71,11 +126,11 @@ module Raven
71
126
  end
72
127
 
73
128
  def parse_json_or_nil(string)
74
- begin
75
- OkJson.decode(string)
76
- rescue Raven::OkJson::Error, NoMethodError
77
- nil
78
- end
129
+ return unless string.start_with?(*JSON_STARTS_WITH)
130
+
131
+ JSON.parse(string)
132
+ rescue JSON::ParserError, NoMethodError
133
+ nil
79
134
  end
80
135
  end
81
136
  end
@@ -1,28 +1,53 @@
1
1
  module Raven
2
2
  class Processor::UTF8Conversion < Processor
3
+ # Slightly misnamed - actually just removes any bytes with invalid encoding
4
+ # Previously, our JSON backend required UTF-8. Since we now use the built-in
5
+ # JSON, we can use any encoding, but it must be valid anyway so we can do
6
+ # things like call #match and #slice on strings
7
+ REPLACE = "".freeze
8
+
3
9
  def process(value)
4
- if value.is_a? Array
5
- value.map { |v| process v }
6
- elsif value.is_a? Hash
7
- value.merge(value) { |_, v| process v }
8
- elsif value.is_a?(Exception) && !value.message.valid_encoding?
9
- clean_exc = value.class.new(clean_invalid_utf8_bytes(value.message))
10
+ case value
11
+ when Hash
12
+ !value.frozen? ? value.merge!(value) { |_, v| process v } : value.merge(value) { |_, v| process v }
13
+ when Array
14
+ !value.frozen? ? value.map! { |v| process v } : value.map { |v| process v }
15
+ when Exception
16
+ return value if value.message.valid_encoding?
17
+
18
+ clean_exc = value.class.new(remove_invalid_bytes(value.message))
10
19
  clean_exc.set_backtrace(value.backtrace)
11
20
  clean_exc
21
+ when String
22
+ # Encoding::BINARY / Encoding::ASCII_8BIT is a special binary encoding.
23
+ # valid_encoding? will always return true because it contains all codepoints,
24
+ # so instead we check if it only contains actual ASCII codepoints, and if
25
+ # not we assume it's actually just UTF8 and scrub accordingly.
26
+ if value.encoding == Encoding::BINARY && !value.ascii_only?
27
+ value = value.dup
28
+ value.force_encoding(Encoding::UTF_8)
29
+ end
30
+ return value if value.valid_encoding?
31
+
32
+ remove_invalid_bytes(value)
12
33
  else
13
- clean_invalid_utf8_bytes(value)
34
+ value
14
35
  end
15
36
  end
16
37
 
17
38
  private
18
39
 
19
- def clean_invalid_utf8_bytes(obj)
20
- if obj.respond_to?(:to_utf8)
21
- obj.to_utf8
22
- elsif obj.respond_to?(:encoding) && obj.is_a?(String)
23
- obj.encode('UTF-16', :invalid => :replace, :undef => :replace, :replace => '').encode('UTF-8')
24
- else
25
- obj
40
+ # Stolen from RSpec
41
+ # https://github.com/rspec/rspec-support/blob/f0af3fd74a94ff7bb700f6ba06dbdc67bba17fbf/lib/rspec/support/encoded_string.rb#L120-L139
42
+ if String.method_defined?(:scrub) # 2.1+
43
+ def remove_invalid_bytes(string)
44
+ string.scrub(REPLACE)
45
+ end
46
+ else
47
+ def remove_invalid_bytes(string)
48
+ string.chars.map do |char|
49
+ char.valid_encoding? ? char : REPLACE
50
+ end.join
26
51
  end
27
52
  end
28
53
  end
@@ -1,6 +1,10 @@
1
1
  module Raven
2
2
  class Processor
3
- def initialize(client)
3
+ STRING_MASK = '********'.freeze
4
+ INT_MASK = 0
5
+ REGEX_SPECIAL_CHARACTERS = %w(. $ ^ { [ ( | ) * + ?).freeze
6
+
7
+ def initialize(client = nil)
4
8
  @client = client
5
9
  end
6
10
 
@@ -1,8 +1,5 @@
1
1
  require 'faraday'
2
2
 
3
- require 'raven/transports'
4
- require 'raven/error'
5
-
6
3
  module Raven
7
4
  module Transports
8
5
  class HTTP < Transport
@@ -15,43 +12,55 @@ module Raven
15
12
  end
16
13
 
17
14
  def send_event(auth_header, data, options = {})
15
+ unless configuration.sending_allowed?
16
+ configuration.logger.debug("Event not sent: #{configuration.error_messages}")
17
+ return
18
+ end
19
+
18
20
  project_id = configuration[:project_id]
19
21
  path = configuration[:path] + "/"
20
22
 
21
- response = conn.post "#{path}api/#{project_id}/store/" do |req|
23
+ conn.post "#{path}api/#{project_id}/store/" do |req|
22
24
  req.headers['Content-Type'] = options[:content_type]
23
25
  req.headers['X-Sentry-Auth'] = auth_header
24
26
  req.body = data
25
27
  end
26
- Raven.logger.warn "Error from Sentry server (#{response.status}): #{response.body}" unless response.status == 200
27
- response
28
+ rescue Faraday::Error => e
29
+ error_info = e.message
30
+ if e.response && e.response[:headers]['x-sentry-error']
31
+ error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}"
32
+ end
33
+ raise Raven::Error, error_info
28
34
  end
29
35
 
30
36
  private
31
37
 
32
38
  def set_conn
33
- verify_configuration
34
-
35
- Raven.logger.debug "Raven HTTP Transport connecting to #{configuration.server}"
39
+ configuration.logger.debug "Raven HTTP Transport connecting to #{configuration.server}"
36
40
 
37
- ssl_configuration = configuration.ssl || {}
38
- ssl_configuration[:verify] = configuration.ssl_verification
39
- ssl_configuration[:ca_file] = configuration.ssl_ca_file
41
+ proxy = configuration.public_send(:proxy)
40
42
 
41
- conn = Faraday.new(
42
- :url => configuration[:server],
43
- :ssl => ssl_configuration
44
- ) do |builder|
43
+ Faraday.new(configuration.server, :ssl => ssl_configuration, :proxy => proxy) do |builder|
44
+ configuration.faraday_builder&.call(builder)
45
+ builder.response :raise_error
46
+ builder.options.merge! faraday_opts
47
+ builder.headers[:user_agent] = "sentry-ruby/#{Raven::VERSION}"
45
48
  builder.adapter(*adapter)
46
49
  end
50
+ end
47
51
 
48
- conn.headers[:user_agent] = "sentry-ruby/#{Raven::VERSION}"
49
-
50
- conn.options[:proxy] = configuration.proxy if configuration.proxy
51
- conn.options[:timeout] = configuration.timeout if configuration.timeout
52
- conn.options[:open_timeout] = configuration.open_timeout if configuration.open_timeout
52
+ # TODO: deprecate and replace where possible w/Faraday Builder
53
+ def faraday_opts
54
+ [:timeout, :open_timeout].each_with_object({}) do |opt, memo|
55
+ memo[opt] = configuration.public_send(opt) if configuration.public_send(opt)
56
+ end
57
+ end
53
58
 
54
- conn
59
+ def ssl_configuration
60
+ (configuration.ssl || {}).merge(
61
+ :verify => configuration.ssl_verification,
62
+ :ca_file => configuration.ssl_ca_file
63
+ )
55
64
  end
56
65
  end
57
66
  end
@@ -0,0 +1,20 @@
1
+ module Raven
2
+ module Transports
3
+ class Stdout < Transport
4
+ attr_accessor :events
5
+
6
+ def initialize(*)
7
+ super
8
+ end
9
+
10
+ def send_event(_auth_header, data, _options = {})
11
+ unless configuration.sending_allowed?
12
+ logger.debug("Event not sent: #{configuration.error_messages}")
13
+ end
14
+
15
+ $stdout.puts data
16
+ $stdout.flush
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,3 @@
1
- require 'raven/error'
2
-
3
1
  module Raven
4
2
  module Transports
5
3
  class Transport
@@ -9,15 +7,13 @@ module Raven
9
7
  @configuration = configuration
10
8
  end
11
9
 
12
- def send_event #(auth_header, data, options = {})
13
- raise NotImplementedError.new('Abstract method not implemented')
14
- end
15
-
16
- protected
17
-
18
- def verify_configuration
19
- configuration.verify!
10
+ def send_event # (auth_header, data, options = {})
11
+ raise NotImplementedError, 'Abstract method not implemented'
20
12
  end
21
13
  end
22
14
  end
23
15
  end
16
+
17
+ require "raven/transports/dummy"
18
+ require "raven/transports/http"
19
+ require "raven/transports/stdout"
@@ -0,0 +1,42 @@
1
+ module Raven
2
+ module Utils
3
+ module ContextFilter
4
+ class << self
5
+ ACTIVEJOB_RESERVED_PREFIX_REGEX = /^_aj_/.freeze
6
+ HAS_GLOBALID = const_defined?('GlobalID')
7
+
8
+ # Once an ActiveJob is queued, ActiveRecord references get serialized into
9
+ # some internal reserved keys, such as _aj_globalid.
10
+ #
11
+ # The problem is, if this job in turn gets queued back into ActiveJob with
12
+ # these magic reserved keys, ActiveJob will throw up and error. We want to
13
+ # capture these and mutate the keys so we can sanely report it.
14
+ def filter_context(context)
15
+ case context
16
+ when Array
17
+ context.map { |arg| filter_context(arg) }
18
+ when Hash
19
+ Hash[context.map { |key, value| filter_context_hash(key, value) }]
20
+ else
21
+ format_globalid(context)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def filter_context_hash(key, value)
28
+ key = key.to_s.sub(ACTIVEJOB_RESERVED_PREFIX_REGEX, "") if key.match(ACTIVEJOB_RESERVED_PREFIX_REGEX)
29
+ [key, filter_context(value)]
30
+ end
31
+
32
+ def format_globalid(context)
33
+ if HAS_GLOBALID && context.is_a?(GlobalID)
34
+ context.to_s
35
+ else
36
+ context
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -7,21 +7,15 @@ module Raven
7
7
  end
8
8
 
9
9
  def self.deep_merge!(hash, other_hash, &block)
10
- other_hash.each_pair do |current_key, other_value|
11
- this_value = hash[current_key]
12
-
13
- hash[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
14
- this_value.deep_merge(other_value, &block)
10
+ hash.merge!(other_hash) do |key, this_val, other_val|
11
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
12
+ deep_merge(this_val, other_val, &block)
13
+ elsif block_given?
14
+ block.call(key, this_val, other_val)
15
15
  else
16
- if block_given? && key?(current_key)
17
- block.call(current_key, this_value, other_value)
18
- else
19
- other_value
20
- end
16
+ other_val
21
17
  end
22
18
  end
23
-
24
- hash
25
19
  end
26
20
  end
27
21
  end
@@ -0,0 +1,20 @@
1
+ module Raven
2
+ module Utils
3
+ module ExceptionCauseChain
4
+ def self.exception_to_array(exception)
5
+ if exception.respond_to?(:cause) && exception.cause
6
+ exceptions = [exception]
7
+ while exception.cause
8
+ exception = exception.cause
9
+ break if exceptions.any? { |e| e.object_id == exception.object_id }
10
+
11
+ exceptions << exception
12
+ end
13
+ exceptions
14
+ else
15
+ [exception]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ require 'ipaddr'
2
+
3
+ # Based on ActionDispatch::RemoteIp. All security-related precautions from that
4
+ # middleware have been removed, because the Event IP just needs to be accurate,
5
+ # and spoofing an IP here only makes data inaccurate, not insecure. Don't re-use
6
+ # this module if you have to *trust* the IP address.
7
+ module Raven
8
+ module Utils
9
+ class RealIp
10
+ LOCAL_ADDRESSES = [
11
+ "127.0.0.1", # localhost IPv4
12
+ "::1", # localhost IPv6
13
+ "fc00::/7", # private IPv6 range fc00::/7
14
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
15
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
16
+ "192.168.0.0/16" # private IPv4 range 192.168.x.x
17
+ ].map { |proxy| IPAddr.new(proxy) }
18
+
19
+ attr_accessor :ip, :ip_addresses
20
+
21
+ def initialize(ip_addresses)
22
+ self.ip_addresses = ip_addresses
23
+ end
24
+
25
+ def calculate_ip
26
+ # CGI environment variable set by Rack
27
+ remote_addr = ips_from(ip_addresses[:remote_addr]).last
28
+
29
+ # Could be a CSV list and/or repeated headers that were concatenated.
30
+ client_ips = ips_from(ip_addresses[:client_ip])
31
+ real_ips = ips_from(ip_addresses[:real_ip])
32
+ forwarded_ips = ips_from(ip_addresses[:forwarded_for])
33
+
34
+ ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
35
+
36
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
37
+ self.ip = filter_local_addresses(ips).first || remote_addr
38
+ end
39
+
40
+ protected
41
+
42
+ def ips_from(header)
43
+ # Split the comma-separated list into an array of strings
44
+ ips = header ? header.strip.split(/[,\s]+/) : []
45
+ ips.select do |ip|
46
+ begin
47
+ # Only return IPs that are valid according to the IPAddr#new method
48
+ range = IPAddr.new(ip).to_range
49
+ # we want to make sure nobody is sneaking a netmask in
50
+ range.begin == range.end
51
+ rescue ArgumentError
52
+ nil
53
+ end
54
+ end
55
+ end
56
+
57
+ def filter_local_addresses(ips)
58
+ ips.reject { |ip| LOCAL_ADDRESSES.any? { |proxy| proxy === ip } }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ module Raven
2
+ module Utils
3
+ module RequestId
4
+ REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
5
+
6
+ # Request ID based on ActionDispatch::RequestId
7
+ def self.read_from(env_hash)
8
+ REQUEST_ID_HEADERS.each do |key|
9
+ request_id = env_hash[key]
10
+ return request_id if request_id
11
+ end
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/raven/version.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Raven
3
- VERSION = "1.1.0".freeze
4
+ VERSION = "3.1.2"
4
5
  end
@@ -1 +1,6 @@
1
- require 'raven/base'
1
+ require "raven/helpers/deprecation_helper"
2
+
3
+ filename = "sentry_raven_without_integrations"
4
+ DeprecationHelper.deprecate_dasherized_filename(filename)
5
+
6
+ require filename
@@ -0,0 +1 @@
1
+ require 'raven/base'
@@ -0,0 +1,28 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'raven/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "sentry-raven"
6
+ gem.authors = ["Sentry Team"]
7
+ gem.description = gem.summary = "A gem that provides a client interface for the Sentry error logger"
8
+ gem.email = "accounts@sentry.io"
9
+ gem.license = 'Apache-2.0'
10
+ gem.homepage = "https://github.com/getsentry/raven-ruby"
11
+
12
+ gem.version = Raven::VERSION
13
+ gem.platform = Gem::Platform::RUBY
14
+ gem.required_ruby_version = '>= 2.3'
15
+ gem.extra_rdoc_files = ["README.md", "LICENSE"]
16
+ gem.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
17
+ gem.bindir = "exe"
18
+ gem.executables = "raven"
19
+
20
+ gem.add_dependency "faraday", ">= 1.0"
21
+
22
+ gem.post_install_message = <<~EOS
23
+ `sentry-raven` is deprecated! Please migrate to `sentry-ruby`
24
+
25
+ See https://docs.sentry.io/platforms/ruby/migration for the migration guide.
26
+
27
+ EOS
28
+ end