polyswarm-api 3.5.2__tar.gz → 3.7.0__tar.gz
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.
- {polyswarm-api-3.5.2/src/polyswarm_api.egg-info → polyswarm-api-3.7.0}/PKG-INFO +7 -3
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/README.md +1 -1
- polyswarm-api-3.7.0/requirements.txt +7 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/setup.py +8 -6
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/__init__.py +1 -1
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/api.py +62 -21
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/core.py +24 -29
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/exceptions.py +1 -1
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/resources.py +153 -66
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0/src/polyswarm_api.egg-info}/PKG-INFO +7 -3
- polyswarm-api-3.7.0/src/polyswarm_api.egg-info/requires.txt +2 -0
- polyswarm-api-3.5.2/requirements.txt +0 -7
- polyswarm-api-3.5.2/src/polyswarm_api.egg-info/requires.txt +0 -6
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/LICENSE +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/MANIFEST.in +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/setup.cfg +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/settings.py +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/SOURCES.txt +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/dependency_links.txt +0 -0
- {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: polyswarm-api
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.0
|
|
4
4
|
Summary: Client library to simplify interacting with the PolySwarm consumer API
|
|
5
5
|
Home-page: https://github.com/polyswarm/polyswarm-api
|
|
6
6
|
Author: PolySwarm Developers
|
|
@@ -11,8 +11,12 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.7
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
18
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
-
Requires-Python:
|
|
19
|
+
Requires-Python: >=3.7,<4
|
|
16
20
|
Description-Content-Type: text/markdown
|
|
17
21
|
License-File: LICENSE
|
|
18
22
|
|
|
@@ -21,7 +25,7 @@ License-File: LICENSE
|
|
|
21
25
|
An interface to the PolySwarm Customer APIs.
|
|
22
26
|
For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
|
|
23
27
|
|
|
24
|
-
Supports Python
|
|
28
|
+
Supports Python 3.7 and greater.
|
|
25
29
|
|
|
26
30
|
## Installation
|
|
27
31
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
An interface to the PolySwarm Customer APIs.
|
|
4
4
|
For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
|
|
5
5
|
|
|
6
|
-
Supports Python
|
|
6
|
+
Supports Python 3.7 and greater.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -9,7 +9,7 @@ with open('README.md', 'r') as readme:
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name='polyswarm-api',
|
|
12
|
-
version='3.
|
|
12
|
+
version='3.7.0',
|
|
13
13
|
description='Client library to simplify interacting with the PolySwarm consumer API',
|
|
14
14
|
long_description=long_description,
|
|
15
15
|
long_description_content_type='text/markdown',
|
|
@@ -17,13 +17,11 @@ setup(
|
|
|
17
17
|
author_email='info@polyswarm.io',
|
|
18
18
|
url='https://github.com/polyswarm/polyswarm-api',
|
|
19
19
|
license='MIT',
|
|
20
|
-
python_requires='
|
|
20
|
+
python_requires='>=3.7,<4',
|
|
21
21
|
install_requires=[
|
|
22
|
-
'requests~=2.
|
|
23
|
-
'
|
|
24
|
-
'python-dateutil~=2.8',
|
|
22
|
+
'requests~=2.31',
|
|
23
|
+
'python-dateutil~=2.9',
|
|
25
24
|
],
|
|
26
|
-
extras_require={':python_version < "3.0"': ['enum34==1.1.6']},
|
|
27
25
|
include_package_data=True,
|
|
28
26
|
packages=find_packages('src'),
|
|
29
27
|
package_dir={'': 'src'},
|
|
@@ -33,6 +31,10 @@ setup(
|
|
|
33
31
|
'Operating System :: OS Independent',
|
|
34
32
|
'Programming Language :: Python :: 3.7',
|
|
35
33
|
'Programming Language :: Python :: 3.8',
|
|
34
|
+
'Programming Language :: Python :: 3.9',
|
|
35
|
+
'Programming Language :: Python :: 3.10',
|
|
36
|
+
'Programming Language :: Python :: 3.11',
|
|
37
|
+
'Programming Language :: Python :: 3.12',
|
|
36
38
|
'Programming Language :: Python :: Implementation :: PyPy',
|
|
37
39
|
]
|
|
38
40
|
)
|
|
@@ -3,19 +3,14 @@ import time
|
|
|
3
3
|
|
|
4
4
|
import polyswarm_api.core
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from urllib.parse import urlparse
|
|
8
|
-
except ImportError:
|
|
9
|
-
from urlparse import urlparse
|
|
10
|
-
|
|
11
|
-
from future.utils import string_types
|
|
6
|
+
from urllib.parse import urlparse
|
|
12
7
|
|
|
13
8
|
from polyswarm_api import exceptions, resources, settings
|
|
14
9
|
|
|
15
10
|
logger = logging.getLogger(__name__)
|
|
16
11
|
|
|
17
12
|
|
|
18
|
-
class PolyswarmAPI
|
|
13
|
+
class PolyswarmAPI:
|
|
19
14
|
"""A synchronous interface to the public and private PolySwarm APIs."""
|
|
20
15
|
|
|
21
16
|
def __init__(self, key, uri=None, community=None, timeout=None, verify=True, **kwargs):
|
|
@@ -35,6 +30,11 @@ class PolyswarmAPI(object):
|
|
|
35
30
|
self.session = polyswarm_api.core.PolyswarmSession(key, retries=settings.DEFAULT_RETRIES, verify=verify, **kwargs)
|
|
36
31
|
self._engines = None
|
|
37
32
|
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
clsname = f'{self.__class__.__module__}.{self.__class__.__name__}'
|
|
35
|
+
attrs = f'uri={self.uri!r}, community={self.community!r}, timeout={self.timeout!r}'
|
|
36
|
+
return f'<{clsname}({attrs}) at 0x{id(self):x}>'
|
|
37
|
+
|
|
38
38
|
@property
|
|
39
39
|
def engines(self):
|
|
40
40
|
if not self._engines:
|
|
@@ -44,11 +44,11 @@ class PolyswarmAPI(object):
|
|
|
44
44
|
|
|
45
45
|
def refresh_engine_cache(self):
|
|
46
46
|
"""
|
|
47
|
-
|
|
47
|
+
Refresh the cached engine listing
|
|
48
48
|
"""
|
|
49
49
|
engines = list(resources.Engine.list(self).result())
|
|
50
50
|
if not engines:
|
|
51
|
-
raise exceptions.InvalidValueException("
|
|
51
|
+
raise exceptions.InvalidValueException("Received empty engines listing")
|
|
52
52
|
self._engines = engines
|
|
53
53
|
|
|
54
54
|
def wait_for(self, scan, timeout=settings.DEFAULT_SCAN_TIMEOUT):
|
|
@@ -66,8 +66,8 @@ class PolyswarmAPI(object):
|
|
|
66
66
|
if scan_result.failed or scan_result.window_closed:
|
|
67
67
|
return scan_result
|
|
68
68
|
elif -1 < timeout < time.time() - start:
|
|
69
|
-
raise exceptions.TimeoutException(
|
|
70
|
-
|
|
69
|
+
raise exceptions.TimeoutException(
|
|
70
|
+
f'Timed out waiting for scan {scan} to finish. Please try again.')
|
|
71
71
|
else:
|
|
72
72
|
time.sleep(settings.POLL_FREQUENCY)
|
|
73
73
|
|
|
@@ -229,7 +229,7 @@ class PolyswarmAPI(object):
|
|
|
229
229
|
if hasattr(artifact, 'read') and hasattr(artifact.read, '__call__'):
|
|
230
230
|
artifact = resources.LocalArtifact.from_handle(self, artifact, artifact_name=artifact_name or '',
|
|
231
231
|
artifact_type=artifact_type)
|
|
232
|
-
elif isinstance(artifact,
|
|
232
|
+
elif isinstance(artifact, str):
|
|
233
233
|
if artifact_type == resources.ArtifactType.FILE:
|
|
234
234
|
artifact = resources.LocalArtifact.from_path(self, artifact, artifact_type=artifact_type,
|
|
235
235
|
artifact_name=artifact_name)
|
|
@@ -245,7 +245,7 @@ class PolyswarmAPI(object):
|
|
|
245
245
|
scan_config=scan_config,
|
|
246
246
|
community=self.community).result()
|
|
247
247
|
instance.upload_file(artifact)
|
|
248
|
-
return resources.ArtifactInstance.update(self, id=instance.id).result()
|
|
248
|
+
return resources.ArtifactInstance.update(self, id=instance.id, community=self.community).result()
|
|
249
249
|
else:
|
|
250
250
|
raise exceptions.InvalidValueException('Artifacts should be a path to a file or a LocalArtifact instance')
|
|
251
251
|
|
|
@@ -284,7 +284,7 @@ class PolyswarmAPI(object):
|
|
|
284
284
|
return resources.ArtifactInstance.rescan_id(self, scan, scan_config=scan_config).result()
|
|
285
285
|
|
|
286
286
|
def _parse_rule(self, rule):
|
|
287
|
-
if isinstance(rule,
|
|
287
|
+
if isinstance(rule, str):
|
|
288
288
|
rule, rule_id = resources.YaraRuleset(dict(yara=rule), api=self), None
|
|
289
289
|
elif isinstance(rule, (resources.YaraRuleset, int)):
|
|
290
290
|
rule, rule_id = None, rule
|
|
@@ -699,7 +699,7 @@ class PolyswarmAPI(object):
|
|
|
699
699
|
if hasattr(artifact, 'read') and hasattr(artifact.read, '__call__'):
|
|
700
700
|
artifact = resources.LocalArtifact.from_handle(self, artifact, artifact_name=artifact_name or '',
|
|
701
701
|
artifact_type=artifact_type)
|
|
702
|
-
elif isinstance(artifact,
|
|
702
|
+
elif isinstance(artifact, str):
|
|
703
703
|
if artifact_type == resources.ArtifactType.FILE:
|
|
704
704
|
artifact = resources.LocalArtifact.from_path(self, artifact, artifact_type=artifact_type,
|
|
705
705
|
artifact_name=artifact_name)
|
|
@@ -716,7 +716,7 @@ class PolyswarmAPI(object):
|
|
|
716
716
|
vm_slug=vm_slug,
|
|
717
717
|
network_enabled=network_enabled).result()
|
|
718
718
|
task.upload_file(artifact)
|
|
719
|
-
return resources.SandboxTask.update_file(self, id=task.id).result()
|
|
719
|
+
return resources.SandboxTask.update_file(self, id=task.id, community=self.community).result()
|
|
720
720
|
else:
|
|
721
721
|
raise exceptions.InvalidValueException(
|
|
722
722
|
'Artifacts should be a path to a file or a LocalArtifact instance')
|
|
@@ -733,7 +733,7 @@ class PolyswarmAPI(object):
|
|
|
733
733
|
browser=browser,
|
|
734
734
|
network_enabled=True).result()
|
|
735
735
|
task.upload_file(artifact)
|
|
736
|
-
return resources.SandboxTask.update_file(self, id=task.id).result()
|
|
736
|
+
return resources.SandboxTask.update_file(self, id=task.id, community=self.community).result()
|
|
737
737
|
|
|
738
738
|
def sandbox_providers(self):
|
|
739
739
|
"""
|
|
@@ -823,7 +823,48 @@ class PolyswarmAPI(object):
|
|
|
823
823
|
logger.info('List events')
|
|
824
824
|
return resources.Events.list(self, **kwargs).result()
|
|
825
825
|
|
|
826
|
-
def
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
826
|
+
def report_create(self, **kwargs):
|
|
827
|
+
return resources.ReportTask.create(self, **kwargs).result()
|
|
828
|
+
|
|
829
|
+
def report_get(self, **kwargs):
|
|
830
|
+
return resources.ReportTask.get(self, **kwargs).result()
|
|
831
|
+
|
|
832
|
+
def report_download(self, report_id, folder):
|
|
833
|
+
report = resources.ReportTask.get(self, id=report_id).result()
|
|
834
|
+
result = report.download_report(folder=folder).result()
|
|
835
|
+
result.handle.close()
|
|
836
|
+
return result
|
|
837
|
+
|
|
838
|
+
def report_template_logo_download(self, template_id, folder):
|
|
839
|
+
report = resources.ReportTemplate.get(self, id=template_id).result()
|
|
840
|
+
result = report.download_logo(folder).result()
|
|
841
|
+
result.handle.close()
|
|
842
|
+
return result
|
|
843
|
+
|
|
844
|
+
def report_template_logo_delete(self, template_id):
|
|
845
|
+
report = resources.ReportTemplate.get(self, id=template_id).result()
|
|
846
|
+
result = report.delete_logo().result()
|
|
847
|
+
return result
|
|
848
|
+
|
|
849
|
+
def report_template_logo_upload(self, template_id, logo_file, content_tpe):
|
|
850
|
+
report = resources.ReportTemplate.get(self, id=template_id).result()
|
|
851
|
+
result = report.upload_logo(logo_file, content_tpe).result()
|
|
852
|
+
return result
|
|
853
|
+
|
|
854
|
+
def report_template_create(self, **kwargs):
|
|
855
|
+
return resources.ReportTemplate.create(self, **kwargs).result()
|
|
856
|
+
|
|
857
|
+
def report_template_update(self, template_id, **kwargs):
|
|
858
|
+
return resources.ReportTemplate.update(self, id=template_id, **kwargs).result()
|
|
859
|
+
|
|
860
|
+
def report_template_get(self, template_id):
|
|
861
|
+
return resources.ReportTemplate.get(self, id=template_id).result()
|
|
862
|
+
|
|
863
|
+
def report_template_delete(self, template_id):
|
|
864
|
+
return resources.ReportTemplate.delete(self, id=template_id).result()
|
|
865
|
+
|
|
866
|
+
def report_template_list(self, is_default=None):
|
|
867
|
+
params = {}
|
|
868
|
+
if is_default is not None:
|
|
869
|
+
params['is_default'] = is_default
|
|
870
|
+
return resources.ReportTemplate.list(self, **params).result()
|
|
@@ -3,15 +3,11 @@ import logging
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from urllib3 import Retry
|
|
5
5
|
from binascii import unhexlify
|
|
6
|
-
|
|
7
|
-
from json.decoder import JSONDecodeError
|
|
8
|
-
except ImportError:
|
|
9
|
-
JSONDecodeError = ValueError
|
|
6
|
+
from json.decoder import JSONDecodeError
|
|
10
7
|
|
|
11
8
|
import requests
|
|
12
9
|
import datetime as dt
|
|
13
10
|
from dateutil import parser
|
|
14
|
-
from future.utils import raise_from
|
|
15
11
|
from requests.adapters import HTTPAdapter
|
|
16
12
|
from urllib3.exceptions import InsecureRequestWarning
|
|
17
13
|
|
|
@@ -22,7 +18,7 @@ logger = logging.getLogger(__name__)
|
|
|
22
18
|
|
|
23
19
|
class PolyswarmSession(requests.Session):
|
|
24
20
|
def __init__(self, key, retries, user_agent=settings.DEFAULT_USER_AGENT, verify=True, **kwargs):
|
|
25
|
-
super(
|
|
21
|
+
super().__init__(**kwargs)
|
|
26
22
|
logger.debug('Creating PolyswarmHTTP instance')
|
|
27
23
|
self.requests_retry_session(retries=retries)
|
|
28
24
|
|
|
@@ -118,13 +114,12 @@ class PolyswarmRequest(object):
|
|
|
118
114
|
|
|
119
115
|
def _bad_status_message(self):
|
|
120
116
|
request_parameters = json.dumps(self.request_parameters, indent=4, sort_keys=True, cls=RequestParamsEncoder)
|
|
121
|
-
message = "Error when running the request:\n{}\n" \
|
|
122
|
-
"Return code: {}\n" \
|
|
123
|
-
"Message: {}"
|
|
124
|
-
self.status_code,
|
|
125
|
-
self._result)
|
|
117
|
+
message = f"Error when running the request:\n{request_parameters}\n" \
|
|
118
|
+
f"Return code: {self.status_code}\n" \
|
|
119
|
+
f"Message: {self._result}"
|
|
126
120
|
if self.errors:
|
|
127
|
-
|
|
121
|
+
errors = '\n'.join(str(error) for error in self.errors)
|
|
122
|
+
message = f'{message}\nErrors:\n{errors}'
|
|
128
123
|
return message
|
|
129
124
|
|
|
130
125
|
def _extract_json_body(self, result):
|
|
@@ -146,10 +141,10 @@ class PolyswarmRequest(object):
|
|
|
146
141
|
if self.status_code // 100 != 2:
|
|
147
142
|
self._extract_json_body(result)
|
|
148
143
|
if self.status_code == 429:
|
|
149
|
-
message = '{} This may mean you need to purchase a ' \
|
|
144
|
+
message = f'{self._result} This may mean you need to purchase a ' \
|
|
150
145
|
'larger package, or that you have exceeded ' \
|
|
151
146
|
'rate limits. If you continue to have issues, ' \
|
|
152
|
-
'please contact us at info@polyswarm.io.'
|
|
147
|
+
'please contact us at info@polyswarm.io.'
|
|
153
148
|
raise exceptions.UsageLimitsExceededException(self, message)
|
|
154
149
|
elif self.status_code == 404:
|
|
155
150
|
raise exceptions.NotFoundException(self, self._result)
|
|
@@ -185,10 +180,10 @@ class PolyswarmRequest(object):
|
|
|
185
180
|
self._result = self.result_parser.parse_result(self.api_instance, result, **self.parser_kwargs)
|
|
186
181
|
except JSONDecodeError as e:
|
|
187
182
|
if self.status_code == 404:
|
|
188
|
-
raise
|
|
183
|
+
raise exceptions.NotFoundException(self, 'The requested endpoint does not exist.') from e
|
|
189
184
|
else:
|
|
190
|
-
err_msg = 'Server returned non-JSON response [{}]: {}'
|
|
191
|
-
raise
|
|
185
|
+
err_msg = f'Server returned non-JSON response [{self.status_code}]: {result}'
|
|
186
|
+
raise exceptions.RequestException(self, err_msg) from e
|
|
192
187
|
|
|
193
188
|
def __iter__(self):
|
|
194
189
|
return self.consume_results()
|
|
@@ -232,12 +227,12 @@ class PolyswarmRequest(object):
|
|
|
232
227
|
).execute()
|
|
233
228
|
|
|
234
229
|
|
|
235
|
-
class BaseResource
|
|
230
|
+
class BaseResource:
|
|
236
231
|
def __init__(self, content, *args, **kwargs):
|
|
237
232
|
# hack to behave as in python 3, signature should be
|
|
238
233
|
# __init__(self, content, *args, api=None, **kwargs)
|
|
239
234
|
api = kwargs.pop('api', None)
|
|
240
|
-
super(
|
|
235
|
+
super().__init__(*args, **kwargs)
|
|
241
236
|
self.api = api
|
|
242
237
|
self._content = content
|
|
243
238
|
|
|
@@ -252,13 +247,13 @@ class BaseJsonResource(BaseResource):
|
|
|
252
247
|
RESOURCE_ID_KEYS = ['id']
|
|
253
248
|
|
|
254
249
|
def __init__(self, content, *args, **kwargs):
|
|
255
|
-
super(
|
|
250
|
+
super().__init__(content, *args, **kwargs)
|
|
256
251
|
self.json = content
|
|
257
252
|
|
|
258
253
|
def __int__(self):
|
|
259
254
|
id_ = getattr(self, 'id', None)
|
|
260
255
|
if id_ is None:
|
|
261
|
-
raise TypeError('Resource {} does not have an id and can not be cast to int'
|
|
256
|
+
raise TypeError(f'Resource {type(self).__name__} does not have an id and can not be cast to int')
|
|
262
257
|
return int(id_)
|
|
263
258
|
|
|
264
259
|
def _get(self, path, default=None, content=None):
|
|
@@ -271,16 +266,16 @@ class BaseJsonResource(BaseResource):
|
|
|
271
266
|
try:
|
|
272
267
|
for attribute in path.split('.'):
|
|
273
268
|
if obj is None:
|
|
274
|
-
raise KeyError('{} is None, can not resolve full path'
|
|
269
|
+
raise KeyError(f'{previous_attribute} is None, can not resolve full path')
|
|
275
270
|
if attribute.endswith(']'):
|
|
276
271
|
# handling the list case, e.g.: "root.list_attr[2]"
|
|
277
272
|
attribute, _, index = attribute.rpartition('[')
|
|
278
273
|
index = int(index.rstrip(']'))
|
|
279
274
|
obj = obj[attribute]
|
|
280
275
|
if obj is None:
|
|
281
|
-
raise KeyError('{} is None, but is it supposed to be a list'
|
|
276
|
+
raise KeyError(f'{attribute} is None, but is it supposed to be a list')
|
|
282
277
|
elif not isinstance(obj, list):
|
|
283
|
-
raise ValueError('Can not access index for {}, it is not a list.'
|
|
278
|
+
raise ValueError(f'Can not access index for {attribute}, it is not a list.')
|
|
284
279
|
else:
|
|
285
280
|
obj = obj[index]
|
|
286
281
|
else:
|
|
@@ -469,7 +464,7 @@ def is_valid_sha256(value):
|
|
|
469
464
|
return is_hex(value)
|
|
470
465
|
|
|
471
466
|
|
|
472
|
-
class Hashable
|
|
467
|
+
class Hashable:
|
|
473
468
|
SUPPORTED_HASH_TYPES = {
|
|
474
469
|
'sha1': is_valid_sha1,
|
|
475
470
|
'sha256': is_valid_sha256,
|
|
@@ -482,7 +477,7 @@ class Hashable(object):
|
|
|
482
477
|
hash_value = kwargs.pop('hash_value', None)
|
|
483
478
|
hash_type = kwargs.pop('hash_type', None)
|
|
484
479
|
validate_hash = kwargs.pop('validate_hash', False)
|
|
485
|
-
super(
|
|
480
|
+
super().__init__(*args, **kwargs)
|
|
486
481
|
|
|
487
482
|
self._hash = hash_value.strip() if hash_value is not None else None
|
|
488
483
|
|
|
@@ -494,7 +489,7 @@ class Hashable(object):
|
|
|
494
489
|
self._hash_type = self.resolve_hash_type()
|
|
495
490
|
|
|
496
491
|
if self._hash_type is None:
|
|
497
|
-
raise exceptions.InvalidValueException('Invalid hash provided: {
|
|
492
|
+
raise exceptions.InvalidValueException(f'Invalid hash provided: {self._hash}')
|
|
498
493
|
|
|
499
494
|
if validate_hash:
|
|
500
495
|
self.validate()
|
|
@@ -514,8 +509,8 @@ class Hashable(object):
|
|
|
514
509
|
def validate(self):
|
|
515
510
|
hash_type = self.resolve_hash_type()
|
|
516
511
|
if self.hash_type != hash_type:
|
|
517
|
-
raise exceptions.InvalidValueException(
|
|
518
|
-
|
|
512
|
+
raise exceptions.InvalidValueException(
|
|
513
|
+
f'Detected hash type {hash_type}, got type {self.hash_type} for hash {self.hash}')
|
|
519
514
|
|
|
520
515
|
def resolve_hash_type(self):
|
|
521
516
|
for hash_type, validator in self.SUPPORTED_HASH_TYPES.items():
|
|
@@ -5,15 +5,9 @@ import functools
|
|
|
5
5
|
import requests
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from hashlib import sha256 as _sha256, sha1 as _sha1, md5 as _md5
|
|
8
|
+
from urllib.parse import urlparse
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
from urllib.parse import urlparse
|
|
11
|
-
except ImportError:
|
|
12
|
-
from urlparse import urlparse
|
|
13
|
-
|
|
14
|
-
from future.utils import raise_from, string_types
|
|
15
|
-
|
|
16
|
-
# Windows might rase an OSError instead of an ImportError like this
|
|
10
|
+
# Windows might raise an OSError instead of an ImportError like this
|
|
17
11
|
# OSError: [WinError 193] %1 is not a valid Win32 application
|
|
18
12
|
try:
|
|
19
13
|
import yara
|
|
@@ -33,7 +27,7 @@ class Engine(core.BaseJsonResource):
|
|
|
33
27
|
RESOURCE_ENDPOINT = '/microengines'
|
|
34
28
|
|
|
35
29
|
def __init__(self, content, api=None):
|
|
36
|
-
super(
|
|
30
|
+
super().__init__(content=content, api=api)
|
|
37
31
|
self.id = str(content['id'])
|
|
38
32
|
self.name = content['name']
|
|
39
33
|
|
|
@@ -95,7 +89,7 @@ class Metadata(core.BaseJsonResource):
|
|
|
95
89
|
KNOWN_KEYS = {'artifact', 'exiftool', 'hash', 'lief', 'pefile', 'scan', 'strings'}
|
|
96
90
|
|
|
97
91
|
def __init__(self, content, api=None):
|
|
98
|
-
super(
|
|
92
|
+
super().__init__(content=content, api=api)
|
|
99
93
|
self.created = core.parse_isoformat(self.artifact.get('created'))
|
|
100
94
|
|
|
101
95
|
self.id = self._get('artifact.id')
|
|
@@ -144,7 +138,7 @@ class Metadata(core.BaseJsonResource):
|
|
|
144
138
|
params.extend(('ips', v) for v in ips)
|
|
145
139
|
params.extend(('urls', v) for v in urls)
|
|
146
140
|
params.extend(('domains', v) for v in domains)
|
|
147
|
-
super_params, json_params = super(
|
|
141
|
+
super_params, json_params = super()._get_params(**kwargs)
|
|
148
142
|
params.extend(super_params.items())
|
|
149
143
|
return params, json_params
|
|
150
144
|
|
|
@@ -159,7 +153,7 @@ class IOC(core.BaseJsonResource):
|
|
|
159
153
|
api,
|
|
160
154
|
{
|
|
161
155
|
'method': 'GET',
|
|
162
|
-
'url': '{}/{}/{}/{}'
|
|
156
|
+
'url': f'{api.uri}/{path}/{hash_type}/{hash_value}',
|
|
163
157
|
'params': {
|
|
164
158
|
'hide_known_good': hide_known_good,
|
|
165
159
|
'community': api.community,
|
|
@@ -183,7 +177,7 @@ class IOC(core.BaseJsonResource):
|
|
|
183
177
|
api,
|
|
184
178
|
{
|
|
185
179
|
'method': 'GET',
|
|
186
|
-
'url': '{}/ioc/search'
|
|
180
|
+
'url': f'{api.uri}/ioc/search',
|
|
187
181
|
'params': params
|
|
188
182
|
},
|
|
189
183
|
result_parser=cls,
|
|
@@ -195,7 +189,7 @@ class IOC(core.BaseJsonResource):
|
|
|
195
189
|
api,
|
|
196
190
|
{
|
|
197
191
|
'method': 'GET',
|
|
198
|
-
'url': '{}/ioc/known'
|
|
192
|
+
'url': f'{api.uri}/ioc/known',
|
|
199
193
|
'params': {
|
|
200
194
|
'ip': ips,
|
|
201
195
|
'domain': domains
|
|
@@ -210,7 +204,7 @@ class IOC(core.BaseJsonResource):
|
|
|
210
204
|
api,
|
|
211
205
|
{
|
|
212
206
|
'method': 'POST',
|
|
213
|
-
'url': '{}/ioc/known'
|
|
207
|
+
'url': f'{api.uri}/ioc/known',
|
|
214
208
|
'json': {
|
|
215
209
|
'type': type,
|
|
216
210
|
'host': host,
|
|
@@ -227,7 +221,7 @@ class IOC(core.BaseJsonResource):
|
|
|
227
221
|
api,
|
|
228
222
|
{
|
|
229
223
|
'method': 'POST',
|
|
230
|
-
'url': '{}/ioc/known'
|
|
224
|
+
'url': f'{api.uri}/ioc/known',
|
|
231
225
|
'json': {
|
|
232
226
|
'type': type,
|
|
233
227
|
'host': host,
|
|
@@ -244,7 +238,7 @@ class IOC(core.BaseJsonResource):
|
|
|
244
238
|
api,
|
|
245
239
|
{
|
|
246
240
|
'method': 'PUT',
|
|
247
|
-
'url': '{}/ioc/known'
|
|
241
|
+
'url': f'{api.uri}/ioc/known',
|
|
248
242
|
'json': {
|
|
249
243
|
'id': id,
|
|
250
244
|
'type': type,
|
|
@@ -262,7 +256,7 @@ class IOC(core.BaseJsonResource):
|
|
|
262
256
|
api,
|
|
263
257
|
{
|
|
264
258
|
'method': 'DELETE',
|
|
265
|
-
'url': '{}/ioc/known'
|
|
259
|
+
'url': f'{api.uri}/ioc/known',
|
|
266
260
|
'params': {
|
|
267
261
|
'id': id
|
|
268
262
|
}
|
|
@@ -275,8 +269,8 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
275
269
|
RESOURCE_ENDPOINT = '/instance'
|
|
276
270
|
|
|
277
271
|
def __init__(self, content, api=None):
|
|
278
|
-
super(
|
|
279
|
-
|
|
272
|
+
super().__init__(content=content, api=api,
|
|
273
|
+
hash_value=content['sha256'], hash_type='sha256')
|
|
280
274
|
# Artifact fields
|
|
281
275
|
self.sha256 = content['sha256']
|
|
282
276
|
self.artifact_id = content.get('artifact_id')
|
|
@@ -307,7 +301,10 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
307
301
|
self.votes = [Vote(v, api=api, scanfile=self) for v in content.get('votes', [])]
|
|
308
302
|
self.window_closed = content.get('window_closed')
|
|
309
303
|
self.polyscore = float(content['polyscore']) if content.get('polyscore') is not None else None
|
|
310
|
-
|
|
304
|
+
if content.get('permalink'):
|
|
305
|
+
self.permalink = content.get('permalink')
|
|
306
|
+
else:
|
|
307
|
+
self.permalink = settings.DEFAULT_PERMALINK_BASE + '/' + str(self.hash) + '/' + str(self.id)
|
|
311
308
|
|
|
312
309
|
self._malicious_assertions = None
|
|
313
310
|
self._benign_assertions = None
|
|
@@ -339,7 +336,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
339
336
|
api,
|
|
340
337
|
{
|
|
341
338
|
'method': 'HEAD',
|
|
342
|
-
'url': '{}/search/hash/{}'
|
|
339
|
+
'url': f'{api.uri}/search/hash/{hash_type}',
|
|
343
340
|
'params': {
|
|
344
341
|
'hash': hash_value,
|
|
345
342
|
'community': api.community,
|
|
@@ -353,7 +350,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
353
350
|
api,
|
|
354
351
|
{
|
|
355
352
|
'method': 'GET',
|
|
356
|
-
'url': '{}/search/hash/{}'
|
|
353
|
+
'url': f'{api.uri}/search/hash/{hash_type}',
|
|
357
354
|
'params': {
|
|
358
355
|
'hash': hash_value,
|
|
359
356
|
'community': api.community,
|
|
@@ -368,7 +365,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
368
365
|
api,
|
|
369
366
|
{
|
|
370
367
|
'method': 'GET',
|
|
371
|
-
'url': '{}/search/url'
|
|
368
|
+
'url': f'{api.uri}/search/url',
|
|
372
369
|
'params': {
|
|
373
370
|
'url': url,
|
|
374
371
|
'community': api.community,
|
|
@@ -383,7 +380,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
383
380
|
api,
|
|
384
381
|
{
|
|
385
382
|
'method': 'GET',
|
|
386
|
-
'url': '{}/search/instances'
|
|
383
|
+
'url': f'{api.uri}/search/instances',
|
|
387
384
|
'params': {
|
|
388
385
|
'hash': hash_value,
|
|
389
386
|
'community': api.community,
|
|
@@ -396,7 +393,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
396
393
|
def submit(cls, api, artifact, artifact_name, artifact_type, scan_config=None):
|
|
397
394
|
parameters = {
|
|
398
395
|
'method': 'POST',
|
|
399
|
-
'url': '{}/consumer/submission/{
|
|
396
|
+
'url': f'{api.uri}/consumer/submission/{api.community}',
|
|
400
397
|
'files': {
|
|
401
398
|
'file': (artifact_name, artifact),
|
|
402
399
|
},
|
|
@@ -417,8 +414,8 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
417
414
|
def rescan(cls, api, hash_value, hash_type, scan_config=None):
|
|
418
415
|
parameters = {
|
|
419
416
|
'method': 'POST',
|
|
420
|
-
'url': '{}/consumer/submission/{}/rescan/{}/{}'
|
|
421
|
-
'data': {
|
|
417
|
+
'url': f'{api.uri}/consumer/submission/{api.community}/rescan/{hash_type}/{hash_value}',
|
|
418
|
+
'data': {'community': api.community}
|
|
422
419
|
}
|
|
423
420
|
if scan_config:
|
|
424
421
|
parameters['data']['scan-config'] = scan_config
|
|
@@ -432,7 +429,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
432
429
|
def rescan_id(cls, api, submission_id, scan_config=None):
|
|
433
430
|
parameters = {
|
|
434
431
|
'method': 'POST',
|
|
435
|
-
'url': '{}/consumer/submission/{}/rescan/{
|
|
432
|
+
'url': f'{api.uri}/consumer/submission/{api.community}/rescan/{int(submission_id)}',
|
|
436
433
|
}
|
|
437
434
|
if scan_config:
|
|
438
435
|
parameters.setdefault('data', {})['scan-config'] = scan_config
|
|
@@ -448,7 +445,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
448
445
|
api,
|
|
449
446
|
{
|
|
450
447
|
'method': 'GET',
|
|
451
|
-
'url': '{}/consumer/submission/{}/{
|
|
448
|
+
'url': f'{api.uri}/consumer/submission/{api.community}/{int(submission_id)}',
|
|
452
449
|
},
|
|
453
450
|
result_parser=cls,
|
|
454
451
|
).execute()
|
|
@@ -457,7 +454,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
457
454
|
def metadata_rerun(cls, api, hashes, analyses=None, skip_es=None):
|
|
458
455
|
parameters = {
|
|
459
456
|
'method': 'POST',
|
|
460
|
-
'url': '{}/consumer/metadata'
|
|
457
|
+
'url': f'{api.uri}/consumer/metadata',
|
|
461
458
|
'json': {'hashes': hashes},
|
|
462
459
|
}
|
|
463
460
|
if analyses:
|
|
@@ -496,7 +493,7 @@ class ArtifactArchive(core.BaseJsonResource):
|
|
|
496
493
|
RESOURCE_ENDPOINT = '/consumer/download/stream'
|
|
497
494
|
|
|
498
495
|
def __init__(self, content, api=None):
|
|
499
|
-
super(
|
|
496
|
+
super().__init__(content=content, api=api)
|
|
500
497
|
self.id = content['id']
|
|
501
498
|
self.community = content['community']
|
|
502
499
|
self.created = core.parse_isoformat(content['created'])
|
|
@@ -507,7 +504,7 @@ class AssertionsJob(core.BaseJsonResource):
|
|
|
507
504
|
RESOURCE_ENDPOINT = '/consumer/assertions-job'
|
|
508
505
|
|
|
509
506
|
def __init__(self, content, api=None):
|
|
510
|
-
super(
|
|
507
|
+
super().__init__(content=content, api=api)
|
|
511
508
|
self.id = content['id']
|
|
512
509
|
self.engine_id = content['engine_id']
|
|
513
510
|
self.created = core.parse_isoformat(content['created'])
|
|
@@ -527,7 +524,7 @@ class VotesJob(core.BaseJsonResource):
|
|
|
527
524
|
RESOURCE_ENDPOINT = '/consumer/votes-job'
|
|
528
525
|
|
|
529
526
|
def __init__(self, content, api=None):
|
|
530
|
-
super(
|
|
527
|
+
super().__init__(content=content, api=api)
|
|
531
528
|
self.id = content['id']
|
|
532
529
|
self.engine_id = content['engine_id']
|
|
533
530
|
self.created = core.parse_isoformat(content['created'])
|
|
@@ -578,7 +575,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
578
575
|
raise exceptions.InvalidValueException('At least one of path or handle must be defined.')
|
|
579
576
|
|
|
580
577
|
# initialize super classes and default values
|
|
581
|
-
super(
|
|
578
|
+
super().__init__(response, api=api, hash_type='sha256')
|
|
582
579
|
self.sha256 = None
|
|
583
580
|
self.sha1 = None
|
|
584
581
|
self.md5 = None
|
|
@@ -611,13 +608,8 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
611
608
|
remove_on_error = False
|
|
612
609
|
try:
|
|
613
610
|
if folder:
|
|
614
|
-
# TODO: this should be replaced with os.makedirs(path, exist_ok=True)
|
|
615
|
-
# once we drop support to python 2.7
|
|
616
611
|
if not os.path.exists(folder):
|
|
617
|
-
|
|
618
|
-
os.makedirs(folder)
|
|
619
|
-
except FileExistsError:
|
|
620
|
-
pass
|
|
612
|
+
os.makedirs(folder, exist_ok=True)
|
|
621
613
|
remove_on_error = True
|
|
622
614
|
self.handle = open(os.path.join(folder, self.artifact_name), mode='wb+', **kwargs)
|
|
623
615
|
else:
|
|
@@ -649,7 +641,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
649
641
|
api,
|
|
650
642
|
{
|
|
651
643
|
'method': 'GET',
|
|
652
|
-
'url': '{}/consumer/download/{}/{}'
|
|
644
|
+
'url': f'{api.uri}/consumer/download/{hash_type}/{hash_value}',
|
|
653
645
|
'stream': True,
|
|
654
646
|
'params': { 'community': api.community },
|
|
655
647
|
},
|
|
@@ -665,7 +657,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
665
657
|
api,
|
|
666
658
|
{
|
|
667
659
|
'method': 'GET',
|
|
668
|
-
'url': '{}/instance/download'
|
|
660
|
+
'url': f'{api.uri}/instance/download',
|
|
669
661
|
'stream': True,
|
|
670
662
|
'params': {'instance_id': instance_id},
|
|
671
663
|
},
|
|
@@ -726,7 +718,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
726
718
|
|
|
727
719
|
@classmethod
|
|
728
720
|
def from_path(cls, api, path, artifact_type=None, analyze=False, artifact_name=None, **kwargs):
|
|
729
|
-
if not isinstance(path,
|
|
721
|
+
if not isinstance(path, str):
|
|
730
722
|
raise exceptions.InvalidValueException('Path should be a string')
|
|
731
723
|
artifact_name = artifact_name or os.path.basename(path)
|
|
732
724
|
handle = open(path, mode='rb', **kwargs)
|
|
@@ -736,7 +728,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
736
728
|
|
|
737
729
|
@classmethod
|
|
738
730
|
def from_content(cls, api, content, artifact_name=None, artifact_type=None, analyze=False):
|
|
739
|
-
if isinstance(content,
|
|
731
|
+
if isinstance(content, str):
|
|
740
732
|
content = content.encode("utf8")
|
|
741
733
|
handle = io.BytesIO(content)
|
|
742
734
|
# create the LocalHandle with the given handle and don't write anything to it
|
|
@@ -746,7 +738,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
|
|
|
746
738
|
@property
|
|
747
739
|
def hash(self):
|
|
748
740
|
self.analyze_artifact()
|
|
749
|
-
return super(
|
|
741
|
+
return super().hash
|
|
750
742
|
|
|
751
743
|
def analyze_artifact(self, force=False):
|
|
752
744
|
if not self.analyzed or force:
|
|
@@ -773,7 +765,7 @@ class YaraRuleset(core.BaseJsonResource):
|
|
|
773
765
|
RESOURCE_ENDPOINT = '/hunt/rule'
|
|
774
766
|
|
|
775
767
|
def __init__(self, content, api=None):
|
|
776
|
-
super(
|
|
768
|
+
super().__init__(content, api=api)
|
|
777
769
|
self.id = content.get('id')
|
|
778
770
|
self.livescan_id = content.get('livescan_id')
|
|
779
771
|
self.livescan_created = content.get('livescan_created')
|
|
@@ -793,7 +785,7 @@ class LiveHuntResult(core.BaseJsonResource):
|
|
|
793
785
|
RESOURCE_ENDPOINT = '/hunt/live'
|
|
794
786
|
|
|
795
787
|
def __init__(self, content, api=None):
|
|
796
|
-
super(
|
|
788
|
+
super().__init__(content=content, api=api)
|
|
797
789
|
self.id = content['id']
|
|
798
790
|
self.livescan_id = content['livescan_id']
|
|
799
791
|
self.instance_id = content['instance_id']
|
|
@@ -817,7 +809,7 @@ class HistoricalHunt(core.BaseJsonResource):
|
|
|
817
809
|
RESOURCE_ENDPOINT = '/hunt/historical'
|
|
818
810
|
|
|
819
811
|
def __init__(self, content, api=None):
|
|
820
|
-
super(
|
|
812
|
+
super().__init__(content=content, api=api)
|
|
821
813
|
# active only present for live hunts
|
|
822
814
|
self.id = content['id']
|
|
823
815
|
self.created = core.parse_isoformat(content['created'])
|
|
@@ -839,7 +831,7 @@ class HistoricalHuntResult(core.BaseJsonResource):
|
|
|
839
831
|
RESOURCE_ENDPOINT = '/hunt/historical/results'
|
|
840
832
|
|
|
841
833
|
def __init__(self, content, api=None):
|
|
842
|
-
super(
|
|
834
|
+
super().__init__(content=content, api=api)
|
|
843
835
|
self.id = content['id']
|
|
844
836
|
self.historicalscan_id = content['historicalscan_id']
|
|
845
837
|
self.instance_id = content['instance_id']
|
|
@@ -853,6 +845,7 @@ class HistoricalHuntResult(core.BaseJsonResource):
|
|
|
853
845
|
self.download_url = content.get('download_url')
|
|
854
846
|
self.community = content.get('community')
|
|
855
847
|
|
|
848
|
+
|
|
856
849
|
class HistoricalHuntResultList(HistoricalHuntResult):
|
|
857
850
|
RESOURCE_ENDPOINT = '/hunt/historical/results/list'
|
|
858
851
|
|
|
@@ -862,7 +855,7 @@ class TagLink(core.BaseJsonResource):
|
|
|
862
855
|
RESOURCE_ID_KEYS = ['hash']
|
|
863
856
|
|
|
864
857
|
def __init__(self, content, api=None):
|
|
865
|
-
super(
|
|
858
|
+
super().__init__(content, api=api)
|
|
866
859
|
self.id = content.get('id')
|
|
867
860
|
self.sha256 = content.get('sha256')
|
|
868
861
|
self.created = core.parse_isoformat(content.get('created'))
|
|
@@ -889,7 +882,7 @@ class MalwareFamily(core.BaseJsonResource):
|
|
|
889
882
|
RESOURCE_ID_KEYS = ['name']
|
|
890
883
|
|
|
891
884
|
def __init__(self, content, api=None):
|
|
892
|
-
super(
|
|
885
|
+
super().__init__(content, api=api)
|
|
893
886
|
self.id = content.get('id')
|
|
894
887
|
self.created = core.parse_isoformat(content.get('created'))
|
|
895
888
|
self.updated = core.parse_isoformat(content.get('updated'))
|
|
@@ -902,7 +895,7 @@ class Tag(core.BaseJsonResource):
|
|
|
902
895
|
RESOURCE_ID_KEYS = ['name']
|
|
903
896
|
|
|
904
897
|
def __init__(self, content, api=None):
|
|
905
|
-
super(
|
|
898
|
+
super().__init__(content, api=api)
|
|
906
899
|
self.id = content.get('id')
|
|
907
900
|
self.created = core.parse_isoformat(content.get('created'))
|
|
908
901
|
self.updated = core.parse_isoformat(content.get('updated'))
|
|
@@ -916,7 +909,7 @@ class Tag(core.BaseJsonResource):
|
|
|
916
909
|
|
|
917
910
|
class Assertion(core.BaseJsonResource):
|
|
918
911
|
def __init__(self, content, api=None, scanfile=None):
|
|
919
|
-
super(
|
|
912
|
+
super().__init__(content, api=api)
|
|
920
913
|
self.scanfile = scanfile
|
|
921
914
|
self.author = content['author']
|
|
922
915
|
self.author_name = content['author_name']
|
|
@@ -933,7 +926,7 @@ class Assertion(core.BaseJsonResource):
|
|
|
933
926
|
|
|
934
927
|
class Vote(core.BaseJsonResource):
|
|
935
928
|
def __init__(self, content, api=None, scanfile=None):
|
|
936
|
-
super(
|
|
929
|
+
super().__init__(content, api=api)
|
|
937
930
|
self.scanfile = scanfile
|
|
938
931
|
self.arbiter = content['arbiter']
|
|
939
932
|
self.vote = content['vote']
|
|
@@ -958,9 +951,8 @@ class ArtifactType(Enum):
|
|
|
958
951
|
try:
|
|
959
952
|
return ArtifactType[value.upper()]
|
|
960
953
|
except Exception as e:
|
|
961
|
-
raise
|
|
962
|
-
|
|
963
|
-
'Unable to get the artifact type from the provided value {}'.format(value)), e)
|
|
954
|
+
raise exceptions.InvalidValueException(
|
|
955
|
+
f'Unable to get the artifact type from the provided value {value}') from e
|
|
964
956
|
|
|
965
957
|
@staticmethod
|
|
966
958
|
def to_string(artifact_type):
|
|
@@ -981,7 +973,7 @@ class ArtifactType(Enum):
|
|
|
981
973
|
|
|
982
974
|
class Hash(core.Hashable):
|
|
983
975
|
def __init__(self, hash_, hash_type=None, validate_hash=True):
|
|
984
|
-
super(
|
|
976
|
+
super().__init__(hash_value=hash_, hash_type=hash_type, validate_hash=validate_hash)
|
|
985
977
|
|
|
986
978
|
@classmethod
|
|
987
979
|
def from_hashable(cls, hash_, hash_type=None):
|
|
@@ -995,8 +987,8 @@ class Hash(core.Hashable):
|
|
|
995
987
|
"""
|
|
996
988
|
if issubclass(type(hash_), core.Hashable):
|
|
997
989
|
if hash_type and hash_.hash_type != hash_type:
|
|
998
|
-
raise exceptions.InvalidValueException(
|
|
999
|
-
|
|
990
|
+
raise exceptions.InvalidValueException(
|
|
991
|
+
f'Detected hash type {hash_.hash_type}, got {hash_type} for hashable {hash_.hash}')
|
|
1000
992
|
return Hash(hash_.hash, hash_type=hash_type)
|
|
1001
993
|
return Hash(hash_, hash_type=hash_type)
|
|
1002
994
|
|
|
@@ -1007,14 +999,14 @@ class Hash(core.Hashable):
|
|
|
1007
999
|
return self.hash
|
|
1008
1000
|
|
|
1009
1001
|
def __repr__(self):
|
|
1010
|
-
return "{}={
|
|
1002
|
+
return f"{self.hash_type}={self.hash}"
|
|
1011
1003
|
|
|
1012
1004
|
|
|
1013
1005
|
class SandboxTask(core.BaseJsonResource):
|
|
1014
1006
|
RESOURCE_ENDPOINT = '/sandbox/sandboxtask'
|
|
1015
1007
|
|
|
1016
1008
|
def __init__(self, content, api=None):
|
|
1017
|
-
super(
|
|
1009
|
+
super().__init__(content, api=api)
|
|
1018
1010
|
self.id = content['id']
|
|
1019
1011
|
self.community = content['community']
|
|
1020
1012
|
self.sandbox = content['sandbox']
|
|
@@ -1080,7 +1072,7 @@ class SandboxTask(core.BaseJsonResource):
|
|
|
1080
1072
|
|
|
1081
1073
|
class SandboxArtifact(core.BaseJsonResource):
|
|
1082
1074
|
def __init__(self, content, api=None):
|
|
1083
|
-
super(
|
|
1075
|
+
super().__init__(content, api=api)
|
|
1084
1076
|
self.created = content['created']
|
|
1085
1077
|
self.id = content['id']
|
|
1086
1078
|
self.instance_id = content['instance_id']
|
|
@@ -1094,7 +1086,7 @@ class SandboxProvider(core.BaseJsonResource):
|
|
|
1094
1086
|
RESOURCE_ENDPOINT = "/sandbox/provider"
|
|
1095
1087
|
|
|
1096
1088
|
def __init__(self, content, api=None):
|
|
1097
|
-
super(
|
|
1089
|
+
super().__init__(content, api=api)
|
|
1098
1090
|
self.slug = content['slug']
|
|
1099
1091
|
self.name = content['name']
|
|
1100
1092
|
self.tool = content['tool']
|
|
@@ -1110,9 +1102,104 @@ class Events(core.BaseJsonResource):
|
|
|
1110
1102
|
RESOURCE_ENDPOINT = "/activity"
|
|
1111
1103
|
|
|
1112
1104
|
def __init__(self, content, api=None):
|
|
1113
|
-
super(
|
|
1105
|
+
super().__init__(content, api=api)
|
|
1114
1106
|
self.event_timestamp = content['event_timestamp']
|
|
1115
1107
|
self.event_type = content['event_type']
|
|
1116
1108
|
self.source = content['source']
|
|
1117
1109
|
self.team_account_id = content['team_account_id']
|
|
1118
1110
|
self.user_account_id = content['user_account_id']
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
class ReportTask(core.BaseJsonResource):
|
|
1114
|
+
RESOURCE_ENDPOINT = "/reports"
|
|
1115
|
+
|
|
1116
|
+
def __init__(self, content, api=None):
|
|
1117
|
+
super().__init__(content, api=api)
|
|
1118
|
+
self.id = content['id']
|
|
1119
|
+
self.type = content['type']
|
|
1120
|
+
self.format = content['format']
|
|
1121
|
+
self.state = content['state']
|
|
1122
|
+
self.community = content['community']
|
|
1123
|
+
self.created = content['created']
|
|
1124
|
+
self.template_id = content.get('template_id')
|
|
1125
|
+
self.template_metadata = content.get('template_metadata', {})
|
|
1126
|
+
self.sandbox_task_id = content.get('sandbox_task_id')
|
|
1127
|
+
self.instance_id = content.get('instance_id')
|
|
1128
|
+
self.url = content['url']
|
|
1129
|
+
|
|
1130
|
+
def download_report(self, folder=None):
|
|
1131
|
+
""" This method is special, in that it is simply for downloading from S3 """
|
|
1132
|
+
return core.PolyswarmRequest(
|
|
1133
|
+
self.api,
|
|
1134
|
+
{
|
|
1135
|
+
'method': 'GET',
|
|
1136
|
+
'url': self.url,
|
|
1137
|
+
'stream': True,
|
|
1138
|
+
'headers': {'Authorization': None}
|
|
1139
|
+
},
|
|
1140
|
+
result_parser=LocalArtifact,
|
|
1141
|
+
folder=folder,
|
|
1142
|
+
).execute()
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
class ReportTemplate(core.BaseJsonResource):
|
|
1146
|
+
RESOURCE_ENDPOINT = "/reports/templates"
|
|
1147
|
+
|
|
1148
|
+
def __init__(self, content, api=None):
|
|
1149
|
+
super().__init__(content, api=api)
|
|
1150
|
+
if content:
|
|
1151
|
+
self.id = content['id']
|
|
1152
|
+
self.created = content['created']
|
|
1153
|
+
self.template_name = content['template_name']
|
|
1154
|
+
self.includes = content.get('includes')
|
|
1155
|
+
self.primary_color = content.get('primary_color')
|
|
1156
|
+
self.footer_text = content.get('footer_text')
|
|
1157
|
+
self.last_page_text = content.get('last_page_text')
|
|
1158
|
+
self.is_default = content.get('is_default', False)
|
|
1159
|
+
self.logo_content_length = content.get('logo_content_length')
|
|
1160
|
+
self.logo_url = f"{self.api.uri}/reports/templates/logo?id={self.id}"
|
|
1161
|
+
self.logo_content_type = content.get('logo_content_type')
|
|
1162
|
+
self.logo_height = content.get('logo_height')
|
|
1163
|
+
self.logo_width = content.get('logo_width')
|
|
1164
|
+
|
|
1165
|
+
def download_logo(self, folder):
|
|
1166
|
+
return core.PolyswarmRequest(
|
|
1167
|
+
self.api,
|
|
1168
|
+
{
|
|
1169
|
+
'method': 'GET',
|
|
1170
|
+
'url': self.logo_url,
|
|
1171
|
+
},
|
|
1172
|
+
result_parser=LocalArtifact,
|
|
1173
|
+
folder=folder,
|
|
1174
|
+
).execute()
|
|
1175
|
+
|
|
1176
|
+
def delete_logo(self):
|
|
1177
|
+
return core.PolyswarmRequest(
|
|
1178
|
+
self.api,
|
|
1179
|
+
{
|
|
1180
|
+
'method': 'DELETE',
|
|
1181
|
+
'url': self.logo_url,
|
|
1182
|
+
},
|
|
1183
|
+
).execute()
|
|
1184
|
+
|
|
1185
|
+
def upload_logo(self, logo_file, content_tpe):
|
|
1186
|
+
if not logo_file:
|
|
1187
|
+
raise exceptions.InvalidValueException('A local file must be provided in order to upload')
|
|
1188
|
+
logo_file.seek(0, io.SEEK_END)
|
|
1189
|
+
length = logo_file.tell()
|
|
1190
|
+
logo_file.seek(0)
|
|
1191
|
+
if not length:
|
|
1192
|
+
raise exceptions.InvalidValueException('Empty file')
|
|
1193
|
+
# r = requests.put(self.upload_url, data=logo_file, **kwargs)
|
|
1194
|
+
# r.raise_for_status()
|
|
1195
|
+
# return r
|
|
1196
|
+
return core.PolyswarmRequest(
|
|
1197
|
+
self.api,
|
|
1198
|
+
{
|
|
1199
|
+
'method': 'PUT',
|
|
1200
|
+
'url': f'{self.api.uri}/reports/templates/logo?id={self.id}',
|
|
1201
|
+
'data': logo_file,
|
|
1202
|
+
'headers': {'Content-Type': content_tpe}
|
|
1203
|
+
},
|
|
1204
|
+
result_parser=self.__class__
|
|
1205
|
+
).execute()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: polyswarm-api
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.0
|
|
4
4
|
Summary: Client library to simplify interacting with the PolySwarm consumer API
|
|
5
5
|
Home-page: https://github.com/polyswarm/polyswarm-api
|
|
6
6
|
Author: PolySwarm Developers
|
|
@@ -11,8 +11,12 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.7
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
18
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
-
Requires-Python:
|
|
19
|
+
Requires-Python: >=3.7,<4
|
|
16
20
|
Description-Content-Type: text/markdown
|
|
17
21
|
License-File: LICENSE
|
|
18
22
|
|
|
@@ -21,7 +25,7 @@ License-File: LICENSE
|
|
|
21
25
|
An interface to the PolySwarm Customer APIs.
|
|
22
26
|
For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
|
|
23
27
|
|
|
24
|
-
Supports Python
|
|
28
|
+
Supports Python 3.7 and greater.
|
|
25
29
|
|
|
26
30
|
## Installation
|
|
27
31
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|