mihari 0.17.5 → 1.0.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/.gitignore +3 -0
- data/.rubocop.yml +155 -0
- data/.travis.yml +1 -0
- data/Gemfile +2 -0
- data/README.md +30 -72
- data/config/pre_commit.yml +3 -0
- data/lib/mihari.rb +12 -8
- data/lib/mihari/alert_viewer.rb +6 -28
- data/lib/mihari/analyzers/base.rb +7 -19
- data/lib/mihari/analyzers/basic.rb +3 -1
- data/lib/mihari/analyzers/binaryedge.rb +2 -2
- data/lib/mihari/analyzers/censys.rb +2 -2
- data/lib/mihari/analyzers/circl.rb +2 -2
- data/lib/mihari/analyzers/onyphe.rb +3 -3
- data/lib/mihari/analyzers/passivetotal.rb +2 -2
- data/lib/mihari/analyzers/pulsedive.rb +2 -2
- data/lib/mihari/analyzers/securitytrails.rb +2 -2
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +2 -2
- data/lib/mihari/analyzers/shodan.rb +2 -2
- data/lib/mihari/analyzers/virustotal.rb +2 -2
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/cli.rb +2 -2
- data/lib/mihari/config.rb +68 -2
- data/lib/mihari/configurable.rb +1 -1
- data/lib/mihari/database.rb +45 -0
- data/lib/mihari/emitters/base.rb +1 -1
- data/lib/mihari/emitters/misp.rb +8 -1
- data/lib/mihari/emitters/slack.rb +2 -2
- data/lib/mihari/emitters/sqlite.rb +29 -0
- data/lib/mihari/emitters/stdout.rb +2 -1
- data/lib/mihari/emitters/the_hive.rb +28 -14
- data/lib/mihari/models/alert.rb +11 -0
- data/lib/mihari/models/artifact.rb +27 -0
- data/lib/mihari/models/tag.rb +10 -0
- data/lib/mihari/models/tagging.rb +10 -0
- data/lib/mihari/notifiers/slack.rb +4 -4
- data/lib/mihari/serializers/alert.rb +12 -0
- data/lib/mihari/serializers/artifact.rb +9 -0
- data/lib/mihari/serializers/tag.rb +9 -0
- data/lib/mihari/status.rb +1 -1
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +11 -5
- metadata +120 -31
- data/lib/mihari/artifact.rb +0 -36
- data/lib/mihari/cache.rb +0 -35
- data/lib/mihari/the_hive.rb +0 -42
- data/lib/mihari/the_hive/alert.rb +0 -25
- data/lib/mihari/the_hive/artifact.rb +0 -33
- data/lib/mihari/the_hive/base.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '099c3dece3c31a745979b619c361d3bcff09e67abb3214c32747a935d57be963'
|
4
|
+
data.tar.gz: 1f777f2ca61721716e32e85846817a5183d11063573813adec757919bb27f457
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8e0babf78935a90d1d4d39493ed7db77d498697a1ea8d2bbdf3277745544a0f7a59de6dc444e93065f66c339b9c5208df408748194cb490ffe3901681a9aa0b
|
7
|
+
data.tar.gz: 0b430d270f34f1fae64f384b672a4270500407e725f0f8f99bba278b134d74196fda894b4ed425ed21d363583da93e40648dce77b9ff39a9b0da07c7f5a7a5d2
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Relaxed.Ruby.Style
|
2
|
+
## Version 2.5
|
3
|
+
|
4
|
+
require:
|
5
|
+
- rubocop-performance
|
6
|
+
|
7
|
+
Style/Alias:
|
8
|
+
Enabled: false
|
9
|
+
StyleGuide: https://relaxed.ruby.style/#stylealias
|
10
|
+
|
11
|
+
Style/AsciiComments:
|
12
|
+
Enabled: false
|
13
|
+
StyleGuide: https://relaxed.ruby.style/#styleasciicomments
|
14
|
+
|
15
|
+
Style/BeginBlock:
|
16
|
+
Enabled: false
|
17
|
+
StyleGuide: https://relaxed.ruby.style/#stylebeginblock
|
18
|
+
|
19
|
+
Style/BlockDelimiters:
|
20
|
+
Enabled: false
|
21
|
+
StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
|
22
|
+
|
23
|
+
Style/CommentAnnotation:
|
24
|
+
Enabled: false
|
25
|
+
StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
|
26
|
+
|
27
|
+
Style/Documentation:
|
28
|
+
Enabled: false
|
29
|
+
StyleGuide: https://relaxed.ruby.style/#styledocumentation
|
30
|
+
|
31
|
+
Layout/DotPosition:
|
32
|
+
Enabled: false
|
33
|
+
StyleGuide: https://relaxed.ruby.style/#layoutdotposition
|
34
|
+
|
35
|
+
Style/DoubleNegation:
|
36
|
+
Enabled: false
|
37
|
+
StyleGuide: https://relaxed.ruby.style/#styledoublenegation
|
38
|
+
|
39
|
+
Style/EndBlock:
|
40
|
+
Enabled: false
|
41
|
+
StyleGuide: https://relaxed.ruby.style/#styleendblock
|
42
|
+
|
43
|
+
Style/FormatString:
|
44
|
+
Enabled: false
|
45
|
+
StyleGuide: https://relaxed.ruby.style/#styleformatstring
|
46
|
+
|
47
|
+
Style/IfUnlessModifier:
|
48
|
+
Enabled: false
|
49
|
+
StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
|
50
|
+
|
51
|
+
Style/Lambda:
|
52
|
+
Enabled: false
|
53
|
+
StyleGuide: https://relaxed.ruby.style/#stylelambda
|
54
|
+
|
55
|
+
Style/ModuleFunction:
|
56
|
+
Enabled: false
|
57
|
+
StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
|
58
|
+
|
59
|
+
Style/MultilineBlockChain:
|
60
|
+
Enabled: false
|
61
|
+
StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
|
62
|
+
|
63
|
+
Style/NegatedIf:
|
64
|
+
Enabled: false
|
65
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedif
|
66
|
+
|
67
|
+
Style/NegatedWhile:
|
68
|
+
Enabled: false
|
69
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
|
70
|
+
|
71
|
+
Style/NumericPredicate:
|
72
|
+
Enabled: false
|
73
|
+
StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
|
74
|
+
|
75
|
+
Style/ParallelAssignment:
|
76
|
+
Enabled: false
|
77
|
+
StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
|
78
|
+
|
79
|
+
Style/PercentLiteralDelimiters:
|
80
|
+
Enabled: false
|
81
|
+
StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
|
82
|
+
|
83
|
+
Style/PerlBackrefs:
|
84
|
+
Enabled: false
|
85
|
+
StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
|
86
|
+
|
87
|
+
Style/Semicolon:
|
88
|
+
Enabled: false
|
89
|
+
StyleGuide: https://relaxed.ruby.style/#stylesemicolon
|
90
|
+
|
91
|
+
Style/SignalException:
|
92
|
+
Enabled: false
|
93
|
+
StyleGuide: https://relaxed.ruby.style/#stylesignalexception
|
94
|
+
|
95
|
+
Style/SingleLineBlockParams:
|
96
|
+
Enabled: false
|
97
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
|
98
|
+
|
99
|
+
Style/SingleLineMethods:
|
100
|
+
Enabled: false
|
101
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
|
102
|
+
|
103
|
+
Layout/SpaceBeforeBlockBraces:
|
104
|
+
Enabled: false
|
105
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
|
106
|
+
|
107
|
+
Layout/SpaceInsideParens:
|
108
|
+
Enabled: false
|
109
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
|
110
|
+
|
111
|
+
Style/SpecialGlobalVars:
|
112
|
+
Enabled: false
|
113
|
+
StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
|
114
|
+
|
115
|
+
Style/StringLiterals:
|
116
|
+
Enabled: false
|
117
|
+
StyleGuide: https://relaxed.ruby.style/#stylestringliterals
|
118
|
+
|
119
|
+
Style/TrailingCommaInArguments:
|
120
|
+
Enabled: false
|
121
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
|
122
|
+
|
123
|
+
Style/TrailingCommaInArrayLiteral:
|
124
|
+
Enabled: false
|
125
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
|
126
|
+
|
127
|
+
Style/TrailingCommaInHashLiteral:
|
128
|
+
Enabled: false
|
129
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
|
130
|
+
|
131
|
+
Style/SymbolArray:
|
132
|
+
Enabled: false
|
133
|
+
StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
|
134
|
+
|
135
|
+
Style/WhileUntilModifier:
|
136
|
+
Enabled: false
|
137
|
+
StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
|
138
|
+
|
139
|
+
Style/WordArray:
|
140
|
+
Enabled: false
|
141
|
+
StyleGuide: https://relaxed.ruby.style/#stylewordarray
|
142
|
+
|
143
|
+
Lint/AmbiguousRegexpLiteral:
|
144
|
+
Enabled: false
|
145
|
+
StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
|
146
|
+
|
147
|
+
Lint/AssignmentInCondition:
|
148
|
+
Enabled: false
|
149
|
+
StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
|
150
|
+
|
151
|
+
Layout/LineLength:
|
152
|
+
Enabled: false
|
153
|
+
|
154
|
+
Metrics:
|
155
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,19 +10,15 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
|
|
10
10
|
|
11
11
|
## How it works
|
12
12
|
|
13
|
-
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts from the results.
|
14
|
-
- Mihari checks whether
|
13
|
+
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes) from the results.
|
14
|
+
- Mihari checks whether a DB (SQLite3) contains the artifacts or not.
|
15
15
|
- If it doesn't contain the artifacts:
|
16
|
-
- Mihari creates an alert on TheHive.
|
16
|
+
- Mihari creates an alert on TheHive. (Optional)
|
17
17
|
- Mihari sends a notification to Slack. (Optional)
|
18
18
|
- Mihari creates an event on MISP. (Optional)
|
19
19
|
|
20
20
|

|
21
21
|
|
22
|
-
Check this blog post for more details: [Continuous C2 hunting with Censys, Shodan, Onyphe and TheHive](https://hackmd.io/s/SkUaSrqoE).
|
23
|
-
|
24
|
-
You can use mihari without TheHive but note that mihari depends on TheHive to manage artifacts. It means mihari might make duplications when without TheHive.
|
25
|
-
|
26
22
|
### Screenshots
|
27
23
|
|
28
24
|
- TheHive alert example
|
@@ -156,49 +152,13 @@ mihari http_hash --html /tmp/index.html
|
|
156
152
|
|
157
153
|
```bash
|
158
154
|
# Censys lookup for PANDA C2
|
159
|
-
|
160
|
-
{
|
161
|
-
"title": "PANDA C2",
|
162
|
-
"description": "query = (\"PANDA\" AND \"SMAdmin\" AND \"layui\")",
|
163
|
-
"artifacts": [
|
164
|
-
"154.223.165.223",
|
165
|
-
"154.194.2.31",
|
166
|
-
"45.114.127.119",
|
167
|
-
"..."
|
168
|
-
],
|
169
|
-
"tags": []
|
170
|
-
}
|
155
|
+
mihari censys '("PANDA" AND "SMAdmin" AND "layui")' --title "PANDA C2"
|
171
156
|
|
172
157
|
# VirusTotal passive DNS lookup of a FAKESPY host
|
173
|
-
|
174
|
-
{
|
175
|
-
"title": "FAKESPY host passive DNS results",
|
176
|
-
"description": "indicator = jppost-hi.top",
|
177
|
-
"artifacts": [
|
178
|
-
"185.22.152.28",
|
179
|
-
"192.236.200.44",
|
180
|
-
"193.148.69.12",
|
181
|
-
"..."
|
182
|
-
],
|
183
|
-
"tags": []
|
184
|
-
}
|
158
|
+
mihari virustotal "jppost-hi.top" --title "FAKESPY passive DNS"
|
185
159
|
|
186
160
|
# You can pass a "defanged" indicator as an input
|
187
|
-
|
188
|
-
|
189
|
-
# SecurityTrails domain feed lookup for finding (possibly) Apple phishing websites
|
190
|
-
$ mihari securitytrails_domain_feed "apple-" --type new
|
191
|
-
{
|
192
|
-
"title": "SecurityTrails domain feed lookup",
|
193
|
-
"description": "Regexp = /apple-/",
|
194
|
-
"artifacts": [
|
195
|
-
"apple-sign.online",
|
196
|
-
"apple-log-in.com",
|
197
|
-
"apple-locator-id.info",
|
198
|
-
"..."
|
199
|
-
],
|
200
|
-
"tags": []
|
201
|
-
}
|
161
|
+
mihari virustotal "jppost-hi[.]top" --title "FAKESPY passive DNS"
|
202
162
|
```
|
203
163
|
|
204
164
|
### Import from JSON
|
@@ -229,28 +189,29 @@ The input is a JSON data should have `title`, `description` and `artifacts` key.
|
|
229
189
|
|
230
190
|
Configuration can be done via environment variables or a YAML file.
|
231
191
|
|
232
|
-
| Key |
|
233
|
-
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
| ZOOMEYE_PASSWORD | ZoomEye password |
|
192
|
+
| Key | Description | Default |
|
193
|
+
|------------------------|--------------------------------|-------------|
|
194
|
+
| DATABASE | A path to the SQLite database | `mihari.db` |
|
195
|
+
| BINARYEDGE_API_KEY | BinaryEdge API key | |
|
196
|
+
| CENSYS_ID | Censys API ID | |
|
197
|
+
| CENSYS_SECRET | Censys secret | |
|
198
|
+
| CIRCL_PASSIVE_PASSWORD | CIRCL passive DNS/SSL password | |
|
199
|
+
| CIRCL_PASSIVE_USERNAME | CIRCL passive DNS/SSL username | |
|
200
|
+
| MISP_API_ENDPOINT | MISP URL | |
|
201
|
+
| MISP_API_KEY | MISP API key | |
|
202
|
+
| ONYPHE_API_KEY | Onyphe API key | |
|
203
|
+
| PASSIVETOTAL_API_KEY | PassiveTotal API key | |
|
204
|
+
| PASSIVETOTAL_USERNAME | PassiveTotal username | |
|
205
|
+
| PULSEDIVE_API_KEY | Pulsedive API key | |
|
206
|
+
| SECURITYTRAILS_API_KEY | SecurityTrails API key | |
|
207
|
+
| SHODAN_API_KEY | Shodan API key | |
|
208
|
+
| SLACK_CHANNEL | Slack channel name | `#general` |
|
209
|
+
| SLACK_WEBHOOK_URL | Slack Webhook URL | |
|
210
|
+
| THEHIVE_API_ENDPOINT | TheHive URL | |
|
211
|
+
| THEHIVE_API_KEY | TheHive API key | |
|
212
|
+
| VIRUSTOTAL_API_KEY | VirusTotal API key | |
|
213
|
+
| ZOOMEYE_PASSWORD | ZoomEye password | |
|
214
|
+
| ZOOMEYE_USERNAMME | ZoomEye username | |
|
254
215
|
|
255
216
|
Instead of using environment variables, you can use a YAML file for configuration.
|
256
217
|
|
@@ -261,6 +222,7 @@ mihari virustotal 1.1.1.1 --config /path/to/yaml.yml
|
|
261
222
|
The YAML file should be a YAML hash like below:
|
262
223
|
|
263
224
|
```yaml
|
225
|
+
database: /tmp/mihari.db
|
264
226
|
thehive_api_endpoint: https://localhost
|
265
227
|
thehive_api_key: foo
|
266
228
|
virustotal_api_key: foo
|
@@ -314,10 +276,6 @@ example.run
|
|
314
276
|
|
315
277
|
See `/examples` for more.
|
316
278
|
|
317
|
-
## Caching
|
318
|
-
|
319
|
-
Mihari caches execution results in `/tmp/mihari` and the default cache duration is 7 days. If you want to clear the cache, please clear `/tmp/mihari`.
|
320
|
-
|
321
279
|
## Using it with Docker
|
322
280
|
|
323
281
|
```bash
|
data/lib/mihari.rb
CHANGED
@@ -19,24 +19,27 @@ module Mihari
|
|
19
19
|
end
|
20
20
|
|
21
21
|
require "mihari/version"
|
22
|
-
|
23
22
|
require "mihari/errors"
|
24
23
|
|
25
|
-
require "mihari/artifact"
|
26
|
-
require "mihari/cache"
|
27
24
|
require "mihari/config"
|
25
|
+
|
26
|
+
require "mihari/database"
|
28
27
|
require "mihari/type_checker"
|
29
28
|
|
29
|
+
require "mihari/models/alert"
|
30
|
+
require "mihari/models/artifact"
|
31
|
+
require "mihari/models/tag"
|
32
|
+
require "mihari/models/tagging"
|
33
|
+
|
34
|
+
require "mihari/serializers/alert"
|
35
|
+
require "mihari/serializers/artifact"
|
36
|
+
require "mihari/serializers/tag"
|
37
|
+
|
30
38
|
require "mihari/html"
|
31
39
|
|
32
40
|
require "mihari/configurable"
|
33
41
|
require "mihari/retriable"
|
34
42
|
|
35
|
-
require "mihari/the_hive/base"
|
36
|
-
require "mihari/the_hive/alert"
|
37
|
-
require "mihari/the_hive/artifact"
|
38
|
-
require "mihari/the_hive"
|
39
|
-
|
40
43
|
require "mihari/analyzers/base"
|
41
44
|
require "mihari/analyzers/basic"
|
42
45
|
|
@@ -70,6 +73,7 @@ require "mihari/notifiers/exception_notifier"
|
|
70
73
|
require "mihari/emitters/base"
|
71
74
|
require "mihari/emitters/misp"
|
72
75
|
require "mihari/emitters/slack"
|
76
|
+
require "mihari/emitters/sqlite"
|
73
77
|
require "mihari/emitters/stdout"
|
74
78
|
require "mihari/emitters/the_hive"
|
75
79
|
|
data/lib/mihari/alert_viewer.rb
CHANGED
@@ -3,39 +3,17 @@
|
|
3
3
|
module Mihari
|
4
4
|
class AlertViewer
|
5
5
|
attr_reader :limit
|
6
|
-
attr_reader :the_hive
|
7
|
-
|
8
|
-
ALERT_KEYS = %w(title description artifacts tags createdAt status).freeze
|
9
6
|
|
10
7
|
def initialize(limit: 5)
|
11
|
-
@limit = limit
|
12
|
-
|
13
|
-
|
14
|
-
@the_hive = TheHive.new
|
15
|
-
raise Error, "Cannot connect to the TheHive instance" unless the_hive.valid?
|
8
|
+
@limit = limit.to_i
|
9
|
+
raise ArgumentError, "limit should be bigger than zero" unless @limit.positive?
|
16
10
|
end
|
17
11
|
|
18
12
|
def list
|
19
|
-
|
20
|
-
alerts
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def validate_limit
|
27
|
-
return true if limit == "all"
|
28
|
-
|
29
|
-
raise ArgumentError, "limit should be bigger than zero" unless limit.to_i.positive?
|
30
|
-
end
|
31
|
-
|
32
|
-
def convert(alert)
|
33
|
-
attributes = alert.select { |k, _v| ALERT_KEYS.include? k }
|
34
|
-
attributes["createdAt"] = Time.at(attributes["createdAt"] / 1000).to_s
|
35
|
-
attributes["artifacts"] = (attributes.dig("artifacts") || []).map do |artifact|
|
36
|
-
artifact.dig("data")
|
37
|
-
end.sort
|
38
|
-
attributes
|
13
|
+
alerts = Alert.order(id: :desc).limit(limit).includes(:tags, :artifacts)
|
14
|
+
alerts.map do |alert|
|
15
|
+
AlertSerializer.new(alert).as_json
|
16
|
+
end
|
39
17
|
end
|
40
18
|
end
|
41
19
|
end
|
@@ -23,6 +23,10 @@ module Mihari
|
|
23
23
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
24
24
|
end
|
25
25
|
|
26
|
+
def source
|
27
|
+
self.class.to_s.split("::").last
|
28
|
+
end
|
29
|
+
|
26
30
|
# @return [Array<String>]
|
27
31
|
def tags
|
28
32
|
[]
|
@@ -37,7 +41,7 @@ module Mihari
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def run_emitter(emitter)
|
40
|
-
emitter.run(title: title, description: description, artifacts: unique_artifacts, tags: tags)
|
44
|
+
emitter.run(title: title, description: description, artifacts: unique_artifacts, source: source, tags: tags)
|
41
45
|
rescue StandardError => e
|
42
46
|
puts "Emission by #{emitter.class} is failed: #{e}"
|
43
47
|
end
|
@@ -48,32 +52,16 @@ module Mihari
|
|
48
52
|
|
49
53
|
private
|
50
54
|
|
51
|
-
def the_hive
|
52
|
-
@the_hive ||= TheHive.new
|
53
|
-
end
|
54
|
-
|
55
|
-
def cache
|
56
|
-
@cache ||= Cache.new
|
57
|
-
end
|
58
|
-
|
59
55
|
# @return [Array<Mihari::Artifact>]
|
60
56
|
def normalized_artifacts
|
61
57
|
@normalized_artifacts ||= artifacts.compact.uniq.sort.map do |artifact|
|
62
|
-
artifact.is_a?(Artifact) ? artifact : Artifact.new(artifact)
|
58
|
+
artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
|
63
59
|
end.select(&:valid?)
|
64
60
|
end
|
65
61
|
|
66
|
-
def uncached_artifacts
|
67
|
-
@uncached_artifacts ||= normalized_artifacts.reject do |artifact|
|
68
|
-
cache.cached? artifact.data
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
62
|
# @return [Array<Mihari::Artifact>]
|
73
63
|
def unique_artifacts
|
74
|
-
|
75
|
-
|
76
|
-
@unique_artifacts ||= the_hive.artifact.find_non_existing_artifacts(uncached_artifacts)
|
64
|
+
@unique_artifacts ||= normalized_artifacts.select(&:unique?)
|
77
65
|
end
|
78
66
|
|
79
67
|
def set_unique_artifacts
|