hachi 1.0.0 → 2.0.0

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