mirror of
https://github.com/suitenumerique/django-lasuite
synced 2026-04-25 17:15:14 +02:00
♻️(malware) log filehash when a submission fail
We want to log the file has when a submission fail in order to aggregate all this logs to send them to JCOP.
This commit is contained in:
@@ -173,6 +173,7 @@ class JCOPBackend(BaseBackend):
|
||||
# start a new analysis
|
||||
trigger_new_analysis.delay(
|
||||
file_path,
|
||||
file_hash,
|
||||
**kwargs,
|
||||
)
|
||||
return False
|
||||
@@ -211,7 +212,7 @@ class JCOPBackend(BaseBackend):
|
||||
)
|
||||
return False
|
||||
|
||||
def trigger_new_analysis(self, file_path: str, **kwargs):
|
||||
def trigger_new_analysis(self, file_path: str, file_hash: str, **kwargs):
|
||||
"""Trigger a new analysis for a file."""
|
||||
with default_storage.open(file_path, "rb") as file:
|
||||
encoder = MultipartEncoder(
|
||||
@@ -230,8 +231,8 @@ class JCOPBackend(BaseBackend):
|
||||
data=encoder,
|
||||
timeout=(30, self.submit_timeout),
|
||||
)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
logger.error("Error submitting file to JCOP: %s", exc)
|
||||
except requests.exceptions.RequestException:
|
||||
logger.error("Error while sending file %s to JCOP with hash %s", file_path, file_hash)
|
||||
raise
|
||||
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
|
||||
@@ -54,12 +54,13 @@ def analyse_file_async(
|
||||
def trigger_new_analysis(
|
||||
self,
|
||||
file_path: str,
|
||||
file_hash: str,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Trigger a new analysis for a file."""
|
||||
backend = MalwareDetectionHandler()() # JCOPBackend
|
||||
try:
|
||||
backend.trigger_new_analysis(file_path, **kwargs)
|
||||
backend.trigger_new_analysis(file_path, file_hash, **kwargs)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
if self.request.retries >= self.max_retries:
|
||||
backend.failed_analysis(
|
||||
|
||||
@@ -515,10 +515,7 @@ def test_jcop_backend_analyse_file_async_request_error(
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
@pytest.mark.parametrize("with_file_hash", [True, False])
|
||||
def test_jcop_backend_analyse_file_async_no_existing_result(
|
||||
jcop_generate_file_path, jcop_backend, used_kwargs, with_file_hash
|
||||
):
|
||||
def test_jcop_backend_analyse_file_async_no_existing_result(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test when no result found should start the trigger_new_analysis task."""
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
@@ -533,13 +530,12 @@ def test_jcop_backend_analyse_file_async_no_existing_result(
|
||||
status=404,
|
||||
)
|
||||
with mock.patch.object(trigger_new_analysis, "delay") as mock_trigger_new_analysis:
|
||||
should_retry = jcop_backend.check_analysis(
|
||||
file_path, file_hash=file_hash if with_file_hash else None, **used_kwargs
|
||||
)
|
||||
should_retry = jcop_backend.check_analysis(file_path, file_hash=file_hash, **used_kwargs)
|
||||
assert should_retry is False
|
||||
|
||||
mock_trigger_new_analysis.assert_called_once_with(
|
||||
file_path,
|
||||
file_hash,
|
||||
**used_kwargs,
|
||||
)
|
||||
|
||||
@@ -563,7 +559,7 @@ def test_jcop_backend_trigger_new_analysis_success(jcop_generate_file_path, jcop
|
||||
)
|
||||
|
||||
with mock.patch.object(analyse_file_async, "apply_async") as mock_apply_async:
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
mock_apply_async.assert_called_once_with(
|
||||
countdown=5,
|
||||
args=(file_path,),
|
||||
@@ -578,7 +574,7 @@ def test_jcop_backend_trigger_new_analysis_success(jcop_generate_file_path, jcop
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
def test_jcop_backend_trigger_new_analysis_unauthorized(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test submission with invalid API key."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
# Mock the submit endpoint
|
||||
responses.add(
|
||||
@@ -592,7 +588,7 @@ def test_jcop_backend_trigger_new_analysis_unauthorized(jcop_generate_file_path,
|
||||
)
|
||||
|
||||
with pytest.raises(exceptions.MalwareDetectionInvalidAuthenticationError):
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
jcop_callback.assert_called_once_with(
|
||||
file_path,
|
||||
@@ -611,7 +607,7 @@ def test_jcop_backend_trigger_new_analysis_unauthorized_complete_flow(
|
||||
jcop_generate_file_path, jcop_backend, used_kwargs
|
||||
):
|
||||
"""Test submission with invalid API key."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
malware_detection = factories.MalwareDetectionFactory(path=file_path, status=MalwareDetectionStatus.PROCESSING)
|
||||
next_record = factories.MalwareDetectionFactory(status=MalwareDetectionStatus.PENDING)
|
||||
|
||||
@@ -630,7 +626,7 @@ def test_jcop_backend_trigger_new_analysis_unauthorized_complete_flow(
|
||||
mock.patch.object(analyse_file_async, "delay") as mock_apply_async,
|
||||
pytest.raises(exceptions.MalwareDetectionInvalidAuthenticationError),
|
||||
):
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
mock_apply_async.assert_called_once_with(next_record.path)
|
||||
|
||||
@@ -655,7 +651,7 @@ def test_jcop_backend_trigger_new_analysis_unauthorized_complete_flow(
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
def test_jcop_backend_trigger_new_analysis_timeout_max_retries(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test submission with timeout after max retries."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
# Mock the submit endpoint
|
||||
responses.add(
|
||||
@@ -669,7 +665,7 @@ def test_jcop_backend_trigger_new_analysis_timeout_max_retries(jcop_generate_fil
|
||||
)
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
jcop_callback.assert_not_called()
|
||||
|
||||
@@ -678,7 +674,7 @@ def test_jcop_backend_trigger_new_analysis_timeout_max_retries(jcop_generate_fil
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
def test_jcop_backend_trigger_new_analysis_file_too_large(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test submission with file too large."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
# Mock the submit endpoint
|
||||
responses.add(
|
||||
@@ -691,7 +687,7 @@ def test_jcop_backend_trigger_new_analysis_file_too_large(jcop_generate_file_pat
|
||||
status=413,
|
||||
)
|
||||
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
jcop_callback.assert_called_once_with(
|
||||
file_path,
|
||||
@@ -710,7 +706,7 @@ def test_jcop_backend_trigger_new_analysis_file_too_large_complete_flow(
|
||||
jcop_generate_file_path, jcop_backend, used_kwargs
|
||||
):
|
||||
"""Test submission with file too large."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
malware_detection = factories.MalwareDetectionFactory(path=file_path, status=MalwareDetectionStatus.PROCESSING)
|
||||
next_record = factories.MalwareDetectionFactory(status=MalwareDetectionStatus.PENDING)
|
||||
# Mock the submit endpoint
|
||||
@@ -725,7 +721,7 @@ def test_jcop_backend_trigger_new_analysis_file_too_large_complete_flow(
|
||||
)
|
||||
|
||||
with mock.patch.object(analyse_file_async, "delay") as mock_apply_async:
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
mock_apply_async.assert_called_once_with(next_record.path)
|
||||
|
||||
jcop_callback.assert_called_once_with(
|
||||
@@ -749,7 +745,7 @@ def test_jcop_backend_trigger_new_analysis_file_too_large_complete_flow(
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
def test_jcop_backend_trigger_new_analysis_request_error(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test submission with request error."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
# Mock the submit endpoint
|
||||
responses.add(
|
||||
@@ -763,7 +759,7 @@ def test_jcop_backend_trigger_new_analysis_request_error(jcop_generate_file_path
|
||||
status=200,
|
||||
)
|
||||
with pytest.raises(requests.exceptions.RequestException):
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
jcop_callback.assert_not_called()
|
||||
|
||||
@@ -772,7 +768,7 @@ def test_jcop_backend_trigger_new_analysis_request_error(jcop_generate_file_path
|
||||
@pytest.mark.parametrize("used_kwargs", [{}, {"foo": "bar"}])
|
||||
def test_jcop_backend_trigger_new_analysis_unknown_status(jcop_generate_file_path, jcop_backend, used_kwargs):
|
||||
"""Test submission with unknown status code."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
|
||||
# Mock the submit endpoint
|
||||
responses.add(
|
||||
@@ -785,7 +781,7 @@ def test_jcop_backend_trigger_new_analysis_unknown_status(jcop_generate_file_pat
|
||||
status=500,
|
||||
)
|
||||
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
|
||||
jcop_callback.assert_called_once_with(
|
||||
file_path,
|
||||
@@ -804,7 +800,7 @@ def test_jcop_backend_trigger_new_analysis_unknown_status_complete_flow(
|
||||
jcop_generate_file_path, jcop_backend, used_kwargs
|
||||
):
|
||||
"""Test submission with unknown status code."""
|
||||
file_path, _ = jcop_generate_file_path
|
||||
file_path, file_hash = jcop_generate_file_path
|
||||
malware_detection = factories.MalwareDetectionFactory(path=file_path, status=MalwareDetectionStatus.PROCESSING)
|
||||
next_record = factories.MalwareDetectionFactory(status=MalwareDetectionStatus.PENDING)
|
||||
# Mock the submit endpoint
|
||||
@@ -819,7 +815,7 @@ def test_jcop_backend_trigger_new_analysis_unknown_status_complete_flow(
|
||||
)
|
||||
|
||||
with mock.patch.object(analyse_file_async, "delay") as mock_apply_async:
|
||||
jcop_backend.trigger_new_analysis(file_path, **used_kwargs)
|
||||
jcop_backend.trigger_new_analysis(file_path, file_hash, **used_kwargs)
|
||||
mock_apply_async.assert_called_once_with(next_record.path)
|
||||
|
||||
jcop_callback.assert_called_once_with(
|
||||
|
||||
@@ -97,9 +97,9 @@ def test_trigger_new_analysis_success(mock_backend):
|
||||
backend, handler = mock_backend
|
||||
|
||||
with mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler):
|
||||
trigger_new_analysis("file.txt", extra_param="value")
|
||||
trigger_new_analysis("file.txt", "file_hash", extra_param="value")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", extra_param="value")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash", extra_param="value")
|
||||
|
||||
|
||||
def test_trigger_new_analysis_request_exception_retry(mock_backend):
|
||||
@@ -111,9 +111,9 @@ def test_trigger_new_analysis_request_exception_retry(mock_backend):
|
||||
mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler),
|
||||
pytest.raises(requests.exceptions.RequestException),
|
||||
):
|
||||
trigger_new_analysis("file.txt")
|
||||
trigger_new_analysis("file.txt", "file_hash")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash")
|
||||
backend.failed_analysis.assert_not_called()
|
||||
|
||||
|
||||
@@ -126,9 +126,9 @@ def test_trigger_new_analysis_timeout_retry(mock_backend):
|
||||
mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler),
|
||||
pytest.raises(TimeoutError),
|
||||
):
|
||||
trigger_new_analysis("file.txt")
|
||||
trigger_new_analysis("file.txt", "file_hash")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash")
|
||||
backend.failed_analysis.assert_not_called()
|
||||
|
||||
|
||||
@@ -139,9 +139,9 @@ def test_trigger_new_analysis_request_exception_max_retries(mock_backend):
|
||||
backend.failed_analysis = mock.MagicMock()
|
||||
with mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler):
|
||||
trigger_new_analysis.max_retries = 0
|
||||
trigger_new_analysis("file.txt")
|
||||
trigger_new_analysis("file.txt", "file_hash")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash")
|
||||
backend.failed_analysis.assert_called_once_with(
|
||||
"file.txt",
|
||||
error_code=MaxRetriesErrorCodes.TRIGGER_NEW_ANALYSIS,
|
||||
@@ -156,9 +156,9 @@ def test_trigger_new_analysis_timeout_max_retries(mock_backend):
|
||||
backend.failed_analysis = mock.MagicMock()
|
||||
with mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler):
|
||||
trigger_new_analysis.max_retries = 0
|
||||
trigger_new_analysis("file.txt")
|
||||
trigger_new_analysis("file.txt", "file_hash")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash")
|
||||
backend.failed_analysis.assert_called_once_with(
|
||||
"file.txt",
|
||||
error_code=MaxRetriesErrorCodes.TRIGGER_NEW_ANALYSIS_TIMEOUT,
|
||||
@@ -175,7 +175,7 @@ def test_trigger_new_analysis_with_auth_error_no_retry(mock_backend):
|
||||
mock.patch("lasuite.malware_detection.tasks.jcop.MalwareDetectionHandler", return_value=handler),
|
||||
pytest.raises(MalwareDetectionInvalidAuthenticationError),
|
||||
):
|
||||
trigger_new_analysis("file.txt")
|
||||
trigger_new_analysis("file.txt", "file_hash")
|
||||
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt")
|
||||
backend.trigger_new_analysis.assert_called_once_with("file.txt", "file_hash")
|
||||
backend.failed_analysis.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user