diff --git a/borgmatic/hooks/credential/keepassxc.py b/borgmatic/hooks/credential/keepassxc.py index 7e6ac913..1a0da572 100644 --- a/borgmatic/hooks/credential/keepassxc.py +++ b/borgmatic/hooks/credential/keepassxc.py @@ -11,27 +11,23 @@ def load_credential(hook_config, config, credential_parameters): ''' Given the hook configuration dict, the configuration dict, and a credential parameters tuple containing a KeePassXC database path and an attribute name to load, run keepassxc-cli to fetch - the corresponidng KeePassXC credential and return it. + the corresponding KeePassXC credential and return it. Raise ValueError if keepassxc-cli can't retrieve the credential. ''' try: - (database_path, attribute_name) = credential_parameters + database_path, attribute_name = credential_parameters[:2] + extra_args = credential_parameters[2:] # Handle additional arguments like --key-file or --yubikey except ValueError: - path_and_name = ' '.join(credential_parameters) - - raise ValueError( - f'Cannot load credential with invalid KeePassXC database path and attribute name: "{path_and_name}"' - ) + raise ValueError( f'Invalid KeePassXC credential parameters: {credential_parameters}') 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}' - ) + raise ValueError( f'KeePassXC database path does not exist: {database_path}') - return borgmatic.execute.execute_command_and_capture_output( + # Build the keepassxc-cli command + command = ( tuple(shlex.split((hook_config or {}).get('keepassxc_cli_command', 'keepassxc-cli'))) + ( 'show', @@ -41,4 +37,10 @@ def load_credential(hook_config, config, credential_parameters): expanded_database_path, attribute_name, ) - ).rstrip(os.linesep) + + tuple(extra_args) # Append extra arguments + ) + + try: + return borgmatic.execute.execute_command_and_capture_output(command).rstrip(os.linesep) + except Exception as e: + raise ValueError(f'Failed to retrieve credential: {e}') diff --git a/tests/unit/hooks/credential/test_keepassxc.py b/tests/unit/hooks/credential/test_keepassxc.py index 0c460e23..9fddf133 100644 --- a/tests/unit/hooks/credential/test_keepassxc.py +++ b/tests/unit/hooks/credential/test_keepassxc.py @@ -116,3 +116,68 @@ def test_load_credential_with_expanded_directory_with_present_database_fetches_p ) == 'password' ) + + +def test_load_credential_with_key_file(): + 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' + ).with_args( + ( + 'keepassxc-cli', + 'show', + '--show-protected', + '--attributes', + 'Password', + 'database.kdbx', + 'mypassword', + '--key-file', + '/path/to/keyfile', + ) + ).and_return( + 'password' + ).once() + + assert ( + module.load_credential( + hook_config={}, + config={}, + credential_parameters=('database.kdbx', 'mypassword', '--key-file', '/path/to/keyfile'), + ) + == 'password' + ) + + +def test_load_credential_with_yubikey(): + 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' + ).with_args( + ( + 'keepassxc-cli', + 'show', + '--show-protected', + '--attributes', + 'Password', + 'database.kdbx', + 'mypassword', + '--yubikey', + ) + ).and_return( + 'password' + ).once() + + assert ( + module.load_credential( + hook_config={}, + config={}, + credential_parameters=('database.kdbx', 'mypassword', '--yubikey'), + ) + == 'password' + )