Skip to content

Commit

Permalink
Merge branch 'release/2.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nadouani committed Jun 23, 2021
2 parents 629069a + 940739b commit 98365d0
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 27 deletions.
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: python
python:
- '3.6'
install:
- sudo apt-get install pandoc
- pip install -r requirements.txt
script:
#- python -m pytest
- python setup.py bdist_wheel --universal
deploy:
provider: pypi
user: "thehive-project"
password:
secure: UFT+5CY4uqrCuZJvm4wbAyY7XEKcDz//VSt+jGPktj2/PxnmL7Qj5EA+2BFwOpLwUpZw3eWHPZrLkquDYsuR+BtXK08QVxtYHZJiNLdc+bM+R8UQh+VSuu4IQYqtUMVBKZtMkkx8ss+LgAdB+ArUKfVn5HVOOmEV8D4Ghx1Yf90D3zBrDfu6i/h3OajNgSrSdy6i/B7EyIjqZ5rfffCroxl9jPvWu8kPimaknRav6qDFykT4golJGoe64IUEz5AnuhbyBc1VTXCOKcjXCaYj6VSfXFxQVZz/vO+DGsFajybDyYwts6z5GD9kx9GFwNhUDVtDoEybMaY1a1UwBZi9OPG/fmUv4M7qQ5yh9YgByhw3B20JElgfgGsOSvmXIZhw9lAhkvRSPom64HIWRFZCtMMEH3f5gzwite07rcsCfV+VDypNa5eOkAKnFg21p2ibG+fij7bpajwnMxiZf3KMpW4F5D25MAu7Rf3+dyfoZj7sA0ElEdzUTbMAZHTST1Zk2CCyoE69PNuPt6ZTmv9oDgWd5GreXfyw4pP/ehR5VDRG/eNv1hzp1Mg328IZhFcS7wwaCb8yh4ZHq4uUF4Egsmx+IhvqXgtrLHKEW9t5ndS+Z7oe+EKU1sLlCFPqzNFVPmqWotZO4gHQ6cF3due6AZnAGlBE69rIkmPrtR8rsW0=
distributions: "bdist_wheel"
on:
tags: true
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
[![Join the chat at https://gitter.im/TheHive-Project/TheHive](https://badges.gitter.im/TheHive-Project/TheHive.svg)](https://gitter.im/TheHive-Project/TheHive?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

<div>
<p align="center">
<a href="https://travis-ci.org/github/TheHive-Project/Cortex4py" target="_blank">
<img src="https://travis-ci.org/TheHive-Project/Cortex4py.svg?branch=1.x" alt="Build status">
</a>
<a href="https://chat.thehive-project.org" target"_blank">
<img src="https://img.shields.io/discord/779945042039144498" alt="Discord">
</a>
<a href="./LICENSE" target"_blank">
<img src="https://img.shields.io/github/license/TheHive-Project/Cortex4py" alt="License">
</a>
<a href="https://pypi.org/project/cortex4py" target"_blank">
<img src="https://img.shields.io/pypi/dm/cortex4py" alt="Pypi page">
</a>
</p>
</div>

# Cortex4py
Cortex4py is a Python API client for [Cortex](https://thehive-project.org/), a powerful observable analysis engine where observables such as IP and email addresses, URLs, domain names, files or hashes can be analyzed one by one using a Web interface.
Expand Down Expand Up @@ -44,7 +58,7 @@ We welcome your contributions. Please feel free to fork the code, play with it,
We do have a [Code of conduct](code_of_conduct.md). Make sure to check it out before contributing.

# Support
Please [open an issue on GitHub](https://github.com/CERT-BDF/Cortex4py/issues/new) if you'd like to report a bug or request a feature. We are also available on [Gitter](https://gitter.im/TheHive-Project/TheHive) to help you out.
Please [open an issue on GitHub](https://github.com/TheHive-Project/Cortex4py/issues/new) if you'd like to report a bug or request a feature. We are also available on [Discord](https://chat.thehive-project.org) to help you out.

If you need to contact the project team, send an email to <[email protected]>.

Expand Down
163 changes: 149 additions & 14 deletions Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ Cortex4py 2 requires Python 3. It does not work with Cortex 1.x.
* [Model](#model-2)
* [Methods](#methods-2)
* [Examples](#examples-2)
* [Job operations](#job-operations)
* [Responder operations](#responder-operations)
* [Model](#model-3)
* [Methods](#methods-3)
* [Examples](#examples-3)
* [Job operations](#job-operations)
* [Model](#model-4)
* [Methods](#methods-4)
* [Examples](#examples-4)

## Introduction

Expand Down Expand Up @@ -226,7 +230,7 @@ org = api.organizations.get_by_id('demo')
print(json.dumps(org.json(), indent=2))

# Fetch the last 5 created and active users
users = api.organizations.get_users(org.id, Eq('status', 'Active'), range='0-5', sort='-createdAt')
users = api.organizations.get_users(org.id, Eq('status', 'Ok'), range='0-5', sort='-createdAt')

# Display the usernames
for user in users:
Expand Down Expand Up @@ -392,6 +396,7 @@ An analyzer is represented by the following model class:
| `dataTypeList` | Allowed datatypes | readonly |
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the analyzer's flavors | readonly |
| `jobCache` | Report cache timeout in minutes, visible for `orgAdmin` users only | writable |
| `jobTimeout` | Job timeout in minutes, visible for `orgAdmin` users only | writable |
| `rate` | Numeric amount of analyzer calls authorized for the specified `rateUnit`, visible for `orgAdmin` users only | writable |
| `rateUnit` | Period of availability of the rate limite: `Day` or `Month`, visible for `orgAdmin` users only | writable |
| `configuration` | A JSON object where key/value pairs represent the config names, and their values. It includes the default properties `proxy_http`, `proxy_https`, `auto_extract_artifacts`, `check_tlp`, and `max_tlp`, visible for `orgAdmin` users only | writable |
Expand Down Expand Up @@ -445,16 +450,18 @@ analyzer = api.analyzers.enable('Test_1_0', {
"proxy_https": "http://localhost:9999",
"auto_extract_artifacts": False,
"check_tlp": True,
"max_tlp": 2
"max_tlp": 2,
"max_pap": 2
},
"jobCache": 10,
"jobTimeout": 30,
"rate": 1000,
"rateUnit": "Day",
"jobCache": 5
"rateUnit": "Day"
})

# Print the details of the enaled analyzer
print(json.dumps(analyzer.json(), indent=2))
print(analyzer.analyzerDefinitionId == 'Test_1_0')
print(analyzer.workerDefinitionId == 'Test_1_0')

# Update the configuration
analyzer_id = analyzer.id
Expand All @@ -468,7 +475,8 @@ analyzer = api.analyzers.update(analyzer.id, {
"proxy_https": null,
"auto_extract_artifacts": True,
"check_tlp": false,
"max_tlp": null
"max_tlp": null,
"max_pap": 2
}
})

Expand Down Expand Up @@ -498,9 +506,135 @@ print(json.dumps(job2.json(), indent=2))
api.analyzers.disable(analyzer_id)
```

## Responder Operations

The `RespondersController` class provides a set of methods to handle responders.

### Model

A responder is an instance of a responder definition, and both models share the same fields.

A responder definition is represented by the following model class:

| Field | Description | Type |
| --------- | ----------- | ---- |
| `id` | Responder ID once enabled within an organization | readonly |
| `workerDefinitionId`| Responder definition name | readonly |
| `name` | Name of the responder | readonly |
| `version` | Version of the responder | readonly |
| `description` | Description of the responder | readonly |
| `author` | Author of the responder | readonly |
| `url` | URL where the responder has been published | readonly |
| `license` | License of the responder | readonly |
| `dataTypeList` | Allowed datatypes | readonly |
| `configurationItems` | A list that describes the configuration options of the responder | readonly |
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the responder's flavors | readonly |
| `createdBy` | User who enabled the responder | computed |
| `updatedAt` | Last update date | computed |
| `updatedBy` | User who last updated the responder | computed |

A responder is represented by the following model class:

| Field | Description | Type |
| --------- | ----------- | ---- |
| `id` | Responder ID once enabled within an organization | readonly |
| `workerDefinitionId`| Responder definition name | readonly |
| `name` | Name of the responder | readonly |
| `version` | Version of the responder | readonly |
| `description` | Description of the responder | readonly |
| `author` | Author of the responder | readonly |
| `url` | URL where the responder has been published | readonly |
| `license` | License of the responder | readonly |
| `dataTypeList` | Allowed datatypes | readonly |
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the responder's flavors | readonly |
| `jobCache` | Report cache timeout in minutes, visible for `orgAdmin` users only | writable |
| `rate` | Numeric amount of responder calls authorized for the specified `rateUnit`, visible for `orgAdmin` users only | writable |
| `rateUnit` | Period of availability of the rate limite: `Day` or `Month`, visible for `orgAdmin` users only | writable |
| `configuration` | A JSON object where key/value pairs represent the config names, and their values. It includes the default properties `proxy_http`, `proxy_https`, `auto_extract_artifacts`, `check_tlp`, and `max_tlp`, visible for `orgAdmin` users only | writable |
| `createdBy` | User who enabled the analyzer | computed |
| `updatedAt` | Last update date | computed |
| `updatedBy` | User who last updated the analyzer | computed |

### Methods

| Method | Description | Return type |
| --------- | ----------- | ---- |
|`find_all(query,**kwargs)` | Returns a list of `Responder` objects, based on `query`, `range` and `sort` parameters | List[Responder] |
|`find_one_by(query,**kwargs)` | Returns the first `Responder` object, based on `query` and `sort` parameters | Responder |
|`get_by_id(worker_id)` | Returns a `Responder` by its `id` | Responder |
|`get_by_name(name)` | Returns a `Responder` by its `name` | Responder |
|`get_by_type(data_type)` | Returns a list of available `Responder` applicable to the given `data_type` | List[Responder] |
|`enable(responder_name,config)` | Activate an responder and returns its `Responder` object | Responder |
|`update(worker_id)` | Update the configuration of an `Responder` and returns the updated version | Responder |
|`disable(worker_id)` | Removes a responder from an organization and returns `true` if it completes successfully | Boolean |
|`run_by_id(worker_id, data,**kwargs)` | Returns a `Job` by its `name` | Job |
|`run_by_name(responder_name, data,**kwargs)` | Runs a responder by its name and returns the resulting `Job` | Job |
|`definitions()` | Returns the list of all the responder definitions including the enabled and disabled responders | List[ResponderDefinition] |

### Examples

The following example shows how to manipulate responders:

```python
import json

from cortex4py.api import Api
from cortex4py.query import *

api = Api('http://CORTEX_APP_URL:9001', '**API_KEY**')

# Get enabled responders
responders = api.responders.find_all({}, range='all')

# Display enabled responders' names
for responder in responders:
print('Responder {} is enabled'.format(responder.name))

# Get enabled responders that available for TheHive cases
case_responders = api.responders.get_by_type('thehive:case')

# Display responders details
for responder in case_responders:
print(json.dumps(responder.json(), indent=2))

# Enable the responder called Test_1_0
responder = api.responders.enable('Test_1_0', {
"configuration": {
"api_key": "XXXXXXXXXXXXXx",
"proxy_http": "http://localhost:9999",
"proxy_https": "http://localhost:9999",
"check_tlp": True,
"max_tlp": 2,
"max_pap": 2
},
"jobTimeout": 30,
"rate": 1000,
"rateUnit": "Day"
})

# Print the details of the enaled responder
print(json.dumps(responder.json(), indent=2))
print(responder.workerDefinitionId == 'Test_1_0')

# Run a responder
job = api.responders.run_by_name('File_Info_2_0', {
'data': {
'title': 'Sample case',
'description': 'This is a sample case',
...
},
'dataType': 'thehive:case',
'tlp': 1
})
print(json.dumps(job.json(), indent=2))

# Disable a responder
api.responders.disable(responder.id)
```

## Job Operations

The `JobsController` class provides a set of methods to handle jobs. A job is the execution of a specific analyzer.
The `JobsController` class provides a set of methods to handle jobs. A job is the execution of a specific worker (analyzer or responder).

### Model

Expand All @@ -509,18 +643,19 @@ A job is represented by the following model class:
| Attribute | Description | Type |
| --------- | ----------- | ---- |
| `id` | Job ID | computed |
| `analyzerDefinitionId`| Analyzer definition name | readonly |
| `analyzerId` | Instance ID of the analyzer to which the job is associated | readonly |
| `type` | Job type: `responder` or `analyzer` | computed |
| `workerDefinitionId`| Worker definition name | readonly |
| `workerId` | Instance ID of the worker to which the job is associated | readonly |
| `workerName` | Name of the worker to which the job is associated | readonly |
| `organization` | Organization to which the user belongs (set upon account creation) | readonly |
| `analyzerName` | Name of the analyzer to which the job is associated | readonly |
| `dataType` | the datatype of the analyzed observable | readonly |
| `dataType` | the datatype of the worker's input data | readonly |
| `status` | Status of the job (`Waiting`, `InProgress`, `Success`, `Failure`, `Deleted`) | computed |
| `data` | Value of the analyzed observable (does not apply to `file` observables) | readonly |
| `data` | Value of the worker's input (does not apply to `file` observables). Contains all the data of a `Case` if the job is a result of a case responder. | readonly |
| `attachment` | JSON object representing `file` observables (does not apply to non-`file` observables). It defines the`name`, `hashes`, `size`, `contentType` and `id` of the `file` observable | readonly |
| `parameters` | JSON object of key/value pairs set during job creation | readonly |
| `message` | A free text field to set additional text/context for a job | readonly |
| `tlp` | The TLP of the analyzed observable | readonly |
| `report` | The analysy report as a JSON object including `success`, `full`, `summary` and `artifacts` peoperties.<br>In case of failure, the resport contains a `errorMessage` property | readonly |
| `report` | The analysis report as a JSON object including `success`, `full`, `summary` and `artifacts` peoperties.<br>In case of failure, the resport contains a `errorMessage` property | readonly |
| `startDate` | Start date | computed |
| `endDate` | End date | computed |
| `createdAt` | Creation date. Please note that a job can be requested but not immediately honored. The actual time at which it is started is the value of `startDate` | computed |
Expand Down
6 changes: 4 additions & 2 deletions cortex4py/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .controllers.users import UsersController
from .controllers.jobs import JobsController
from .controllers.analyzers import AnalyzersController
from .controllers.responders import RespondersController


class Api(object):
Expand All @@ -33,6 +34,7 @@ def __init__(self, url, api_key, **kwargs):
self.users = UsersController(self)
self.jobs = JobsController(self)
self.analyzers = AnalyzersController(self)
self.responders = RespondersController(self)

@staticmethod
def __recover(exception):
Expand Down Expand Up @@ -151,8 +153,8 @@ def get_analyzers(self, data_type=None):
'api.get_analyzers() is considered deprecated. Use api.analyzers.get_by_[id|name|type]() instead.',
DeprecationWarning
)
if data_type is not None:
return self.analyzers.find_all()
if data_type is None:
return self.analyzers.find_all({})
else:
return self.analyzers.get_by_type(data_type)

Expand Down
1 change: 1 addition & 0 deletions cortex4py/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .users import UsersController
from .jobs import JobsController
from .analyzers import AnalyzersController
from .responders import RespondersController
8 changes: 7 additions & 1 deletion cortex4py/controllers/analyzers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from cortex4py.query import *
from .abstract import AbstractController
from ..models import Analyzer, Job, AnalyzerDefinition
from ..exceptions import CortexError


class AnalyzersController(AbstractController):
Expand Down Expand Up @@ -48,11 +49,13 @@ def disable(self, analyzer_id) -> bool:

def run_by_id(self, analyzer_id, observable, **kwargs) -> Job:
tlp = observable.get('tlp', 2)
pap = observable.get('pap', 2)
data_type = observable.get('dataType', None)

post = {
'dataType': data_type,
'tlp': tlp
'tlp': tlp,
'pap': pap
}

params = {}
Expand Down Expand Up @@ -85,4 +88,7 @@ def run_by_id(self, analyzer_id, observable, **kwargs) -> Job:
def run_by_name(self, analyzer_name, observable, **kwargs) -> Job:
analyzer = self.get_by_name(analyzer_name)

if analyzer is None:
raise CortexError("Analyzer %s not found" % analyzer_name)

return self.run_by_id(analyzer.id, observable, **kwargs)
Loading

0 comments on commit 98365d0

Please sign in to comment.