Skip to content

Commit 98365d0

Browse files
committed
Merge branch 'release/2.1.0'
2 parents 629069a + 940739b commit 98365d0

14 files changed

Lines changed: 320 additions & 27 deletions

.travis.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
language: python
2+
python:
3+
- '3.6'
4+
install:
5+
- sudo apt-get install pandoc
6+
- pip install -r requirements.txt
7+
script:
8+
#- python -m pytest
9+
- python setup.py bdist_wheel --universal
10+
deploy:
11+
provider: pypi
12+
user: "thehive-project"
13+
password:
14+
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=
15+
distributions: "bdist_wheel"
16+
on:
17+
tags: true

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
[![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)
2-
1+
<div>
2+
<p align="center">
3+
<a href="https://travis-ci.org/github/TheHive-Project/Cortex4py" target="_blank">
4+
<img src="https://travis-ci.org/TheHive-Project/Cortex4py.svg?branch=1.x" alt="Build status">
5+
</a>
6+
<a href="https://chat.thehive-project.org" target"_blank">
7+
<img src="https://img.shields.io/discord/779945042039144498" alt="Discord">
8+
</a>
9+
<a href="./LICENSE" target"_blank">
10+
<img src="https://img.shields.io/github/license/TheHive-Project/Cortex4py" alt="License">
11+
</a>
12+
<a href="https://pypi.org/project/cortex4py" target"_blank">
13+
<img src="https://img.shields.io/pypi/dm/cortex4py" alt="Pypi page">
14+
</a>
15+
</p>
16+
</div>
317

418
# Cortex4py
519
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.
@@ -44,7 +58,7 @@ We welcome your contributions. Please feel free to fork the code, play with it,
4458
We do have a [Code of conduct](code_of_conduct.md). Make sure to check it out before contributing.
4559

4660
# Support
47-
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.
61+
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.
4862

4963
If you need to contact the project team, send an email to <support@thehive-project.org>.
5064

Usage.md

Lines changed: 149 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ Cortex4py 2 requires Python 3. It does not work with Cortex 1.x.
2424
* [Model](#model-2)
2525
* [Methods](#methods-2)
2626
* [Examples](#examples-2)
27-
* [Job operations](#job-operations)
27+
* [Responder operations](#responder-operations)
2828
* [Model](#model-3)
2929
* [Methods](#methods-3)
3030
* [Examples](#examples-3)
31+
* [Job operations](#job-operations)
32+
* [Model](#model-4)
33+
* [Methods](#methods-4)
34+
* [Examples](#examples-4)
3135

3236
## Introduction
3337

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

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

231235
# Display the usernames
232236
for user in users:
@@ -392,6 +396,7 @@ An analyzer is represented by the following model class:
392396
| `dataTypeList` | Allowed datatypes | readonly |
393397
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the analyzer's flavors | readonly |
394398
| `jobCache` | Report cache timeout in minutes, visible for `orgAdmin` users only | writable |
399+
| `jobTimeout` | Job timeout in minutes, visible for `orgAdmin` users only | writable |
395400
| `rate` | Numeric amount of analyzer calls authorized for the specified `rateUnit`, visible for `orgAdmin` users only | writable |
396401
| `rateUnit` | Period of availability of the rate limite: `Day` or `Month`, visible for `orgAdmin` users only | writable |
397402
| `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 |
@@ -445,16 +450,18 @@ analyzer = api.analyzers.enable('Test_1_0', {
445450
"proxy_https": "http://localhost:9999",
446451
"auto_extract_artifacts": False,
447452
"check_tlp": True,
448-
"max_tlp": 2
453+
"max_tlp": 2,
454+
"max_pap": 2
449455
},
456+
"jobCache": 10,
457+
"jobTimeout": 30,
450458
"rate": 1000,
451-
"rateUnit": "Day",
452-
"jobCache": 5
459+
"rateUnit": "Day"
453460
})
454461

455462
# Print the details of the enaled analyzer
456463
print(json.dumps(analyzer.json(), indent=2))
457-
print(analyzer.analyzerDefinitionId == 'Test_1_0')
464+
print(analyzer.workerDefinitionId == 'Test_1_0')
458465

459466
# Update the configuration
460467
analyzer_id = analyzer.id
@@ -468,7 +475,8 @@ analyzer = api.analyzers.update(analyzer.id, {
468475
"proxy_https": null,
469476
"auto_extract_artifacts": True,
470477
"check_tlp": false,
471-
"max_tlp": null
478+
"max_tlp": null,
479+
"max_pap": 2
472480
}
473481
})
474482

@@ -498,9 +506,135 @@ print(json.dumps(job2.json(), indent=2))
498506
api.analyzers.disable(analyzer_id)
499507
```
500508

509+
## Responder Operations
510+
511+
The `RespondersController` class provides a set of methods to handle responders.
512+
513+
### Model
514+
515+
A responder is an instance of a responder definition, and both models share the same fields.
516+
517+
A responder definition is represented by the following model class:
518+
519+
| Field | Description | Type |
520+
| --------- | ----------- | ---- |
521+
| `id` | Responder ID once enabled within an organization | readonly |
522+
| `workerDefinitionId`| Responder definition name | readonly |
523+
| `name` | Name of the responder | readonly |
524+
| `version` | Version of the responder | readonly |
525+
| `description` | Description of the responder | readonly |
526+
| `author` | Author of the responder | readonly |
527+
| `url` | URL where the responder has been published | readonly |
528+
| `license` | License of the responder | readonly |
529+
| `dataTypeList` | Allowed datatypes | readonly |
530+
| `configurationItems` | A list that describes the configuration options of the responder | readonly |
531+
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the responder's flavors | readonly |
532+
| `createdBy` | User who enabled the responder | computed |
533+
| `updatedAt` | Last update date | computed |
534+
| `updatedBy` | User who last updated the responder | computed |
535+
536+
A responder is represented by the following model class:
537+
538+
| Field | Description | Type |
539+
| --------- | ----------- | ---- |
540+
| `id` | Responder ID once enabled within an organization | readonly |
541+
| `workerDefinitionId`| Responder definition name | readonly |
542+
| `name` | Name of the responder | readonly |
543+
| `version` | Version of the responder | readonly |
544+
| `description` | Description of the responder | readonly |
545+
| `author` | Author of the responder | readonly |
546+
| `url` | URL where the responder has been published | readonly |
547+
| `license` | License of the responder | readonly |
548+
| `dataTypeList` | Allowed datatypes | readonly |
549+
| `baseConfig` | Base configuration name. This identifies the shared set of configuration with all the responder's flavors | readonly |
550+
| `jobCache` | Report cache timeout in minutes, visible for `orgAdmin` users only | writable |
551+
| `rate` | Numeric amount of responder calls authorized for the specified `rateUnit`, visible for `orgAdmin` users only | writable |
552+
| `rateUnit` | Period of availability of the rate limite: `Day` or `Month`, visible for `orgAdmin` users only | writable |
553+
| `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 |
554+
| `createdBy` | User who enabled the analyzer | computed |
555+
| `updatedAt` | Last update date | computed |
556+
| `updatedBy` | User who last updated the analyzer | computed |
557+
558+
### Methods
559+
560+
| Method | Description | Return type |
561+
| --------- | ----------- | ---- |
562+
|`find_all(query,**kwargs)` | Returns a list of `Responder` objects, based on `query`, `range` and `sort` parameters | List[Responder] |
563+
|`find_one_by(query,**kwargs)` | Returns the first `Responder` object, based on `query` and `sort` parameters | Responder |
564+
|`get_by_id(worker_id)` | Returns a `Responder` by its `id` | Responder |
565+
|`get_by_name(name)` | Returns a `Responder` by its `name` | Responder |
566+
|`get_by_type(data_type)` | Returns a list of available `Responder` applicable to the given `data_type` | List[Responder] |
567+
|`enable(responder_name,config)` | Activate an responder and returns its `Responder` object | Responder |
568+
|`update(worker_id)` | Update the configuration of an `Responder` and returns the updated version | Responder |
569+
|`disable(worker_id)` | Removes a responder from an organization and returns `true` if it completes successfully | Boolean |
570+
|`run_by_id(worker_id, data,**kwargs)` | Returns a `Job` by its `name` | Job |
571+
|`run_by_name(responder_name, data,**kwargs)` | Runs a responder by its name and returns the resulting `Job` | Job |
572+
|`definitions()` | Returns the list of all the responder definitions including the enabled and disabled responders | List[ResponderDefinition] |
573+
574+
### Examples
575+
576+
The following example shows how to manipulate responders:
577+
578+
```python
579+
import json
580+
581+
from cortex4py.api import Api
582+
from cortex4py.query import *
583+
584+
api = Api('http://CORTEX_APP_URL:9001', '**API_KEY**')
585+
586+
# Get enabled responders
587+
responders = api.responders.find_all({}, range='all')
588+
589+
# Display enabled responders' names
590+
for responder in responders:
591+
print('Responder {} is enabled'.format(responder.name))
592+
593+
# Get enabled responders that available for TheHive cases
594+
case_responders = api.responders.get_by_type('thehive:case')
595+
596+
# Display responders details
597+
for responder in case_responders:
598+
print(json.dumps(responder.json(), indent=2))
599+
600+
# Enable the responder called Test_1_0
601+
responder = api.responders.enable('Test_1_0', {
602+
"configuration": {
603+
"api_key": "XXXXXXXXXXXXXx",
604+
"proxy_http": "http://localhost:9999",
605+
"proxy_https": "http://localhost:9999",
606+
"check_tlp": True,
607+
"max_tlp": 2,
608+
"max_pap": 2
609+
},
610+
"jobTimeout": 30,
611+
"rate": 1000,
612+
"rateUnit": "Day"
613+
})
614+
615+
# Print the details of the enaled responder
616+
print(json.dumps(responder.json(), indent=2))
617+
print(responder.workerDefinitionId == 'Test_1_0')
618+
619+
# Run a responder
620+
job = api.responders.run_by_name('File_Info_2_0', {
621+
'data': {
622+
'title': 'Sample case',
623+
'description': 'This is a sample case',
624+
...
625+
},
626+
'dataType': 'thehive:case',
627+
'tlp': 1
628+
})
629+
print(json.dumps(job.json(), indent=2))
630+
631+
# Disable a responder
632+
api.responders.disable(responder.id)
633+
```
634+
501635
## Job Operations
502636

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

505639
### Model
506640

@@ -509,18 +643,19 @@ A job is represented by the following model class:
509643
| Attribute | Description | Type |
510644
| --------- | ----------- | ---- |
511645
| `id` | Job ID | computed |
512-
| `analyzerDefinitionId`| Analyzer definition name | readonly |
513-
| `analyzerId` | Instance ID of the analyzer to which the job is associated | readonly |
646+
| `type` | Job type: `responder` or `analyzer` | computed |
647+
| `workerDefinitionId`| Worker definition name | readonly |
648+
| `workerId` | Instance ID of the worker to which the job is associated | readonly |
649+
| `workerName` | Name of the worker to which the job is associated | readonly |
514650
| `organization` | Organization to which the user belongs (set upon account creation) | readonly |
515-
| `analyzerName` | Name of the analyzer to which the job is associated | readonly |
516-
| `dataType` | the datatype of the analyzed observable | readonly |
651+
| `dataType` | the datatype of the worker's input data | readonly |
517652
| `status` | Status of the job (`Waiting`, `InProgress`, `Success`, `Failure`, `Deleted`) | computed |
518-
| `data` | Value of the analyzed observable (does not apply to `file` observables) | readonly |
653+
| `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 |
519654
| `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 |
520655
| `parameters` | JSON object of key/value pairs set during job creation | readonly |
521656
| `message` | A free text field to set additional text/context for a job | readonly |
522657
| `tlp` | The TLP of the analyzed observable | readonly |
523-
| `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 |
658+
| `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 |
524659
| `startDate` | Start date | computed |
525660
| `endDate` | End date | computed |
526661
| `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 |

cortex4py/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .controllers.users import UsersController
1111
from .controllers.jobs import JobsController
1212
from .controllers.analyzers import AnalyzersController
13+
from .controllers.responders import RespondersController
1314

1415

1516
class Api(object):
@@ -33,6 +34,7 @@ def __init__(self, url, api_key, **kwargs):
3334
self.users = UsersController(self)
3435
self.jobs = JobsController(self)
3536
self.analyzers = AnalyzersController(self)
37+
self.responders = RespondersController(self)
3638

3739
@staticmethod
3840
def __recover(exception):
@@ -151,8 +153,8 @@ def get_analyzers(self, data_type=None):
151153
'api.get_analyzers() is considered deprecated. Use api.analyzers.get_by_[id|name|type]() instead.',
152154
DeprecationWarning
153155
)
154-
if data_type is not None:
155-
return self.analyzers.find_all()
156+
if data_type is None:
157+
return self.analyzers.find_all({})
156158
else:
157159
return self.analyzers.get_by_type(data_type)
158160

cortex4py/controllers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
from .users import UsersController
55
from .jobs import JobsController
66
from .analyzers import AnalyzersController
7+
from .responders import RespondersController

cortex4py/controllers/analyzers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from cortex4py.query import *
88
from .abstract import AbstractController
99
from ..models import Analyzer, Job, AnalyzerDefinition
10+
from ..exceptions import CortexError
1011

1112

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

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

5355
post = {
5456
'dataType': data_type,
55-
'tlp': tlp
57+
'tlp': tlp,
58+
'pap': pap
5659
}
5760

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

91+
if analyzer is None:
92+
raise CortexError("Analyzer %s not found" % analyzer_name)
93+
8894
return self.run_by_id(analyzer.id, observable, **kwargs)

0 commit comments

Comments
 (0)