mihari 5.2.1 → 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/lib/mihari/analyzers/base.rb +20 -115
  4. data/lib/mihari/analyzers/binaryedge.rb +0 -1
  5. data/lib/mihari/analyzers/censys.rb +26 -3
  6. data/lib/mihari/analyzers/circl.rb +1 -1
  7. data/lib/mihari/analyzers/onyphe.rb +1 -1
  8. data/lib/mihari/analyzers/passivetotal.rb +1 -1
  9. data/lib/mihari/analyzers/rule.rb +122 -75
  10. data/lib/mihari/analyzers/shodan.rb +1 -1
  11. data/lib/mihari/analyzers/urlscan.rb +6 -9
  12. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -6
  13. data/lib/mihari/cli/main.rb +2 -2
  14. data/lib/mihari/clients/base.rb +1 -1
  15. data/lib/mihari/commands/database.rb +12 -11
  16. data/lib/mihari/commands/rule.rb +47 -45
  17. data/lib/mihari/commands/search.rb +88 -0
  18. data/lib/mihari/commands/version.rb +8 -6
  19. data/lib/mihari/commands/web.rb +26 -23
  20. data/lib/mihari/emitters/base.rb +14 -1
  21. data/lib/mihari/emitters/database.rb +3 -10
  22. data/lib/mihari/emitters/misp.rb +16 -5
  23. data/lib/mihari/emitters/slack.rb +13 -15
  24. data/lib/mihari/emitters/the_hive.rb +17 -19
  25. data/lib/mihari/emitters/webhook.rb +23 -23
  26. data/lib/mihari/enrichers/whois.rb +1 -0
  27. data/lib/mihari/feed/parser.rb +1 -0
  28. data/lib/mihari/feed/reader.rb +29 -14
  29. data/lib/mihari/mixins/configurable.rb +13 -4
  30. data/lib/mihari/mixins/error_notification.rb +0 -2
  31. data/lib/mihari/models/artifact.rb +1 -1
  32. data/lib/mihari/schemas/rule.rb +2 -17
  33. data/lib/mihari/structs/censys.rb +226 -56
  34. data/lib/mihari/structs/config.rb +48 -18
  35. data/lib/mihari/structs/google_public_dns.rb +56 -14
  36. data/lib/mihari/structs/greynoise.rb +122 -29
  37. data/lib/mihari/structs/ipinfo.rb +40 -0
  38. data/lib/mihari/structs/onyphe.rb +112 -26
  39. data/lib/mihari/structs/rule.rb +4 -2
  40. data/lib/mihari/structs/shodan.rb +189 -47
  41. data/lib/mihari/structs/urlscan.rb +123 -20
  42. data/lib/mihari/structs/virustotal_intelligence.rb +129 -26
  43. data/lib/mihari/type_checker.rb +10 -8
  44. data/lib/mihari/version.rb +1 -1
  45. data/lib/mihari.rb +1 -0
  46. data/mihari.gemspec +11 -10
  47. metadata +35 -36
  48. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  49. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  50. data/.github/workflows/test.yml +0 -90
  51. data/config/pre_commit.yml +0 -3
  52. data/docker/Dockerfile +0 -14
  53. data/examples/ipinfo_hosted_domains.rb +0 -45
  54. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  55. data/images/alert.png +0 -0
  56. data/images/logo.png +0 -0
  57. data/images/misp.png +0 -0
  58. data/images/overview.jpg +0 -0
  59. data/images/slack.png +0 -0
  60. data/images/tines.png +0 -0
  61. data/images/web_alerts.png +0 -0
  62. data/images/web_config.png +0 -0
  63. data/lib/mihari/commands/searcher.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ce7e22d3d54cc3e66fec62b494ce5f9def9107129513cd51803c71b8ae48b18
4
- data.tar.gz: fd85f33a666a77a8b43d12dc3234ab2656cd843c8f7e23025c1a5f076947fbcf
3
+ metadata.gz: 2a66fb2d71bcae401062277921a8ade6a3e9e9d961b193a80deacd3a8a934d4c
4
+ data.tar.gz: b4dcccfa58019f819241f8679b5d1ae846002f0a95307cd95856f2b6d04a6dd1
5
5
  SHA512:
6
- metadata.gz: a17809e3c6f52e7d37f6df2fc92b0ad5a1d134556e6ef13cb64b3802074a44f2ecd0c2475ab632e5da0f57e7c68a44fca15f722f31124fbb9e8af512f46aa2ac
7
- data.tar.gz: d777cac77ceeb2a98c8f37a8e001d9240403b47a1e2326a6f1bc42c3cf48e7813dd69ab2260ce5a29a34b7b9b3e0ef54fa541785f8b5dbc82b787381e549525e
6
+ metadata.gz: e215400c8dce2b864bc26a951ed8ea35757441e7d25bdba6c66632d6716991bad202170f9984d09b7a709f6c9aceb731e5b40396efe621ffc55810a10db45db2
7
+ data.tar.gz: 745522a9cefaed75e5b266a429dea7afc0efe34f26f8591af18e3bc27a0746659ed8d64f6aa64f6edc0f9195acbf26e4a7ee078c052bab760f985da779c4e6e4
data/.rubocop.yml CHANGED
@@ -8,3 +8,5 @@ Metrics/BlockLength:
8
8
  - "*.gemspec"
9
9
  Metrics/ClassLength:
10
10
  Enabled: false
11
+ Metrics/MethodLength:
12
+ Max: 20
@@ -5,32 +5,34 @@ module Mihari
5
5
  class Base
6
6
  extend Dry::Initializer
7
7
 
8
- option :rule, default: proc {}
9
-
10
8
  include Mixins::Configurable
11
9
  include Mixins::Retriable
12
10
 
13
- # @return [Mihari::Structs::Rule, nil]
14
- attr_reader :rule
15
-
16
- def initialize(*args, **kwargs)
17
- super(*args, **kwargs)
18
-
19
- @base_time = Time.now.utc
11
+ # @return [Array<String>, Array<Mihari::Artifact>]
12
+ def artifacts
13
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
20
14
  end
21
15
 
22
16
  #
23
- # Load/overwrite rule
17
+ # Normalize artifacts
18
+ # - Convert data (string) into an artifact
19
+ # - Reject an invalid artifact
24
20
  #
25
- # @param [String] path_or_id
21
+ # @return [Array<Mihari::Artifact>]
26
22
  #
27
- def load_rule(path_or_id)
28
- @rule = Structs::Rule.from_path_or_id path_or_id
29
- end
30
-
31
- # @return [Array<String>, Array<Mihari::Artifact>]
32
- def artifacts
33
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
23
+ def normalized_artifacts
24
+ retry_on_error do
25
+ @normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
26
+ # No need to set data_type manually
27
+ # It is set automatically in #initialize
28
+ artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
29
+ artifact
30
+ end.select(&:valid?).uniq(&:data).map do |artifact|
31
+ # set source
32
+ artifact.source = source
33
+ artifact
34
+ end
35
+ end
34
36
  end
35
37
 
36
38
  # @return [String]
@@ -43,109 +45,12 @@ module Mihari
43
45
  self.class.to_s.split("::").last
44
46
  end
45
47
 
46
- #
47
- # Set artifacts & run emitters in parallel
48
- #
49
- # @return [Mihari::Alert, nil]
50
- #
51
- def run
52
- raise ConfigurationError, "#{class_name} is not configured correctly" unless configured?
53
-
54
- alert_or_something = bulk_emit
55
- # returns Mihari::Alert created by the database emitter
56
- alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
57
- end
58
-
59
- #
60
- # Bulk emit
61
- #
62
- # @return [Array<Mihari::Alert>]
63
- #
64
- def bulk_emit
65
- Parallel.map(valid_emitters) { |emitter| emit emitter }.compact
66
- end
67
-
68
- #
69
- # Emit an alert
70
- #
71
- # @param [Mihari::Emitters::Base] emitter
72
- #
73
- # @return [Mihari::Alert, nil]
74
- #
75
- def emit(emitter)
76
- return if enriched_artifacts.empty?
77
-
78
- alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
79
-
80
- Mihari.logger.info "Emission by #{emitter.class} is succedded"
81
-
82
- alert_or_something
83
- rescue StandardError => e
84
- Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
85
- end
86
-
87
48
  class << self
88
49
  def inherited(child)
89
50
  super
90
51
  Mihari.analyzers << child
91
52
  end
92
53
  end
93
-
94
- #
95
- # Normalize artifacts
96
- # - Convert data (string) into an artifact
97
- # - Set rule ID
98
- # - Reject an invalid artifact
99
- # - Uniquefy artifacts by data
100
- #
101
- # @return [Array<Mihari::Artifact>]
102
- #
103
- def normalized_artifacts
104
- @normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
105
- # No need to set data_type manually
106
- # It is set automatically in #initialize
107
- artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
108
- artifact.rule_id = rule&.id
109
- artifact
110
- end.select(&:valid?).uniq(&:data)
111
- end
112
-
113
- private
114
-
115
- #
116
- # Uniquefy artifacts (assure rule level uniqueness)
117
- #
118
- # @return [Array<Mihari::Artifact>]
119
- #
120
- def unique_artifacts
121
- @unique_artifacts ||= normalized_artifacts.select do |artifact|
122
- artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
123
- end
124
- end
125
-
126
- #
127
- # Enriched artifacts
128
- #
129
- # @return [Array<Mihari::Artifact>]
130
- #
131
- def enriched_artifacts
132
- @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
133
- artifact.enrich_all
134
- artifact
135
- end
136
- end
137
-
138
- #
139
- # Select valid emitters
140
- #
141
- # @return [Array<Mihari::Emitters::Base>]
142
- #
143
- def valid_emitters
144
- @valid_emitters ||= Mihari.emitters.filter_map do |klass|
145
- emitter = klass.new
146
- emitter.valid? ? emitter : nil
147
- end.compact
148
- end
149
54
  end
150
55
  end
151
56
  end
@@ -42,7 +42,6 @@ module Mihari
42
42
  #
43
43
  # Search with pagination
44
44
  #
45
- # @param [String] query
46
45
  # @param [Integer] page
47
46
  #
48
47
  # @return [Hash]
@@ -26,15 +26,23 @@ module Mihari
26
26
  @secret = kwargs[:secret] || Mihari.config.censys_secret
27
27
  end
28
28
 
29
+ #
30
+ # @return [Array<Mihari::Artifact>]
31
+ #
29
32
  def artifacts
30
33
  artifacts = []
31
34
 
32
35
  cursor = nil
33
36
  loop do
34
37
  response = client.search(query, cursor: cursor)
35
- artifacts << response.result.to_artifacts(source)
38
+ artifacts << response.result.to_artifacts
36
39
  cursor = response.result.links.next
37
- break if cursor == ""
40
+ # NOTE: Censys's search API is unstable recently
41
+ # it may returns empty links or empty string cursors
42
+ # - Empty links: "links": {}
43
+ # - Empty cursors: "links": { "next": "", "prev": "" }
44
+ # So it needs to check both cases
45
+ break if cursor.nil? || cursor.empty?
38
46
 
39
47
  # sleep #{interval} seconds to avoid the rate limitation (if it is set)
40
48
  sleep interval
@@ -43,24 +51,39 @@ module Mihari
43
51
  artifacts.flatten.uniq(&:data)
44
52
  end
45
53
 
54
+ #
55
+ # @return [Boolean]
56
+ #
46
57
  def configured?
47
- configuration_keys.all? { |key| Mihari.config.send(key) } || (id? && secret?)
58
+ configuration_keys? || (id? && secret?)
48
59
  end
49
60
 
50
61
  private
51
62
 
63
+ #
64
+ # @return [Array<String>]
65
+ #
52
66
  def configuration_keys
53
67
  %w[censys_id censys_secret]
54
68
  end
55
69
 
70
+ #
71
+ # @return [Mihari::Clients::Censys]
72
+ #
56
73
  def client
57
74
  @client ||= Clients::Censys.new(id: id, secret: secret)
58
75
  end
59
76
 
77
+ #
78
+ # @return [Boolean]
79
+ #
60
80
  def id?
61
81
  !id.nil?
62
82
  end
63
83
 
84
+ #
85
+ # @return [Boolean]
86
+ #
64
87
  def secret?
65
88
  !secret.nil?
66
89
  end
@@ -41,7 +41,7 @@ module Mihari
41
41
  end
42
42
 
43
43
  def configured?
44
- configuration_keys.all? { |key| Mihari.config.send(key) } || (username? && password?)
44
+ configuration_keys? || (username? && password?)
45
45
  end
46
46
 
47
47
  private
@@ -28,7 +28,7 @@ module Mihari
28
28
  responses = search
29
29
  return [] unless responses
30
30
 
31
- responses.map { |response| response.to_artifacts(source) }.flatten
31
+ responses.map(&:to_artifacts).flatten
32
32
  end
33
33
 
34
34
  private
@@ -43,7 +43,7 @@ module Mihari
43
43
  end
44
44
 
45
45
  def configured?
46
- configuration_keys.all? { |key| Mihari.config.send(key) } || (username? && api_key?)
46
+ configuration_keys? || (username? && api_key?)
47
47
  end
48
48
 
49
49
  private
@@ -34,59 +34,40 @@ module Mihari
34
34
  "webhook" => Emitters::Webhook
35
35
  }.freeze
36
36
 
37
- # @return [Mihari::Structs::Rule]
38
- attr_reader :rule
39
-
40
- class Rule < Base
37
+ class Rule
41
38
  include Mixins::FalsePositive
42
39
 
43
- def initialize(**kwargs)
44
- super(**kwargs)
40
+ # @return [Mihari::Structs::Rule]
41
+ attr_reader :rule
42
+
43
+ # @return [Time]
44
+ attr_reader :base_time
45
+
46
+ #
47
+ # @param [Mihari::Structs::Rule] rule
48
+ #
49
+ def initialize(rule:)
50
+ @rule = rule
51
+ @base_time = Time.now.utc
45
52
 
46
53
  validate_analyzer_configurations
47
54
  end
48
55
 
49
56
  #
50
- # Returns a list of artifacts matched with queries
57
+ # Returns a list of artifacts matched with queries/analyzers
51
58
  #
52
59
  # @return [Array<Mihari::Artifact>]
53
60
  #
54
61
  def artifacts
55
- artifacts = []
56
-
57
- rule.queries.each do |original_params|
58
- parmas = original_params.deep_dup
59
-
60
- analyzer_name = parmas[:analyzer]
61
- klass = get_analyzer_class(analyzer_name)
62
-
63
- query = parmas[:query]
64
-
65
- # set interval in the top level
66
- options = parmas[:options] || {}
67
- interval = options[:interval]
68
-
69
- parmas[:interval] = interval if interval
70
-
71
- # set rule
72
- parmas[:rule] = rule
73
-
74
- analyzer = klass.new(query, **parmas)
75
-
76
- # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
77
- # So Mihari::Artifact object has "source" attribute (e.g. "Shodan")
78
- artifacts << analyzer.normalized_artifacts
79
- end
80
-
81
- artifacts.flatten
62
+ analyzers.flat_map(&:normalized_artifacts)
82
63
  end
83
64
 
84
65
  #
85
66
  # Normalize artifacts
86
- # - Uniquefy artifacts by #uniq(&:data)
87
- # - Reject an invalid artifact (for just in case)
67
+ # - Reject invalid artifacts (for just in case)
88
68
  # - Select artifacts with allowed data types
89
- # - Reject artifacts with disallowed data values
69
+ # - Reject artifacts with false positive values
70
+ # - Set rule ID
90
71
  #
91
72
  # @return [Array<Mihari::Artifact>]
92
73
  #
@@ -95,6 +76,20 @@ module Mihari
95
76
  rule.data_types.include? artifact.data_type
96
77
  end.reject do |artifact|
97
78
  falsepositive? artifact.data
79
+ end.map do |artifact|
80
+ artifact.rule_id = rule.id
81
+ artifact
82
+ end
83
+ end
84
+
85
+ #
86
+ # Uniquify artifacts (assure rule level uniqueness)
87
+ #
88
+ # @return [Array<Mihari::Artifact>]
89
+ #
90
+ def unique_artifacts
91
+ @unique_artifacts ||= normalized_artifacts.select do |artifact|
92
+ artifact.unique?(base_time: base_time, artifact_lifetime: rule.artifact_lifetime)
98
93
  end
99
94
  end
100
95
 
@@ -105,39 +100,105 @@ module Mihari
105
100
  #
106
101
  def enriched_artifacts
107
102
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
108
- rule.enrichers.each do |enricher|
109
- artifact.enrich_by_enricher(enricher[:enricher])
110
- end
111
-
103
+ rule.enrichers.each { |enricher| artifact.enrich_by_enricher enricher[:enricher] }
112
104
  artifact
113
105
  end
114
106
  end
115
107
 
116
108
  #
117
- # Normalized disallowed data values
109
+ # Bulk emit
118
110
  #
119
- # @return [Array<Regexp, String>]
111
+ # @return [Array<Mihari::Alert>]
120
112
  #
121
- def normalized_falsepositives
122
- @normalized_falsepositives ||= rule.falsepositives.map { |v| normalize_falsepositive v }
113
+ def bulk_emit
114
+ return [] if enriched_artifacts.empty?
115
+
116
+ Parallel.map(valid_emitters) do |emitter|
117
+ emission = emitter.emit
118
+ Mihari.logger.info "Emission by #{emitter.class} is succeeded"
119
+ emission
120
+ rescue StandardError => e
121
+ Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
122
+ end.compact
123
123
  end
124
124
 
125
+ #
126
+ # Set artifacts & run emitters in parallel
127
+ #
128
+ # @return [Mihari::Alert, nil]
129
+ #
130
+ def run
131
+ # memoize enriched artifacts
132
+ enriched_artifacts
133
+
134
+ alert_or_something = bulk_emit
135
+ # returns Mihari::Alert created by the database emitter
136
+ alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
137
+ end
138
+
139
+ private
140
+
125
141
  #
126
142
  # Check whether a value is a falsepositive value or not
127
143
  #
128
144
  # @return [Boolean]
129
145
  #
130
146
  def falsepositive?(value)
131
- return true if normalized_falsepositives.include?(value)
147
+ return true if rule.falsepositives.include?(value)
132
148
 
133
- normalized_falsepositives.select do |falsepositive|
149
+ rule.falsepositives.select do |falsepositive|
134
150
  falsepositive.is_a?(Regexp)
135
151
  end.any? do |falseposistive|
136
152
  falseposistive.match?(value)
137
153
  end
138
154
  end
139
155
 
140
- private
156
+ #
157
+ # Deep copied queries
158
+ #
159
+ # @return [Array<Hash>]
160
+ #
161
+ def queries
162
+ rule.queries.map(&:deep_dup)
163
+ end
164
+
165
+ #
166
+ # Get analyzer class
167
+ #
168
+ # @param [String] analyzer_name
169
+ #
170
+ # @return [Class<Mihari::Analyzers::Base>] analyzer class
171
+ #
172
+ def get_analyzer_class(analyzer_name)
173
+ analyzer = ANALYZER_TO_CLASS[analyzer_name]
174
+ return analyzer if analyzer
175
+
176
+ raise ArgumentError, "#{analyzer_name} is not supported"
177
+ end
178
+
179
+ #
180
+ # @return [Array<Mihari::Analyzers::Base>] <description>
181
+ #
182
+ def analyzers
183
+ @analyzers ||= queries.map do |params|
184
+ analyzer_name = params[:analyzer]
185
+ klass = get_analyzer_class(analyzer_name)
186
+
187
+ # set interval in the top level
188
+ options = params[:options] || {}
189
+ interval = options[:interval]
190
+ params[:interval] = interval if interval
191
+
192
+ # set rule
193
+ params[:rule] = rule
194
+ query = params[:query]
195
+
196
+ analyzer = klass.new(query, **params)
197
+ raise ConfigurationError, "#{analyzer.source} is not configured correctly" unless analyzer.configured?
198
+
199
+ analyzer
200
+ end
201
+ end
141
202
 
142
203
  #
143
204
  # Get emitter class
@@ -153,48 +214,34 @@ module Mihari
153
214
  raise ArgumentError, "#{emitter_name} is not supported"
154
215
  end
155
216
 
156
- def valid_emitters
157
- @valid_emitters ||= rule.emitters.filter_map do |original_params|
158
- params = original_params.deep_dup
159
-
217
+ #
218
+ # Deep copied emitters
219
+ #
220
+ # @return [Array<Mihari::Emitters::Base>]
221
+ #
222
+ def emitters
223
+ rule.emitters.map(&:deep_dup).map do |params|
160
224
  name = params[:emitter]
161
225
  params.delete(:emitter)
162
226
 
163
227
  klass = get_emitter_class(name)
164
- emitter = klass.new(**params)
165
-
166
- emitter.valid? ? emitter : nil
228
+ klass.new(artifacts: enriched_artifacts, rule: rule, **params)
167
229
  end
168
230
  end
169
231
 
170
232
  #
171
- # Get analyzer class
172
- #
173
- # @param [String] analyzer_name
174
- #
175
- # @return [Class<Mihari::Analyzers::Base>] analyzer class
233
+ # @return [Array<Mihari::Emitters::Base>]
176
234
  #
177
- def get_analyzer_class(analyzer_name)
178
- analyzer = ANALYZER_TO_CLASS[analyzer_name]
179
- return analyzer if analyzer
180
-
181
- raise ArgumentError, "#{analyzer_name} is not supported"
235
+ def valid_emitters
236
+ @valid_emitters ||= emitters.select(&:valid?)
182
237
  end
183
238
 
184
239
  #
185
240
  # Validate configuration of analyzers
186
241
  #
187
242
  def validate_analyzer_configurations
188
- rule.queries.each do |params|
189
- analyzer_name = params[:analyzer]
190
- klass = get_analyzer_class(analyzer_name)
191
-
192
- instance = klass.new("dummy")
193
- unless instance.configured?
194
- klass_name = klass.to_s.split("::").last
195
- raise ConfigurationError, "#{klass_name} is not configured correctly"
196
- end
197
- end
243
+ # memoize analyzers & raise ConfigurationError if there is an analyzer which is not configured
244
+ analyzers
198
245
  end
199
246
  end
200
247
  end
@@ -26,7 +26,7 @@ module Mihari
26
26
  results = search
27
27
  return [] if results.empty?
28
28
 
29
- results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
29
+ results.map(&:to_artifacts).flatten.uniq(&:data)
30
30
  end
31
31
 
32
32
  private
@@ -36,15 +36,12 @@ module Mihari
36
36
 
37
37
  def artifacts
38
38
  responses = search
39
- results = responses.map(&:results).flatten
40
-
41
- allowed_data_types.map do |type|
42
- results.filter_map do |result|
43
- page = result.page
44
- data = page.send(type.to_sym)
45
- data.nil? ? nil : Artifact.new(data: data, source: source, metadata: result)
46
- end
47
- end.flatten
39
+ # @type [Array<Mihari::Artifact>]
40
+ artifacts = responses.map { |res| res.to_artifacts }.flatten
41
+
42
+ artifacts.select do |artifact|
43
+ allowed_data_types.include? artifact.data_type
44
+ end
48
45
  end
49
46
 
50
47
  private
@@ -25,12 +25,7 @@ module Mihari
25
25
  end
26
26
 
27
27
  def artifacts
28
- responses = search_with_cursor
29
- responses.map do |response|
30
- response.data.map do |datum|
31
- Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
32
- end
33
- end.flatten
28
+ search_with_cursor.map(&:to_artifacts).flatten
34
29
  end
35
30
 
36
31
  private
@@ -3,7 +3,7 @@
3
3
  require "thor"
4
4
 
5
5
  # Commands
6
- require "mihari/commands/searcher"
6
+ require "mihari/commands/search"
7
7
  require "mihari/commands/version"
8
8
  require "mihari/commands/web"
9
9
  require "mihari/commands/database"
@@ -17,7 +17,7 @@ require "mihari/cli/rule"
17
17
  module Mihari
18
18
  module CLI
19
19
  class Main < Base
20
- include Mihari::Commands::Searcher
20
+ include Mihari::Commands::Search
21
21
  include Mihari::Commands::Version
22
22
  include Mihari::Commands::Web
23
23
 
@@ -31,7 +31,7 @@ module Mihari
31
31
 
32
32
  #
33
33
  # @param [String] path
34
- # @param [Hashk, nil] params
34
+ # @param [Hash, nil] params
35
35
  #
36
36
  # @return [String] <description>
37
37
  #
@@ -3,18 +3,19 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Database
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "migrate", "Migrate DB schemas"
9
- method_option :verbose, type: :boolean, default: true
10
- #
11
- # @param [String] direction
12
- #
13
- def migrate(direction = "up")
14
- verbose = options["verbose"]
15
- ActiveRecord::Migration.verbose = verbose
6
+ class << self
7
+ def included(thor)
8
+ thor.class_eval do
9
+ desc "migrate", "Migrate DB schemas"
10
+ method_option :verbose, type: :boolean, default: true
11
+ #
12
+ # @param [String] direction
13
+ #
14
+ def migrate(direction = "up")
15
+ ActiveRecord::Migration.verbose = options["verbose"]
16
16
 
17
- Mihari::Database.with_db_connection { Mihari::Database.migrate(direction.to_sym) }
17
+ Mihari::Database.with_db_connection { Mihari::Database.migrate direction.to_sym }
18
+ end
18
19
  end
19
20
  end
20
21
  end