mihari 7.0.3 → 7.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5bc182d0f1c5e1ecfb08cd72d083c30a5775ea2890795dc812357da493ffb63
4
- data.tar.gz: 87fe1706c56301184b79f02d1d8be0b2deeb5591b8cfd76c3bea6d1a12005311
3
+ metadata.gz: aca048c797a73db1c57f0a5ad5f29066fbfba69451c71df1a26c3ea57853a1e3
4
+ data.tar.gz: 5ce2d298f325cc9d0ae50a98a6c18a64ec11cc06386504535c8ca647c7c68ad0
5
5
  SHA512:
6
- metadata.gz: 543e6850571451e862e95db1697482c39e796783728ddcb48dca6008b0a29e10bbe75dfabb4512dcf0b9c4ff6d31168138bc6285c2ff7131a82a2e61d5353ab1
7
- data.tar.gz: fc4e7fe111efa45d68a7b106cc61b5312f5c5be62fdc662e4daf2d3e466054deaf66eac08217f35680fbab0688eaad7642dc1a17df4bcf385b7b61c38eb31906
6
+ metadata.gz: ac1d6ab49351a2c9c60e703a5fe5fe0f94c84c4294271f297d529a781d3a87a37b515d090a78676bcaf3da225f03617f8900966d108e31557e86319a2407b00b
7
+ data.tar.gz: f5aad0f8648783f4d3c3cc42a087f646358e5e93586cb394a2644e8f6c32a296b3668ea7758348c0dd55d4868527b4aa6a3d774ee113d5a4a14c39ae3ecd2857
@@ -92,7 +92,15 @@ module Mihari
92
92
 
93
93
  return res.recover { [] } if ignore_error?
94
94
 
95
- res.to_result
95
+ result = res.to_result
96
+ return result if result.success?
97
+
98
+ # Wrap failure with AnalyzerError to explicitly name a failed analyzer
99
+ Failure AnalyzerError.new(
100
+ result.failure.message,
101
+ self.class.class_key,
102
+ cause: result.failure
103
+ )
96
104
  end
97
105
 
98
106
  class << self
@@ -41,17 +41,17 @@ module Mihari
41
41
  def safe_execute
42
42
  yield
43
43
  rescue StandardError => e
44
- err = unwrap_error(e)
44
+ error = unwrap_error(e)
45
45
 
46
- raise err if options["debug"]
46
+ raise error if options["debug"]
47
47
 
48
- case err
49
- when ValidationError
50
- warn JSON.pretty_generate(err.errors.to_h)
51
- when StandardError
52
- Sentry.capture_exception(err) if Sentry.initialized?
53
- warn err
54
- end
48
+ data = Entities::ErrorMessage.represent(
49
+ message: error.message,
50
+ detail: error.respond_to?(:detail) ? error.detail : nil
51
+ )
52
+ warn JSON.pretty_generate(data.as_json)
53
+
54
+ Sentry.capture_exception(error) if Sentry.initialized? && !error.is_a?(ValidationError)
55
55
 
56
56
  exit 1
57
57
  end
@@ -52,7 +52,10 @@ module Mihari
52
52
  def search(query, page:, size: PAGE_SIZE)
53
53
  qbase64 = Base64.urlsafe_encode64(query)
54
54
  params = { qbase64: qbase64, size: size, page: page, email: email, key: api_key }.compact
55
- Structs::Fofa::Response.from_dynamic! get_json("/api/v1/search/all", params: params)
55
+ res = Structs::Fofa::Response.from_dynamic!(get_json("/api/v1/search/all", params: params))
56
+ raise ResponseError, res.errmsg if res.error
57
+
58
+ res
56
59
  end
57
60
 
58
61
  #
@@ -34,8 +34,6 @@ module Mihari
34
34
  return if Pathname(path).exist? && !(yes? warning)
35
35
 
36
36
  Services::RuleInitializer.call(path)
37
-
38
- puts "A new rule file has been initialized: #{path}."
39
37
  end
40
38
 
41
39
  desc "list [QUERY]", "List/search rules"
@@ -17,9 +17,10 @@ module Mihari
17
17
  receiver = err.receiver
18
18
  case receiver
19
19
  when Dry::Monads::Try::Error
20
- receiver.exception
20
+ # Error may be wrapped like Matryoshka
21
+ unwrap_error receiver.exception
21
22
  when Dry::Monads::Failure
22
- receiver.failure
23
+ unwrap_error receiver.failure
23
24
  else
24
25
  err
25
26
  end
@@ -8,35 +8,48 @@ module Mihari
8
8
  module Retriable
9
9
  extend ActiveSupport::Concern
10
10
 
11
- DEFAULT_ON = [
12
- Errno::ECONNRESET,
13
- Errno::ECONNABORTED,
14
- Errno::EPIPE,
11
+ RETRIABLE_ERRORS = [
15
12
  OpenSSL::SSL::SSLError,
16
13
  Timeout::Error,
17
- RetryableError,
18
- NetworkError,
19
- TimeoutError,
20
- StatusCodeError
14
+ ::HTTP::ConnectionError,
15
+ ::HTTP::ResponseError,
16
+ ::HTTP::TimeoutError
21
17
  ].freeze
22
18
 
19
+ DEFAULT_CONDITION = lambda do |error|
20
+ return true if RETRIABLE_ERRORS.any? { |klass| error.is_a? klass }
21
+
22
+ case error
23
+ when StatusCodeError
24
+ error.status_code != 404
25
+ else
26
+ false
27
+ end
28
+ end
29
+
23
30
  #
24
31
  # Retry on error
25
32
  #
26
33
  # @param [Integer] times
27
34
  # @param [Integer] interval
28
35
  # @param [Boolean] exponential_backoff
29
- # @param [Array<StandardError>] on
36
+ # @param [Proc] condition
30
37
  #
31
- def retry_on_error(times: 3, interval: 5, exponential_backoff: true, on: DEFAULT_ON)
38
+ # @param [Object] on
39
+ def retry_on_error(times: 3, interval: 5, exponential_backoff: true, condition: DEFAULT_CONDITION)
32
40
  try = 0
33
41
  begin
34
42
  try += 1
35
43
  yield
36
- rescue *on => e
44
+ rescue StandardError => e
45
+ # Raise error if it's not a retriable error
46
+ raise e unless condition.call(e)
47
+
37
48
  sleep_seconds = exponential_backoff ? interval * (2**(try - 1)) : interval
38
49
  sleep sleep_seconds
39
50
  retry if try < times
51
+
52
+ # Raise error if retry times exceed a given times
40
53
  raise e
41
54
  end
42
55
  end
data/lib/mihari/errors.rb CHANGED
@@ -1,27 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "http"
4
+
3
5
  module Mihari
4
6
  class Error < StandardError; end
5
7
 
6
8
  class ValueError < Error; end
7
9
 
8
- class RetryableError < Error; end
9
-
10
10
  class ConfigurationError < Error; end
11
11
 
12
- class IntegrityError < Error; end
12
+ class ResponseError < Error; end
13
13
 
14
- # errors for HTTP interactions
15
- class HTTPError < Error; end
14
+ class AnalyzerError < Error
15
+ # @return [StandardException, nil]
16
+ attr_reader :cause
16
17
 
17
- class NetworkError < HTTPError; end
18
+ #
19
+ # @param [String] msg
20
+ # @param [String] analyzer
21
+ # @param [StandardException, nil] cause
22
+ #
23
+ def initialize(msg, analyzer, cause: nil)
24
+ super("#{msg} (from #{analyzer})")
25
+
26
+ @cause = cause
27
+ set_backtrace(cause.backtrace) if cause
28
+ end
18
29
 
19
- class TimeoutError < HTTPError; end
30
+ def detail
31
+ return nil unless cause.respond_to?(:detail)
32
+
33
+ cause.detail
34
+ end
35
+ end
36
+
37
+ class IntegrityError < Error; end
20
38
 
21
39
  #
22
40
  # HTTP status code error
23
41
  #
24
- class StatusCodeError < HTTPError
42
+ class StatusCodeError < ::HTTP::Error
25
43
  # @return [Integer]
26
44
  attr_reader :status_code
27
45
 
@@ -39,6 +57,10 @@ module Mihari
39
57
  @status_code = status_code
40
58
  @body = body
41
59
  end
60
+
61
+ def detail
62
+ { status_code: status_code, body: body }
63
+ end
42
64
  end
43
65
 
44
66
  #
@@ -56,5 +78,9 @@ module Mihari
56
78
 
57
79
  @errors = errors
58
80
  end
81
+
82
+ def detail
83
+ errors.to_h
84
+ end
59
85
  end
60
86
  end
data/lib/mihari/http.rb CHANGED
@@ -18,11 +18,6 @@ module Mihari
18
18
  )
19
19
  end
20
20
 
21
- def on_error(_request, error)
22
- raise TimeoutError, error if error.is_a?(::HTTP::TimeoutError)
23
- raise NetworkError, error if error.is_a?(::HTTP::Error)
24
- end
25
-
26
21
  ::HTTP::Options.register_feature(:better_error, self)
27
22
  end
28
23
 
data/lib/mihari/rule.rb CHANGED
@@ -83,6 +83,20 @@ module Mihari
83
83
  data[:data_types]
84
84
  end
85
85
 
86
+ #
87
+ # @return [Date, nil]
88
+ #
89
+ def created_on
90
+ data[:created_on]
91
+ end
92
+
93
+ #
94
+ # @return [Date, nil]
95
+ #
96
+ def updated_on
97
+ data[:updated_on]
98
+ end
99
+
86
100
  #
87
101
  # @return [Array<Mihari::Models::Tag>]
88
102
  #
@@ -231,7 +245,7 @@ module Mihari
231
245
  #
232
246
  def diff?
233
247
  model = Mihari::Models::Rule.find(id)
234
- model.data != data.deep_stringify_keys
248
+ model.data != diff_comparable_data
235
249
  rescue ActiveRecord::RecordNotFound
236
250
  false
237
251
  end
@@ -272,6 +286,18 @@ module Mihari
272
286
 
273
287
  private
274
288
 
289
+ #
290
+ # @return [Hash]
291
+ #
292
+ def diff_comparable_data
293
+ # data is serialized as JSON so dates (created_on & updated_on) are stringified in there
294
+ # thus dates & (hash) keys have to be stringified when comparing
295
+ data.deep_dup.tap do |data|
296
+ data[:created_on] = created_on.to_s unless created_on.nil?
297
+ data[:updated_on] = updated_on.to_s unless updated_on.nil?
298
+ end.deep_stringify_keys
299
+ end
300
+
275
301
  #
276
302
  # Check whether a value is a falsepositive value or not
277
303
  #
@@ -8,6 +8,10 @@ module Mihari
8
8
  # @return [Boolean]
9
9
  attribute :error, Types::Bool
10
10
 
11
+ # @!attribute [r] errmsg
12
+ # @return [String, nil]
13
+ attribute? :errmsg, Types::String.optional
14
+
11
15
  # @!attribute [r] size
12
16
  # @return [Integer, nil]
13
17
  attribute? :size, Types::Int.optional
@@ -35,6 +39,7 @@ module Mihari
35
39
  def from_dynamic!(d)
36
40
  new(
37
41
  error: d.fetch("error"),
42
+ errmsg: d["errmsg"],
38
43
  size: d["size"],
39
44
  page: d["page"],
40
45
  mode: d["mode"],
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "7.0.3"
4
+ VERSION = "7.0.5"
5
5
  end
@@ -8,13 +8,16 @@ module Mihari
8
8
  #
9
9
  class IPAddresses < Grape::API
10
10
  namespace :ip_addresses do
11
- desc "Get an IP address", {
11
+ desc "Get IP address data", {
12
12
  success: Entities::IPAddress,
13
- failure: [{ code: 404, model: Entities::ErrorMessage }],
14
- summary: "Get an IP address"
13
+ failure: [
14
+ { code: 404, model: Entities::ErrorMessage },
15
+ { code: 422, model: Entities::ErrorMessage }
16
+ ],
17
+ summary: "Get IP address data"
15
18
  }
16
19
  params do
17
- requires :ip, type: String, regexp: /\A[0-9.]+\z/
20
+ requires :ip, type: String
18
21
  end
19
22
  get "/:ip", requirements: { ip: %r{[^/]+} } do
20
23
  ip = params[:ip].to_s
@@ -34,7 +37,8 @@ module Mihari
34
37
  failure = result.failure
35
38
  case failure
36
39
  when Mihari::StatusCodeError
37
- error!({ message: "ID:#{id} not found" }, 404) if failure.status_code == 404
40
+ error!({ message: "IP:#{ip} not found" }, failure.status_code) if failure.status_code == 404
41
+ error!({ message: "IP format invalid" }, failure.status_code) if failure.status_code == 422
38
42
  end
39
43
  raise failure
40
44
  end
@@ -17,7 +17,10 @@ module Mihari
17
17
  def call(yaml, overwrite: true)
18
18
  rule = Rule.from_yaml(yaml)
19
19
 
20
- raise IntegrityError, "ID:#{rule.id} is already registered" if rule.exists? && !overwrite
20
+ # To invoke ActiveRecord::RecordNotFound
21
+ Models::Rule.find(rule.id) if overwrite
22
+
23
+ raise IntegrityError, "ID:#{rule.id} already registered" if rule.exists? && !overwrite
21
24
 
22
25
  rule.update_or_create
23
26
  rule
@@ -141,13 +144,11 @@ module Mihari
141
144
  summary: "Update a rule"
142
145
  }
143
146
  params do
144
- requires :id, type: String, documentation: { param_type: "body" }
145
147
  requires :yaml, type: String, documentation: { param_type: "body" }
146
148
  end
147
149
  put "/" do
148
150
  status 201
149
151
 
150
- id = params[:id].to_s
151
152
  yaml = params[:yaml].to_s
152
153
 
153
154
  result = RuleCreateUpdater.result(yaml, overwrite: true)
@@ -156,11 +157,11 @@ module Mihari
156
157
  failure = result.failure
157
158
  case failure
158
159
  when ActiveRecord::RecordNotFound
159
- error!({ message: "ID:#{id} not found" }, 404)
160
+ error!({ message: "Rule not found" }, 404)
160
161
  when Psych::SyntaxError
161
162
  error!({ message: failure.message }, 422)
162
163
  when ValidationError
163
- error!({ message: "Rule format is invalid", detail: failure.errors.to_h }, 422)
164
+ error!({ message: "Rule format invalid", detail: failure.errors.to_h }, 422)
164
165
  end
165
166
  raise failure
166
167
  end
@@ -9,8 +9,6 @@ module Mihari
9
9
  class CaptureExceptions < Sentry::Rack::CaptureExceptions
10
10
  include Concerns::ErrorUnwrappable
11
11
 
12
- private
13
-
14
12
  def capture_exception(exception, env)
15
13
  unwrapped = unwrap_error(exception)
16
14
  Mihari.logger.error unwrapped