hachi 0.3.1 → 2.0.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: e065fd460cb9324e124aef7c1f779c2c4d2eb4368248be8b64de1ccff687dc99
4
- data.tar.gz: ee69559c7d0d3d2e494bf8fc64fc743b60404f15701f8ad2acfec1fb7d629e9f
3
+ metadata.gz: a88c7c477df9e4dbd8566ff3464424dbd994f9d976f9e235b6c1244200d32fd7
4
+ data.tar.gz: 1729ececbdb34f98abff15b7d80c189729d10d840f199876c87dc8eb22411581
5
5
  SHA512:
6
- metadata.gz: efd71a3a72134fd70f2caf17b6e835212972640b7535b294c26224ce944ac77e5d7866889b78b3836449cbd04b6169203af76da167443524ca4dd4397974fce7
7
- data.tar.gz: 0b052f52652fb095bd1786c2394439f774e876d48175628a1e701a32debe487b65ece8881d24cdea3b00231d811ef8b80817507c12901dfe58c1dcb2df57e157
6
+ metadata.gz: 40517bf2830268e88af14dc70ffc1af9f1e7c32d509e53883b6e125b48b7e689b81f628eb6225f2b76c517479ab19efb721ccea0b24b8bf9df11b598796aeb4d
7
+ data.tar.gz: e334655c99088a1038618b85a03f068fe3c459bd25f5eb179cc6a69bfb654aed185893e0d139ea270854cb87e80e6c62120c70a8f7e1a2f79e4325207b66c0be
@@ -0,0 +1,23 @@
1
+ name: Ruby CI
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby: [2.7, "3.0", 3.1]
13
+
14
+ steps:
15
+ - uses: actions/checkout@v3
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ bundler-cache: true
21
+ - name: Test with Rake
22
+ run: |
23
+ bundle exec rake
data/.gitignore CHANGED
@@ -11,7 +11,7 @@
11
11
  /tmp/
12
12
 
13
13
  # Used by dotenv library to load environment variables.
14
- # .env
14
+ .env
15
15
 
16
16
  ## Specific to RubyMotion:
17
17
  .dat*
@@ -51,3 +51,7 @@ Gemfile.lock
51
51
 
52
52
  # RSpec
53
53
  .rspec_status
54
+
55
+ # Docker
56
+ Dockerfile
57
+ docker-compose.yml
data/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # Hachi
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/hachi.svg)](https://badge.fury.io/rb/hachi)
4
- [![Build Status](https://travis-ci.com/ninoseki/hachi.svg?branch=master)](https://travis-ci.com/ninoseki/hachi)
4
+ [![Ruby CI](https://github.com/ninoseki/hachi/actions/workflows/test.yml/badge.svg)](https://github.com/ninoseki/hachi/actions/workflows/test.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/ninoseki/hachi/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/hachi?branch=master)
6
6
  [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/hachi/badge)](https://www.codefactor.io/repository/github/ninoseki/hachi)
7
7
 
8
8
  Hachi(`蜂`) is a dead simple [TheHive](https://github.com/TheHive-Project/TheHive) API wrapper for Ruby.
9
9
 
10
+ **Note**: This library supports TheHive v4 & v5.
11
+
10
12
  ## Installation
11
13
 
12
14
  ```bash
@@ -15,89 +17,23 @@ gem install hachi
15
17
 
16
18
  ## Usage
17
19
 
20
+ Hachi tries to load API settings from `ENV` by default. Or you can set them manually.
21
+
22
+ | Name | Default | Desc. |
23
+ |--------------|-----------------------------|-------------------------------------------------|
24
+ | api_key | ENV["THEHIVE_API_KEY"] | TheHive API key |
25
+ | api_endpoint | ENV["THEHIVE_API_ENDPOINT"] | TheHive API endpoint |
26
+ | api_version | ENV["THEHIVE_API_VERSION"] | TheHive API version (`nil` for v4, `v1` for v5) |
27
+
18
28
  ```ruby
19
29
  require "hachi"
20
30
 
21
- # when given nothing, it tries to load your API key from ENV["THEHIVE_API_KEY"] & API endpoint from ENV["THEHIVE_API_ENDPOINT"]
31
+ # when given nothing, it tries to load your API key from ENV
22
32
  api = Hachi::API.new
23
33
  # or you can set them manually
24
34
  api = Hachi::API.new(api_endpoint: "http://your_api_endpoint", api_key: "yoru_api_key")
25
-
26
- # list alerts
27
- api.alert.list
28
-
29
- # search atrifacts
30
- api.artifact.search(data: "1.1.1.1", data_type: "ip")
31
- # you can do a bulk search by giving an array as an input
32
- api.artifact.search(data: %w(1.1.1.1 8.8.8.8 github.com))
33
35
  ```
34
36
 
35
- See `samples` for more.
36
-
37
- ## Implemented methods
38
-
39
- ### Alert
40
-
41
- | HTTP Method | URI | Action | API method |
42
- |-------------|-----------------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
43
- | GET | /api/alert | List alerts | `#api.alert.list` |
44
- | POST | /api/alert/_search | Find alerts | `#api.alert.search(attributes, range: "all")` |
45
- | PATCH | /api/alert/_bulk | Update alerts in bulk | N/A |
46
- | POST | /api/alert/_stats | Compute stats on alerts | N/A |
47
- | POST | /api/alert | Create an alert | `#api.alert.create(title:, description:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil, type:, source:, source_ref: nil, artifacts: nil, follow: nil)` |
48
- | GET | /api/alert/:alertId | Get an alert | `#api.alert.get_by_id(id)` |
49
- | PATCH | /api/alert/:alertId | Update an alert | `#api.alert.update(id, title:, description:, severity: nil, tags: nil, tlp: nil, artifacts: nil)` |
50
- | DELETE | /api/alert/:alertId | Delete an alert | `#api.alert.delete_by_id(id)` |
51
- | POST | /api/alert/:alertId/markAsRead | Mark an alert as read | `#api.alert.mark_as_read(id)` |
52
- | POST | /api/alert/:alertId/markAsUnread | Mark an alert as unread | `#api.alert.mark_as_unread(id)` |
53
- | POST | /api/alert/:alertId/createCase | Create a case from an alert | `#api.alert.promote_to_case(id)` |
54
- | POST | /api/alert/:alertId/follow | Follow an alert | N/A |
55
- | POST | /api/alert/:alertId/unfollow | Unfollow an alert | N/A |
56
- | POST | /api/alert/:alertId/merge/:caseId | Merge an alert in a case | `#api.alert.merge_into_case(*ids, case_id)` |
57
-
58
- ### Artifact(Observable)
59
-
60
- | HTTP Method | URI | Action | API method |
61
- |-------------|----------------------------------------|---------------------------------|---------------------------------------------------------------------------------------|
62
- | POST | /api/case/artifact/_search | Find observables | `#api.artifact.search(attributes, range: "all")` |
63
- | POST | /api/case/artifact/_stats | Compute stats on observables | N/A |
64
- | POST | /api/case/:caseId/artifact | Create an observable | `#api.artifact.create(case_id, data:, data_type:, message: nil, tlp: nil, tags: nil)` |
65
- | GET | /api/case/artifact/:artifactId | Get an observable | `#api.artifact.get_by_id(id)` |
66
- | DELETE | /api/case/artifact/:artifactId | Remove an observable | `#api.artifact.delete_by_id(id)` |
67
- | PATCH | /api/case/artifact/:artifactId | Update an observable | N/A |
68
- | GET | /api/case/artifact/:artifactId/similar | Get list of similar observables | `#api.artifact.similar(id)` |
69
- | PATCH | /api/case/artifact/_bulk | Update observables in bulk | N/A |
70
-
71
- ### Case
72
-
73
- | HTTP Method | URI | Action | API method |
74
- |-------------|------------------------------------|---------------------------------------|----------------------------------------------------------------------------------------------------------------------|
75
- | GET | /api/case | List cases | `#api.case.list` |
76
- | POST | /api/case/_search | Find cases | `#api.case.search(attributes, range: "all")` |
77
- | PATCH | /api/case/_bulk | Update cases in bulk | N/A |
78
- | POST | /api/case/_stats | Compute stats on cases | N/A |
79
- | POST | /api/case | Create a case | `#api.case.create(title:, description:, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil)` |
80
- | GET | /api/case/:caseId | Get a case | `#api.case.get_by_id(id)` |
81
- | PATCH | /api/case/:caseId | Update a case | N/A |
82
- | DELETE | /api/case/:caseId | Remove a case | `#api.case.delete_by_id(id)` |
83
- | GET | /api/case/:caseId/links | Get list of cases linked to this case | `#api.case.links(id)` |
84
- | POST | /api/case/:caseId1/_merge/:caseId2 | Merge two cases | `#api.case.merge(id1, id2)` |
85
-
86
- ### User
87
-
88
- | HTTP Method | URI | Action | API method |
89
- |-------------|-----------------------------------|---------------------|------------------------------------------------------|
90
- | GET | /api/logout | Logout | N/A |
91
- | POST | /api/login | User login | N/A |
92
- | GET | /api/user/current | Get current user | `#api.user.current` |
93
- | POST | /api/user/_search | Find user | N/A |
94
- | POST | /api/user | Create a user | `#api.user.create(login:, name:, roles:, password:)` |
95
- | GET | /api/user/:userId | Get a user | `#api.user.get_by_id(id)` |
96
- | DELETE | /api/user/:userId | Delete a case | `#api.user.delete_by_id(id)` |
97
- | PATCH | /api/user/:userId | Update user details | N/A |
98
- | POST | /api/user/:userId/password/set | Set password | N/A |
99
- | POST | /api/user/:userId/password/change | Change password | N/A |
100
-
101
37
  ## License
102
38
 
103
39
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
data/hachi.gemspec CHANGED
@@ -24,10 +24,12 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ["lib"]
26
26
 
27
- spec.add_development_dependency "bundler", "~> 2.0"
28
- spec.add_development_dependency "coveralls", "~> 0.8"
27
+ spec.add_development_dependency "bundler", "~> 2.3"
28
+ spec.add_development_dependency "coveralls_reborn", "~> 0.24"
29
+ spec.add_development_dependency "dotenv", "~> 2.7"
29
30
  spec.add_development_dependency "rake", "~> 13.0"
30
- spec.add_development_dependency "rspec", "~> 3.9"
31
- spec.add_development_dependency "vcr", "~> 5.0"
32
- spec.add_development_dependency "webmock", "~> 3.7"
31
+ spec.add_development_dependency "rspec", "~> 3.11"
32
+ spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
33
+ spec.add_development_dependency "vcr", "~> 6.1"
34
+ spec.add_development_dependency "webmock", "~> 3.14"
33
35
  end
data/lib/hachi/api.rb CHANGED
@@ -1,34 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Hachi
4
6
  class API
7
+ extend Forwardable
8
+
5
9
  # @return [String] TheHive API endpoint
6
10
  attr_reader :api_endpoint
7
11
 
8
12
  # @return [String] TheHive API key
9
13
  attr_reader :api_key
10
14
 
15
+ # @return [String, nil] TheHive API version
16
+ attr_reader :api_version
17
+
11
18
  #
12
19
  # @param [String, nil] api_endpoint TheHive API endpoint
13
20
  # @param [String, nil] api_key TheHive API key
21
+ # @param [String, nil] api_version TheHive API version
14
22
  #
15
23
  # @raise [ArgumentError] When given or an empty endpoint or key
16
24
  #
17
- def initialize(api_endpoint: ENV["THEHIVE_API_ENDPOINT"], api_key: ENV["THEHIVE_API_KEY"])
25
+ def initialize(api_endpoint: ENV.fetch("THEHIVE_API_ENDPOINT", nil), api_key: ENV.fetch("THEHIVE_API_KEY", nil), api_version: ENV.fetch("THEHIVE_API_VERSION", nil))
18
26
  @api_endpoint = api_endpoint
19
27
  raise ArgumentError, "api_endpoint argument is required" unless api_endpoint
20
28
 
21
29
  @api_key = api_key
22
30
  raise ArgumentError, "api_key argument is required" unless api_key
31
+
32
+ @api_version = api_version
33
+
34
+ @base = Clients::Base.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
23
35
  end
24
36
 
37
+ def_delegators :@base, :get, :post, :delete, :push
38
+
25
39
  #
26
40
  # Alert API endpoint client
27
41
  #
28
42
  # @return [Clients::Alert]
29
43
  #
30
44
  def alert
31
- @alert ||= Clients::Alert.new(api_endpoint: api_endpoint, api_key: api_key)
45
+ @alert ||= Clients::Alert.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
32
46
  end
33
47
 
34
48
  #
@@ -37,7 +51,7 @@ module Hachi
37
51
  # @return [Clients::Artifact]
38
52
  #
39
53
  def artifact
40
- @artifact ||= Clients::Artifact.new(api_endpoint: api_endpoint, api_key: api_key)
54
+ @artifact ||= Clients::Artifact.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
41
55
  end
42
56
 
43
57
  #
@@ -46,7 +60,7 @@ module Hachi
46
60
  # @return [Clients::Case]
47
61
  #
48
62
  def case
49
- @case ||= Clients::Case.new(api_endpoint: api_endpoint, api_key: api_key)
63
+ @case ||= Clients::Case.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
50
64
  end
51
65
 
52
66
  #
@@ -55,7 +69,25 @@ module Hachi
55
69
  # @return [Clients::User]
56
70
  #
57
71
  def user
58
- @user ||= Clients::User.new(api_endpoint: api_endpoint, api_key: api_key)
72
+ @user ||= Clients::User.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
73
+ end
74
+
75
+ #
76
+ # Observable API endpoint client
77
+ #
78
+ # @return [Clients::Observable]
79
+ #
80
+ def observable
81
+ @observable ||= Clients::Observable.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
82
+ end
83
+
84
+ #
85
+ # Query API endpoint client
86
+ #
87
+ # @return [Clients::Query]
88
+ #
89
+ def query
90
+ @query ||= Clients::Query.new(api_endpoint: api_endpoint, api_key: api_key, api_version: api_version)
59
91
  end
60
92
  end
61
93
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # forked from https://github.com/technicalpanda/awrence
4
+ module Hachi
5
+ module Awrence
6
+ module Methods
7
+ # Recursively converts Rubyish snake_case hash keys to camelBack JSON-style
8
+ # hash keys suitable for use with a JSON API.
9
+ #
10
+ def to_camelback_keys(value = self)
11
+ process_value(:to_camelback_keys, value, first_upper: false)
12
+ end
13
+
14
+ # Recursively converts Rubyish snake_case hash keys to CamelCase JSON-style
15
+ # hash keys suitable for use with a JSON API.
16
+ #
17
+ def to_camel_keys(value = self)
18
+ process_value(:to_camel_keys, value, first_upper: true)
19
+ end
20
+
21
+ private
22
+
23
+ def camelize_key(key, first_upper: true)
24
+ case key
25
+ when Symbol
26
+ camelize(key.to_s, first_upper: first_upper).to_sym
27
+ when String
28
+ camelize(key, first_upper: first_upper)
29
+ else
30
+ key # Awrence can't camelize anything except strings and symbols
31
+ end
32
+ end
33
+
34
+ def camelize(snake_word, first_upper: true)
35
+ return snake_word if snake_word.start_with? "_"
36
+
37
+ if first_upper
38
+ str = snake_word.to_s
39
+ str = gsubbed(str, /(?:^|_)([^_\s]+)/)
40
+ gsubbed(str, %r{/([^/]*)}, "::")
41
+ else
42
+ parts = snake_word.split("_", 2)
43
+ parts[0] << camelize(parts[1]) if parts.size > 1
44
+ parts[0] || ""
45
+ end
46
+ end
47
+
48
+ def gsubbed(str, pattern, extra = "")
49
+ str.gsub(pattern) do
50
+ extra + (Hachi::Awrence.acronyms[Regexp.last_match(1)] || Regexp.last_match(1).capitalize)
51
+ end
52
+ end
53
+
54
+ def process_value(method, value, first_upper: true)
55
+ case value
56
+ when Array
57
+ value.map { |v| send(method, v) }
58
+ when Hash
59
+ value.to_h { |k, v| [camelize_key(k, first_upper: first_upper), send(method, v)] }
60
+ else
61
+ value
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -6,15 +6,6 @@ require "securerandom"
6
6
  module Hachi
7
7
  module Clients
8
8
  class Alert < Base
9
- #
10
- # List alerts
11
- #
12
- # @return [Array]
13
- #
14
- def list
15
- get("/api/alert") { |json| json }
16
- end
17
-
18
9
  #
19
10
  # Get an alert
20
11
  #
@@ -23,7 +14,7 @@ module Hachi
23
14
  # @return [Hash]
24
15
  #
25
16
  def get_by_id(id)
26
- get("/api/alert/#{id}") { |json| json }
17
+ get("/alert/#{id}") { |json| json }
27
18
  end
28
19
 
29
20
  #
@@ -34,130 +25,29 @@ module Hachi
34
25
  # @return [String]
35
26
  #
36
27
  def delete_by_id(id)
37
- delete("/api/alert/#{id}") { |json| json }
28
+ delete("/alert/#{id}") { |json| json }
38
29
  end
39
30
 
40
31
  #
41
32
  # Create an alert
42
33
  #
43
- # @param [String] title
44
- # @param [String] description
45
- # @param [String, nil] severity
46
- # @param [String, nil] date
47
- # @param [String, nil] tags
48
- # @param [String, nil] tlp
49
- # @param [String, nil] status
50
- # @param [String, nil] type
51
- # @param [String, nil] source
52
- # @param [String, nil] source_ref
53
- # @param [String, nil] artifacts
54
- # @param [String, nil] follow
55
- #
56
- # @return [Hash]
57
- #
58
- def create(title:, description:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil, type:, source:, source_ref: nil, artifacts: nil, follow: nil)
59
- alert = Models::Alert.new(
60
- title: title,
61
- description: description,
62
- severity: severity,
63
- date: date,
64
- tags: tags,
65
- tlp: tlp,
66
- status: status,
67
- type: type,
68
- source: source,
69
- source_ref: source_ref,
70
- artifacts: artifacts,
71
- follow: follow,
72
- )
73
- post("/api/alert", alert.payload) { |json| json }
74
- end
75
-
76
- #
77
- # Find alerts
78
- #
79
- # @param [Hash] attributes
80
- # @param [String] range
81
- # @param [String, nil] sort
82
- #
83
- # @return [Array]
84
- #
85
- def search(attributes, range: "all", sort: nil)
86
- _search("/api/alert/_search", attributes: attributes, range: range, sort: sort) { |json| json }
87
- end
88
-
89
- #
90
- # Mark an alert as read
91
- #
92
- # @param [String] id Alert ID
93
- #
94
- # @return [Hash]
95
- #
96
- def mark_as_read(id)
97
- post("/api/alert/#{id}/markAsRead") { |json| json }
98
- end
99
-
100
- #
101
- # Mark an alert as unread
102
- #
103
- # @param [String] id Alert ID
104
- #
105
- # @return [Hash] hash
106
- #
107
- def mark_as_unread(id)
108
- post("/api/alert/#{id}/markAsUnread") { |json| json }
109
- end
110
-
111
- #
112
- # Create a case from an alert
113
- #
114
- # @param [String] id Alert ID
115
- #
116
- # @return [Hash]
117
- #
118
- def promote_to_case(id)
119
- post("/api/alert/#{id}/createCase") { |json| json }
120
- end
121
-
122
- #
123
- # Merge an alert / alerts in a case
124
- #
125
- # @param [String, Array] *ids Alert ID(s)
126
- # @param [String] case_id Case ID
34
+ # @param [Hash] payload
127
35
  #
128
36
  # @return [Hash]
129
37
  #
130
- def merge_into_case(*ids, case_id)
131
- params = {
132
- alertIds: ids.flatten,
133
- caseId: case_id
134
- }
135
- post("/api/alert/merge/_bulk", params) { |json| json }
38
+ def create(**payload)
39
+ post("/alert", json: payload) { |json| json }
136
40
  end
137
41
 
138
- #
139
42
  # Update an alert
140
43
  #
141
- # @param [String, nil] id
142
- # @param [String, nil] title
143
- # @param [String, nil] description
144
- # @param [String, nil] severity
145
- # @param [String, nil] tags
146
- # @param [String, nil] tlp
147
- # @param [String, nil] artifacts
44
+ # @param [String] id
45
+ # @param [Hash] payload
148
46
  #
149
47
  # @return [Hash]
150
48
  #
151
- def update(id, title: nil, description: nil, severity: nil, tags: nil, tlp: nil, artifacts: nil)
152
- attributes = {
153
- title: title,
154
- description: description,
155
- severity: severity,
156
- tags: tags,
157
- tlp: tlp,
158
- artifacts: artifacts,
159
- }.compact
160
- patch("/api/alert/#{id}", attributes) { |json| json }
49
+ def update(id, **payload)
50
+ patch("/alert/#{id}", json: payload) { |json| json }
161
51
  end
162
52
  end
163
53
  end
@@ -7,24 +7,12 @@ module Hachi
7
7
  # Create an artifact
8
8
  #
9
9
  # @param [String] case_id Artifact ID
10
- # @param [String] data
11
- # @param [String] data_type
12
- # @param [String, nil] message
13
- # @param [Integer, nil] tlp
14
- # @param [Array<String>, nil] tags
10
+ # @param [Hash] payload
15
11
  #
16
12
  # @return [Hash]
17
13
  #
18
- def create(case_id, data:, data_type:, message: nil, tlp: nil, tags: nil)
19
- artifact = Models::Artifact.new(
20
- data: data,
21
- data_type: data_type,
22
- message: message,
23
- tlp: tlp,
24
- tags: tags,
25
- )
26
-
27
- post("/api/case/#{case_id}/artifact", artifact.payload) { |json| json }
14
+ def create(case_id, **payload)
15
+ post("/api/case/#{case_id}/artifact", json: payload) { |json| json }
28
16
  end
29
17
 
30
18
  #
@@ -48,29 +36,6 @@ module Hachi
48
36
  def delete_by_id(id)
49
37
  delete("/api/case/artifact/#{id}") { |json| json }
50
38
  end
51
-
52
- #
53
- # Find artifacts
54
- #
55
- # @param [Hash] attributes
56
- # @param [String] range
57
- #
58
- # @return [Array]
59
- #
60
- def search(attributes, range: "all")
61
- _search("/api/case/artifact/_search", attributes: attributes, range: range) { |json| json }
62
- end
63
-
64
- #
65
- # Get list of similar observables
66
- #
67
- # @param [String] id Artifact ID
68
- #
69
- # @return [Array]
70
- #
71
- def similar(id)
72
- get("/api/case/artifact/#{id}/similar") { |json| json }
73
- end
74
39
  end
75
40
  end
76
41
  end