Skip to content

Commit 3978d77

Browse files
committed
Merge branch 'release/2.0.0'
2 parents 949e886 + f63222d commit 3978d77

File tree

6 files changed

+119
-58
lines changed

6 files changed

+119
-58
lines changed

cortexutils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def runner(worker_cls):
2+
worker = worker_cls()
3+
worker.run()

cortexutils/analyzer.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
# encoding: utf-8
33

44
import json
5-
from cortexutils.worker import Worker
5+
import os
6+
67
from cortexutils.extractor import Extractor
8+
from cortexutils.worker import Worker
9+
from shutil import copyfileobj
10+
import tempfile
11+
import ntpath
712

813

914
class Analyzer(Worker):
1015

11-
def __init__(self):
12-
Worker.__init__(self)
16+
def __init__(self, job_directory=None):
17+
Worker.__init__(self, job_directory)
1318

1419
# Not breaking compatibility
1520
self.artifact = self._input
@@ -23,7 +28,17 @@ def get_data(self):
2328
:return: Data (observable value) given through Cortex"""
2429
if self.data_type == 'file':
2530
return self.get_param('filename', None, 'Missing filename.')
26-
return self.get_param('data', None, 'Missing data field')
31+
else:
32+
return self.get_param('data', None, 'Missing data field')
33+
34+
def get_param(self, name, default=None, message=None):
35+
data = super(Analyzer, self).get_param(name, default, message)
36+
if name == 'file' and self.data_type == 'file' and self.job_directory is not None:
37+
path = '%s/input/%s' % (self.job_directory, data)
38+
if os.path.isfile(path):
39+
return path
40+
else:
41+
return data
2742

2843
def build_taxonomy(self, level, namespace, predicate, value):
2944
"""
@@ -37,11 +52,11 @@ def build_taxonomy(self, level, namespace, predicate, value):
3752
if level not in ['info', 'safe', 'suspicious', 'malicious']:
3853
level = 'info'
3954
return {
40-
'level': level,
41-
'namespace': namespace,
42-
'predicate': predicate,
43-
'value': value
44-
}
55+
'level': level,
56+
'namespace': namespace,
57+
'predicate': predicate,
58+
'value': value
59+
}
4560

4661
def summary(self, raw):
4762
"""Returns a summary, needed for 'short.html' template. Overwrite it for your needs!
@@ -58,6 +73,19 @@ def artifacts(self, raw):
5873
# Return empty list
5974
return []
6075

76+
def build_artifact(self, data_type, data, **kwargs):
77+
if data_type == 'file':
78+
if os.path.isfile(data):
79+
(dst, filename) = tempfile.mkstemp(dir=os.path.join(self.job_directory, "output"))
80+
with open(data, 'r') as src:
81+
copyfileobj(src, os.fdopen(dst, 'w'))
82+
kwargs.update({'dataType': data_type, 'file': ntpath.basename(filename),
83+
'filename': ntpath.basename(data)})
84+
return kwargs
85+
else:
86+
kwargs.update({'dataType': data_type, 'data': data})
87+
return kwargs
88+
6189
def report(self, full_report, ensure_ascii=False):
6290
"""Returns a json dict via stdout.
6391
@@ -70,13 +98,12 @@ def report(self, full_report, ensure_ascii=False):
7098
except Exception:
7199
pass
72100

73-
report = {
101+
super(Analyzer, self).report({
74102
'success': True,
75103
'summary': summary,
76104
'artifacts': self.artifacts(full_report),
77105
'full': full_report
78-
}
79-
json.dump(report, self.fpoutput, ensure_ascii=ensure_ascii)
106+
}, ensure_ascii)
80107

81108
def run(self):
82109
"""Overwritten by analyzers"""
@@ -103,4 +130,4 @@ def getParam(self, name, default=None, message=None):
103130
# Not breaking compatibility
104131
def checkTlp(self, message):
105132
if not (self.__check_tlp()):
106-
self.error(message)
133+
self.error(message)

cortexutils/extractor.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ def check_iterable(self, iterable):
164164
dt = self.__checktype(iterable)
165165
if len(dt) > 0:
166166
results.append({
167-
'type': dt,
168-
'value': iterable
167+
'dataType': dt,
168+
'data': iterable
169169
})
170170
elif isinstance(iterable, list):
171171
for item in iterable:
@@ -175,8 +175,8 @@ def check_iterable(self, iterable):
175175
dt = self.__checktype(item)
176176
if len(dt) > 0:
177177
results.append({
178-
'type': dt,
179-
'value': item
178+
'dataType': dt,
179+
'data': item
180180
})
181181
elif isinstance(iterable, dict):
182182
for _, item in iterable.items():
@@ -186,10 +186,22 @@ def check_iterable(self, iterable):
186186
dt = self.__checktype(item)
187187
if len(dt) > 0:
188188
results.append({
189-
'type': dt,
190-
'value': item
189+
'dataType': dt,
190+
'data': item
191191
})
192192
else:
193193
raise TypeError('Not supported type.')
194194

195-
return results
195+
return self.deduplicate(results)
196+
197+
@staticmethod
198+
def deduplicate(list_of_objects):
199+
dedup_list = []
200+
for obj in list_of_objects:
201+
present = False
202+
for new_object in dedup_list:
203+
if obj['dataType'] == new_object['dataType'] and obj['data'] == new_object['data']:
204+
present = True
205+
if not present:
206+
dedup_list.append(obj)
207+
return dedup_list

cortexutils/responder.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
# encoding: utf-8
33

44
import json
5+
import os
56
from cortexutils.worker import Worker
67

78

89
class Responder(Worker):
910

10-
def __init__(self):
11-
Worker.__init__(self)
11+
def __init__(self, job_directory=None):
12+
Worker.__init__(self, job_directory)
1213

1314
# Not breaking compatibility
1415
self.artifact = self._input
@@ -50,13 +51,11 @@ def report(self, full_report, ensure_ascii=False):
5051
operation_list = self.operations(full_report)
5152
except Exception:
5253
pass
53-
54-
report = {
54+
super(Responder, self).report({
5555
'success': True,
5656
'full': full_report,
5757
'operations': operation_list
58-
}
59-
json.dump(report, self.fpoutput, ensure_ascii=ensure_ascii)
58+
}, ensure_ascii)
6059

6160
def run(self):
6261
"""Overwritten by responders"""

cortexutils/worker.py

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
#!/usr/bin/env python
22
# encoding: utf-8
3+
34
import os
45
import sys
56
import codecs
67
import json
8+
import select
79

810

9-
class Worker:
10-
11-
def __init__(self):
12-
self.__set_encoding()
13-
14-
# Prepare in/out/err streams
15-
self.fperror = sys.stderr
16-
self.fpinput = sys.stdin
17-
self.fpoutput = sys.stdout
11+
class Worker(object):
12+
READ_TIMEOUT = 3 # seconds
1813

14+
def __init__(self, job_directory):
15+
if job_directory is None:
16+
if len(sys.argv) > 1:
17+
job_directory = sys.argv[1]
18+
else:
19+
job_directory = '/job'
20+
self.job_directory = job_directory
1921
# Load input
20-
self._input = json.load(self.fpinput)
22+
self._input = {}
23+
if os.path.isfile('%s/input/input.json' % self.job_directory):
24+
with open('%s/input/input.json' % self.job_directory) as f_input:
25+
self._input = json.load(f_input)
26+
else: # If input file doesn't exist, fallback to old behavior and read input from stdin
27+
self.job_directory = None
28+
self.__set_encoding()
29+
r, w, e = select.select([sys.stdin], [], [], self.READ_TIMEOUT)
30+
if sys.stdin in r:
31+
self._input = json.load(sys.stdin)
32+
else:
33+
self.error('Input file doesn''t exist')
2134

2235
# Set parameters
2336
self.data_type = self.get_param('dataType', None, 'Missing dataType field')
@@ -98,10 +111,21 @@ def __check_pap(self):
98111

99112
return not (self.enable_check_pap and self.pap > self.max_pap)
100113

114+
def __write_output(self, data, ensure_ascii=False):
115+
if self.job_directory is None:
116+
json.dump(data, sys.stdout, ensure_ascii=ensure_ascii)
117+
else:
118+
try:
119+
os.makedirs('%s/output' % self.job_directory)
120+
except:
121+
pass
122+
with open('%s/output/output.json' % self.job_directory, mode='w') as f_output:
123+
json.dump(data, f_output, ensure_ascii=ensure_ascii)
124+
101125
def get_data(self):
102126
"""Wrapper for getting data from input dict.
103127
104-
:return: Data (observable value) given through Cortex"""
128+
:return: Data (observable value) given through Cortex"""
105129
return self.get_param('data', None, 'Missing data field')
106130

107131
def get_param(self, name, default=None, message=None):
@@ -128,34 +152,30 @@ def error(self, message, ensure_ascii=False):
128152
if 'api_key' in analyzer_input.get('config', {}):
129153
analyzer_input['config']['api_key'] = 'REMOVED'
130154

131-
json.dump({'success': False,
132-
'input': analyzer_input,
133-
'errorMessage': message},
134-
self.fpoutput,
135-
ensure_ascii=ensure_ascii)
155+
self.__write_output({'success': False,
156+
'input': analyzer_input,
157+
'errorMessage': message},
158+
ensure_ascii=ensure_ascii)
136159

137160
# Force exit after error
138161
sys.exit(1)
139162

140-
def report(self, full_report, ensure_ascii=False):
163+
def summary(self, raw):
164+
"""Returns a summary, needed for 'short.html' template. Overwrite it for your needs!
165+
166+
:returns: by default return an empty dict"""
167+
return {}
168+
169+
def artifacts(self, raw):
170+
return []
171+
172+
def report(self, output, ensure_ascii=False):
141173
"""Returns a json dict via stdout.
142174
143-
:param full_report: Analyzer results as dict.
175+
:param output: worker output.
144176
:param ensure_ascii: Force ascii output. Default: False"""
145177

146-
summary = {}
147-
try:
148-
summary = self.summary(full_report)
149-
except Exception:
150-
pass
151-
152-
report = {
153-
'success': True,
154-
'summary': summary,
155-
'artifacts': self.artifacts(full_report),
156-
'full': full_report
157-
}
158-
json.dump(report, self.fpoutput, ensure_ascii=ensure_ascii)
178+
self.__write_output(output, ensure_ascii=ensure_ascii)
159179

160180
def run(self):
161181
"""Overwritten by analyzers"""

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='cortexutils',
5-
version='1.3.0',
5+
version='2.0.0',
66
description='A Python library for including utility classes for Cortex analyzers and responders',
77
long_description=open('README').read(),
88
author='TheHive-Project',

0 commit comments

Comments
 (0)