hachi 1.0.0 → 2.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.
@@ -7,11 +7,21 @@ require "uri"
7
7
  module Hachi
8
8
  module Clients
9
9
  class Base
10
- attr_reader :api_endpoint, :api_key
10
+ # @return [String]
11
+ attr_reader :api_endpoint
11
12
 
12
- def initialize(api_endpoint:, api_key:)
13
+ # @return [String]
14
+ attr_reader :api_key
15
+
16
+ # @return [String, nil]
17
+ attr_reader :api_version
18
+
19
+ include Hachi::Awrence::Methods
20
+
21
+ def initialize(api_endpoint:, api_key:, api_version:)
13
22
  @api_endpoint = URI(api_endpoint)
14
23
  @api_key = api_key
24
+ @api_version = api_version
15
25
  end
16
26
 
17
27
  def get(path, params: {}, &block)
@@ -27,6 +37,8 @@ module Hachi
27
37
  url = url_for(path)
28
38
  url.query = URI.encode_www_form(params) unless params.empty?
29
39
 
40
+ json = to_camelback_keys(json.compact) if json.is_a?(Hash)
41
+
30
42
  post = Net::HTTP::Post.new(url)
31
43
  post.body = json.is_a?(Hash) ? json.to_json : json.to_s
32
44
 
@@ -40,6 +52,8 @@ module Hachi
40
52
  url = url_for(path)
41
53
  url.query = URI.encode_www_form(params) unless params.empty?
42
54
 
55
+ json = to_camelback_keys(json.compact) if json.is_a?(Hash)
56
+
43
57
  delete = Net::HTTP::Delete.new(url)
44
58
  delete.body = json.is_a?(Hash) ? json.to_json : json.to_s
45
59
 
@@ -51,6 +65,8 @@ module Hachi
51
65
  url = url_for(path)
52
66
  url.query = URI.encode_www_form(params) unless params.empty?
53
67
 
68
+ json = to_camelback_keys(json.compact) if json.is_a?(Hash)
69
+
54
70
  patch = Net::HTTP::Patch.new(url)
55
71
  patch.body = json.is_a?(Hash) ? json.to_json : json.to_s
56
72
 
@@ -63,7 +79,11 @@ module Hachi
63
79
  private
64
80
 
65
81
  def base_url
66
- "#{api_endpoint.scheme}://#{api_endpoint.hostname}:#{api_endpoint.port}"
82
+ if api_version.nil? || api_version.to_s.empty?
83
+ "#{api_endpoint.scheme}://#{api_endpoint.hostname}:#{api_endpoint.port}/api"
84
+ else
85
+ "#{api_endpoint.scheme}://#{api_endpoint.hostname}:#{api_endpoint.port}/api/#{api_version}"
86
+ end
67
87
  end
68
88
 
69
89
  def url_for(path)
@@ -73,13 +93,13 @@ module Hachi
73
93
  def https_options
74
94
  return nil if api_endpoint.scheme != "https"
75
95
 
76
- if proxy = ENV["HTTPS_PROXY"] || ENV["https_proxy"]
96
+ if proxy = ENV.fetch("HTTPS_PROXY") { ENV.fetch("https_proxy", nil) }
77
97
  uri = URI(proxy)
78
98
  {
79
99
  proxy_address: uri.hostname,
80
100
  proxy_port: uri.port,
81
101
  proxy_from_env: false,
82
- use_ssl: true,
102
+ use_ssl: true
83
103
  }
84
104
  else
85
105
  { use_ssl: true }
@@ -87,12 +107,12 @@ module Hachi
87
107
  end
88
108
 
89
109
  def http_options
90
- if proxy = ENV["HTTP_PROXY"] || ENV["http_proxy"]
110
+ if proxy = ENV.fetch("HTTP_PROXY") { ENV.fetch("http_proxy", nil) }
91
111
  uri = URI(proxy)
92
112
  {
93
113
  proxy_address: uri.hostname,
94
114
  proxy_port: uri.port,
95
- proxy_from_env: false,
115
+ proxy_from_env: false
96
116
  }
97
117
  else
98
118
  {}
@@ -119,26 +139,8 @@ module Hachi
119
139
  end
120
140
  end
121
141
 
122
- def validate_range(range)
123
- return true if range == "all"
124
- raise ArgumentError, "range should be 'all' or `from-to`" unless range.match?(/(\d+)-(\d+)/)
125
-
126
- from, to = range.split("-").map(&:to_i)
127
- return true if from < to
128
-
129
- raise ArgumentError, "from should be smaller than to"
130
- end
131
-
132
- def _search(path, query:, range: "all", sort: nil)
133
- validate_range range
134
-
135
- query_string = build_query_string(range: range, sort: sort)
136
-
137
- post("#{path}?#{query_string}", json: { query: query }) { |json| json }
138
- end
139
-
140
142
  def build_query_string(params)
141
- URI.encode_www_form(params.reject { |_k, v| v.nil? })
143
+ URI.encode_www_form(params.compact)
142
144
  end
143
145
  end
144
146
  end
@@ -3,15 +3,6 @@
3
3
  module Hachi
4
4
  module Clients
5
5
  class Case < Base
6
- #
7
- # List cases
8
- #
9
- # @return [Array]
10
- #
11
- def list
12
- get("/api/case") { |json| json }
13
- end
14
-
15
6
  #
16
7
  # Get a case
17
8
  #
@@ -20,7 +11,7 @@ module Hachi
20
11
  # @return [Hash]
21
12
  #
22
13
  def get_by_id(id)
23
- get("/api/case/#{id}") { |json| json }
14
+ get("/case/#{id}") { |json| json }
24
15
  end
25
16
 
26
17
  #
@@ -31,114 +22,30 @@ module Hachi
31
22
  # @return [String]
32
23
  #
33
24
  def delete_by_id(id)
34
- delete("/api/case/#{id}") { |json| json }
25
+ delete("/case/#{id}") { |json| json }
35
26
  end
36
27
 
37
28
  #
38
29
  # Create a case
39
30
  #
40
- # @param [String, nil] title
41
- # @param [String, nil] description
42
- # @param [Integer, nil] severity
43
- # @param [String, nil] start_date
44
- # @param [String, nil] owner
45
- # @param [Boolean, nil] flag
46
- # @param [Intege, nil] tlp
47
- # @param [String, nil] tags
48
- #
49
- # @return [Hash]
50
- #
51
- def create(title:, description:, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil)
52
- kase = Models::Case.new(
53
- title: title,
54
- description: description,
55
- severity: severity,
56
- start_date: start_date,
57
- owner: owner,
58
- flag: flag,
59
- tlp: tlp,
60
- tags: tags,
61
- )
62
-
63
- post("/api/case", json: kase.payload) { |json| json }
64
- end
65
-
66
- #
67
- # Find cases
68
- #
69
- # @param [Hash] query
70
- # @param [String] range
71
- #
72
- # @return [Hash]
73
- #
74
- def search(query, range: "all")
75
- _search("/api/case/_search", query: query, range: range) { |json| json }
76
- end
77
-
78
- #
79
- # Get list of cases linked to this case
80
- #
81
- # @param [String] id Case ID
82
- #
83
- # @return [Array]
84
- #
85
- def links(id)
86
- get("/api/case/#{id}/links") { |json| json }
87
- end
88
-
89
- #
90
- # Merge two cases
91
- #
92
- # @param [String] id1 Case ID
93
- # @param [String] id2 Case ID
31
+ # @param [Hash] payload
94
32
  #
95
33
  # @return [Hash]
96
34
  #
97
- def merge(id1, id2)
98
- post("/api/case/#{id1}/_merge/#{id2}") { |json| json }
35
+ def create(**payload)
36
+ post("/case", json: payload) { |json| json }
99
37
  end
100
38
 
101
39
  #
102
40
  # Update a case
103
41
  #
104
- # @param [String, nil] id
105
- # @param [String, nil] title
106
- # @param [String, nil] description
107
- # @param [String, nil] severity
108
- # @param [String, nil] start_date
109
- # @param [String, nil] owner
110
- # @param [Boolean, nil] flag
111
- # @param [Integer, nil] tlp
112
- # @param [String, nil] tags
113
- # @param [String, nil] status
114
- # @param [String, nil] resolution_status
115
- # @param [String, nil] impact_status
116
- # @param [String, nil] summary
117
- # @param [String, nil] end_date
118
- # @param [String, nil] metrics
119
- # @param [String, nil] custom_fields
42
+ # @param [String] id
43
+ # @param [Hash] payload
120
44
  #
121
45
  # @return [Hash]
122
46
  #
123
- def update(id, title: nil, description: nil, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil, status: nil, resolution_status: nil, impact_status: nil, summary: nil, end_date: nil, metrics: nil, custom_fields: nil )
124
- attributes = {
125
- title: title,
126
- description: description,
127
- severity: severity,
128
- startDate: start_date,
129
- owner: owner,
130
- flag: flag,
131
- tlp: tlp,
132
- tags: tags,
133
- status: status,
134
- resolutionStatus: resolution_status,
135
- impactStatus: impact_status,
136
- summary: summary,
137
- endDate: end_date,
138
- metrics: metrics,
139
- customFields: custom_fields
140
- }.compact
141
- patch("/api/case/#{id}", json: attributes) { |json| json }
47
+ def update(id, **payload)
48
+ patch("/case/#{id}", json: payload) { |json| json }
142
49
  end
143
50
  end
144
51
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hachi
4
+ module Clients
5
+ class Observable < Base
6
+ #
7
+ # Create an observable in a case
8
+ #
9
+ # @param [String] case_id Observable ID
10
+ # @param [Hash] payload
11
+ #
12
+ # @return [Hash]
13
+ #
14
+ def create_in_case(case_id, **payload)
15
+ post("/case/#{case_id}/observable", json: payload) { |json| json }
16
+ end
17
+
18
+ #
19
+ # Create an observable in an alert
20
+ #
21
+ # @param [String] alert_id Observable ID
22
+ # @param [Hash] payload
23
+ #
24
+ # @return [Hash]
25
+ #
26
+ def create_in_alert(alert_id, **payload)
27
+ post("/alert/#{alert_id}/observable", json: payload) { |json| json }
28
+ end
29
+
30
+ #
31
+ # Get an observable
32
+ #
33
+ # @param [String] id observable ID
34
+ #
35
+ # @return [Hash]
36
+ #
37
+ def get_by_id(id)
38
+ get("/observable/#{id}") { |json| json }
39
+ end
40
+
41
+ #
42
+ # Delete an observable
43
+ #
44
+ # @param [String] id observable ID
45
+ #
46
+ # @return [String]
47
+ #
48
+ def delete_by_id(id)
49
+ delete("/observable/#{id}") { |json| json }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hachi
4
+ module Clients
5
+ class Query < Base
6
+ #
7
+ # Query
8
+ #
9
+ # @param [Hash] payload
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ def query(**payload)
14
+ post("/query", json: payload) { |json| json }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -9,7 +9,7 @@ module Hachi
9
9
  # @return [Hash]
10
10
  #
11
11
  def current
12
- get("/api/user/current") { |json| json }
12
+ get("/user/current") { |json| json }
13
13
  end
14
14
 
15
15
  #
@@ -20,7 +20,7 @@ module Hachi
20
20
  # @return [Hash]
21
21
  #
22
22
  def get_by_id(id)
23
- get("/api/user/#{id}") { |json| json }
23
+ get("/user/#{id}") { |json| json }
24
24
  end
25
25
 
26
26
  #
@@ -31,28 +31,18 @@ module Hachi
31
31
  # @return [String]
32
32
  #
33
33
  def delete_by_id(id)
34
- delete("/api/user/#{id}") { |json| json }
34
+ delete("/user/#{id}") { |json| json }
35
35
  end
36
36
 
37
37
  #
38
38
  # Create a user
39
39
  #
40
- # @param [String] login
41
- # @param [String] name
42
- # @param [Array<String>] roles
43
- # @param [String] password
40
+ # @param [Hash] payload
44
41
  #
45
42
  # @return [Hash]
46
43
  #
47
- def create(login:, name:, roles:, password:)
48
- user = Models::User.new(
49
- login: login,
50
- name: name,
51
- roles: roles,
52
- password: password
53
- )
54
-
55
- post("/api/user", json: user.payload) { |json| json }
44
+ def create(**payload)
45
+ post("/user", json: payload) { |json| json }
56
46
  end
57
47
  end
58
48
  end
data/lib/hachi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hachi
4
- VERSION = "1.0.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/hachi.rb CHANGED
@@ -2,20 +2,29 @@
2
2
 
3
3
  require "hachi/version"
4
4
 
5
- require "hachi/api"
5
+ require "hachi/awrence/methods"
6
6
 
7
- require "hachi/models/base"
8
- require "hachi/models/alert"
9
- require "hachi/models/artifact"
10
- require "hachi/models/case"
11
- require "hachi/models/user"
7
+ require "hachi/api"
12
8
 
13
9
  require "hachi/clients/base"
10
+
14
11
  require "hachi/clients/alert"
15
12
  require "hachi/clients/artifact"
16
13
  require "hachi/clients/case"
14
+ require "hachi/clients/observable"
15
+ require "hachi/clients/query"
17
16
  require "hachi/clients/user"
18
17
 
19
18
  module Hachi
19
+ module Awrence
20
+ class << self
21
+ attr_writer :acronyms
22
+
23
+ def acronyms
24
+ @acronyms ||= {}
25
+ end
26
+ end
27
+ end
28
+
20
29
  class Error < StandardError; end
21
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hachi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manabu Niseki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-22 00:00:00.000000000 Z
11
+ date: 2022-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.2'
19
+ version: '2.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.2'
26
+ version: '2.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: coveralls
28
+ name: coveralls_reborn
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.8'
33
+ version: '0.24'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.8'
40
+ version: '0.24'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.7'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -58,42 +72,56 @@ dependencies:
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '3.10'
75
+ version: '3.11'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov-lcov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.0
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: '3.10'
96
+ version: 0.8.0
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: vcr
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
101
  - - "~>"
74
102
  - !ruby/object:Gem::Version
75
- version: '6.0'
103
+ version: '6.1'
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: '6.0'
110
+ version: '6.1'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: webmock
85
113
  requirement: !ruby/object:Gem::Requirement
86
114
  requirements:
87
115
  - - "~>"
88
116
  - !ruby/object:Gem::Version
89
- version: '3.12'
117
+ version: '3.14'
90
118
  type: :development
91
119
  prerelease: false
92
120
  version_requirements: !ruby/object:Gem::Requirement
93
121
  requirements:
94
122
  - - "~>"
95
123
  - !ruby/object:Gem::Version
96
- version: '3.12'
124
+ version: '3.14'
97
125
  description: A dead simple TheHive API wrapper.
98
126
  email:
99
127
  - manabu.niseki@gmail.com
@@ -113,22 +141,16 @@ files:
113
141
  - hachi.gemspec
114
142
  - lib/hachi.rb
115
143
  - lib/hachi/api.rb
144
+ - lib/hachi/awrence/methods.rb
116
145
  - lib/hachi/clients/alert.rb
117
146
  - lib/hachi/clients/artifact.rb
118
147
  - lib/hachi/clients/base.rb
119
148
  - lib/hachi/clients/case.rb
149
+ - lib/hachi/clients/observable.rb
150
+ - lib/hachi/clients/query.rb
120
151
  - lib/hachi/clients/user.rb
121
- - lib/hachi/models/alert.rb
122
- - lib/hachi/models/artifact.rb
123
- - lib/hachi/models/base.rb
124
- - lib/hachi/models/case.rb
125
- - lib/hachi/models/user.rb
126
152
  - lib/hachi/version.rb
127
153
  - renovate.json
128
- - samples/01_create_an_alert.rb
129
- - samples/02_search_artifacts.rb
130
- - samples/03_list_cases.rb
131
- - samples/04_merge_alerts.rb
132
154
  homepage: https://github.com/ninoseki/hachi
133
155
  licenses:
134
156
  - MIT
@@ -148,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
170
  - !ruby/object:Gem::Version
149
171
  version: '0'
150
172
  requirements: []
151
- rubygems_version: 3.2.3
173
+ rubygems_version: 3.2.14
152
174
  signing_key:
153
175
  specification_version: 4
154
176
  summary: A dead simple TheHive API wrapper.
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "date"
4
- require "securerandom"
5
-
6
- module Hachi
7
- module Models
8
- class Alert < Base
9
- attr_reader :title, :description, :severity, :date, :tags, :tlp, :status, :type, :source, :source_ref, :artifacts, :follow
10
-
11
- def initialize(title:, description:, type:, source:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil, source_ref: nil, artifacts: nil, follow: nil)
12
- @title = title
13
- @description = description
14
- @severity = severity
15
- @date = date
16
- @tags = tags
17
- @tlp = tlp
18
- @status = status
19
- @type = type
20
- @source = source
21
- @source_ref = source_ref || SecureRandom.hex(10)
22
- @artifacts = artifacts.nil? ? nil : artifacts.map { |a| Artifact.new(**a) }
23
- @follow = follow
24
-
25
- validate_date if date
26
- validate_severity if severity
27
- validate_status if status
28
- validate_tlp if tlp
29
- validate_artifacts if artifacts
30
- end
31
-
32
- def payload
33
- {
34
- title: title,
35
- description: description,
36
- severity: severity,
37
- date: date,
38
- tags: tags,
39
- tlp: tlp,
40
- status: status,
41
- type: type,
42
- source: source,
43
- sourceRef: source_ref,
44
- artifacts: artifacts&.map(&:payload),
45
- follow: follow
46
- }.compact
47
- end
48
-
49
- private
50
-
51
- def validate_date
52
- DateTime.parse(date)
53
- true
54
- rescue ArgumentError => _e
55
- raise ArgumentError, "date should be Date format"
56
- end
57
-
58
- def validate_artifacts
59
- artifacts.each(&:validate_for_creation)
60
- end
61
- end
62
- end
63
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hachi
4
- module Models
5
- class Artifact < Base
6
- DATA_TYPES = %w(filename file fqdn hash uri_path ip domain mail autonomous-system registry mail_subject regexp user-agent other url).freeze
7
-
8
- attr_reader :data, :data_type, :message, :tlp, :tags
9
-
10
- def initialize(data:, data_type:, message: nil, tlp: nil, tags: nil)
11
- @data = data
12
- @data_type = data_type
13
- @message = message
14
- @tlp = tlp
15
- @tags = tags
16
-
17
- raise(ArgumentError, "data is required") unless data
18
- raise(ArgumentError, "data_type is required") unless data_type
19
- raise(ArgumentError, "invalid data type") unless DATA_TYPES.include?(data_type)
20
-
21
- validate_tags if tags
22
- validate_tlp if tlp
23
- end
24
-
25
- def payload
26
- {
27
- data: data,
28
- dataType: data_type,
29
- message: message,
30
- tlp: tlp,
31
- tags: tags
32
- }.compact
33
- end
34
-
35
- def validate_for_creation
36
- raise(ArgumentError, "message or tags is requried for artifact creation") unless message || tags
37
- end
38
- end
39
- end
40
- end