mihari 3.11.0 → 3.12.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -1
  3. data/lib/mihari/analyzers/base.rb +1 -1
  4. data/lib/mihari/analyzers/feed.rb +39 -0
  5. data/lib/mihari/analyzers/rule.rb +1 -0
  6. data/lib/mihari/cli/analyzer.rb +2 -0
  7. data/lib/mihari/commands/feed.rb +26 -0
  8. data/lib/mihari/database.rb +4 -4
  9. data/lib/mihari/enrichers/ipinfo.rb +2 -0
  10. data/lib/mihari/errors.rb +4 -0
  11. data/lib/mihari/feed/parser.rb +34 -0
  12. data/lib/mihari/feed/reader.rb +113 -0
  13. data/lib/mihari/mixins/autonomous_system.rb +2 -0
  14. data/lib/mihari/mixins/configuration.rb +2 -2
  15. data/lib/mihari/mixins/disallowed_data_value.rb +2 -0
  16. data/lib/mihari/mixins/rule.rb +2 -0
  17. data/lib/mihari/models/alert.rb +4 -5
  18. data/lib/mihari/models/artifact.rb +2 -3
  19. data/lib/mihari/models/dns.rb +2 -2
  20. data/lib/mihari/schemas/rule.rb +12 -1
  21. data/lib/mihari/status.rb +2 -2
  22. data/lib/mihari/structs/alert.rb +3 -1
  23. data/lib/mihari/structs/censys.rb +2 -0
  24. data/lib/mihari/structs/greynoise.rb +2 -0
  25. data/lib/mihari/structs/ipinfo.rb +2 -0
  26. data/lib/mihari/structs/onyphe.rb +2 -0
  27. data/lib/mihari/structs/shodan.rb +2 -0
  28. data/lib/mihari/structs/urlscan.rb +2 -0
  29. data/lib/mihari/structs/virustotal_intelligence.rb +2 -0
  30. data/lib/mihari/types.rb +6 -0
  31. data/lib/mihari/version.rb +1 -1
  32. data/lib/mihari/web/api.rb +2 -0
  33. data/lib/mihari/web/entities/artifact.rb +2 -2
  34. data/lib/mihari/web/entities/whois.rb +1 -1
  35. data/lib/mihari/web/public/index.html +1 -1
  36. data/lib/mihari/web/public/redoc-static.html +181 -183
  37. data/lib/mihari/web/public/static/js/app.5dc97aae.js +21 -0
  38. data/lib/mihari/web/public/static/js/app.5dc97aae.js.map +1 -0
  39. data/lib/mihari/web/public/static/js/app.f2b8890f.js +21 -0
  40. data/lib/mihari/web/public/static/js/app.f2b8890f.js.map +1 -0
  41. data/lib/mihari.rb +1 -0
  42. data/mihari.gemspec +17 -17
  43. data/sig/lib/mihari/analyzers/feed.rbs +23 -0
  44. data/sig/lib/mihari/cli/analyzer.rbs +4 -0
  45. data/sig/lib/mihari/commands/feed.rbs +7 -0
  46. data/sig/lib/mihari/feed/parser.rbs +11 -0
  47. data/sig/lib/mihari/feed/reader.rbs +56 -0
  48. data/sig/lib/mihari/structs/alert.rbs +1 -1
  49. data/sig/lib/mihari/types.rbs +4 -0
  50. metadata +90 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c8fda839e1bb1ec4733b7da17e20e3b47bd5795d5aca25371d09e5a0fa9a575
4
- data.tar.gz: a4551c61c625bd08167608051750bf1785a31ad5215e95e857753fc5fed31d00
3
+ metadata.gz: 5a17c14dffe68b2f82316858615371036996a3a2b6d31d3c6a5c431294c38634
4
+ data.tar.gz: 97afaf2f8b20985b0908cc1fe5778fe75ab7d0e1bf13b990a7b4165c92843604
5
5
  SHA512:
6
- metadata.gz: 81a1ca91b65b03f98bb3504a85c8113b2cf189a4911109e113da1058fb9a1bd61c8807e6f3bec72484786b89e68d0027782d38013676bd6ae78aa57434315972
7
- data.tar.gz: b42b1658310a7ee87940fbebb6507edca2187d6be92957a149f9234af8bf32ba06e702c401d321b553901dc56094964e78ff602549734f51abbf24b4b7a1fa4d
6
+ metadata.gz: 418d7f278536e245196723d2ebbe0d99934b0db3278e8f8178470241c67f25e05d0aacf7bab2b766ce69d0589a79e87eca554a91fe4cfae5cfa12cd52d97fb61
7
+ data.tar.gz: 9e90193273ee40c9956a138c2c73b713eb610a3ebdc1c9def73a6d354c4124a97e6842e438004956b3edc3206dfee8bae5771a86e964b59a2a7065a1f910891f
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/mihari.svg)](https://badge.fury.io/rb/mihari)
4
4
  [![Ruby CI](https://github.com/ninoseki/mihari/actions/workflows/test.yml/badge.svg)](https://github.com/ninoseki/mihari/actions/workflows/test.yml)
5
- [![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/ninoseki/mihari)](https://hub.docker.com/r/ninoseki/mihari)
6
5
  [![Coverage Status](https://coveralls.io/repos/github/ninoseki/mihari/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/mihari?branch=master)
7
6
  [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/mihari/badge)](https://www.codefactor.io/repository/github/ninoseki/mihari)
8
7
 
@@ -111,7 +111,7 @@ module Mihari
111
111
  # @return [Array<Mihari::Artifact>]
112
112
  #
113
113
  def enriched_artifacts
114
- @enriched_artifacts ||= unique_artifacts.map do |artifact|
114
+ @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
115
115
  artifact.enrich_all
116
116
  artifact
117
117
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mihari/feed/reader"
4
+ require "mihari/feed/parser"
5
+
6
+ module Mihari
7
+ module Analyzers
8
+ class Feed < Base
9
+ param :query
10
+ option :title, default: proc { "Feed" }
11
+ option :description, default: proc { "query = #{query}" }
12
+ option :tags, default: proc { [] }
13
+
14
+ option :http_request_method, default: proc { "GET" }
15
+ option :http_request_headers, default: proc { {} }
16
+ option :http_request_payload, default: proc { {} }
17
+ option :http_request_payload_type, default: proc {}
18
+
19
+ option :selector, default: proc { "" }
20
+
21
+ def artifacts
22
+ Mihari::Feed::Parser.new(data).parse selector
23
+ end
24
+
25
+ private
26
+
27
+ def data
28
+ reader = Mihari::Feed::Reader.new(
29
+ query,
30
+ http_request_method: http_request_method,
31
+ http_request_headers: http_request_headers,
32
+ http_request_payload: http_request_payload,
33
+ http_request_payload_type: http_request_payload_type
34
+ )
35
+ reader.read
36
+ end
37
+ end
38
+ end
39
+ end
@@ -11,6 +11,7 @@ module Mihari
11
11
  "crtsh" => Crtsh,
12
12
  "dnpedia" => DNPedia,
13
13
  "dnstwister" => DNSTwister,
14
+ "feed" => Feed,
14
15
  "greynoise" => GreyNoise,
15
16
  "onyphe" => Onyphe,
16
17
  "otx" => OTX,
@@ -6,6 +6,7 @@ require "mihari/commands/circl"
6
6
  require "mihari/commands/crtsh"
7
7
  require "mihari/commands/dnpedia"
8
8
  require "mihari/commands/dnstwister"
9
+ require "mihari/commands/feed"
9
10
  require "mihari/commands/greynoise"
10
11
  require "mihari/commands/onyphe"
11
12
  require "mihari/commands/otx"
@@ -35,6 +36,7 @@ module Mihari
35
36
  include Mihari::Commands::Crtsh
36
37
  include Mihari::Commands::DNPedia
37
38
  include Mihari::Commands::DNSTwister
39
+ include Mihari::Commands::Feed
38
40
  include Mihari::Commands::GreyNoise
39
41
  include Mihari::Commands::JSON
40
42
  include Mihari::Commands::Onyphe
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Feed
6
+ def self.included(thor)
7
+ thor.class_eval do
8
+ desc "feed [URL]", "ingest feed"
9
+ method_option :title, type: :string, desc: "title"
10
+ method_option :description, type: :string, desc: "description"
11
+ method_option :tags, type: :array, desc: "tags"
12
+ method_option :http_request_method, type: :string, desc: "HTTP request method"
13
+ method_option :http_request_headers, type: :hash, desc: "HTTP request headers"
14
+ method_option :http_request_payload_type, type: :string, desc: "HTTP request payload type"
15
+ method_option :http_request_payload, type: :hash, desc: "HTTP request payload"
16
+ method_option :selector, type: :string, desc: "jr selector", required: true
17
+ def feed(query)
18
+ with_error_handling do
19
+ run_analyzer Analyzers::Feed, query: query, options: options
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "active_record"
4
4
 
5
- class InitialSchema < ActiveRecord::Migration[6.1]
5
+ class InitialSchema < ActiveRecord::Migration[7.0]
6
6
  def change
7
7
  create_table :tags, if_not_exists: true do |t|
8
8
  t.string :name, null: false
@@ -32,13 +32,13 @@ class InitialSchema < ActiveRecord::Migration[6.1]
32
32
  end
33
33
  end
34
34
 
35
- class AddeSourceToArtifactSchema < ActiveRecord::Migration[6.1]
35
+ class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
36
36
  def change
37
37
  add_column :artifacts, :source, :string, if_not_exists: true
38
38
  end
39
39
  end
40
40
 
41
- class EnrichmentsSchema < ActiveRecord::Migration[6.1]
41
+ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
42
42
  def change
43
43
  create_table :autonomous_systems, if_not_exists: true do |t|
44
44
  t.integer :asn, null: false
@@ -74,7 +74,7 @@ class EnrichmentsSchema < ActiveRecord::Migration[6.1]
74
74
  end
75
75
  end
76
76
 
77
- class EnrichmentCreatedAtSchema < ActiveRecord::Migration[6.1]
77
+ class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
78
78
  def change
79
79
  # Add created_at column because now it is able to enrich an atrifact after the creation
80
80
  add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "http"
2
4
  require "json"
3
5
  require "memist"
data/lib/mihari/errors.rb CHANGED
@@ -8,4 +8,8 @@ module Mihari
8
8
  class RetryableError < Error; end
9
9
 
10
10
  class FileNotFoundError < Error; end
11
+
12
+ class HttpError < Error; end
13
+
14
+ class FeedParseError < Error; end
11
15
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jr/cli/core_ext"
4
+
5
+ module Mihari
6
+ module Feed
7
+ class Parser
8
+ attr_reader :data
9
+
10
+ #
11
+ # @param [Array<Hash>, Array<Array<String>>] data
12
+ #
13
+ def initialize(data)
14
+ @data = data
15
+ end
16
+
17
+ #
18
+ # Parse data by selector
19
+ #
20
+ # @param [String] selector
21
+ #
22
+ # @return [Array<String>]
23
+ #
24
+ def parse(selector)
25
+ parsed = data.instance_eval(selector)
26
+
27
+ raise FeedParseError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
28
+ raise FeedParseError unless parsed.all?(String)
29
+
30
+ parsed.to_a
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+ require "json"
5
+ require "net/https"
6
+ require "uri"
7
+
8
+ module Mihari
9
+ module Feed
10
+ class Reader
11
+ attr_reader :uri, :http_request_headers, :http_request_method, :http_request_payload_type, :http_request_payload
12
+
13
+ def initialize(url, http_request_headers: {}, http_request_method: "GET", http_request_payload_type: nil, http_request_payload: {})
14
+ @uri = URI(url)
15
+ @http_request_headers = http_request_headers
16
+ @http_request_method = http_request_method
17
+ @http_request_payload_type = http_request_payload_type
18
+ @http_request_payload = http_request_payload
19
+ end
20
+
21
+ def read
22
+ return get if http_request_method == "GET"
23
+
24
+ post
25
+ end
26
+
27
+ def get
28
+ uri.query = URI.encode_www_form(http_request_payload)
29
+ get = Net::HTTP::Get.new(uri)
30
+
31
+ request(get)
32
+ end
33
+
34
+ def post
35
+ post = Net::HTTP::Post.new(uri)
36
+
37
+ case http_request_payload_type
38
+ when "application/json"
39
+ post.body = JSON.generate(http_request_payload)
40
+ when "application/x-www-form-urlencoded"
41
+ post.set_form_data(http_request_payload)
42
+ end
43
+
44
+ request(post)
45
+ end
46
+
47
+ #
48
+ # Convert text as JSON
49
+ #
50
+ # @param [String] text
51
+ #
52
+ # @return [Array<Hash>]
53
+ #
54
+ def convert_as_json(text)
55
+ data = JSON.parse(text, symbolize_names: true)
56
+ return data if data.is_a?(Array)
57
+
58
+ [data]
59
+ end
60
+
61
+ #
62
+ # Convert text as CSV
63
+ #
64
+ # @param [String] text
65
+ #
66
+ # @return [Array<Hash>]
67
+ #
68
+ def convert_as_csv(text)
69
+ text_without_comments = text.lines.reject { |line| line.start_with? "#" }.join("\n")
70
+
71
+ CSV.new(text_without_comments).to_a.reject(&:empty?)
72
+ end
73
+
74
+ def https_options
75
+ return { use_ssl: true } if uri.scheme == "https"
76
+
77
+ {}
78
+ end
79
+
80
+ #
81
+ # Make a HTTP request
82
+ #
83
+ # @param [Net::HTTPRequest] req
84
+ #
85
+ # @return [Array<Hash>]
86
+ #
87
+ def request(req)
88
+ Net::HTTP.start(uri.host, uri.port, https_options) do |http|
89
+ # set headers
90
+ http_request_headers.each do |k, v|
91
+ req[k] = v
92
+ end
93
+
94
+ response = http.request(req)
95
+
96
+ code = response.code.to_i
97
+ raise HttpError, "Unsupported response code returned: #{code}" if code != 200
98
+
99
+ body = response.body
100
+
101
+ content_type = response["Content-Type"].to_s
102
+ data = if content_type.include?("application/json")
103
+ convert_as_json(body)
104
+ else
105
+ convert_as_csv(body)
106
+ end
107
+
108
+ data
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mihari
2
4
  module Mixins
3
5
  module AutonomousSystem
@@ -51,9 +51,9 @@ module Mihari
51
51
  # @return [String] A template for config
52
52
  #
53
53
  def config_template
54
- config = Mihari.config.values.keys.map do |key|
54
+ config = Mihari.config.values.keys.to_h do |key|
55
55
  [key.to_s, nil]
56
- end.to_h
56
+ end
57
57
 
58
58
  YAML.dump(config)
59
59
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "mem"
2
4
 
3
5
  module Mihari
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "date"
2
4
  require "pathname"
3
5
  require "yaml"
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
- require "active_record/filter"
5
4
 
6
5
  module Mihari
7
6
  class Alert < ActiveRecord::Base
@@ -55,7 +54,7 @@ module Mihari
55
54
  artifact = artifact.where(dns_records: { value: filter.dns_record }) if filter.dns_record
56
55
  artifact = artifact.where(reverse_dns_names: { name: filter.reverse_dns_name }) if filter.reverse_dns_name
57
56
  # get artifact ids if there is any valid filter for artifact
58
- if filter.has_valid_artifact_filters
57
+ if filter.valid_artifact_filters?
59
58
  artifact_ids = artifact.pluck(:id)
60
59
  # set invalid ID if nothing is matched with the filters
61
60
  artifact_ids = [-1] if artifact_ids.empty?
@@ -70,10 +69,10 @@ module Mihari
70
69
  relation = relation.where(source: filter.source) if filter.source
71
70
  relation = relation.where(title: filter.title) if filter.title
72
71
 
73
- relation = relation.filter(description: { like: "%#{filter.description}%" }) if filter.description
72
+ relation = relation.where("description LIKE ?", "%#{filter.description}%") if filter.description
74
73
 
75
- relation = relation.filter(created_at: { gte: filter.from_at }) if filter.from_at
76
- relation = relation.filter(created_at: { lte: filter.to_at }) if filter.to_at
74
+ relation = relation.where("alerts.created_at >= ?", filter.from_at) if filter.from_at
75
+ relation = relation.where("alerts.created_at <= ?", filter.to_at) if filter.to_at
77
76
 
78
77
  relation
79
78
  end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
- require "active_record/filter"
5
4
  require "active_support/core_ext/integer/time"
6
5
  require "active_support/core_ext/numeric/time"
7
- require "uri"
6
+ require "addressable/uri"
8
7
 
9
8
  class ArtifactValidator < ActiveModel::Validator
10
9
  def validate(record)
@@ -119,7 +118,7 @@ module Mihari
119
118
  def normalize_as_domain(url_or_domain)
120
119
  return url_or_domain if data_type == "domain"
121
120
 
122
- URI.parse(url_or_domain).host
121
+ Addressable::URI.parse(url_or_domain).host
123
122
  end
124
123
 
125
124
  def can_enrich_whois?
@@ -37,7 +37,7 @@ module Mihari
37
37
  resources = Resolv::DNS.new.getresources(domain, resource_type)
38
38
  resource_name = resource_type.to_s.split("::").last
39
39
 
40
- resources.map do |resource|
40
+ resources.filter_map do |resource|
41
41
  # A, AAAA
42
42
  if resource.respond_to?(:address)
43
43
  new(resource: resource_name, value: resource.address.to_s)
@@ -48,7 +48,7 @@ module Mihari
48
48
  elsif resource.respond_to?(:data)
49
49
  new(resource: resource_name, value: resource.data.to_s)
50
50
  end
51
- end.compact
51
+ end
52
52
  end
53
53
  end
54
54
  end
@@ -44,6 +44,17 @@ module Mihari
44
44
  optional(:options).hash(AnalyzerOptions)
45
45
  end
46
46
 
47
+ Feed = Dry::Schema.Params do
48
+ required(:analyzer).value(Types::String.enum("feed"))
49
+ required(:query).value(:string)
50
+ required(:http_request_method).value(Types::FeedHttpRequestMethods).default("GET")
51
+ required(:http_request_headers).value(:hash).default({})
52
+ required(:http_request_payload).value(:hash).default({})
53
+ required(:selector).value(:string)
54
+ optional(:http_request_payload_type).value(Types::FeedHttpRequestPayloadTypes)
55
+ optional(:options).hash(AnalyzerOptions)
56
+ end
57
+
47
58
  Rule = Dry::Schema.Params do
48
59
  required(:title).value(:string)
49
60
  required(:description).value(:string)
@@ -55,7 +66,7 @@ module Mihari
55
66
  optional(:created_on).value(:date)
56
67
  optional(:updated_on).value(:date)
57
68
 
58
- required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh }
69
+ required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
59
70
 
60
71
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
61
72
  optional(:disallowed_data_values).value(array[:string]).default([])
data/lib/mihari/status.rb CHANGED
@@ -18,11 +18,11 @@ module Mihari
18
18
  # @return [Array<Hash>]
19
19
  #
20
20
  def statuses
21
- (Mihari.analyzers + Mihari.emitters + Mihari.enrichers).map do |klass|
21
+ (Mihari.analyzers + Mihari.emitters + Mihari.enrichers).to_h do |klass|
22
22
  name = klass.to_s.split("::").last.to_s
23
23
 
24
24
  [name, build_status(klass)]
25
- end.to_h.compact
25
+ end.compact
26
26
  end
27
27
 
28
28
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -16,7 +18,7 @@ module Mihari
16
18
  attribute? :dns_record, Types::String.optional
17
19
  attribute? :reverse_dns_name, Types::String.optional
18
20
 
19
- def has_valid_artifact_filters
21
+ def valid_artifact_filters?
20
22
  !(artifact_data || asn || dns_record || reverse_dns_name).nil?
21
23
  end
22
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "dry/struct"
3
5
 
data/lib/mihari/types.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "dry/types"
2
4
 
3
5
  module Mihari
@@ -22,6 +24,7 @@ module Mihari
22
24
  "circl",
23
25
  "dnpedia",
24
26
  "dnstwister",
27
+ "feed",
25
28
  "greynoise",
26
29
  "onyphe",
27
30
  "otx",
@@ -36,5 +39,8 @@ module Mihari
36
39
  "vt_intel",
37
40
  "vt"
38
41
  )
42
+
43
+ FeedHttpRequestMethods = Types::String.enum("GET", "POST")
44
+ FeedHttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
39
45
  end
40
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "3.11.0"
4
+ VERSION = "3.12.0"
5
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Entities
2
4
  require "mihari/web/entities/message"
3
5
 
@@ -14,10 +14,10 @@ module Mihari
14
14
  expose :whois_record, using: Entities::WhoisRecord, documentation: { type: Entities::WhoisRecord, required: false }, as: :whoisRecord
15
15
 
16
16
  expose :reverse_dns_names, using: Entities::ReverseDnsName, documentation: { type: Entities::ReverseDnsName, is_array: true, required: false }, as: :reverseDnsNames do |status, _options|
17
- status.reverse_dns_names.length > 0 ? status.reverse_dns_names : nil
17
+ status.reverse_dns_names.empty? ? nil : status.reverse_dns_names
18
18
  end
19
19
  expose :dns_records, using: Entities::DnsRecord, documentation: { type: Entities::DnsRecord, is_array: true, required: false }, as: :dnsRecords do |status, _options|
20
- status.dns_records.length > 0 ? status.dns_records : nil
20
+ status.dns_records.empty? ? nil : status.dns_records
21
21
  end
22
22
  end
23
23
  end
@@ -9,7 +9,7 @@ module Mihari
9
9
  expose :expires_on, documentation: { type: Date, required: false }, as: :expiresOn
10
10
  expose :registrar, documentation: { type: Hash, required: false }
11
11
  expose :contacts, documentation: { type: Hash, is_array: true, required: true } do |whois_record, _options|
12
- whois_record.contacts.map { |h| h.to_camelback_keys }
12
+ whois_record.contacts.map(&:to_camelback_keys)
13
13
  end
14
14
  end
15
15
  end
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Mihari</title><link href="/static/js/app.fbc19869.js" rel="preload" as="script"></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/app.fbc19869.js"></script></body></html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Mihari</title><link href="/static/js/app.5dc97aae.js" rel="preload" as="script"></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/app.5dc97aae.js"></script></body></html>