diff --git a/monkey/common/configuration/__init__.py b/monkey/common/configuration/__init__.py index 59186d56c..7a9131cc7 100644 --- a/monkey/common/configuration/__init__.py +++ b/monkey/common/configuration/__init__.py @@ -2,3 +2,4 @@ from .agent_configuration import ( AgentConfiguration, AgentConfigurationSchema, ) +from .default_agent_configuration import DEFAULT_AGENT_CONFIGURATION diff --git a/monkey/common/configuration/default_agent_configuration.py b/monkey/common/configuration/default_agent_configuration.py new file mode 100644 index 000000000..933b723ea --- /dev/null +++ b/monkey/common/configuration/default_agent_configuration.py @@ -0,0 +1,217 @@ +DEFAULT_AGENT_CONFIGURATION = """{ + "keep_tunnel_open_time": 30, + "post_breach_actions": [ + { + "name": "CommunicateAsBackdoorUser", + "options": {} + }, + { + "name": "ModifyShellStartupFiles", + "options": {} + }, + { + "name": "HiddenFiles", + "options": {} + }, + { + "name": "TrapCommand", + "options": {} + }, + { + "name": "ChangeSetuidSetgid", + "options": {} + }, + { + "name": "ScheduleJobs", + "options": {} + }, + { + "name": "Timestomping", + "options": {} + }, + { + "name": "AccountDiscovery", + "options": {} + }, + { + "name": "ProcessListCollection", + "options": {} + } + ], + "credential_collectors": [ + { + "name": "MimikatzCollector", + "options": {} + }, + { + "name": "SSHCollector", + "options": {} + } + ], + "payloads": [ + { + "name": "ransomware", + "options": { + "encryption": { + "enabled": true, + "directories": { + "linux_target_dir": "", + "windows_target_dir": "" + } + }, + "other_behaviors": { + "readme": true + } + } + } + ], + "custom_pbas": { + "linux_command": "", + "linux_filename": "", + "windows_command": "", + "windows_filename": "" + }, + "propagation": { + "maximum_depth": 2, + "network_scan": { + "tcp": { + "timeout": 3000, + "ports": [ + 22, + 80, + 135, + 443, + 445, + 2222, + 3306, + 3389, + 5985, + 5986, + 7001, + 8008, + 8080, + 8088, + 8983, + 9200, + 9600 + ] + }, + "icmp": { + "timeout": 1000 + }, + "fingerprinters": [ + { + "name": "elastic", + "options": {} + }, + { + "name": "http", + "options": { + "http_ports": [ + 80, + 443, + 7001, + 8008, + 8080, + 8983, + 9200, + 9600 + ] + } + }, + { + "name": "mssql", + "options": {} + }, + { + "name": "smb", + "options": {} + }, + { + "name": "ssh", + "options": {} + } + ], + "targets": { + "blocked_ips": [], + "inaccessible_subnets": [], + "local_network_scan": true, + "subnets": [] + } + }, + "exploitation": { + "options": { + "http_ports": [ + 80, + 443, + 7001, + 8008, + 8080, + 8983, + 9200, + 9600 + ] + }, + "brute_force": [ + { + "name": "MSSQLExploiter", + "options": {}, + "supported_os": [ + "WINDOWS" + ] + }, + { + "name": "PowerShellExploiter", + "options": {}, + "supported_os": [ + "WINDOWS" + ] + }, + { + "name": "SSHExploiter", + "options": {}, + "supported_os": [ + "LINUX" + ] + }, + { + "name": "SmbExploiter", + "options": { + "smb_download_timeout": 30 + }, + "supported_os": [ + "WINDOWS" + ] + }, + { + "name": "WmiExploiter", + "options": { + "smb_download_timeout": 30 + }, + "supported_os": [ + "WINDOWS" + ] + } + ], + "vulnerability": [ + { + "name": "HadoopExploiter", + "options": {}, + "supported_os": [ + "LINUX", + "WINDOWS" + ] + }, + { + "name": "Log4ShellExploiter", + "options": {}, + "supported_os": [ + "LINUX", + "WINDOWS" + ] + } + ] + } + } + } +""" diff --git a/monkey/monkey_island/cc/repository/__init__.py b/monkey/monkey_island/cc/repository/__init__.py index 885d467be..cc89c5c57 100644 --- a/monkey/monkey_island/cc/repository/__init__.py +++ b/monkey/monkey_island/cc/repository/__init__.py @@ -1,3 +1,6 @@ +from .errors import RetrievalError from .file_storage import FileRetrievalError, IFileRepository, LocalStorageFileRepository from .i_agent_binary_repository import IAgentBinaryRepository, AgentRetrievalError from .agent_binary_repository import AgentBinaryRepository +from .i_agent_configuration_repository import IAgentConfigurationRepository +from .file_agent_configuration_repository import FileAgentConfigurationRepository diff --git a/monkey/monkey_island/cc/repository/errors.py b/monkey/monkey_island/cc/repository/errors.py new file mode 100644 index 000000000..b7beb36fe --- /dev/null +++ b/monkey/monkey_island/cc/repository/errors.py @@ -0,0 +1,2 @@ +class RetrievalError(RuntimeError): + pass diff --git a/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py b/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py new file mode 100644 index 000000000..fcccd49a1 --- /dev/null +++ b/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py @@ -0,0 +1,38 @@ +import io + +from common.configuration import ( + DEFAULT_AGENT_CONFIGURATION, + AgentConfiguration, + AgentConfigurationSchema, +) +from monkey_island.cc.repository import ( + IAgentConfigurationRepository, + IFileRepository, + RetrievalError, +) + +AGENT_CONFIGURATION_FILE_NAME = "agent_configuration.json" + + +class FileAgentConfigurationRepository(IAgentConfigurationRepository): + def __init__(self, file_repository: IFileRepository): + self._file_repository = file_repository + self._schema = AgentConfigurationSchema() + + def get_configuration(self) -> AgentConfiguration: + try: + with self._file_repository.open_file(AGENT_CONFIGURATION_FILE_NAME) as f: + configuration_json = f.read().decode() + + return self._schema.loads(configuration_json) + # TODO: Handle FileRetrievalError vs FileNotFoundError + # https://github.com/guardicore/monkey/blob/e8001d8cf76340e42bf17ff62523bd2d85fc4841/monkey/monkey_island/cc/repository/file_storage/local_storage_file_repository.py#L47-L50 + except RetrievalError: + return self._schema.loads(DEFAULT_AGENT_CONFIGURATION) + + def store_configuration(self, agent_configuration: AgentConfiguration): + configuration_json = self._schema.dumps(agent_configuration) + + self._file_repository.save_file( + AGENT_CONFIGURATION_FILE_NAME, io.BytesIO(configuration_json.encode()) + ) diff --git a/monkey/monkey_island/cc/repository/file_storage/i_file_repository.py b/monkey/monkey_island/cc/repository/file_storage/i_file_repository.py index a0865b797..2531fd711 100644 --- a/monkey/monkey_island/cc/repository/file_storage/i_file_repository.py +++ b/monkey/monkey_island/cc/repository/file_storage/i_file_repository.py @@ -1,8 +1,10 @@ import abc from typing import BinaryIO +from monkey_island.cc.repository import RetrievalError -class FileRetrievalError(RuntimeError): + +class FileRetrievalError(RetrievalError): pass diff --git a/monkey/monkey_island/cc/repository/i_agent_configuration_repository.py b/monkey/monkey_island/cc/repository/i_agent_configuration_repository.py new file mode 100644 index 000000000..105d26fe9 --- /dev/null +++ b/monkey/monkey_island/cc/repository/i_agent_configuration_repository.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod + +from common.configuration import AgentConfiguration + + +class IAgentConfigurationRepository(ABC): + """ + A repository used to store and retrieve the agent configuration. + """ + + @abstractmethod + def get_configuration(self) -> AgentConfiguration: + """ + Retrieve the agent configuration from the repository + + :return: The agent configuration as retrieved from the repository, or the default + configuration if none could be retrieved. + :raises RetrievalError: if the configuration can not be retrieved + """ + pass + + @abstractmethod + def store_configuration(self, agent_configuration: AgentConfiguration): + """ + Store the agent configuration in the repository + + :param agent_configuration: The agent configuration to store in the repository + """ + pass diff --git a/monkey/monkey_island/cc/repository/i_config_repository.py b/monkey/monkey_island/cc/repository/i_config_repository.py deleted file mode 100644 index 70e59a89f..000000000 --- a/monkey/monkey_island/cc/repository/i_config_repository.py +++ /dev/null @@ -1,30 +0,0 @@ -from abc import ABC -from typing import Any, Mapping, Sequence - - -class IConfigRepository(ABC): - - # Config - ############################################### - - # This returns the current config - # TODO investigate if encryption should be here or where - # TODO potentially should be a DTO as well, but it's structure is defined in schema already - def get_config(self) -> Mapping: - pass - - def set_config(self, config: dict): - pass - - # Used when only a subset of config is submitted, for example only PBAFiles - # Used by passing keys, like ['monkey', 'post_breach_actions', 'linux_filename'] - # Using a list is less ambiguous IMO, than using . notation - def set_config_field(self, key_list: Sequence[str], value: Any): - pass - - # Used when only a subset of config is needed, for example only PBAFiles - # Used by passing keys, like ['monkey', 'post_breach_actions', 'linux_filename'] - # Using a list is less ambiguous IMO, than using . notation - # TODO Still in doubt about encryption, this should probably be determined automatically - def get_config_field(self, key_list: Sequence[str]) -> Any: - pass diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index a5d283cb3..74d447c90 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -7,7 +7,9 @@ from common.utils.file_utils import get_binary_io_sha256_hash from monkey_island.cc.repository import ( AgentBinaryRepository, AgentRetrievalError, + FileAgentConfigurationRepository, IAgentBinaryRepository, + IAgentConfigurationRepository, IFileRepository, LocalStorageFileRepository, ) @@ -31,11 +33,14 @@ def initialize_services(data_dir: Path) -> DIContainer: container.register_instance(AWSInstance, AWSInstance()) container.register_instance( - IFileRepository, LocalStorageFileRepository(data_dir / "custom_pbas") + IFileRepository, LocalStorageFileRepository(data_dir / "runtime_data") ) container.register_instance(AWSService, container.resolve(AWSService)) container.register_instance(IAgentBinaryRepository, _build_agent_binary_repository()) container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService)) + container.register_instance( + IAgentConfigurationRepository, container.resolve(FileAgentConfigurationRepository) + ) # This is temporary until we get DI all worked out. PostBreachFilesService.initialize(container.resolve(IFileRepository)) diff --git a/monkey/tests/common/example_agent_configuration.py b/monkey/tests/common/example_agent_configuration.py new file mode 100644 index 000000000..640771df4 --- /dev/null +++ b/monkey/tests/common/example_agent_configuration.py @@ -0,0 +1,75 @@ +PLUGIN_NAME = "bond" +PLUGIN_OPTIONS = {"gun": "Walther PPK", "car": "Aston Martin DB5"} +PLUGIN_CONFIGURATION = {"name": PLUGIN_NAME, "options": PLUGIN_OPTIONS} + +LINUX_COMMAND = "a" +LINUX_FILENAME = "b" +WINDOWS_COMMAND = "c" +WINDOWS_FILENAME = "d" +CUSTOM_PBA_CONFIGURATION = { + "linux_command": LINUX_COMMAND, + "linux_filename": LINUX_FILENAME, + "windows_command": WINDOWS_COMMAND, + "windows_filename": WINDOWS_FILENAME, +} + +BLOCKED_IPS = ["10.0.0.1", "192.168.1.1"] +INACCESSIBLE_SUBNETS = ["172.0.0.0/24", "172.2.2.0/24", "192.168.56.0/24"] +LOCAL_NETWORK_SCAN = True +SUBNETS = ["10.0.0.2", "10.0.0.2/16"] +SCAN_TARGET_CONFIGURATION = { + "blocked_ips": BLOCKED_IPS, + "inaccessible_subnets": INACCESSIBLE_SUBNETS, + "local_network_scan": LOCAL_NETWORK_SCAN, + "subnets": SUBNETS, +} + +TIMEOUT = 2.525 +ICMP_CONFIGURATION = {"timeout": TIMEOUT} + +PORTS = [8080, 443] +TCP_SCAN_CONFIGURATION = {"timeout": TIMEOUT, "ports": PORTS} + +FINGERPRINTERS = [{"name": "mssql", "options": {}}] +NETWORK_SCAN_CONFIGURATION = { + "tcp": TCP_SCAN_CONFIGURATION, + "icmp": ICMP_CONFIGURATION, + "fingerprinters": FINGERPRINTERS, + "targets": SCAN_TARGET_CONFIGURATION, +} + +BRUTE_FORCE = [ + {"name": "ex1", "options": {}, "supported_os": ["LINUX"]}, + { + "name": "ex2", + "options": {"smb_download_timeout": 10}, + "supported_os": ["LINUX", "WINDOWS"], + }, +] +VULNERABILITY = [ + { + "name": "ex3", + "options": {"smb_download_timeout": 10}, + "supported_os": ["WINDOWS"], + }, +] +EXPLOITATION_CONFIGURATION = { + "options": {"http_ports": PORTS}, + "brute_force": BRUTE_FORCE, + "vulnerability": VULNERABILITY, +} + +PROPAGATION_CONFIGURATION = { + "maximum_depth": 5, + "network_scan": NETWORK_SCAN_CONFIGURATION, + "exploitation": EXPLOITATION_CONFIGURATION, +} + +AGENT_CONFIGURATION = { + "keep_tunnel_open_time": 30, + "custom_pbas": CUSTOM_PBA_CONFIGURATION, + "post_breach_actions": [PLUGIN_CONFIGURATION], + "credential_collectors": [PLUGIN_CONFIGURATION], + "payloads": [PLUGIN_CONFIGURATION], + "propagation": PROPAGATION_CONFIGURATION, +} diff --git a/monkey/tests/monkey_island/__init__.py b/monkey/tests/monkey_island/__init__.py new file mode 100644 index 000000000..7bd9a314d --- /dev/null +++ b/monkey/tests/monkey_island/__init__.py @@ -0,0 +1 @@ +from .single_file_repository import SingleFileRepository diff --git a/monkey/tests/monkey_island/single_file_repository.py b/monkey/tests/monkey_island/single_file_repository.py new file mode 100644 index 000000000..d00ee9ae9 --- /dev/null +++ b/monkey/tests/monkey_island/single_file_repository.py @@ -0,0 +1,23 @@ +import io +from typing import BinaryIO + +from monkey_island.cc.repository import FileRetrievalError, IFileRepository + + +class SingleFileRepository(IFileRepository): + def __init__(self): + self._file = None + + def save_file(self, unsafe_file_name: str, file_contents: BinaryIO): + self._file = io.BytesIO(file_contents.read()) + + def open_file(self, unsafe_file_name: str) -> BinaryIO: + if self._file is None: + raise FileRetrievalError() + return self._file + + def delete_file(self, unsafe_file_name: str): + self._file = None + + def delete_all_files(self): + self.delete_file("") diff --git a/monkey/tests/unit_tests/common/test_agent_configuration.py b/monkey/tests/unit_tests/common/test_agent_configuration.py index 512b46bc4..733e2e708 100644 --- a/monkey/tests/unit_tests/common/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/test_agent_configuration.py @@ -1,5 +1,34 @@ +from tests.common.example_agent_configuration import ( + AGENT_CONFIGURATION, + BLOCKED_IPS, + CUSTOM_PBA_CONFIGURATION, + EXPLOITATION_CONFIGURATION, + FINGERPRINTERS, + ICMP_CONFIGURATION, + INACCESSIBLE_SUBNETS, + LINUX_COMMAND, + LINUX_FILENAME, + LOCAL_NETWORK_SCAN, + NETWORK_SCAN_CONFIGURATION, + PLUGIN_CONFIGURATION, + PLUGIN_NAME, + PLUGIN_OPTIONS, + PORTS, + PROPAGATION_CONFIGURATION, + SCAN_TARGET_CONFIGURATION, + SUBNETS, + TCP_SCAN_CONFIGURATION, + TIMEOUT, + WINDOWS_COMMAND, + WINDOWS_FILENAME, +) + from common import OperatingSystems -from common.configuration import AgentConfiguration, AgentConfigurationSchema +from common.configuration import ( + DEFAULT_AGENT_CONFIGURATION, + AgentConfiguration, + AgentConfigurationSchema, +) from common.configuration.agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, ExploitationConfigurationSchema, @@ -20,30 +49,14 @@ from common.configuration.agent_sub_configurations import ( PropagationConfiguration, ) -NAME = "bond" -OPTIONS = {"gun": "Walther PPK", "car": "Aston Martin DB5"} -PLUGIN_CONFIGURATION = {"name": NAME, "options": OPTIONS} - def test_build_plugin_configuration(): schema = PluginConfigurationSchema() config = schema.load(PLUGIN_CONFIGURATION) - assert config.name == NAME - assert config.options == OPTIONS - - -LINUX_COMMAND = "a" -LINUX_FILENAME = "b" -WINDOWS_COMMAND = "c" -WINDOWS_FILENAME = "d" -CUSTOM_PBA_CONFIGURATION = { - "linux_command": LINUX_COMMAND, - "linux_filename": LINUX_FILENAME, - "windows_command": WINDOWS_COMMAND, - "windows_filename": WINDOWS_FILENAME, -} + assert config.name == PLUGIN_NAME + assert config.options == PLUGIN_OPTIONS def test_custom_pba_configuration_schema(): @@ -57,18 +70,6 @@ def test_custom_pba_configuration_schema(): assert config.windows_filename == WINDOWS_FILENAME -BLOCKED_IPS = ["10.0.0.1", "192.168.1.1"] -INACCESSIBLE_SUBNETS = ["172.0.0.0/24", "172.2.2.0/24", "192.168.56.0/24"] -LOCAL_NETWORK_SCAN = True -SUBNETS = ["10.0.0.2", "10.0.0.2/16"] -SCAN_TARGET_CONFIGURATION = { - "blocked_ips": BLOCKED_IPS, - "inaccessible_subnets": INACCESSIBLE_SUBNETS, - "local_network_scan": LOCAL_NETWORK_SCAN, - "subnets": SUBNETS, -} - - def test_scan_target_configuration(): schema = ScanTargetConfigurationSchema() @@ -80,10 +81,6 @@ def test_scan_target_configuration(): assert config.subnets == SUBNETS -TIMEOUT = 2.525 -ICMP_CONFIGURATION = {"timeout": TIMEOUT} - - def test_icmp_scan_configuration_schema(): schema = ICMPScanConfigurationSchema() @@ -92,11 +89,6 @@ def test_icmp_scan_configuration_schema(): assert config.timeout == TIMEOUT -PORTS = [8080, 443] - -TCP_SCAN_CONFIGURATION = {"timeout": TIMEOUT, "ports": PORTS} - - def test_tcp_scan_configuration_schema(): schema = TCPScanConfigurationSchema() @@ -106,15 +98,6 @@ def test_tcp_scan_configuration_schema(): assert config.ports == PORTS -FINGERPRINTERS = [{"name": "mssql", "options": {}}] -NETWORK_SCAN_CONFIGURATION = { - "tcp": TCP_SCAN_CONFIGURATION, - "icmp": ICMP_CONFIGURATION, - "fingerprinters": FINGERPRINTERS, - "targets": SCAN_TARGET_CONFIGURATION, -} - - def test_network_scan_configuration(): schema = NetworkScanConfigurationSchema() @@ -155,28 +138,6 @@ def test_exploiter_configuration_schema(): assert config.supported_os == supported_os -BRUTE_FORCE = [ - {"name": "ex1", "options": {}, "supported_os": ["LINUX"]}, - { - "name": "ex2", - "options": {"smb_download_timeout": 10}, - "supported_os": ["LINUX", "WINDOWS"], - }, -] -VULNERABILITY = [ - { - "name": "ex3", - "options": {"smb_download_timeout": 10}, - "supported_os": ["WINDOWS"], - }, -] -EXPLOITATION_CONFIGURATION = { - "options": {"http_ports": PORTS}, - "brute_force": BRUTE_FORCE, - "vulnerability": VULNERABILITY, -} - - def test_exploitation_configuration(): schema = ExploitationConfigurationSchema() @@ -187,13 +148,6 @@ def test_exploitation_configuration(): assert config_dict == EXPLOITATION_CONFIGURATION -PROPAGATION_CONFIGURATION = { - "maximum_depth": 5, - "network_scan": NETWORK_SCAN_CONFIGURATION, - "exploitation": EXPLOITATION_CONFIGURATION, -} - - def test_propagation_configuration(): schema = PropagationConfigurationSchema() @@ -208,17 +162,9 @@ def test_propagation_configuration(): def test_agent_configuration(): - agent_configuration = { - "keep_tunnel_open_time": 30, - "custom_pbas": CUSTOM_PBA_CONFIGURATION, - "post_breach_actions": [PLUGIN_CONFIGURATION], - "credential_collectors": [PLUGIN_CONFIGURATION], - "payloads": [PLUGIN_CONFIGURATION], - "propagation": PROPAGATION_CONFIGURATION, - } schema = AgentConfigurationSchema() - config = schema.load(agent_configuration) + config = schema.load(AGENT_CONFIGURATION) config_dict = schema.dump(config) assert isinstance(config, AgentConfiguration) @@ -228,4 +174,12 @@ def test_agent_configuration(): assert isinstance(config.credential_collectors[0], PluginConfiguration) assert isinstance(config.payloads[0], PluginConfiguration) assert isinstance(config.propagation, PropagationConfiguration) - assert config_dict == agent_configuration + assert config_dict == AGENT_CONFIGURATION + + +def test_default_agent_configuration(): + schema = AgentConfigurationSchema() + + config = schema.loads(DEFAULT_AGENT_CONFIGURATION) + + assert isinstance(config, AgentConfiguration) diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py new file mode 100644 index 000000000..7ad066623 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py @@ -0,0 +1,26 @@ +from tests.common.example_agent_configuration import AGENT_CONFIGURATION +from tests.monkey_island import SingleFileRepository + +from common.configuration import DEFAULT_AGENT_CONFIGURATION, AgentConfigurationSchema +from monkey_island.cc.repository import FileAgentConfigurationRepository + + +def test_store_agent_config(): + repository = FileAgentConfigurationRepository(SingleFileRepository()) + schema = AgentConfigurationSchema() + agent_configuration = schema.load(AGENT_CONFIGURATION) + + repository.store_configuration(agent_configuration) + retrieved_agent_configuration = repository.get_configuration() + + assert retrieved_agent_configuration == agent_configuration + + +def test_get_default_agent_config(): + repository = FileAgentConfigurationRepository(SingleFileRepository()) + schema = AgentConfigurationSchema() + default_agent_configuration = schema.loads(DEFAULT_AGENT_CONFIGURATION) + + retrieved_agent_configuration = repository.get_configuration() + + assert retrieved_agent_configuration == default_agent_configuration diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_pba_file_upload.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_pba_file_upload.py index 2642c0758..50da88915 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_pba_file_upload.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_pba_file_upload.py @@ -1,12 +1,10 @@ -import io -from typing import BinaryIO - import pytest from tests.common import StubDIContainer +from tests.monkey_island import SingleFileRepository from tests.unit_tests.monkey_island.conftest import get_url_for_resource from tests.utils import raise_ -from monkey_island.cc.repository import FileRetrievalError, IFileRepository +from monkey_island.cc.repository import IFileRepository from monkey_island.cc.resources.pba_file_upload import LINUX_PBA_TYPE, WINDOWS_PBA_TYPE, FileUpload TEST_FILE_CONTENTS = b"m0nk3y" @@ -40,28 +38,9 @@ def mock_get_config_value(monkeypatch): ) -class MockFileRepository(IFileRepository): - def __init__(self): - self._file = None - - def save_file(self, unsafe_file_name: str, file_contents: BinaryIO): - self._file = io.BytesIO(file_contents.read()) - - def open_file(self, unsafe_file_name: str) -> BinaryIO: - if self._file is None: - raise FileRetrievalError() - return self._file - - def delete_file(self, unsafe_file_name: str): - self._file = None - - def delete_all_files(self): - self.delete_file("") - - @pytest.fixture def file_repository(): - return MockFileRepository() + return SingleFileRepository() @pytest.fixture