From 4bca7bb1980e101871c32f157752caf34b4c41ad Mon Sep 17 00:00:00 2001 From: Nish_ <120EE0980@nitrkl.ac.in> Date: Mon, 24 Mar 2025 21:04:55 +0530 Subject: [PATCH] add directory expansion for file-based and KeyPassXC credentials Signed-off-by: Nish_ <120EE0980@nitrkl.ac.in> --- borgmatic/hooks/credential/file.py | 4 +- borgmatic/hooks/credential/keepassxc.py | 6 ++- tests/unit/hooks/credential/test_file.py | 28 ++++++++++++++ tests/unit/hooks/credential/test_keepassxc.py | 38 +++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/borgmatic/hooks/credential/file.py b/borgmatic/hooks/credential/file.py index 06854703b..366a7d4ac 100644 --- a/borgmatic/hooks/credential/file.py +++ b/borgmatic/hooks/credential/file.py @@ -19,9 +19,11 @@ def load_credential(hook_config, config, credential_parameters): raise ValueError(f'Cannot load invalid credential: "{name}"') + expanded_credential_path = os.path.expanduser(credential_path) + try: with open( - os.path.join(config.get('working_directory', ''), credential_path) + os.path.join(config.get('working_directory', ''), expanded_credential_path) ) as credential_file: return credential_file.read().rstrip(os.linesep) except (FileNotFoundError, OSError) as error: diff --git a/borgmatic/hooks/credential/keepassxc.py b/borgmatic/hooks/credential/keepassxc.py index e37be4996..7e6ac9134 100644 --- a/borgmatic/hooks/credential/keepassxc.py +++ b/borgmatic/hooks/credential/keepassxc.py @@ -24,7 +24,9 @@ def load_credential(hook_config, config, credential_parameters): f'Cannot load credential with invalid KeePassXC database path and attribute name: "{path_and_name}"' ) - if not os.path.exists(database_path): + expanded_database_path = os.path.expanduser(database_path) + + if not os.path.exists(expanded_database_path): raise ValueError( f'Cannot load credential because KeePassXC database path does not exist: {database_path}' ) @@ -36,7 +38,7 @@ def load_credential(hook_config, config, credential_parameters): '--show-protected', '--attributes', 'Password', - database_path, + expanded_database_path, attribute_name, ) ).rstrip(os.linesep) diff --git a/tests/unit/hooks/credential/test_file.py b/tests/unit/hooks/credential/test_file.py index aff10b89d..42827a85a 100644 --- a/tests/unit/hooks/credential/test_file.py +++ b/tests/unit/hooks/credential/test_file.py @@ -26,6 +26,9 @@ def test_load_credential_reads_named_credential_from_file(): credential_stream = io.StringIO('password') credential_stream.name = '/credentials/mycredential' builtins = flexmock(sys.modules['builtins']) + flexmock(module.os.path).should_receive('expanduser').with_args( + '/credentials/mycredential' + ).and_return('/credentials/mycredential') builtins.should_receive('open').with_args('/credentials/mycredential').and_return( credential_stream ) @@ -42,6 +45,9 @@ def test_load_credential_reads_named_credential_from_file_using_working_director credential_stream = io.StringIO('password') credential_stream.name = '/working/credentials/mycredential' builtins = flexmock(sys.modules['builtins']) + flexmock(module.os.path).should_receive('expanduser').with_args( + 'credentials/mycredential' + ).and_return('credentials/mycredential') builtins.should_receive('open').with_args('/working/credentials/mycredential').and_return( credential_stream ) @@ -58,6 +64,9 @@ def test_load_credential_reads_named_credential_from_file_using_working_director def test_load_credential_with_file_not_found_error_raises(): builtins = flexmock(sys.modules['builtins']) + flexmock(module.os.path).should_receive('expanduser').with_args( + '/credentials/mycredential' + ).and_return('/credentials/mycredential') builtins.should_receive('open').with_args('/credentials/mycredential').and_raise( FileNotFoundError ) @@ -66,3 +75,22 @@ def test_load_credential_with_file_not_found_error_raises(): module.load_credential( hook_config={}, config={}, credential_parameters=('/credentials/mycredential',) ) + + +def test_load_credential_reads_named_credential_from_expanded_directory(): + credential_stream = io.StringIO('password') + credential_stream.name = '/root/credentials/mycredential' + builtins = flexmock(sys.modules['builtins']) + flexmock(module.os.path).should_receive('expanduser').with_args( + '~/credentials/mycredential' + ).and_return('/root/credentials/mycredential') + builtins.should_receive('open').with_args('/root/credentials/mycredential').and_return( + credential_stream + ) + + assert ( + module.load_credential( + hook_config={}, config={}, credential_parameters=('~/credentials/mycredential',) + ) + == 'password' + ) diff --git a/tests/unit/hooks/credential/test_keepassxc.py b/tests/unit/hooks/credential/test_keepassxc.py index e25c87176..0c460e233 100644 --- a/tests/unit/hooks/credential/test_keepassxc.py +++ b/tests/unit/hooks/credential/test_keepassxc.py @@ -15,6 +15,9 @@ def test_load_credential_with_invalid_credential_parameters_raises(credential_pa def test_load_credential_with_missing_database_raises(): + flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return( + 'database.kdbx' + ) flexmock(module.os.path).should_receive('exists').and_return(False) flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never() @@ -25,6 +28,9 @@ def test_load_credential_with_missing_database_raises(): def test_load_credential_with_present_database_fetches_password_from_keepassxc(): + flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return( + 'database.kdbx' + ) flexmock(module.os.path).should_receive('exists').and_return(True) flexmock(module.borgmatic.execute).should_receive( 'execute_command_and_capture_output' @@ -51,6 +57,9 @@ def test_load_credential_with_present_database_fetches_password_from_keepassxc() def test_load_credential_with_custom_keepassxc_cli_command_calls_it(): + flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return( + 'database.kdbx' + ) config = {'keepassxc': {'keepassxc_cli_command': '/usr/local/bin/keepassxc-cli --some-option'}} flexmock(module.os.path).should_receive('exists').and_return(True) flexmock(module.borgmatic.execute).should_receive( @@ -78,3 +87,32 @@ def test_load_credential_with_custom_keepassxc_cli_command_calls_it(): ) == 'password' ) + + +def test_load_credential_with_expanded_directory_with_present_database_fetches_password_from_keepassxc(): + flexmock(module.os.path).should_receive('expanduser').with_args('~/database.kdbx').and_return( + '/root/database.kdbx' + ) + flexmock(module.os.path).should_receive('exists').and_return(True) + flexmock(module.borgmatic.execute).should_receive( + 'execute_command_and_capture_output' + ).with_args( + ( + 'keepassxc-cli', + 'show', + '--show-protected', + '--attributes', + 'Password', + '/root/database.kdbx', + 'mypassword', + ) + ).and_return( + 'password' + ).once() + + assert ( + module.load_credential( + hook_config={}, config={}, credential_parameters=('~/database.kdbx', 'mypassword') + ) + == 'password' + )