diff --git a/src/lasuite/malware_detection/backends/jcop.py b/src/lasuite/malware_detection/backends/jcop.py index 2e7de50..640c65f 100644 --- a/src/lasuite/malware_detection/backends/jcop.py +++ b/src/lasuite/malware_detection/backends/jcop.py @@ -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: diff --git a/src/lasuite/malware_detection/tasks/jcop.py b/src/lasuite/malware_detection/tasks/jcop.py index ad6037f..04bd962 100644 --- a/src/lasuite/malware_detection/tasks/jcop.py +++ b/src/lasuite/malware_detection/tasks/jcop.py @@ -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( diff --git a/tests/malware_detection/backends/test_jcop_backend.py b/tests/malware_detection/backends/test_jcop_backend.py index dc424ef..8c9cfaa 100644 --- a/tests/malware_detection/backends/test_jcop_backend.py +++ b/tests/malware_detection/backends/test_jcop_backend.py @@ -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( diff --git a/tests/malware_detection/tasks/test_jcop_tasks.py b/tests/malware_detection/tasks/test_jcop_tasks.py index 354a44c..c9da705 100644 --- a/tests/malware_detection/tasks/test_jcop_tasks.py +++ b/tests/malware_detection/tasks/test_jcop_tasks.py @@ -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()