hachi 0.2.3 → 1.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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +26 -0
- data/README.md +39 -14
- data/Rakefile +1 -1
- data/hachi.gemspec +5 -5
- data/lib/hachi.rb +2 -0
- data/lib/hachi/api.rb +59 -8
- data/lib/hachi/clients/alert.rb +124 -4
- data/lib/hachi/clients/artifact.rb +44 -3
- data/lib/hachi/clients/base.rb +53 -70
- data/lib/hachi/clients/case.rb +106 -3
- data/lib/hachi/clients/user.rb +59 -0
- data/lib/hachi/models/alert.rb +3 -14
- data/lib/hachi/models/artifact.rb +1 -5
- data/lib/hachi/models/case.rb +1 -8
- data/lib/hachi/models/user.rb +36 -0
- data/lib/hachi/version.rb +1 -1
- data/renovate.json +5 -0
- data/samples/04_merge_alerts.rb +17 -0
- metadata +21 -17
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d03f917e4b7693f16612d130fb9808df41a4f66f962d757ec8e2dbedd6f039e1
|
4
|
+
data.tar.gz: 76ee4ec9ba69e732b17c7bff865fa5aaa5a251339b39b7daacc4e104000f99a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9281deed1829c4efe3ea82ed2ecafa616535cb4038867ab9ef480e28a328b39ad66c270503b5ef40d80b18c99dce849231aaa31e6c59a24ec5073dfebf9c103b
|
7
|
+
data.tar.gz: 637a3945b65862633bdfeeb1723f4179bd7a61ae989cc725b4427d94257dc5aa71c972100110afdadd61717294b7001957349ffb90a1978dcce70060186f82e6
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Ruby CI
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
strategy:
|
11
|
+
fail-fast: false
|
12
|
+
matrix:
|
13
|
+
ruby: [2.7, '3.0']
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby }}
|
21
|
+
bundler-cache: true
|
22
|
+
- name: Build and test with Rake
|
23
|
+
run: |
|
24
|
+
gem install bundler
|
25
|
+
bundle install
|
26
|
+
bundle exec rake
|
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# Hachi
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/hachi)
|
4
|
-
[](https://github.com/ninoseki/hachi/actions/workflows/test.yml)
|
5
5
|
[](https://coveralls.io/github/ninoseki/hachi?branch=master)
|
6
6
|
[](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.
|
11
|
+
|
10
12
|
## Installation
|
11
13
|
|
12
14
|
```bash
|
@@ -26,10 +28,9 @@ api = Hachi::API.new(api_endpoint: "http://your_api_endpoint", api_key: "yoru_ap
|
|
26
28
|
# list alerts
|
27
29
|
api.alert.list
|
28
30
|
|
29
|
-
# search
|
30
|
-
|
31
|
-
|
32
|
-
api.artifact.search(data: %w(1.1.1.1 8.8.8.8 github.com))
|
31
|
+
# search artifacts
|
32
|
+
query = { "_and": [{ "_or": [{ "_field": "data", "_value": "1.1.1.1" }, { "_field": "data", "_value": "example.com" }] }] }
|
33
|
+
api.artifact.search(query)
|
33
34
|
```
|
34
35
|
|
35
36
|
See `samples` for more.
|
@@ -41,25 +42,25 @@ See `samples` for more.
|
|
41
42
|
| HTTP Method | URI | Action | API method |
|
42
43
|
|-------------|-----------------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
43
44
|
| GET | /api/alert | List alerts | `#api.alert.list` |
|
44
|
-
| POST | /api/alert/_search | Find alerts | `#api.alert.search(
|
45
|
+
| POST | /api/alert/_search | Find alerts | `#api.alert.search(query, range: "all")` |
|
45
46
|
| PATCH | /api/alert/_bulk | Update alerts in bulk | N/A |
|
46
47
|
| POST | /api/alert/_stats | Compute stats on alerts | N/A |
|
47
48
|
| 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
49
|
| GET | /api/alert/:alertId | Get an alert | `#api.alert.get_by_id(id)` |
|
49
|
-
| PATCH | /api/alert/:alertId | Update an alert |
|
50
|
+
| PATCH | /api/alert/:alertId | Update an alert | `#api.alert.update(id, title:, description:, severity: nil, tags: nil, tlp: nil, artifacts: nil)` |
|
50
51
|
| DELETE | /api/alert/:alertId | Delete an alert | `#api.alert.delete_by_id(id)` |
|
51
|
-
| POST | /api/alert/:alertId/markAsRead | Mark an alert as read |
|
52
|
-
| POST | /api/alert/:alertId/markAsUnread | Mark an alert as unread |
|
53
|
-
| POST | /api/alert/:alertId/createCase | Create a case from an alert |
|
52
|
+
| POST | /api/alert/:alertId/markAsRead | Mark an alert as read | `#api.alert.mark_as_read(id)` |
|
53
|
+
| POST | /api/alert/:alertId/markAsUnread | Mark an alert as unread | `#api.alert.mark_as_unread(id)` |
|
54
|
+
| POST | /api/alert/:alertId/createCase | Create a case from an alert | `#api.alert.promote_to_case(id)` |
|
54
55
|
| POST | /api/alert/:alertId/follow | Follow an alert | N/A |
|
55
56
|
| POST | /api/alert/:alertId/unfollow | Unfollow an alert | N/A |
|
56
|
-
| POST | /api/alert/:alertId/merge/:caseId | Merge an alert in a case |
|
57
|
+
| POST | /api/alert/:alertId/merge/:caseId | Merge an alert in a case | `#api.alert.merge_into_case(*ids, case_id)` |
|
57
58
|
|
58
59
|
### Artifact(Observable)
|
59
60
|
|
60
61
|
| HTTP Method | URI | Action | API method |
|
61
62
|
|-------------|----------------------------------------|---------------------------------|---------------------------------------------------------------------------------------|
|
62
|
-
| POST | /api/case/artifact/_search | Find observables | `#api.artifact.search(
|
63
|
+
| POST | /api/case/artifact/_search | Find observables | `#api.artifact.search(query, range: "all")` |
|
63
64
|
| POST | /api/case/artifact/_stats | Compute stats on observables | N/A |
|
64
65
|
| POST | /api/case/:caseId/artifact | Create an observable | `#api.artifact.create(case_id, data:, data_type:, message: nil, tlp: nil, tags: nil)` |
|
65
66
|
| GET | /api/case/artifact/:artifactId | Get an observable | `#api.artifact.get_by_id(id)` |
|
@@ -73,7 +74,7 @@ See `samples` for more.
|
|
73
74
|
| HTTP Method | URI | Action | API method |
|
74
75
|
|-------------|------------------------------------|---------------------------------------|----------------------------------------------------------------------------------------------------------------------|
|
75
76
|
| GET | /api/case | List cases | `#api.case.list` |
|
76
|
-
| POST | /api/case/_search | Find cases | `#api.case.search(
|
77
|
+
| POST | /api/case/_search | Find cases | `#api.case.search(query, range: "all")` |
|
77
78
|
| PATCH | /api/case/_bulk | Update cases in bulk | N/A |
|
78
79
|
| POST | /api/case/_stats | Compute stats on cases | N/A |
|
79
80
|
| POST | /api/case | Create a case | `#api.case.create(title:, description:, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil)` |
|
@@ -81,7 +82,31 @@ See `samples` for more.
|
|
81
82
|
| PATCH | /api/case/:caseId | Update a case | N/A |
|
82
83
|
| DELETE | /api/case/:caseId | Remove a case | `#api.case.delete_by_id(id)` |
|
83
84
|
| 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 |
|
85
|
+
| POST | /api/case/:caseId1/_merge/:caseId2 | Merge two cases | `#api.case.merge(id1, id2)` |
|
86
|
+
|
87
|
+
### User
|
88
|
+
|
89
|
+
| HTTP Method | URI | Action | API method |
|
90
|
+
|-------------|-----------------------------------|---------------------|------------------------------------------------------|
|
91
|
+
| GET | /api/logout | Logout | N/A |
|
92
|
+
| POST | /api/login | User login | N/A |
|
93
|
+
| GET | /api/user/current | Get current user | `#api.user.current` |
|
94
|
+
| POST | /api/user/_search | Find user | N/A |
|
95
|
+
| POST | /api/user | Create a user | `#api.user.create(login:, name:, roles:, password:)` |
|
96
|
+
| GET | /api/user/:userId | Get a user | `#api.user.get_by_id(id)` |
|
97
|
+
| DELETE | /api/user/:userId | Delete a case | `#api.user.delete_by_id(id)` |
|
98
|
+
| PATCH | /api/user/:userId | Update user details | N/A |
|
99
|
+
| POST | /api/user/:userId/password/set | Set password | N/A |
|
100
|
+
| POST | /api/user/:userId/password/change | Change password | N/A |
|
101
|
+
|
102
|
+
|
103
|
+
## How to interact with unimplemented API endpoints
|
104
|
+
|
105
|
+
`Hachi::API` exposes `get`, `post`, `delete` and `patch` methods. You can interact with the API endpoints via the methods.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
alerts = api.get("/api/alert" ) { |json| json }
|
109
|
+
```
|
85
110
|
|
86
111
|
## License
|
87
112
|
|
data/Rakefile
CHANGED
data/hachi.gemspec
CHANGED
@@ -24,10 +24,10 @@ 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.
|
27
|
+
spec.add_development_dependency "bundler", "~> 2.2"
|
28
28
|
spec.add_development_dependency "coveralls", "~> 0.8"
|
29
|
-
spec.add_development_dependency "rake", "~>
|
30
|
-
spec.add_development_dependency "rspec", "~> 3.
|
31
|
-
spec.add_development_dependency "vcr", "~>
|
32
|
-
spec.add_development_dependency "webmock", "~> 3.
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.10"
|
31
|
+
spec.add_development_dependency "vcr", "~> 6.0"
|
32
|
+
spec.add_development_dependency "webmock", "~> 3.12"
|
33
33
|
end
|
data/lib/hachi.rb
CHANGED
@@ -8,11 +8,13 @@ require "hachi/models/base"
|
|
8
8
|
require "hachi/models/alert"
|
9
9
|
require "hachi/models/artifact"
|
10
10
|
require "hachi/models/case"
|
11
|
+
require "hachi/models/user"
|
11
12
|
|
12
13
|
require "hachi/clients/base"
|
13
14
|
require "hachi/clients/alert"
|
14
15
|
require "hachi/clients/artifact"
|
15
16
|
require "hachi/clients/case"
|
17
|
+
require "hachi/clients/user"
|
16
18
|
|
17
19
|
module Hachi
|
18
20
|
class Error < StandardError; end
|
data/lib/hachi/api.rb
CHANGED
@@ -1,18 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
3
5
|
module Hachi
|
4
6
|
class API
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
# @return [String] TheHive API endpoint
|
10
|
+
attr_reader :api_endpoint
|
8
11
|
|
12
|
+
# @return [String] TheHive API key
|
13
|
+
attr_reader :api_key
|
14
|
+
|
15
|
+
#
|
16
|
+
# @param [String, nil] api_endpoint TheHive API endpoint
|
17
|
+
# @param [String, nil] api_key TheHive API key
|
18
|
+
#
|
19
|
+
# @raise [ArgumentError] When given or an empty endpoint or key
|
20
|
+
#
|
9
21
|
def initialize(api_endpoint: ENV["THEHIVE_API_ENDPOINT"], api_key: ENV["THEHIVE_API_KEY"])
|
10
|
-
|
11
|
-
raise
|
22
|
+
@api_endpoint = api_endpoint
|
23
|
+
raise ArgumentError, "api_endpoint argument is required" unless api_endpoint
|
24
|
+
|
25
|
+
@api_key = api_key
|
26
|
+
raise ArgumentError, "api_key argument is required" unless api_key
|
27
|
+
|
28
|
+
@base = Clients::Base.new(api_endpoint: api_endpoint, api_key: api_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def_delegators :@base, :get, :post, :delete, :push
|
32
|
+
|
33
|
+
#
|
34
|
+
# Alert API endpoint client
|
35
|
+
#
|
36
|
+
# @return [Clients::Alert]
|
37
|
+
#
|
38
|
+
def alert
|
39
|
+
@alert ||= Clients::Alert.new(api_endpoint: api_endpoint, api_key: api_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Artifact API endpoint client
|
44
|
+
#
|
45
|
+
# @return [Clients::Artifact]
|
46
|
+
#
|
47
|
+
def artifact
|
48
|
+
@artifact ||= Clients::Artifact.new(api_endpoint: api_endpoint, api_key: api_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Case API endpoint client
|
53
|
+
#
|
54
|
+
# @return [Clients::Case]
|
55
|
+
#
|
56
|
+
def case
|
57
|
+
@case ||= Clients::Case.new(api_endpoint: api_endpoint, api_key: api_key)
|
58
|
+
end
|
12
59
|
|
13
|
-
|
14
|
-
|
15
|
-
|
60
|
+
#
|
61
|
+
# User API endpoint client
|
62
|
+
#
|
63
|
+
# @return [Clients::User]
|
64
|
+
#
|
65
|
+
def user
|
66
|
+
@user ||= Clients::User.new(api_endpoint: api_endpoint, api_key: api_key)
|
16
67
|
end
|
17
68
|
end
|
18
69
|
end
|
data/lib/hachi/clients/alert.rb
CHANGED
@@ -6,19 +6,56 @@ require "securerandom"
|
|
6
6
|
module Hachi
|
7
7
|
module Clients
|
8
8
|
class Alert < Base
|
9
|
+
#
|
10
|
+
# List alerts
|
11
|
+
#
|
12
|
+
# @return [Array]
|
13
|
+
#
|
9
14
|
def list
|
10
15
|
get("/api/alert") { |json| json }
|
11
16
|
end
|
12
17
|
|
18
|
+
#
|
19
|
+
# Get an alert
|
20
|
+
#
|
21
|
+
# @param [String] id Alert ID
|
22
|
+
#
|
23
|
+
# @return [Hash]
|
24
|
+
#
|
13
25
|
def get_by_id(id)
|
14
26
|
get("/api/alert/#{id}") { |json| json }
|
15
27
|
end
|
16
28
|
|
29
|
+
#
|
30
|
+
# Delete an alert
|
31
|
+
#
|
32
|
+
# @param [String] id Alert ID
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
#
|
17
36
|
def delete_by_id(id)
|
18
37
|
delete("/api/alert/#{id}") { |json| json }
|
19
38
|
end
|
20
39
|
|
21
|
-
|
40
|
+
#
|
41
|
+
# Create an alert
|
42
|
+
#
|
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:, type:, source:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil, source_ref: nil, artifacts: nil, follow: nil)
|
22
59
|
alert = Models::Alert.new(
|
23
60
|
title: title,
|
24
61
|
description: description,
|
@@ -33,11 +70,94 @@ module Hachi
|
|
33
70
|
artifacts: artifacts,
|
34
71
|
follow: follow,
|
35
72
|
)
|
36
|
-
post("/api/alert", alert.payload) { |json| json }
|
73
|
+
post("/api/alert", json: alert.payload) { |json| json }
|
37
74
|
end
|
38
75
|
|
39
|
-
|
40
|
-
|
76
|
+
#
|
77
|
+
# Find alerts
|
78
|
+
#
|
79
|
+
# @param [Hash] query
|
80
|
+
# @param [String] range
|
81
|
+
# @param [String, nil] sort
|
82
|
+
#
|
83
|
+
# @return [Array]
|
84
|
+
#
|
85
|
+
def search(query, range: "all", sort: nil)
|
86
|
+
_search("/api/alert/_search", query: query, 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
|
127
|
+
#
|
128
|
+
# @return [Hash]
|
129
|
+
#
|
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", json: params) { |json| json }
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Update an alert
|
140
|
+
#
|
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
|
148
|
+
#
|
149
|
+
# @return [Hash]
|
150
|
+
#
|
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}", json: attributes) { |json| json }
|
41
161
|
end
|
42
162
|
end
|
43
163
|
end
|
@@ -3,6 +3,18 @@
|
|
3
3
|
module Hachi
|
4
4
|
module Clients
|
5
5
|
class Artifact < Base
|
6
|
+
#
|
7
|
+
# Create an artifact
|
8
|
+
#
|
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
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
#
|
6
18
|
def create(case_id, data:, data_type:, message: nil, tlp: nil, tags: nil)
|
7
19
|
artifact = Models::Artifact.new(
|
8
20
|
data: data,
|
@@ -12,21 +24,50 @@ module Hachi
|
|
12
24
|
tags: tags,
|
13
25
|
)
|
14
26
|
|
15
|
-
post("/api/case/#{case_id}/artifact", artifact.payload) { |json| json }
|
27
|
+
post("/api/case/#{case_id}/artifact", json: artifact.payload) { |json| json }
|
16
28
|
end
|
17
29
|
|
30
|
+
#
|
31
|
+
# Get an artifact
|
32
|
+
#
|
33
|
+
# @param [String] id Artifact ID
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
#
|
18
37
|
def get_by_id(id)
|
19
38
|
get("/api/case/artifact/#{id}") { |json| json }
|
20
39
|
end
|
21
40
|
|
41
|
+
#
|
42
|
+
# Delete an artifact
|
43
|
+
#
|
44
|
+
# @param [String] id Artifact ID
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
#
|
22
48
|
def delete_by_id(id)
|
23
49
|
delete("/api/case/artifact/#{id}") { |json| json }
|
24
50
|
end
|
25
51
|
|
26
|
-
|
27
|
-
|
52
|
+
#
|
53
|
+
# Find artifacts
|
54
|
+
#
|
55
|
+
# @param [Hash] query
|
56
|
+
# @param [String] range
|
57
|
+
#
|
58
|
+
# @return [Array]
|
59
|
+
#
|
60
|
+
def search(query, range: "all")
|
61
|
+
_search("/api/case/artifact/_search", query: query, range: range) { |json| json }
|
28
62
|
end
|
29
63
|
|
64
|
+
#
|
65
|
+
# Get list of similar observables
|
66
|
+
#
|
67
|
+
# @param [String] id Artifact ID
|
68
|
+
#
|
69
|
+
# @return [Array]
|
70
|
+
#
|
30
71
|
def similar(id)
|
31
72
|
get("/api/case/artifact/#{id}/similar") { |json| json }
|
32
73
|
end
|
data/lib/hachi/clients/base.rb
CHANGED
@@ -2,18 +2,64 @@
|
|
2
2
|
|
3
3
|
require "json"
|
4
4
|
require "net/https"
|
5
|
+
require "uri"
|
5
6
|
|
6
7
|
module Hachi
|
7
8
|
module Clients
|
8
9
|
class Base
|
9
|
-
attr_reader :api_endpoint
|
10
|
-
attr_reader :api_key
|
10
|
+
attr_reader :api_endpoint, :api_key
|
11
11
|
|
12
12
|
def initialize(api_endpoint:, api_key:)
|
13
13
|
@api_endpoint = URI(api_endpoint)
|
14
14
|
@api_key = api_key
|
15
15
|
end
|
16
16
|
|
17
|
+
def get(path, params: {}, &block)
|
18
|
+
url = url_for(path)
|
19
|
+
url.query = URI.encode_www_form(params) unless params.empty?
|
20
|
+
|
21
|
+
get = Net::HTTP::Get.new(url)
|
22
|
+
get.add_field "Authorization", "Bearer #{api_key}"
|
23
|
+
request(get, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def post(path, params: {}, json: {}, &block)
|
27
|
+
url = url_for(path)
|
28
|
+
url.query = URI.encode_www_form(params) unless params.empty?
|
29
|
+
|
30
|
+
post = Net::HTTP::Post.new(url)
|
31
|
+
post.body = json.is_a?(Hash) ? json.to_json : json.to_s
|
32
|
+
|
33
|
+
post.add_field "Content-Type", "application/json"
|
34
|
+
post.add_field "Authorization", "Bearer #{api_key}"
|
35
|
+
|
36
|
+
request(post, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(path, params: {}, json: {}, &block)
|
40
|
+
url = url_for(path)
|
41
|
+
url.query = URI.encode_www_form(params) unless params.empty?
|
42
|
+
|
43
|
+
delete = Net::HTTP::Delete.new(url)
|
44
|
+
delete.body = json.is_a?(Hash) ? json.to_json : json.to_s
|
45
|
+
|
46
|
+
delete.add_field "Authorization", "Bearer #{api_key}"
|
47
|
+
request(delete, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def patch(path, params: {}, json: {}, &block)
|
51
|
+
url = url_for(path)
|
52
|
+
url.query = URI.encode_www_form(params) unless params.empty?
|
53
|
+
|
54
|
+
patch = Net::HTTP::Patch.new(url)
|
55
|
+
patch.body = json.is_a?(Hash) ? json.to_json : json.to_s
|
56
|
+
|
57
|
+
patch.add_field "Content-Type", "application/json"
|
58
|
+
patch.add_field "Authorization", "Bearer #{api_key}"
|
59
|
+
|
60
|
+
request(patch, &block)
|
61
|
+
end
|
62
|
+
|
17
63
|
private
|
18
64
|
|
19
65
|
def base_url
|
@@ -73,36 +119,6 @@ module Hachi
|
|
73
119
|
end
|
74
120
|
end
|
75
121
|
|
76
|
-
def get(path, params = {}, &block)
|
77
|
-
url = url_for(path)
|
78
|
-
url.query = URI.encode_www_form(params) unless params.empty?
|
79
|
-
|
80
|
-
get = Net::HTTP::Get.new(url)
|
81
|
-
get.add_field "Authorization", "Bearer #{api_key}"
|
82
|
-
request(get, &block)
|
83
|
-
end
|
84
|
-
|
85
|
-
def post(path, params = {}, &block)
|
86
|
-
url = url_for(path)
|
87
|
-
|
88
|
-
post = Net::HTTP::Post.new(url)
|
89
|
-
post.body = params.is_a?(Hash) ? params.to_json : params.to_s
|
90
|
-
|
91
|
-
post.add_field "Content-Type", "application/json"
|
92
|
-
post.add_field "Authorization", "Bearer #{api_key}"
|
93
|
-
|
94
|
-
request(post, &block)
|
95
|
-
end
|
96
|
-
|
97
|
-
def delete(path, params = {}, &block)
|
98
|
-
url = url_for(path)
|
99
|
-
url.query = URI.encode_www_form(params) unless params.empty?
|
100
|
-
|
101
|
-
delete = Net::HTTP::Delete.new(url)
|
102
|
-
delete.add_field "Authorization", "Bearer #{api_key}"
|
103
|
-
request(delete, &block)
|
104
|
-
end
|
105
|
-
|
106
122
|
def validate_range(range)
|
107
123
|
return true if range == "all"
|
108
124
|
raise ArgumentError, "range should be 'all' or `from-to`" unless range.match?(/(\d+)-(\d+)/)
|
@@ -113,49 +129,16 @@ module Hachi
|
|
113
129
|
raise ArgumentError, "from should be smaller than to"
|
114
130
|
end
|
115
131
|
|
116
|
-
def _search(path,
|
132
|
+
def _search(path, query:, range: "all", sort: nil)
|
117
133
|
validate_range range
|
118
134
|
|
119
|
-
|
120
|
-
conditions = attributes.map do |key, value|
|
121
|
-
if key == :data && value.is_a?(Array)
|
122
|
-
{ _or: decompose_data(value) }
|
123
|
-
else
|
124
|
-
{ _string: "#{key}:#{value}" }
|
125
|
-
end
|
126
|
-
end
|
135
|
+
query_string = build_query_string(range: range, sort: sort)
|
127
136
|
|
128
|
-
|
129
|
-
_and: [
|
130
|
-
{ _not: { status: "Deleted" } },
|
131
|
-
{ _not: { _in: { _field: "_type", _values: ["dashboard", "data", "user", "analyzer", "caseTemplate", "reportTemplate", "action"] } } },
|
132
|
-
],
|
133
|
-
}
|
134
|
-
|
135
|
-
query = {
|
136
|
-
_and: [conditions, default_conditions].flatten,
|
137
|
-
}
|
138
|
-
|
139
|
-
post("#{path}?range=#{range}", query: query) { |json| json }
|
140
|
-
end
|
141
|
-
|
142
|
-
def decompose_data(data)
|
143
|
-
data.map do |elem|
|
144
|
-
{ _field: "data", _value: elem }
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def normalize_attributes(attributes)
|
149
|
-
h = {}
|
150
|
-
attributes.each do |key, value|
|
151
|
-
h[camelize(key).to_sym] = value
|
152
|
-
end
|
153
|
-
h
|
137
|
+
post("#{path}?#{query_string}", json: { query: query }) { |json| json }
|
154
138
|
end
|
155
139
|
|
156
|
-
def
|
157
|
-
|
158
|
-
[head, others.map(&:capitalize)].flatten.join
|
140
|
+
def build_query_string(params)
|
141
|
+
URI.encode_www_form(params.reject { |_k, v| v.nil? })
|
159
142
|
end
|
160
143
|
end
|
161
144
|
end
|
data/lib/hachi/clients/case.rb
CHANGED
@@ -3,18 +3,51 @@
|
|
3
3
|
module Hachi
|
4
4
|
module Clients
|
5
5
|
class Case < Base
|
6
|
+
#
|
7
|
+
# List cases
|
8
|
+
#
|
9
|
+
# @return [Array]
|
10
|
+
#
|
6
11
|
def list
|
7
12
|
get("/api/case") { |json| json }
|
8
13
|
end
|
9
14
|
|
15
|
+
#
|
16
|
+
# Get a case
|
17
|
+
#
|
18
|
+
# @param [String] id Case ID
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
#
|
10
22
|
def get_by_id(id)
|
11
23
|
get("/api/case/#{id}") { |json| json }
|
12
24
|
end
|
13
25
|
|
26
|
+
#
|
27
|
+
# Delete a case
|
28
|
+
#
|
29
|
+
# @param [String] id Case ID
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
#
|
14
33
|
def delete_by_id(id)
|
15
34
|
delete("/api/case/#{id}") { |json| json }
|
16
35
|
end
|
17
36
|
|
37
|
+
#
|
38
|
+
# Create a case
|
39
|
+
#
|
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
|
+
#
|
18
51
|
def create(title:, description:, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil)
|
19
52
|
kase = Models::Case.new(
|
20
53
|
title: title,
|
@@ -27,16 +60,86 @@ module Hachi
|
|
27
60
|
tags: tags,
|
28
61
|
)
|
29
62
|
|
30
|
-
post("/api/case", kase.payload) { |json| json }
|
63
|
+
post("/api/case", json: kase.payload) { |json| json }
|
31
64
|
end
|
32
65
|
|
33
|
-
|
34
|
-
|
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 }
|
35
76
|
end
|
36
77
|
|
78
|
+
#
|
79
|
+
# Get list of cases linked to this case
|
80
|
+
#
|
81
|
+
# @param [String] id Case ID
|
82
|
+
#
|
83
|
+
# @return [Array]
|
84
|
+
#
|
37
85
|
def links(id)
|
38
86
|
get("/api/case/#{id}/links") { |json| json }
|
39
87
|
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Merge two cases
|
91
|
+
#
|
92
|
+
# @param [String] id1 Case ID
|
93
|
+
# @param [String] id2 Case ID
|
94
|
+
#
|
95
|
+
# @return [Hash]
|
96
|
+
#
|
97
|
+
def merge(id1, id2)
|
98
|
+
post("/api/case/#{id1}/_merge/#{id2}") { |json| json }
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Update a case
|
103
|
+
#
|
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
|
120
|
+
#
|
121
|
+
# @return [Hash]
|
122
|
+
#
|
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 }
|
142
|
+
end
|
40
143
|
end
|
41
144
|
end
|
42
145
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hachi
|
4
|
+
module Clients
|
5
|
+
class User < Base
|
6
|
+
#
|
7
|
+
# Get current user
|
8
|
+
#
|
9
|
+
# @return [Hash]
|
10
|
+
#
|
11
|
+
def current
|
12
|
+
get("/api/user/current") { |json| json }
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Get a user
|
17
|
+
#
|
18
|
+
# @param [String] id User ID
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
#
|
22
|
+
def get_by_id(id)
|
23
|
+
get("/api/user/#{id}") { |json| json }
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Delete a user
|
28
|
+
#
|
29
|
+
# @param [String] id User ID
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
#
|
33
|
+
def delete_by_id(id)
|
34
|
+
delete("/api/user/#{id}") { |json| json }
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Create a user
|
39
|
+
#
|
40
|
+
# @param [String] login
|
41
|
+
# @param [String] name
|
42
|
+
# @param [Array<String>] roles
|
43
|
+
# @param [String] password
|
44
|
+
#
|
45
|
+
# @return [Hash]
|
46
|
+
#
|
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 }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/hachi/models/alert.rb
CHANGED
@@ -6,20 +6,9 @@ require "securerandom"
|
|
6
6
|
module Hachi
|
7
7
|
module Models
|
8
8
|
class Alert < Base
|
9
|
-
attr_reader :title
|
10
|
-
attr_reader :description
|
11
|
-
attr_reader :severity
|
12
|
-
attr_reader :date
|
13
|
-
attr_reader :tags
|
14
|
-
attr_reader :tlp
|
15
|
-
attr_reader :status
|
16
|
-
attr_reader :type
|
17
|
-
attr_reader :source
|
18
|
-
attr_reader :source_ref
|
19
|
-
attr_reader :artifacts
|
20
|
-
attr_reader :follow
|
9
|
+
attr_reader :title, :description, :severity, :date, :tags, :tlp, :status, :type, :source, :source_ref, :artifacts, :follow
|
21
10
|
|
22
|
-
def initialize(title:, description:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil,
|
11
|
+
def initialize(title:, description:, type:, source:, severity: nil, date: nil, tags: nil, tlp: nil, status: nil, source_ref: nil, artifacts: nil, follow: nil)
|
23
12
|
@title = title
|
24
13
|
@description = description
|
25
14
|
@severity = severity
|
@@ -30,7 +19,7 @@ module Hachi
|
|
30
19
|
@type = type
|
31
20
|
@source = source
|
32
21
|
@source_ref = source_ref || SecureRandom.hex(10)
|
33
|
-
@artifacts = artifacts.nil? ? nil : artifacts.map { |a| Artifact.new
|
22
|
+
@artifacts = artifacts.nil? ? nil : artifacts.map { |a| Artifact.new(**a) }
|
34
23
|
@follow = follow
|
35
24
|
|
36
25
|
validate_date if date
|
@@ -5,11 +5,7 @@ module Hachi
|
|
5
5
|
class Artifact < Base
|
6
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
7
|
|
8
|
-
attr_reader :data
|
9
|
-
attr_reader :data_type
|
10
|
-
attr_reader :message
|
11
|
-
attr_reader :tlp
|
12
|
-
attr_reader :tags
|
8
|
+
attr_reader :data, :data_type, :message, :tlp, :tags
|
13
9
|
|
14
10
|
def initialize(data:, data_type:, message: nil, tlp: nil, tags: nil)
|
15
11
|
@data = data
|
data/lib/hachi/models/case.rb
CHANGED
@@ -3,14 +3,7 @@
|
|
3
3
|
module Hachi
|
4
4
|
module Models
|
5
5
|
class Case < Base
|
6
|
-
attr_reader :title
|
7
|
-
attr_reader :description
|
8
|
-
attr_reader :severity
|
9
|
-
attr_reader :start_date
|
10
|
-
attr_reader :owner
|
11
|
-
attr_reader :flag
|
12
|
-
attr_reader :tlp
|
13
|
-
attr_reader :tags
|
6
|
+
attr_reader :title, :description, :severity, :start_date, :owner, :flag, :tlp, :tags
|
14
7
|
|
15
8
|
def initialize(title:, description:, severity: nil, start_date: nil, owner: nil, flag: nil, tlp: nil, tags: nil)
|
16
9
|
@title = title
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hachi
|
4
|
+
module Models
|
5
|
+
class User < Base
|
6
|
+
attr_reader :login, :name, :roles, :password
|
7
|
+
|
8
|
+
ROLES = %w(read write admin).freeze
|
9
|
+
|
10
|
+
def initialize(login:, name:, roles:, password:)
|
11
|
+
@login = login
|
12
|
+
@name = name
|
13
|
+
@roles = roles
|
14
|
+
@password = password
|
15
|
+
|
16
|
+
validate_roles
|
17
|
+
end
|
18
|
+
|
19
|
+
def payload
|
20
|
+
{
|
21
|
+
login: login,
|
22
|
+
name: name,
|
23
|
+
roles: roles,
|
24
|
+
password: password
|
25
|
+
}.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_roles
|
31
|
+
raise ArgumentError, "roles should be an array" unless roles.is_a?(Array)
|
32
|
+
raise ArgumentError, "role should be one of #{ROLES.join('.')}" unless roles.all? { |role| ROLES.include? role }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/hachi/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
4
|
+
|
5
|
+
require "hachi"
|
6
|
+
|
7
|
+
def api
|
8
|
+
@api ||= Hachi::API.new
|
9
|
+
end
|
10
|
+
|
11
|
+
description = ARGV[0].to_s
|
12
|
+
case_id = ARGV[1].to_s
|
13
|
+
|
14
|
+
alerts = api.alert.search(description: description)
|
15
|
+
alert_ids = alerts.map { |alert| alert.dig "id" }
|
16
|
+
|
17
|
+
api.alert.merge_into_case(alert_ids, case_id)
|
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: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manabu Niseki
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.2'
|
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.
|
26
|
+
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: coveralls
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,56 +44,56 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
61
|
+
version: '3.10'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
68
|
+
version: '3.10'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: vcr
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '6.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '6.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: webmock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '3.
|
89
|
+
version: '3.12'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '3.
|
96
|
+
version: '3.12'
|
97
97
|
description: A dead simple TheHive API wrapper.
|
98
98
|
email:
|
99
99
|
- manabu.niseki@gmail.com
|
@@ -101,9 +101,9 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
+
- ".github/workflows/test.yml"
|
104
105
|
- ".gitignore"
|
105
106
|
- ".rspec"
|
106
|
-
- ".travis.yml"
|
107
107
|
- Gemfile
|
108
108
|
- LICENSE.txt
|
109
109
|
- README.md
|
@@ -117,19 +117,23 @@ files:
|
|
117
117
|
- lib/hachi/clients/artifact.rb
|
118
118
|
- lib/hachi/clients/base.rb
|
119
119
|
- lib/hachi/clients/case.rb
|
120
|
+
- lib/hachi/clients/user.rb
|
120
121
|
- lib/hachi/models/alert.rb
|
121
122
|
- lib/hachi/models/artifact.rb
|
122
123
|
- lib/hachi/models/base.rb
|
123
124
|
- lib/hachi/models/case.rb
|
125
|
+
- lib/hachi/models/user.rb
|
124
126
|
- lib/hachi/version.rb
|
127
|
+
- renovate.json
|
125
128
|
- samples/01_create_an_alert.rb
|
126
129
|
- samples/02_search_artifacts.rb
|
127
130
|
- samples/03_list_cases.rb
|
131
|
+
- samples/04_merge_alerts.rb
|
128
132
|
homepage: https://github.com/ninoseki/hachi
|
129
133
|
licenses:
|
130
134
|
- MIT
|
131
135
|
metadata: {}
|
132
|
-
post_install_message:
|
136
|
+
post_install_message:
|
133
137
|
rdoc_options: []
|
134
138
|
require_paths:
|
135
139
|
- lib
|
@@ -144,8 +148,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
148
|
- !ruby/object:Gem::Version
|
145
149
|
version: '0'
|
146
150
|
requirements: []
|
147
|
-
rubygems_version: 3.
|
148
|
-
signing_key:
|
151
|
+
rubygems_version: 3.2.3
|
152
|
+
signing_key:
|
149
153
|
specification_version: 4
|
150
154
|
summary: A dead simple TheHive API wrapper.
|
151
155
|
test_files: []
|