From c3a0ecd557f64bc3f3688a3c464e9feef3755684 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 1 Mar 2025 18:04:38 +0100 Subject: [PATCH] feat: same behavour between bw et rbw --- src/rougail/user_data_bitwarden/config.py | 7 + src/rougail/user_data_bitwarden/data.py | 97 +++++++---- .../1_secret_unknown/errors/bitwarden.json | 6 + .../1_secret_unknown/makedict/bitwarden.json | 3 + .../errors/bitwarden.json | 4 + .../makedict/bitwarden.json | 4 + .../makedict/bitwarden.json | 10 +- .../makedict/bitwarden.json.rbw | 12 ++ .../4_several_secrets/errors/bitwarden.json | 4 +- .../errors/bitwarden.json.rbw | 6 + .../errors/bitwarden.json | 6 + .../errors/bitwarden.json.rbw | 6 + .../makedict/bitwarden.json | 3 + tests/structures/1_secret_unknown/00-base.yml | 8 + .../2_username_secret_upper/00-base.yml | 14 ++ .../4_several_secrets_upper/00-base.yml | 8 + tests/test_load.py | 159 +++++++++++++++--- 17 files changed, 286 insertions(+), 71 deletions(-) create mode 100644 tests/results/1_secret_unknown/errors/bitwarden.json create mode 100644 tests/results/1_secret_unknown/makedict/bitwarden.json create mode 100644 tests/results/2_username_secret_upper/errors/bitwarden.json create mode 100644 tests/results/2_username_secret_upper/makedict/bitwarden.json create mode 100644 tests/results/3_leadership_secret_several/makedict/bitwarden.json.rbw create mode 100644 tests/results/4_several_secrets/errors/bitwarden.json.rbw create mode 100644 tests/results/4_several_secrets_upper/errors/bitwarden.json create mode 100644 tests/results/4_several_secrets_upper/errors/bitwarden.json.rbw create mode 100644 tests/results/4_several_secrets_upper/makedict/bitwarden.json create mode 100644 tests/structures/1_secret_unknown/00-base.yml create mode 100644 tests/structures/2_username_secret_upper/00-base.yml create mode 100644 tests/structures/4_several_secrets_upper/00-base.yml diff --git a/src/rougail/user_data_bitwarden/config.py b/src/rougail/user_data_bitwarden/config.py index 45d5d35..3e943e2 100644 --- a/src/rougail/user_data_bitwarden/config.py +++ b/src/rougail/user_data_bitwarden/config.py @@ -38,6 +38,13 @@ step: {% else %} directory {% endif %} + +bitwarden: + command: + choices: + - rbw + - bw + mandatory: false """ return {'name': 'bitwarden', 'process': 'user data', diff --git a/src/rougail/user_data_bitwarden/data.py b/src/rougail/user_data_bitwarden/data.py index 883c1a7..3fb63a6 100644 --- a/src/rougail/user_data_bitwarden/data.py +++ b/src/rougail/user_data_bitwarden/data.py @@ -53,37 +53,53 @@ class RougailUserDataBitwarden: self.errors = [] self.warnings = [] self.leader_informations = {} - bitwarden_command_line = None + self.bitwarden_command_line = self.get_command(rougailconfig) + + def get_command(self, rougailconfig): + if 'ROUGAIL_BITWARDEN_MOCK_ENABLE' in environ: + return None one_is_find = False - if 'ROUGAIL_BITWARDEN_MOCK_ENABLE' not in environ: - if which('rbw'): + force_command = rougailconfig["bitwarden.command"] + if force_command: + commands = [force_command] + else: + commands = ['rbw', 'bw'] + for command in commands: + status = self.test_command(command) + if status is False: one_is_find = True - try: - cpe = run(['rbw', 'unlocked'], capture_output=True) - except Exception as exc: - pass - else: - if cpe.returncode == 0: - bitwarden_command_line = 'rbw' - if bitwarden_command_line is None and which('bw'): - one_is_find = True - try: - cpe = run(['bw', 'status'], capture_output=True) - except Exception as exc: - pass - else: - if cpe.returncode == 0: - try: - data = loads(cpe.stdout.decode('utf8')) - if data["status"] == "unlocked": - bitwarden_command_line = 'bw' - except: - pass - if bitwarden_command_line is None: - if one_is_find: - raise ExtentionError(_('please unlock Bitwarden password database')) - raise ExtentionError(_('cannot find Bitwarden command (rbw or bw) please install it')) - self.bitwarden_command_line = bitwarden_command_line + elif status: + return command + if not force_command: + command_string = _('"rbw" or "bw"') + else: + command_string = _('"{0}"').format(force_command) + if one_is_find: + raise ExtentionError(_('please unlock Bitwarden password database with {0}').format(command_string)) + raise ExtentionError(_('cannot find Bitwarden command {0} please install it').format(command_string)) + + def test_command(self, command): + if not which(command): + return None + if command == 'rbw': + cmd = ['rbw', 'unlocked'] + else: + cmd = ['bw', 'status'] + try: + cpe = run(cmd, capture_output=True) + except Exception as exc: + return False + if cpe.returncode != 0: + return False + if command == 'rbw': + return True + try: + data = loads(cpe.stdout.decode('utf8')) + if data["status"] == "unlocked": + return True + except: + pass + return False def run(self): self.set_passwords(self.config.forcepermissive) @@ -100,18 +116,25 @@ class RougailUserDataBitwarden: def get_key_from_commandline(self, key_bitwarden: str, allow_multiple: bool) -> list[str]: if self.bitwarden_command_line == 'rbw': - if allow_multiple: - keys = [] - items = self.run_commandline(["rbw", "search", key_bitwarden]).strip() - for item in items.split('\n'): - if item.count('@') != 1: - continue - keys.append(item.split('@', 1)[-1]) + keys = [] + items = self.run_commandline(["rbw", "search", key_bitwarden]).strip() + if items: + items = items.split('\n') else: + items = [] + for item in items: + #if item.count('@') != 1: + # continue + keys.append(item.split('@', 1)[-1]) + if not allow_multiple: + if not items: + return [] + if len(items) > 1: + return [{'name': key} for key in keys] keys = [key_bitwarden] datas = [] for key in keys: - data = loads(self.run_commandline(["rbw", "get", key, '--raw']).strip()) + data = loads(self.run_commandline(["rbw", "get", key, '--raw', '--ignorecase']).strip()) datas.append({'name': key, 'login': data["data"]}) return datas return loads(self.run_commandline(["bw", "list", "items", "--search", key_bitwarden, '--nointeraction'])) diff --git a/tests/results/1_secret_unknown/errors/bitwarden.json b/tests/results/1_secret_unknown/errors/bitwarden.json new file mode 100644 index 0000000..f2582f6 --- /dev/null +++ b/tests/results/1_secret_unknown/errors/bitwarden.json @@ -0,0 +1,6 @@ +{ + "errors": [ + "cannot find secret \"test_unknown\" from Bitwarden for \"rougail.secret\"" + ], + "warnings": [] +} \ No newline at end of file diff --git a/tests/results/1_secret_unknown/makedict/bitwarden.json b/tests/results/1_secret_unknown/makedict/bitwarden.json new file mode 100644 index 0000000..fd8daba --- /dev/null +++ b/tests/results/1_secret_unknown/makedict/bitwarden.json @@ -0,0 +1,3 @@ +{ + "rougail.secret": null +} \ No newline at end of file diff --git a/tests/results/2_username_secret_upper/errors/bitwarden.json b/tests/results/2_username_secret_upper/errors/bitwarden.json new file mode 100644 index 0000000..217db0a --- /dev/null +++ b/tests/results/2_username_secret_upper/errors/bitwarden.json @@ -0,0 +1,4 @@ +{ + "errors": [], + "warnings": [] +} \ No newline at end of file diff --git a/tests/results/2_username_secret_upper/makedict/bitwarden.json b/tests/results/2_username_secret_upper/makedict/bitwarden.json new file mode 100644 index 0000000..dd5d89d --- /dev/null +++ b/tests/results/2_username_secret_upper/makedict/bitwarden.json @@ -0,0 +1,4 @@ +{ + "rougail.username": "bitwarden_username", + "rougail.secret": "bitwarden_password" +} \ No newline at end of file diff --git a/tests/results/3_leadership_secret_several/makedict/bitwarden.json b/tests/results/3_leadership_secret_several/makedict/bitwarden.json index a93641a..3874eb9 100644 --- a/tests/results/3_leadership_secret_several/makedict/bitwarden.json +++ b/tests/results/3_leadership_secret_several/makedict/bitwarden.json @@ -1,12 +1,12 @@ { "rougail.leader.username": [ - { - "rougail.leader.username": "test_multi_username2", - "rougail.leader.secret": "test_multi_password2" - }, { "rougail.leader.username": "test_multi_username1", "rougail.leader.secret": "test_multi_password1" + }, + { + "rougail.leader.username": "test_multi_username2", + "rougail.leader.secret": "test_multi_password2" } ] -} \ No newline at end of file +} diff --git a/tests/results/3_leadership_secret_several/makedict/bitwarden.json.rbw b/tests/results/3_leadership_secret_several/makedict/bitwarden.json.rbw new file mode 100644 index 0000000..140b773 --- /dev/null +++ b/tests/results/3_leadership_secret_several/makedict/bitwarden.json.rbw @@ -0,0 +1,12 @@ +{ + "rougail.leader.username": [ + { + "rougail.leader.username": "test_multi_username2", + "rougail.leader.secret": "test_multi_password2" + }, + { + "rougail.leader.username": "test_multi_username1", + "rougail.leader.secret": "test_multi_password1" + } + ] +} diff --git a/tests/results/4_several_secrets/errors/bitwarden.json b/tests/results/4_several_secrets/errors/bitwarden.json index fb68b31..ca54c81 100644 --- a/tests/results/4_several_secrets/errors/bitwarden.json +++ b/tests/results/4_several_secrets/errors/bitwarden.json @@ -1,6 +1,6 @@ { "errors": [ - "cannot execute the \"rbw\" commandline from Bitwarden for \"rougail.secret\": rbw get: couldn't find entry for 'test_secret_': multiple entries found: bitwarden_username_2@test_secret_2, bitwarden_username@test_secret_1\n (1)" + "several items found with name \"test_secret_\" from Bitwarden for \"rougail.secret\": \"test_secret_1\", \"test_secret_2\"" ], "warnings": [] -} \ No newline at end of file +} diff --git a/tests/results/4_several_secrets/errors/bitwarden.json.rbw b/tests/results/4_several_secrets/errors/bitwarden.json.rbw new file mode 100644 index 0000000..90fd376 --- /dev/null +++ b/tests/results/4_several_secrets/errors/bitwarden.json.rbw @@ -0,0 +1,6 @@ +{ + "errors": [ + "several items found with name \"test_secret_\" from Bitwarden for \"rougail.secret\": \"test_secret_2\", \"test_secret_1\"" + ], + "warnings": [] +} diff --git a/tests/results/4_several_secrets_upper/errors/bitwarden.json b/tests/results/4_several_secrets_upper/errors/bitwarden.json new file mode 100644 index 0000000..a78d3f7 --- /dev/null +++ b/tests/results/4_several_secrets_upper/errors/bitwarden.json @@ -0,0 +1,6 @@ +{ + "errors": [ + "several items found with name \"TEST_SECRET_\" from Bitwarden for \"rougail.secret\": \"test_secret_1\", \"test_secret_2\"" + ], + "warnings": [] +} diff --git a/tests/results/4_several_secrets_upper/errors/bitwarden.json.rbw b/tests/results/4_several_secrets_upper/errors/bitwarden.json.rbw new file mode 100644 index 0000000..c9e75b4 --- /dev/null +++ b/tests/results/4_several_secrets_upper/errors/bitwarden.json.rbw @@ -0,0 +1,6 @@ +{ + "errors": [ + "several items found with name \"TEST_SECRET_\" from Bitwarden for \"rougail.secret\": \"test_secret_2\", \"test_secret_1\"" + ], + "warnings": [] +} \ No newline at end of file diff --git a/tests/results/4_several_secrets_upper/makedict/bitwarden.json b/tests/results/4_several_secrets_upper/makedict/bitwarden.json new file mode 100644 index 0000000..fd8daba --- /dev/null +++ b/tests/results/4_several_secrets_upper/makedict/bitwarden.json @@ -0,0 +1,3 @@ +{ + "rougail.secret": null +} \ No newline at end of file diff --git a/tests/structures/1_secret_unknown/00-base.yml b/tests/structures/1_secret_unknown/00-base.yml new file mode 100644 index 0000000..0e35e43 --- /dev/null +++ b/tests/structures/1_secret_unknown/00-base.yml @@ -0,0 +1,8 @@ +--- +version: 1.1 + +secret: + description: the second variable + type: secret + default: test_unknown + bitwarden: true diff --git a/tests/structures/2_username_secret_upper/00-base.yml b/tests/structures/2_username_secret_upper/00-base.yml new file mode 100644 index 0000000..e0dfdfe --- /dev/null +++ b/tests/structures/2_username_secret_upper/00-base.yml @@ -0,0 +1,14 @@ +--- +version: 1.1 + +username: + description: the username + type: unix_user + default: TEST_SECRET_1 + bitwarden: true + +secret: + description: the secret + type: secret + default: TEST_SECRET_1 + bitwarden: true diff --git a/tests/structures/4_several_secrets_upper/00-base.yml b/tests/structures/4_several_secrets_upper/00-base.yml new file mode 100644 index 0000000..ca39dbd --- /dev/null +++ b/tests/structures/4_several_secrets_upper/00-base.yml @@ -0,0 +1,8 @@ +--- +version: 1.1 + +secret: + description: the second variable + type: secret + default: TEST_SECRET_ + bitwarden: true diff --git a/tests/test_load.py b/tests/test_load.py index 7e36118..bc2459d 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -15,25 +15,30 @@ from rougail_tests.utils import config_to_dict test_dir = Path(__file__).parent / 'structures' -def _test_dictionaries(test_dir): +def _test_dictionaries(test_dir, command): rougailconfig = RougailConfig.copy() rougailconfig['main_dictionaries'] = [str(test_dir)] # rougailconfig['tiramisu_cache'] = "cache.py" rougailconfig['step.user_data'] = ['bitwarden'] + rougailconfig['bitwarden.command'] = command rougail = Rougail(rougailconfig) config = rougail.run() # loads variables in the tiramisu config errors = RougailUserData(config, rougailconfig=rougailconfig).run() #expected output config_dict = dict(config_to_dict(config.forcepermissive.value.get())) - ok_file = Path('tests') / 'results' / test_dir.name / 'makedict' / 'bitwarden.json' + ok_file = Path('tests') / 'results' / test_dir.name / 'makedict' / ('bitwarden.json.' + command) + if not ok_file.is_file(): + ok_file = Path('tests') / 'results' / test_dir.name / 'makedict' / 'bitwarden.json' if not ok_file.is_file(): ok_file.parent.mkdir(parents=True, exist_ok=True) with open(ok_file, 'a') as json_file: dump(config_dict, json_file, indent=4) with open(ok_file) as json_file: expected = load(json_file) - errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / 'bitwarden.json' + errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / ('bitwarden.json.' + command) + if not errors_file.is_file(): + errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / 'bitwarden.json' if not errors_file.is_file(): errors_file.parent.mkdir(parents=True, exist_ok=True) with open(errors_file, 'a') as json_file: @@ -49,71 +54,171 @@ def _test_dictionaries(test_dir): assert expected == config_dict -def test_dictionaries_1_secret(): +def test_dictionaries_1_secret_rbw(): "tests the output" - _test_dictionaries(test_dir / '1_secret') + _test_dictionaries(test_dir / '1_secret', 'rbw') -def test_dictionaries_2_username_secret(): +def test_dictionaries_1_secret_bw(): "tests the output" - _test_dictionaries(test_dir / '2_username_secret') + _test_dictionaries(test_dir / '1_secret', 'bw') -def test_dictionaries_2_username_secret_invalid(): +def test_dictionaries_1_secret_unknown_rbw(): "tests the output" - _test_dictionaries(test_dir / '2_username_secret_invalid') + _test_dictionaries(test_dir / '1_secret_unknown', 'rbw') -def test_dictionaries_2_username_secret_hidden(): +def test_dictionaries_1_secret_unknown_bw(): "tests the output" - _test_dictionaries(test_dir / '2_username_secret_hidden') + _test_dictionaries(test_dir / '1_secret_unknown', 'bw') -def test_dictionaries_3_leadership_secret(): +def test_dictionaries_2_username_secret_rbw(): "tests the output" - _test_dictionaries(test_dir / '3_leadership_secret') + _test_dictionaries(test_dir / '2_username_secret', 'rbw') -def test_dictionaries_3_leadership_secret_several(): +def test_dictionaries_2_username_secret_bw(): "tests the output" - _test_dictionaries(test_dir / '3_leadership_secret_several') + _test_dictionaries(test_dir / '2_username_secret', 'bw') -def test_dictionaries_4_several_secrets(): +def test_dictionaries_2_username_secret_upper_rbw(): "tests the output" - _test_dictionaries(test_dir / '4_several_secrets') + _test_dictionaries(test_dir / '2_username_secret_upper', 'rbw') -def test_dictionaries_5_default_value(): +def test_dictionaries_2_username_secret_upper_bw(): + "tests the output" + _test_dictionaries(test_dir / '2_username_secret_upper', 'bw') + + +def test_dictionaries_2_username_secret_invalid_rbw(): + "tests the output" + _test_dictionaries(test_dir / '2_username_secret_invalid', 'rbw') + + +def test_dictionaries_2_username_secret_invalid_bw(): + "tests the output" + _test_dictionaries(test_dir / '2_username_secret_invalid', 'bw') + + +def test_dictionaries_2_username_secret_hidden_rbw(): + "tests the output" + _test_dictionaries(test_dir / '2_username_secret_hidden', 'rbw') + + +def test_dictionaries_2_username_secret_hidden_bw(): + "tests the output" + _test_dictionaries(test_dir / '2_username_secret_hidden', 'bw') + + +def test_dictionaries_3_leadership_secret_rbw(): + "tests the output" + _test_dictionaries(test_dir / '3_leadership_secret', 'rbw') + + +def test_dictionaries_3_leadership_secret_bw(): + "tests the output" + _test_dictionaries(test_dir / '3_leadership_secret', 'bw') + + +def test_dictionaries_3_leadership_secret_several_rbw(): + "tests the output" + _test_dictionaries(test_dir / '3_leadership_secret_several', 'rbw') + + +def test_dictionaries_3_leadership_secret_several_bw(): + "tests the output" + _test_dictionaries(test_dir / '3_leadership_secret_several', 'bw') + + +def test_dictionaries_4_several_secrets_rbw(): + "tests the output" + _test_dictionaries(test_dir / '4_several_secrets', 'rbw') + + +def test_dictionaries_4_several_secrets_bw(): + "tests the output" + _test_dictionaries(test_dir / '4_several_secrets', 'bw') + + +def test_dictionaries_4_several_secrets_upper_rbw(): + "tests the output" + _test_dictionaries(test_dir / '4_several_secrets_upper', 'rbw') + + +def test_dictionaries_4_several_secrets_upper_bw(): + "tests the output" + _test_dictionaries(test_dir / '4_several_secrets_upper', 'bw') + + +def test_dictionaries_5_default_value_rbw(): "tests the output" with raises(DictConsistencyError) as err: - _test_dictionaries(test_dir / '5_default_value') + _test_dictionaries(test_dir / '5_default_value', 'rbw') assert err.errno == 304 -def test_dictionaries_6_leadership_secret_default_value(): +def test_dictionaries_5_default_value_bw(): "tests the output" with raises(DictConsistencyError) as err: - _test_dictionaries(test_dir / '6_leadership_secret_default_value') + _test_dictionaries(test_dir / '5_default_value', 'bw') assert err.errno == 304 -def test_dictionaries_6_leadership_secret_follower_variable(): +def test_dictionaries_6_leadership_secret_default_value_rbw(): "tests the output" with raises(DictConsistencyError) as err: - _test_dictionaries(test_dir / '6_leadership_secret_follower_variable') + _test_dictionaries(test_dir / '6_leadership_secret_default_value', 'rbw') + assert err.errno == 304 + + +def test_dictionaries_6_leadership_secret_default_value_bw(): + "tests the output" + with raises(DictConsistencyError) as err: + _test_dictionaries(test_dir / '6_leadership_secret_default_value', 'bw') + assert err.errno == 304 + + +def test_dictionaries_6_leadership_secret_follower_variable_rbw(): + "tests the output" + with raises(DictConsistencyError) as err: + _test_dictionaries(test_dir / '6_leadership_secret_follower_variable', 'rbw') assert err.errno == 303 -def test_dictionaries_8_multi_variable(): +def test_dictionaries_6_leadership_secret_follower_variable_bw(): "tests the output" with raises(DictConsistencyError) as err: - _test_dictionaries(test_dir / '8_multi_variable') + _test_dictionaries(test_dir / '6_leadership_secret_follower_variable', 'bw') + assert err.errno == 303 + + +def test_dictionaries_8_multi_variable_rbw(): + "tests the output" + with raises(DictConsistencyError) as err: + _test_dictionaries(test_dir / '8_multi_variable', 'rbw') assert err.errno == 302 -def test_dictionaries_9_unknown_type(): +def test_dictionaries_8_multi_variable_bw(): "tests the output" with raises(DictConsistencyError) as err: - _test_dictionaries(test_dir / '9_unknown_type') + _test_dictionaries(test_dir / '8_multi_variable', 'bw') + assert err.errno == 302 + + +def test_dictionaries_9_unknown_type_rbw(): + "tests the output" + with raises(DictConsistencyError) as err: + _test_dictionaries(test_dir / '9_unknown_type', 'rbw') + assert err.errno == 301 + + +def test_dictionaries_9_unknown_type_bw(): + "tests the output" + with raises(DictConsistencyError) as err: + _test_dictionaries(test_dir / '9_unknown_type', 'bw') assert err.errno == 301