mihari 7.0.4 → 7.1.0
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 +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
|