mihari 6.2.0 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
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