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: []
         |