Skip to content

Commit b1d7c21

Browse files
Merge branch '2.7.0-branch' into 'develop'
v2.7.1 See merge request integrations/sdk/reversinglabs-sdk-py3!7
2 parents fe1370c + 8b2256e commit b1d7c21

File tree

7 files changed

+287
-3
lines changed

7 files changed

+287
-3
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ include:
44
- project: 'integrations/ops/release-pipeline'
55
ref: master
66
file: '/reversinglabs-sdk-py3/gitlab-ci.yml'
7+

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ v2.5.1 (2024-04-02)
332332
- Implemented the default user agent string in embedded `FileAnalysis` calls.
333333

334334

335-
2.7.0 (2024-07-24)
335+
2.7.0 (2024-09-25)
336336
-------------------
337337

338338
#### Improvements
@@ -346,6 +346,13 @@ v2.5.1 (2024-04-02)
346346
- Parameters `internet_simulation` and `sample_name` of the `DynamicAnalysis.detonate_sample` method are now deprecated. Use `**optional_parameters` instead.
347347

348348

349+
2.7.1 (2024-10-09)
350+
-------------------
351+
352+
#### Improvements
353+
- **fie** module:
354+
- Introduced a new module called **fie** which corresponds to the ReversingLabs **File Investigation Engine (FIE)** service.
355+
- The module currently has one class with four methods for sending files to FIE for analysis and fetching the short classification or more detailed analysis reports.
349356

350357

351358
### Scheduled removals

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,37 @@ class TitaniumScale(object):
928928
- `get_yara_id`
929929
- Retrieves the identifier of the current set of YARA rules on the TitaniumScale Worker instance.
930930

931+
***
932+
933+
## Module: fie
934+
A Python module representing the ReversingLabs File Inspection Engine platform.
935+
#### Class:
936+
```python
937+
class FileInspectionEngine(object):
938+
def __init__(self, host, verify, proxies, user_agent)
939+
```
940+
#### Parameters:
941+
`host` - File Inspection Engine address
942+
`verify` - verify SSL certificate
943+
`proxies` - optional proxies in use
944+
`user_agent` - optional user agent string
931945

946+
#### Methods:
947+
- `test_connection`
948+
- Creates a lightweight request towards the FIE scan API to test the connection.
949+
- `scan_using_file_path`
950+
- Sends a file to the FIE for inspection and returns a simple verdict in the submit response.
951+
- Uses a file path string as input.
952+
- `scan_using_open_file`
953+
- Sends a file to the FIE for inspection and returns a simple verdict in the submit response.
954+
- Uses an open file handle as input.
955+
- `report_using_file_path`
956+
- Sends a file to the FIE for inspection and returns a more complex analysis report in the submit response.
957+
- Uses a file path string as input.
958+
- `report_using_open_file`
959+
- Sends a file to the FIE for inspection and returns a more complex analysis report in the submit response.
960+
- Uses an open file handle as input.
961+
932962
***
933963

934964
## Examples
@@ -1065,6 +1095,23 @@ results = titanium_scale.upload_sample_and_get_results(
10651095
)
10661096
```
10671097

1098+
#### File Inspection Engine
1099+
```python
1100+
from ReversingLabs.SDK.fie import FileInspectionEngine
1101+
1102+
1103+
fie = FileInspectionEngine(
1104+
host="http://fie.address",
1105+
verify=True
1106+
)
1107+
1108+
results = fie.scan_using_file_path(
1109+
file_path="/local/path/to/file.exe"
1110+
)
1111+
1112+
print(results.json())
1113+
```
1114+
10681115
#### Error handling
10691116
Each module raises corresponding custom exceptions according to the error status code returned in the response.
10701117
Custom exception classes that correspond to error status codes also carry the original response object in its entirety.

ReversingLabs/SDK/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
A Python SDK for communicating with ReversingLabs services.
66
"""
77

8-
__version__ = "2.7.0"
8+
__version__ = "2.7.1"

ReversingLabs/SDK/fie.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
author: Mislav Sever
3+
4+
File Inspection Engine (FIE)
5+
A Python module for the ReversingLabs File Inspection Engine REST API.
6+
"""
7+
8+
import requests
9+
from io import BytesIO
10+
11+
from ReversingLabs.SDK.helper import DEFAULT_USER_AGENT, RESPONSE_CODE_ERROR_MAP, WrongInputError
12+
13+
14+
class FileInspectionEngine(object):
15+
16+
__SCAN_ENDPOINT = "/scan"
17+
__REPORT_ENDPOINT = "/report"
18+
19+
def __init__(self, host, verify=True, proxies=None, user_agent=DEFAULT_USER_AGENT):
20+
self._host = self.__validate_host(host)
21+
self._url = "{host}{{endpoint}}".format(host=self._host)
22+
self._verify = verify
23+
24+
self._headers = {"User-Agent": user_agent}
25+
26+
if proxies:
27+
if not isinstance(proxies, dict):
28+
raise WrongInputError("proxies parameter must be a dictionary.")
29+
if len(proxies) == 0:
30+
raise WrongInputError("proxies parameter can not be an empty dictionary.")
31+
self._proxies = proxies
32+
33+
@staticmethod
34+
def __validate_host(host):
35+
"""Returns a formatted host URL including the protocol prefix.
36+
:param host: URL string
37+
:type host: str
38+
:returns: formatted URL string
39+
:rtype: str
40+
"""
41+
if not isinstance(host, str):
42+
raise WrongInputError("host parameter must be string.")
43+
44+
if not host.startswith(("http://", "https://")):
45+
raise WrongInputError("host parameter must contain a protocol definition at the beginning.")
46+
47+
host = host.rstrip("/")
48+
49+
return host
50+
51+
def test_connection(self):
52+
"""Creates a lightweight request towards the FIE scan API to test the connection.
53+
"""
54+
fake_file = BytesIO(b'this is a sample text')
55+
56+
response = self.scan_using_open_file(
57+
file_source=fake_file
58+
)
59+
60+
self.__raise_on_error(response)
61+
62+
return
63+
64+
def scan_using_file_path(self, file_path):
65+
"""Sends a file to the FIE for inspection and returns a simple verdict in the submit response.
66+
Uses a file path string as input.
67+
:param file_path: local path to the file
68+
:type file_path: str
69+
:return: response
70+
:rtype: requests.Response
71+
"""
72+
if not isinstance(file_path, str):
73+
raise WrongInputError("file_path must be a string.")
74+
75+
try:
76+
file_handle = open(file_path, "rb")
77+
except IOError as error:
78+
raise WrongInputError("Error while opening file in 'rb' mode - {error}".format(error=str(error)))
79+
80+
response = self.__upload_file(
81+
file_source=file_handle,
82+
endpoint=self.__SCAN_ENDPOINT
83+
)
84+
85+
return response
86+
87+
def scan_using_open_file(self, file_source):
88+
"""Sends a file to the FIE for inspection and returns a simple verdict in the submit response.
89+
Uses an open file handle as input.
90+
:param file_source: open file in rb mode
91+
:type file_source: file or BinaryIO
92+
:return: response
93+
:rtype: requests.Response
94+
"""
95+
response = self.__upload_file(
96+
file_source=file_source,
97+
endpoint=self.__SCAN_ENDPOINT
98+
)
99+
100+
return response
101+
102+
def report_using_file_path(self, file_path):
103+
"""Sends a file to the FIE for inspection and returns a more complex analysis report in the submit response.
104+
Uses a file path string as input.
105+
:param file_path: local path to the file
106+
:type file_path: str
107+
:return: response
108+
:rtype: requests.Response
109+
"""
110+
if not isinstance(file_path, str):
111+
raise WrongInputError("file_path must be a string.")
112+
113+
try:
114+
file_handle = open(file_path, "rb")
115+
except IOError as error:
116+
raise WrongInputError("Error while opening file in 'rb' mode - {error}".format(error=str(error)))
117+
118+
response = self.__upload_file(
119+
file_source=file_handle,
120+
endpoint=self.__REPORT_ENDPOINT
121+
)
122+
123+
return response
124+
125+
def report_using_open_file(self, file_source):
126+
"""Sends a file to the FIE for inspection and returns a more complex analysis report in the submit response.
127+
Uses an open file handle as input.
128+
:param file_source: open file in rb mode
129+
:type file_source: file or BinaryIO
130+
:return: response
131+
:rtype: requests.Response
132+
"""
133+
response = self.__upload_file(
134+
file_source=file_source,
135+
endpoint=self.__REPORT_ENDPOINT
136+
)
137+
138+
return response
139+
140+
def __upload_file(self, file_source, endpoint):
141+
"""Internal method for utilizing the FIE endpoints.
142+
:param file_source: open file in rb mode
143+
:type file_source: file or BinaryIO
144+
:param endpoint: endpoint string
145+
:type endpoint: str
146+
:return: response
147+
:rtype: requests.Response
148+
"""
149+
if not hasattr(file_source, "read"):
150+
raise WrongInputError("file_source parameter must be a file open in 'rb' mode.")
151+
152+
url = self._url.format(endpoint=endpoint)
153+
154+
response = requests.post(
155+
url=url,
156+
data=file_source,
157+
verify=self._verify,
158+
proxies=self._proxies,
159+
headers=self._headers
160+
)
161+
162+
self.__raise_on_error(response)
163+
164+
return response
165+
166+
@staticmethod
167+
def __raise_on_error(response):
168+
"""Accepts a response object for validation and raises an exception if an error status code is received.
169+
:return: response
170+
:rtype: requests.Response
171+
"""
172+
exception = RESPONSE_CODE_ERROR_MAP.get(response.status_code, None)
173+
if not exception:
174+
return
175+
raise exception(response_object=response)

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
],
4444
project_urls={
4545
"Documentation": "https://github.com/reversinglabs/reversinglabs-sdk-py3/blob/main/README.md",
46-
"Source": "https://github.com/reversinglabs/reversinglabs-sdk-py3"
46+
"Source": "https://github.com/reversinglabs/reversinglabs-sdk-py3",
47+
"Changes": "https://github.com/reversinglabs/reversinglabs-sdk-py3/blob/main/CHANGELOG.md"
4748
},
4849
)

tests/test_fie.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
from unittest import mock
3+
from ReversingLabs.SDK import __version__
4+
from ReversingLabs.SDK.fie import FileInspectionEngine
5+
from ReversingLabs.SDK.helper import WrongInputError, DEFAULT_USER_AGENT
6+
7+
8+
def test_fie_object():
9+
invalid_host = "my.host"
10+
valid_host = f"https://{invalid_host}"
11+
12+
fie = FileInspectionEngine(
13+
host=valid_host,
14+
verify=True
15+
)
16+
17+
assert fie._url == valid_host + "{endpoint}"
18+
19+
with pytest.raises(WrongInputError, match=r"host parameter must contain a protocol definition at the beginning."):
20+
FileInspectionEngine(host=invalid_host)
21+
22+
user_agent = fie._headers.get("User-Agent")
23+
assert __version__ in user_agent
24+
25+
26+
@pytest.fixture
27+
def requests_mock():
28+
with mock.patch('ReversingLabs.SDK.fie.requests', autospec=True) as requests_mock:
29+
yield requests_mock
30+
31+
32+
class TestFIE:
33+
host = "http://my.host"
34+
35+
@classmethod
36+
def setup_class(cls):
37+
cls.fie = FileInspectionEngine(cls.host)
38+
39+
def test_scan_using_path(self):
40+
with pytest.raises(WrongInputError, match=r"file_path must be a string."):
41+
self.fie.scan_using_file_path(file_path=123)
42+
43+
def test_scan_using_file(self):
44+
with pytest.raises(WrongInputError, match=r"file_source parameter must be a file open in 'rb' mode."):
45+
self.fie.scan_using_open_file(file_source="/path/to/file")
46+
47+
def test_report_using_path(self):
48+
with pytest.raises(WrongInputError, match=r"file_path must be a string."):
49+
self.fie.report_using_file_path(file_path=123)
50+
51+
def test_report_using_file(self):
52+
with pytest.raises(WrongInputError, match=r"file_source parameter must be a file open in 'rb' mode."):
53+
self.fie.report_using_open_file(file_source="/path/to/file")

0 commit comments

Comments
 (0)