mihari 7.0.4 → 7.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mihari/analyzers/base.rb +9 -1
- data/lib/mihari/cli/application.rb +12 -9
- data/lib/mihari/clients/fofa.rb +4 -1
- data/lib/mihari/commands/alert.rb +37 -2
- data/lib/mihari/commands/artifact.rb +37 -2
- data/lib/mihari/commands/config.rb +1 -1
- data/lib/mihari/commands/rule.rb +37 -2
- data/lib/mihari/commands/tag.rb +37 -2
- data/lib/mihari/concerns/error_unwrappable.rb +3 -2
- data/lib/mihari/concerns/retriable.rb +24 -11
- data/lib/mihari/data_type.rb +3 -3
- data/lib/mihari/emitters/webhook.rb +15 -53
- data/lib/mihari/errors.rb +35 -9
- data/lib/mihari/http.rb +1 -6
- data/lib/mihari/rule.rb +16 -2
- data/lib/mihari/services/renderer.rb +31 -0
- data/lib/mihari/structs/fofa.rb +5 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
- data/lib/mihari/web/public/assets/{index-yeHDhqt5.js → index-U5u7qHZZ.js} +47 -47
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari.rb +1 -0
- data/mihari.gemspec +5 -2
- data/test.json.jbuilder +7 -0
- metadata +51 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6930da0e95068ca8e30d1f226be5692e85375f796b7246cafdd2bec566d00ff7
|
4
|
+
data.tar.gz: 2bf34b1231bffcd88d402ffda335960ad929b1de91cd51b257c9c24f7b2fc16f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd515cdbde67d10c3fcae14c8e45a7929a34931701be176c9f9a294d3db310f522a3e186aefe548d6f119c5c8552723114da4ee6f790dbbe0391138dca9cc00e
|
7
|
+
data.tar.gz: 5f4a3e1049ad55af018cf9ed2186dda49bd18517d37bc84cad6e6f60b10589834ac3bd1f09333e7f9773fab43442400c097c1063f1c304d5be5eb8991af7ec77
|
@@ -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,20 @@ module Mihari
|
|
41
41
|
def safe_execute
|
42
42
|
yield
|
43
43
|
rescue StandardError => e
|
44
|
-
|
44
|
+
error = unwrap_error(e)
|
45
45
|
|
46
|
-
|
46
|
+
# Raise error if it's a Thor::Error to follow Thor's manner
|
47
|
+
raise error if error.is_a?(Thor::Error)
|
48
|
+
# Raise error if debug is set as true
|
49
|
+
raise error if options["debug"]
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
data = Entities::ErrorMessage.represent(
|
52
|
+
message: error.message,
|
53
|
+
detail: error.respond_to?(:detail) ? error.detail : nil
|
54
|
+
)
|
55
|
+
warn JSON.pretty_generate(data.as_json)
|
56
|
+
|
57
|
+
Sentry.capture_exception(error) if Sentry.initialized? && !error.is_a?(ValidationError)
|
55
58
|
|
56
59
|
exit 1
|
57
60
|
end
|
data/lib/mihari/clients/fofa.rb
CHANGED
@@ -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!
|
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
|
#
|
@@ -12,6 +12,20 @@ module Mihari
|
|
12
12
|
thor.class_eval do
|
13
13
|
include Concerns::DatabaseConnectable
|
14
14
|
|
15
|
+
no_commands do
|
16
|
+
#
|
17
|
+
# @param [String] q
|
18
|
+
# @param [Integer] page
|
19
|
+
# @param [Integer] limit
|
20
|
+
#
|
21
|
+
# @return [Mihari::Services::ResultValue]
|
22
|
+
#
|
23
|
+
def _search(q, page: 1, limit: 10)
|
24
|
+
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
|
25
|
+
Services::AlertSearcher.result(filter).value!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
15
29
|
desc "create [PATH]", "Create an alert"
|
16
30
|
around :with_db_connection
|
17
31
|
#
|
@@ -40,8 +54,7 @@ module Mihari
|
|
40
54
|
# @param [String] q
|
41
55
|
#
|
42
56
|
def list(q = "")
|
43
|
-
|
44
|
-
value = Services::AlertSearcher.result(filter).value!
|
57
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
45
58
|
data = Entities::AlertsWithPagination.represent(
|
46
59
|
results: value.results,
|
47
60
|
total: value.total,
|
@@ -51,6 +64,28 @@ module Mihari
|
|
51
64
|
puts JSON.pretty_generate(data.as_json)
|
52
65
|
end
|
53
66
|
|
67
|
+
desc "list-transform QUERY", "List/search alerts with transformation"
|
68
|
+
around :with_db_connection
|
69
|
+
method_option :template, type: :string, required: true, aliases: "-t",
|
70
|
+
description: "Jbuilder template itself or a path to a template file"
|
71
|
+
method_option :page, type: :numeric, default: 1
|
72
|
+
method_option :limit, type: :numeric, default: 10
|
73
|
+
#
|
74
|
+
# @param [String] q
|
75
|
+
#
|
76
|
+
def list_transform(q = "")
|
77
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
78
|
+
puts Services::JbuilderRenderer.call(
|
79
|
+
options["template"],
|
80
|
+
{
|
81
|
+
results: value.results,
|
82
|
+
total: value.total,
|
83
|
+
current_page: value.filter[:page].to_i,
|
84
|
+
page_size: value.filter[:limit].to_i
|
85
|
+
}
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
54
89
|
desc "get [ID]", "Get an alert"
|
55
90
|
around :with_db_connection
|
56
91
|
#
|
@@ -11,6 +11,20 @@ module Mihari
|
|
11
11
|
thor.class_eval do
|
12
12
|
include Concerns::DatabaseConnectable
|
13
13
|
|
14
|
+
no_commands do
|
15
|
+
#
|
16
|
+
# @param [String] q
|
17
|
+
# @param [Integer] page
|
18
|
+
# @param [Integer] limit
|
19
|
+
#
|
20
|
+
# @return [Mihari::Services::ResultValue]
|
21
|
+
#
|
22
|
+
def _search(q, page: 1, limit: 10)
|
23
|
+
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
|
24
|
+
Services::ArtifactSearcher.result(filter).value!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
desc "list [QUERY]", "List/search artifacts"
|
15
29
|
around :with_db_connection
|
16
30
|
method_option :page, type: :numeric, default: 1
|
@@ -19,8 +33,7 @@ module Mihari
|
|
19
33
|
# @param [String] q
|
20
34
|
#
|
21
35
|
def list(q = "")
|
22
|
-
|
23
|
-
value = Services::ArtifactSearcher.result(filter).value!
|
36
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
24
37
|
data = Entities::ArtifactsWithPagination.represent(
|
25
38
|
results: value.results,
|
26
39
|
total: value.total,
|
@@ -30,6 +43,28 @@ module Mihari
|
|
30
43
|
puts JSON.pretty_generate(data.as_json)
|
31
44
|
end
|
32
45
|
|
46
|
+
desc "list-transform QUERY", "List/search artifacts with transformation"
|
47
|
+
around :with_db_connection
|
48
|
+
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
+
description: "Jbuilder template itself or a path to a template file"
|
50
|
+
method_option :page, type: :numeric, default: 1
|
51
|
+
method_option :limit, type: :numeric, default: 10
|
52
|
+
#
|
53
|
+
# @param [String] q
|
54
|
+
#
|
55
|
+
def list_transform(q = "")
|
56
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
57
|
+
puts Services::JbuilderRenderer.call(
|
58
|
+
options["template"],
|
59
|
+
{
|
60
|
+
results: value.results,
|
61
|
+
total: value.total,
|
62
|
+
current_page: value.filter[:page].to_i,
|
63
|
+
page_size: value.filter[:limit].to_i
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
33
68
|
desc "get [ID]", "Get an artifact"
|
34
69
|
around :with_db_connection
|
35
70
|
#
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -12,6 +12,20 @@ module Mihari
|
|
12
12
|
thor.class_eval do
|
13
13
|
include Concerns::DatabaseConnectable
|
14
14
|
|
15
|
+
no_commands do
|
16
|
+
#
|
17
|
+
# @param [String] q
|
18
|
+
# @param [Integer] page
|
19
|
+
# @param [Integer] limit
|
20
|
+
#
|
21
|
+
# @return [Mihari::Services::ResultValue]
|
22
|
+
#
|
23
|
+
def _search(q, page: 1, limit: 10)
|
24
|
+
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
|
25
|
+
Services::RuleSearcher.result(filter).value!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
15
29
|
desc "validate [PATH]", "Validate a rule file"
|
16
30
|
#
|
17
31
|
# Validate format of a rule
|
@@ -44,8 +58,7 @@ module Mihari
|
|
44
58
|
# @param [String] q
|
45
59
|
#
|
46
60
|
def list(q = "")
|
47
|
-
|
48
|
-
value = Services::RuleSearcher.result(filter).value!
|
61
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
49
62
|
data = Entities::RulesWithPagination.represent(
|
50
63
|
results: value.results,
|
51
64
|
total: value.total,
|
@@ -55,6 +68,28 @@ module Mihari
|
|
55
68
|
puts JSON.pretty_generate(data.as_json)
|
56
69
|
end
|
57
70
|
|
71
|
+
desc "list-transform QUERY", "List/search rules with transformation"
|
72
|
+
around :with_db_connection
|
73
|
+
method_option :template, type: :string, required: true, aliases: "-t",
|
74
|
+
description: "Jbuilder template itself or a path to a template file"
|
75
|
+
method_option :page, type: :numeric, default: 1
|
76
|
+
method_option :limit, type: :numeric, default: 10
|
77
|
+
#
|
78
|
+
# @param [String] q
|
79
|
+
#
|
80
|
+
def list_transform(q = "")
|
81
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
82
|
+
puts Services::JbuilderRenderer.call(
|
83
|
+
options["template"],
|
84
|
+
{
|
85
|
+
results: value.results,
|
86
|
+
total: value.total,
|
87
|
+
current_page: value.filter[:page].to_i,
|
88
|
+
page_size: value.filter[:limit].to_i
|
89
|
+
}
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
58
93
|
desc "get [ID]", "Get a rule"
|
59
94
|
around :with_db_connection
|
60
95
|
def get(id)
|
data/lib/mihari/commands/tag.rb
CHANGED
@@ -11,6 +11,20 @@ module Mihari
|
|
11
11
|
thor.class_eval do
|
12
12
|
include Concerns::DatabaseConnectable
|
13
13
|
|
14
|
+
no_commands do
|
15
|
+
#
|
16
|
+
# @param [String] q
|
17
|
+
# @param [Integer] page
|
18
|
+
# @param [Integer] limit
|
19
|
+
#
|
20
|
+
# @return [Mihari::Services::ResultValue]
|
21
|
+
#
|
22
|
+
def _search(q, page: 1, limit: 10)
|
23
|
+
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
|
24
|
+
Services::TagSearcher.result(filter).value!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
desc "list", "List/search tags"
|
15
29
|
around :with_db_connection
|
16
30
|
method_option :page, type: :numeric, default: 1
|
@@ -19,8 +33,7 @@ module Mihari
|
|
19
33
|
# @param [String] q
|
20
34
|
#
|
21
35
|
def list(q = "")
|
22
|
-
|
23
|
-
value = Services::TagSearcher.result(filter).value!
|
36
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
24
37
|
data = Entities::TagsWithPagination.represent(
|
25
38
|
results: value.results,
|
26
39
|
total: value.total,
|
@@ -30,6 +43,28 @@ module Mihari
|
|
30
43
|
puts JSON.pretty_generate(data.as_json)
|
31
44
|
end
|
32
45
|
|
46
|
+
desc "list-transform QUERY", "List/search tags with transformation"
|
47
|
+
around :with_db_connection
|
48
|
+
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
+
description: "Jbuilder template itself or a path to a template file"
|
50
|
+
method_option :page, type: :numeric, default: 1
|
51
|
+
method_option :limit, type: :numeric, default: 10
|
52
|
+
#
|
53
|
+
# @param [String] q
|
54
|
+
#
|
55
|
+
def list_transform(q = "")
|
56
|
+
value = _search(q, page: options["page"], limit: options["limit"])
|
57
|
+
puts Services::JbuilderRenderer.call(
|
58
|
+
options["template"],
|
59
|
+
{
|
60
|
+
results: value.results,
|
61
|
+
total: value.total,
|
62
|
+
current_page: value.filter[:page].to_i,
|
63
|
+
page_size: value.filter[:limit].to_i
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
33
68
|
desc "delete [ID]", "Delete a tag"
|
34
69
|
around :with_db_connection
|
35
70
|
#
|
@@ -17,9 +17,10 @@ module Mihari
|
|
17
17
|
receiver = err.receiver
|
18
18
|
case receiver
|
19
19
|
when Dry::Monads::Try::Error
|
20
|
-
|
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
|
-
|
12
|
-
Errno::ECONNRESET,
|
13
|
-
Errno::ECONNABORTED,
|
14
|
-
Errno::EPIPE,
|
11
|
+
RETRIABLE_ERRORS = [
|
15
12
|
OpenSSL::SSL::SSLError,
|
16
13
|
Timeout::Error,
|
17
|
-
|
18
|
-
|
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 StatusError
|
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 [
|
36
|
+
# @param [Proc] condition
|
30
37
|
#
|
31
|
-
|
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
|
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/data_type.rb
CHANGED
@@ -28,7 +28,7 @@ module Mihari
|
|
28
28
|
def ip?
|
29
29
|
Try[IPAddr::InvalidAddressError] do
|
30
30
|
IPAddr.new(data).to_s == data
|
31
|
-
end.
|
31
|
+
end.recover { false }.value!
|
32
32
|
end
|
33
33
|
|
34
34
|
# @return [Boolean]
|
@@ -36,7 +36,7 @@ module Mihari
|
|
36
36
|
Try[Addressable::URI::InvalidURIError] do
|
37
37
|
uri = Addressable::URI.parse("http://#{data}")
|
38
38
|
uri.host == data && PublicSuffix.valid?(uri.host)
|
39
|
-
end.
|
39
|
+
end.recover { false }.value!
|
40
40
|
end
|
41
41
|
|
42
42
|
# @return [Boolean]
|
@@ -44,7 +44,7 @@ module Mihari
|
|
44
44
|
Try[Addressable::URI::InvalidURIError] do
|
45
45
|
uri = Addressable::URI.parse(data)
|
46
46
|
uri.scheme && uri.host && uri.path && PublicSuffix.valid?(uri.host)
|
47
|
-
end.
|
47
|
+
end.recover { false }.value!
|
48
48
|
end
|
49
49
|
|
50
50
|
# @return [Boolean]
|
@@ -1,49 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "erb"
|
4
|
-
|
5
3
|
module Mihari
|
6
4
|
module Emitters
|
7
|
-
class ERBTemplate < ERB
|
8
|
-
class << self
|
9
|
-
def template
|
10
|
-
%{
|
11
|
-
{
|
12
|
-
"rule": {
|
13
|
-
"id": "<%= @rule.id %>",
|
14
|
-
"title": "<%= @rule.title %>",
|
15
|
-
"description": "<%= @rule.description %>"
|
16
|
-
},
|
17
|
-
"artifacts": [
|
18
|
-
<% @artifacts.each_with_index do |artifact, idx| %>
|
19
|
-
"<%= artifact.data %>"
|
20
|
-
<%= ',' if idx < (@artifacts.length - 1) %>
|
21
|
-
<% end %>
|
22
|
-
],
|
23
|
-
"tags": [
|
24
|
-
<% @rule.tags.each_with_index do |tag, idx| %>
|
25
|
-
"<%= tag.name %>"
|
26
|
-
<%= ',' if idx < (@rule.tags.length - 1) %>
|
27
|
-
<% end %>
|
28
|
-
]
|
29
|
-
}
|
30
|
-
}
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def initialize(artifacts:, rule:, options: {})
|
35
|
-
@artifacts = artifacts
|
36
|
-
@rule = rule
|
37
|
-
|
38
|
-
@template = options.fetch(:template, self.class.template)
|
39
|
-
super(@template)
|
40
|
-
end
|
41
|
-
|
42
|
-
def result
|
43
|
-
super(binding)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
5
|
class Webhook < Base
|
48
6
|
# @return [Addressable::URI, nil]
|
49
7
|
attr_reader :url
|
@@ -54,12 +12,24 @@ module Mihari
|
|
54
12
|
# @return [String]
|
55
13
|
attr_reader :method
|
56
14
|
|
57
|
-
# @return [String
|
15
|
+
# @return [String]
|
58
16
|
attr_reader :template
|
59
17
|
|
60
18
|
# @return [Array<Mihari::Models::Artifact>]
|
61
19
|
attr_accessor :artifacts
|
62
20
|
|
21
|
+
DEFAULT_TEMPLATE = %{
|
22
|
+
json.rule do
|
23
|
+
json.id rule.id
|
24
|
+
json.title rule.title
|
25
|
+
json.description rule.description
|
26
|
+
end
|
27
|
+
|
28
|
+
json.artifacts artifacts.map(&:data)
|
29
|
+
|
30
|
+
json.tags rule.tags.map(&:name)
|
31
|
+
}
|
32
|
+
|
63
33
|
#
|
64
34
|
# @param [Mihari::Rule] rule
|
65
35
|
# @param [Hash, nil] options
|
@@ -71,7 +41,7 @@ module Mihari
|
|
71
41
|
@url = Addressable::URI.parse(params[:url])
|
72
42
|
@headers = params[:headers] || {}
|
73
43
|
@method = params[:method] || "POST"
|
74
|
-
@template = params[:template]
|
44
|
+
@template = params[:template] || DEFAULT_TEMPLATE
|
75
45
|
|
76
46
|
@artifacts = []
|
77
47
|
end
|
@@ -114,15 +84,7 @@ module Mihari
|
|
114
84
|
# @return [String]
|
115
85
|
#
|
116
86
|
def render
|
117
|
-
|
118
|
-
options[:template] = File.read(template) unless template.nil?
|
119
|
-
|
120
|
-
erb_template = ERBTemplate.new(
|
121
|
-
artifacts: artifacts,
|
122
|
-
rule: rule,
|
123
|
-
options: options
|
124
|
-
)
|
125
|
-
erb_template.result
|
87
|
+
Services::JbuilderRenderer.call(template, { rule: rule, artifacts: artifacts })
|
126
88
|
end
|
127
89
|
|
128
90
|
#
|
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
|
12
|
+
class ResponseError < Error; end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
class AnalyzerError < Error
|
15
|
+
# @return [StandardException, nil]
|
16
|
+
attr_reader :cause
|
16
17
|
|
17
|
-
|
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
|
-
|
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
|
-
# HTTP status
|
40
|
+
# HTTP status error
|
23
41
|
#
|
24
|
-
class
|
42
|
+
class StatusError < ::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
@@ -11,18 +11,13 @@ module Mihari
|
|
11
11
|
def wrap_response(response)
|
12
12
|
return response if response.status.success?
|
13
13
|
|
14
|
-
raise
|
14
|
+
raise StatusError.new(
|
15
15
|
"Unsuccessful response code returned: #{response.code}",
|
16
16
|
response.code,
|
17
17
|
response.body.to_s
|
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
|
#
|
@@ -279,8 +293,8 @@ module Mihari
|
|
279
293
|
# data is serialized as JSON so dates (created_on & updated_on) are stringified in there
|
280
294
|
# thus dates & (hash) keys have to be stringified when comparing
|
281
295
|
data.deep_dup.tap do |data|
|
282
|
-
data[:created_on] =
|
283
|
-
data[:updated_on] =
|
296
|
+
data[:created_on] = created_on.to_s unless created_on.nil?
|
297
|
+
data[:updated_on] = updated_on.to_s unless updated_on.nil?
|
284
298
|
end.deep_stringify_keys
|
285
299
|
end
|
286
300
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "tilt/jbuilder"
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Services
|
5
|
+
#
|
6
|
+
# Jbuilder based JSON renderer
|
7
|
+
#
|
8
|
+
class JbuilderRenderer < Service
|
9
|
+
attr_reader :template
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param [String] template
|
13
|
+
# @param [Hash] params
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
def call(template, params = {})
|
18
|
+
@template = template
|
19
|
+
|
20
|
+
jbuilder_template = Tilt::JbuilderTemplate.new { template_string }
|
21
|
+
jbuilder_template.render(nil, params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def template_string
|
25
|
+
return File.read(template) if Pathname(template).exist?
|
26
|
+
|
27
|
+
template
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|