mihari 6.2.0 → 6.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d49d8a079c765cb4b7ad7a08825a9d0bd4e9e21fbaf2c0f185c4f27e8f2f2ca6
4
- data.tar.gz: aa693115eb1dacc09d13ca0942d859e24566c09a4cd6d702840ec9df6c3cc87a
3
+ metadata.gz: f027c5c24759f4e09d5dad277df4cad58a1705478eba1f3b9b6b354a896a29a5
4
+ data.tar.gz: a908c432c313bab4f4af70b0b32fb1f2e65c9f40926dc3099c4869330080005c
5
5
  SHA512:
6
- metadata.gz: e50a254b0c5565c3691cc34df0413258a1903f8864f0ced141fab63cb673f02998451723036917f2adf21f759ec3f0086b772e98a02ee0436869dd5846bf3427
7
- data.tar.gz: 63738367f8edd23331518eb839c188dde8fb67257b83b5cd6a11e79dc725aa89b151753e7429515ff1b2f7c689da77adf4eac998241b7bfd04f88302aa52c923
6
+ metadata.gz: 8110df87f854b6e06ea8477c94c6563b2450dae4585738f256da9c94203d14c6fb3ce11e775a470dae04d4d3934549ae71068e6b3f3d3eca48ea3689d87a4cd4
7
+ data.tar.gz: 375855d7dba59d13ff894472f5f6d5ccc82f36de73aeb12c4144c05863c64283f2ff451dd4531643ba3ddbb218181937d57da1a42bb28970f78170e3fae5dfbf
@@ -42,5 +42,13 @@ module Mihari
42
42
  status.ports.empty? ? nil : status.ports
43
43
  end
44
44
  end
45
+
46
+ class ArtifactsWithPagination < Grape::Entity
47
+ expose :artifacts, using: Entities::Artifact,
48
+ documentation: { type: Entities::Artifact, is_array: true, required: true }
49
+ expose :total, documentation: { type: Integer, required: true }
50
+ expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
51
+ expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
52
+ end
45
53
  end
46
54
  end
@@ -30,8 +30,7 @@ module Mihari
30
30
  offset = (page - 1) * limit
31
31
 
32
32
  relation = build_relation(filter.without_pagination)
33
- alert_ids = relation.limit(limit).offset(offset).order(id: :desc).pluck(:id).uniq
34
- eager_load(:artifacts, :tags).where(id: [alert_ids]).order(id: :desc)
33
+ relation.limit(limit).offset(offset).order(id: :desc)
35
34
  end
36
35
 
37
36
  #
@@ -48,39 +47,17 @@ module Mihari
48
47
 
49
48
  private
50
49
 
51
- #
52
- # @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
53
- #
54
- # @return [Array<Integer>]
55
- #
56
- def get_artifact_ids_by_filter(filter)
57
- artifact_ids = []
58
-
59
- if filter.artifact_data
60
- artifact = Artifact.where(data: filter.artifact_data)
61
- artifact_ids = artifact.pluck(:id)
62
- # set invalid ID if nothing is matched with the filters
63
- artifact_ids = [-1] if artifact_ids.empty?
64
- end
65
-
66
- artifact_ids
67
- end
68
-
69
50
  #
70
51
  # @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
71
52
  #
72
53
  # @return [Mihari::Models::Alert]
73
54
  #
74
55
  def build_relation(filter)
75
- artifact_ids = get_artifact_ids_by_filter(filter)
76
-
77
- relation = includes(:artifacts, :tags)
78
-
79
- relation = relation.where(artifacts: { id: artifact_ids }) unless artifact_ids.empty?
80
- relation = relation.where(tags: { name: filter.tag_name }) if filter.tag_name
56
+ relation = eager_load(:artifacts, :tags)
81
57
 
58
+ relation = relation.where(artifacts: { data: filter.artifact }) if filter.artifact
59
+ relation = relation.where(tags: { name: filter.tag }) if filter.tag
82
60
  relation = relation.where(rule_id: filter.rule_id) if filter.rule_id
83
-
84
61
  relation = relation.where("alerts.created_at >= ?", filter.from_at) if filter.from_at
85
62
  relation = relation.where("alerts.created_at <= ?", filter.to_at) if filter.to_at
86
63
 
@@ -77,6 +77,18 @@ module Mihari
77
77
  artifact.created_at < decayed_at
78
78
  end
79
79
 
80
+ #
81
+ # Count artifacts
82
+ #
83
+ # @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
84
+ #
85
+ # @return [Integer]
86
+ #
87
+ def count(filter)
88
+ relation = build_relation(filter)
89
+ relation.distinct("artifact.id").count
90
+ end
91
+
80
92
  #
81
93
  # Enrich whois record
82
94
  #
@@ -105,7 +117,7 @@ module Mihari
105
117
  # @param [Mihari::Enrichers::Shodan] enricher
106
118
  #
107
119
  def enrich_reverse_dns(enricher = Enrichers::Shodan.new)
108
- return unless can_enrich_revese_dns?
120
+ return unless can_enrich_reverse_dns?
109
121
 
110
122
  self.reverse_dns_names = ReverseDnsName.build_by_ip(data, enricher: enricher)
111
123
  end
@@ -195,6 +207,56 @@ module Mihari
195
207
  methods.each { |method| send(method, enricher) if respond_to?(method) }
196
208
  end
197
209
 
210
+ class << self
211
+ #
212
+ # Search artifacts
213
+ #
214
+ # @param [Mihari::Structs::Filters::Artifact::SearchFilterWithPagination] filter
215
+ #
216
+ # @return [Array<Artifact>]
217
+ #
218
+ def search(filter)
219
+ limit = filter.limit.to_i
220
+ raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
221
+
222
+ page = filter.page.to_i
223
+ raise ArgumentError, "page should be bigger than zero" unless page.positive?
224
+
225
+ offset = (page - 1) * limit
226
+
227
+ relation = build_relation(filter.without_pagination)
228
+ relation.limit(limit).offset(offset).order(id: :desc)
229
+ end
230
+
231
+ #
232
+ # Count artifacts
233
+ #
234
+ # @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
235
+ #
236
+ # @return [Integer]
237
+ #
238
+ def count(filter)
239
+ relation = build_relation(filter)
240
+ relation.distinct("artifacts.id").count
241
+ end
242
+
243
+ #
244
+ # @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
245
+ #
246
+ # @return [Mihari::Models::Artifact]
247
+ #
248
+ def build_relation(filter)
249
+ relation = eager_load(alert: :tags)
250
+
251
+ relation = relation.where(alert: { rule_id: filter.rule_id }) if filter.rule_id
252
+ relation = relation.where(alert: { tags: { name: filter.tag } }) if filter.tag
253
+ relation = relation.where("artifacts.created_at >= ?", filter.from_at) if filter.from_at
254
+ relation = relation.where("artifacts.created_at <= ?", filter.to_at) if filter.to_at
255
+
256
+ relation
257
+ end
258
+ end
259
+
198
260
  private
199
261
 
200
262
  def ipinfo
@@ -219,7 +281,7 @@ module Mihari
219
281
  %w[domain url].include?(data_type) && dns_records.empty?
220
282
  end
221
283
 
222
- def can_enrich_revese_dns?
284
+ def can_enrich_reverse_dns?
223
285
  data_type == "ip" && reverse_dns_names.empty?
224
286
  end
225
287
 
@@ -40,10 +40,7 @@ module Mihari
40
40
  offset = (page - 1) * limit
41
41
 
42
42
  relation = build_relation(filter.without_pagination)
43
-
44
- # TODO: improve queires
45
- rule_ids = relation.limit(limit).offset(offset).order(created_at: :desc).pluck(:id).uniq
46
- where(id: [rule_ids]).order(created_at: :desc)
43
+ relation.limit(limit).offset(offset).order(created_at: :desc)
47
44
  end
48
45
 
49
46
  #
@@ -68,7 +65,7 @@ module Mihari
68
65
  def build_relation(filter)
69
66
  relation = includes(alerts: :tags)
70
67
 
71
- relation = relation.where(alerts: { tags: { name: filter.tag_name } }) if filter.tag_name
68
+ relation = relation.where(alerts: { tags: { name: filter.tag } }) if filter.tag
72
69
 
73
70
  relation = relation.where("rules.title LIKE ?", "%#{filter.title}%") if filter.title
74
71
  relation = relation.where("rules.description LIKE ?", "%#{filter.description}%") if filter.description
@@ -3,19 +3,63 @@
3
3
  module Mihari
4
4
  module Structs
5
5
  module Filters
6
+ module Artifact
7
+ class SearchFilter < Dry::Struct
8
+ # @!attribute [r] data_type
9
+ # @return [String, nil]
10
+ attribute? :data_type, Types::String.optional
11
+
12
+ # @!attribute [r] rule_id
13
+ # @return [String, nil]
14
+ attribute? :rule_id, Types::String.optional
15
+
16
+ # @!attribute [r] tag
17
+ # @return [String, nil]
18
+ attribute? :tag, Types::String.optional
19
+
20
+ # @!attribute [r] from_at
21
+ # @return [DateTime, nil]
22
+ attribute? :from_at, Types::DateTime.optional
23
+
24
+ # @!attribute [r] to_at
25
+ # @return [DateTime, nil]
26
+ attribute? :to_at, Types::DateTime.optional
27
+ end
28
+
29
+ class SearchFilterWithPagination < SearchFilter
30
+ # @!attribute [r] page
31
+ # @return [Integer, nil]
32
+ attribute? :page, Types::Int.default(1)
33
+
34
+ # @!attribute [r] limit
35
+ # @return [Integer, nil]
36
+ attribute? :limit, Types::Int.default(10)
37
+
38
+ def without_pagination
39
+ SearchFilter.new(
40
+ data_type: data_type,
41
+ from_at: from_at,
42
+ rule_id: rule_id,
43
+ tag: tag,
44
+ to_at: to_at
45
+ )
46
+ end
47
+ end
48
+ end
49
+
6
50
  module Alert
7
51
  class SearchFilter < Dry::Struct
8
- # @!attribute [r] artifact_data
52
+ # @!attribute [r] artifact
9
53
  # @return [String, nil]
10
- attribute? :artifact_data, Types::String.optional
54
+ attribute? :artifact, Types::String.optional
11
55
 
12
56
  # @!attribute [r] rule_id
13
57
  # @return [String, nil]
14
58
  attribute? :rule_id, Types::String.optional
15
59
 
16
- # @!attribute [r] tag_name
60
+ # @!attribute [r] tag
17
61
  # @return [String, nil]
18
- attribute? :tag_name, Types::String.optional
62
+ attribute? :tag, Types::String.optional
19
63
 
20
64
  # @!attribute [r] from_at
21
65
  # @return [DateTime, nil]
@@ -37,10 +81,10 @@ module Mihari
37
81
 
38
82
  def without_pagination
39
83
  SearchFilter.new(
40
- artifact_data: artifact_data,
84
+ artifact: artifact,
41
85
  from_at: from_at,
42
86
  rule_id: rule_id,
43
- tag_name: tag_name,
87
+ tag: tag,
44
88
  to_at: to_at
45
89
  )
46
90
  end
@@ -53,9 +97,9 @@ module Mihari
53
97
  # @return [String, nil]
54
98
  attribute? :description, Types::String.optional
55
99
 
56
- # @!attribute [r] tag_name
100
+ # @!attribute [r] tag
57
101
  # @return [String, nil]
58
- attribute? :tag_name, Types::String.optional
102
+ attribute? :tag, Types::String.optional
59
103
 
60
104
  # @!attribute [r] title
61
105
  # @return [String, nil]
@@ -83,7 +127,7 @@ module Mihari
83
127
  SearchFilter.new(
84
128
  description: description,
85
129
  from_at: from_at,
86
- tag_name: tag_name,
130
+ tag: tag,
87
131
  title: title,
88
132
  to_at: to_at
89
133
  )
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "6.2.0"
4
+ VERSION = "6.3.0"
5
5
  end
@@ -36,18 +36,10 @@ module Mihari
36
36
  # @return [ResultValue]
37
37
  #
38
38
  def call(params)
39
- filter = params.to_h.to_snake_keys
40
-
41
- # normalize keys
42
- filter["artifact_data"] = filter["artifact"]
43
- filter["tag_name"] = filter["tag"]
44
- # symbolize hash keys
45
- filter = filter.to_h.symbolize_keys
46
-
39
+ filter = params.to_h.to_snake_keys.symbolize_keys
47
40
  search_filter_with_pagination = Structs::Filters::Alert::SearchFilterWithPagination.new(**filter)
48
41
  alerts = Mihari::Models::Alert.search(search_filter_with_pagination)
49
42
  total = Mihari::Models::Alert.count(search_filter_with_pagination.without_pagination)
50
-
51
43
  ResultValue.new(alerts: alerts, total: total, filter: filter)
52
44
  end
53
45
  end
@@ -83,7 +75,7 @@ module Mihari
83
75
  optional :page, type: Integer, default: 1
84
76
  optional :limit, type: Integer, default: 10
85
77
  optional :artifact, type: String
86
- optional :rule_id, type: String
78
+ optional :ruleId, type: String
87
79
  optional :tag, type: String
88
80
  optional :fromAt, type: DateTime
89
81
  optional :toAt, type: DateTime
@@ -61,7 +61,71 @@ module Mihari
61
61
  end
62
62
  end
63
63
 
64
+ class ArtifactSearcher < Mihari::Service
65
+ class ResultValue
66
+ # @return [Array<Mihari::Models::Artifacts>]
67
+ attr_reader :artifacts
68
+
69
+ # @return [Integer]
70
+ attr_reader :total
71
+
72
+ # @return [Mihari::Structs::Filters::Artifact::SearchFilterWithPagination]
73
+ attr_reader :filter
74
+
75
+ #
76
+ # @param [Array<Mihari::Models::Artifact>] artifacts
77
+ # @param [Integer] total
78
+ # @param [Mihari::Structs::Filters::Artifacts::SearchFilterWithPagination] filter
79
+ #
80
+ def initialize(artifacts:, total:, filter:)
81
+ @artifacts = artifacts
82
+ @total = total
83
+ @filter = filter
84
+ end
85
+ end
86
+
87
+ #
88
+ # @param [Hash] params
89
+ #
90
+ # @return [ResultValue]
91
+ #
92
+ def call(params)
93
+ filter = params.to_h.to_snake_keys.symbolize_keys
94
+ search_filter_with_pagination = Structs::Filters::Artifact::SearchFilterWithPagination.new(**filter)
95
+ artifacts = Mihari::Models::Artifact.search(search_filter_with_pagination)
96
+ total = Mihari::Models::Artifact.count(search_filter_with_pagination.without_pagination)
97
+ ResultValue.new(artifacts: artifacts, total: total, filter: filter)
98
+ end
99
+ end
100
+
64
101
  namespace :artifacts do
102
+ desc "Search artifacts", {
103
+ is_array: true,
104
+ success: Entities::ArtifactsWithPagination,
105
+ summary: "Search artifacts"
106
+ }
107
+ params do
108
+ optional :page, type: Integer, default: 1
109
+ optional :limit, type: Integer, default: 10
110
+ optional :dataType, type: String
111
+ optional :ruleId, type: String
112
+ optional :tag, type: String
113
+ optional :fromAt, type: DateTime
114
+ optional :toAt, type: DateTime
115
+ end
116
+ get "/" do
117
+ value = ArtifactSearcher.call(params.to_h)
118
+ present(
119
+ {
120
+ artifacts: value.artifacts,
121
+ total: value.total,
122
+ current_page: value.filter[:page].to_i,
123
+ page_size: value.filter[:limit].to_i
124
+ },
125
+ with: Entities::ArtifactsWithPagination
126
+ )
127
+ end
128
+
65
129
  desc "Get an artifact", {
66
130
  success: Entities::Artifact,
67
131
  failure: [{ code: 404, model: Entities::Message }],
File without changes
@@ -36,17 +36,10 @@ module Mihari
36
36
  # @return [ResultValue]
37
37
  #
38
38
  def call(params)
39
- filter = params.to_h.to_snake_keys
40
-
41
- # normalize keys
42
- filter["tag_name"] = filter["tag"]
43
- # symbolize hash keys
44
- filter = filter.to_h.symbolize_keys
45
-
39
+ filter = params.to_h.to_snake_keys.symbolize_keys
46
40
  search_filter_with_pagination = Mihari::Structs::Filters::Rule::SearchFilterWithPagination.new(**filter)
47
41
  rules = Mihari::Models::Rule.search(search_filter_with_pagination)
48
42
  total = Mihari::Models::Rule.count(search_filter_with_pagination.without_pagination)
49
-
50
43
  ResultValue.new(rules: rules, total: total, filter: filter)
51
44
  end
52
45
  end