diff --git a/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.po b/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.po index 5653cb8..4361c83 100644 --- a/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.po +++ b/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2025-12-20 20:33+0100\n" -"PO-Revision-Date: 2025-12-20 20:33+0100\n" +"POT-Creation-Date: 2025-12-23 09:42+0100\n" +"PO-Revision-Date: 2025-12-23 09:44+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" @@ -47,57 +47,53 @@ msgstr "Ligne de commande doit être dans le PATH" msgid "Simulate password generation instead of retrieve them" msgstr "Simuler la génération de mots de passe au lieu de les récupérer" -#: src/rougail/user_data_bitwarden/data.py:132 +#: src/rougail/user_data_bitwarden/data.py:141 msgid "\"bitwarden\" is not set in step.user_data" msgstr "\"bitwarden\" n'est pas dans step.user_data" -#: src/rougail/user_data_bitwarden/data.py:169 +#: src/rougail/user_data_bitwarden/data.py:178 msgid "please unlock Bitwarden password database with {0} command line" msgstr "" "veuillez déverrouiller la base de mot de passe Bitwarden avec la ligne de " "commande {0}" -#: src/rougail/user_data_bitwarden/data.py:171 +#: src/rougail/user_data_bitwarden/data.py:180 msgid "cannot find Bitwarden command line {0} please install it" msgstr "" "ne peut trouver la ligne de commande Bitwarden {0} veuillez l'installer" -#: src/rougail/user_data_bitwarden/data.py:208 -msgid "the default value for \"{0}\" must be the Bitwarden item name" -msgstr "" -"la valeur par défaut pour \"{0}\" doit être un nom d'élément de Bitwarden" +#: src/rougail/user_data_bitwarden/data.py:198 +msgid "the default value must be the Bitwarden item name" +msgstr "la valeur par défaut doit être un nom d'élément de Bitwarden" -#: src/rougail/user_data_bitwarden/data.py:219 -msgid "cannot execute the \"{0}\" commandline from Bitwarden for \"{1}\": {2}" -msgstr "" -"ne peut exécuter la ligne de commande \"{0}\" pour Bitwarden pour \"{1}\": " -"{2}" +#: src/rougail/user_data_bitwarden/data.py:210 +msgid "cannot execute the \"{0}\" commandline from Bitwarden: {1}" +msgstr "ne peut exécuter la ligne de commande \"{0}\" pour Bitwarden : {1}" + +#: src/rougail/user_data_bitwarden/data.py:217 +msgid "item \"{0}\" in Bitwarden is not found\"" +msgstr "l'élément \"{0}\" dans Bitwarden n'est pas trouvé" #: src/rougail/user_data_bitwarden/data.py:228 -msgid "item \"{0}\" in Bitwarden is not found for \"{1}\"" -msgstr "l'élément \"{0}\" dans Bitwarden n'est pas trouvé pour \"{1}\"" +msgid "several items found with name \"{0}\" in Bitwarden: \"{1}\"" +msgstr "" +"plusieurs éléments trouvés avec le nom \"{0}\" dans Bitwarden : \"{1}\"" #: src/rougail/user_data_bitwarden/data.py:243 -msgid "several items found with name \"{0}\" in Bitwarden for \"{1}\": \"{2}\"" -msgstr "" -"plusieurs éléments trouvés avec le nom \"{0}\" dans Bitwarden pour \"{1}\": " -"\"{2}\"" +msgid "unexpected datas \"{0}\" from Bitwarden: {1}" +msgstr "données inattendues \"{0}\" pour Bitwarden : {1}" -#: src/rougail/user_data_bitwarden/data.py:262 -msgid "unexpected datas \"{0}\" from Bitwarden for \"{1}\": {2}" -msgstr "données inattendues \"{0}\" pour Bitwarden pour \"{1}\": {2}" - -#: src/rougail/user_data_bitwarden/data.py:270 +#: src/rougail/user_data_bitwarden/data.py:249 msgid "password" msgstr "de mot de passe" -#: src/rougail/user_data_bitwarden/data.py:272 +#: src/rougail/user_data_bitwarden/data.py:251 msgid "username" msgstr "de nom d'utilisateur" -#: src/rougail/user_data_bitwarden/data.py:274 -msgid "item \"{0}\" in Bitwarden has no {1} for \"{2}\"" -msgstr "l'élément \"{0}\" dans Bitwarden n'a pas {1} for \"{2}\"" +#: src/rougail/user_data_bitwarden/data.py:253 +msgid "item \"{0}\" in Bitwarden has no {1}" +msgstr "l'élément \"{0}\" dans Bitwarden n'a pas {1}" #~ msgid "Configuration rougail-user-data-bitwarden" #~ msgstr "Configuration de rougail-user-data-bitwarden" diff --git a/locale/rougail_user_data_bitwarden.pot b/locale/rougail_user_data_bitwarden.pot index 086dfb0..de9210d 100644 --- a/locale/rougail_user_data_bitwarden.pot +++ b/locale/rougail_user_data_bitwarden.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-12-20 20:33+0100\n" +"POT-Creation-Date: 2025-12-23 09:44+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,47 +39,47 @@ msgstr "" msgid "Simulate password generation instead of retrieve them" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:132 +#: src/rougail/user_data_bitwarden/data.py:141 msgid "\"bitwarden\" is not set in step.user_data" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:169 +#: src/rougail/user_data_bitwarden/data.py:178 msgid "please unlock Bitwarden password database with {0} command line" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:171 +#: src/rougail/user_data_bitwarden/data.py:180 msgid "cannot find Bitwarden command line {0} please install it" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:208 -msgid "the default value for \"{0}\" must be the Bitwarden item name" +#: src/rougail/user_data_bitwarden/data.py:198 +msgid "the default value must be the Bitwarden item name" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:219 -msgid "cannot execute the \"{0}\" commandline from Bitwarden for \"{1}\": {2}" +#: src/rougail/user_data_bitwarden/data.py:210 +msgid "cannot execute the \"{0}\" commandline from Bitwarden: {1}" +msgstr "" + +#: src/rougail/user_data_bitwarden/data.py:217 +msgid "item \"{0}\" in Bitwarden is not found\"" msgstr "" #: src/rougail/user_data_bitwarden/data.py:228 -msgid "item \"{0}\" in Bitwarden is not found for \"{1}\"" +msgid "several items found with name \"{0}\" in Bitwarden: \"{1}\"" msgstr "" #: src/rougail/user_data_bitwarden/data.py:243 -msgid "several items found with name \"{0}\" in Bitwarden for \"{1}\": \"{2}\"" +msgid "unexpected datas \"{0}\" from Bitwarden: {1}" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:262 -msgid "unexpected datas \"{0}\" from Bitwarden for \"{1}\": {2}" -msgstr "" - -#: src/rougail/user_data_bitwarden/data.py:270 +#: src/rougail/user_data_bitwarden/data.py:249 msgid "password" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:272 +#: src/rougail/user_data_bitwarden/data.py:251 msgid "username" msgstr "" -#: src/rougail/user_data_bitwarden/data.py:274 -msgid "item \"{0}\" in Bitwarden has no {1} for \"{2}\"" +#: src/rougail/user_data_bitwarden/data.py:253 +msgid "item \"{0}\" in Bitwarden has no {1}" msgstr "" diff --git a/src/rougail/user_data_bitwarden/annotator.py b/src/rougail/user_data_bitwarden/annotator.py index cbda729..d401a3c 100644 --- a/src/rougail/user_data_bitwarden/annotator.py +++ b/src/rougail/user_data_bitwarden/annotator.py @@ -44,5 +44,7 @@ class Annotator(Walk): path = variable.path self.objectspace.informations.add(path, "bitwarden", True) self.objectspace.informations.add(path, "default_value_makes_sense", False) + if "force_default_on_freeze" in self.objectspace.properties.get(path): + self.objectspace.properties.remove(path, "force_default_on_freeze") self.objectspace.properties.add(path, "novalidator", True) variable.default = variable.secret_manager diff --git a/src/rougail/user_data_bitwarden/data.py b/src/rougail/user_data_bitwarden/data.py index ee19f71..8176cba 100644 --- a/src/rougail/user_data_bitwarden/data.py +++ b/src/rougail/user_data_bitwarden/data.py @@ -34,7 +34,7 @@ class FakeBW: def get_key( self, key_bitwarden: str, - multiple: bool, + leader: bool, ): return [{'name': key_bitwarden, 'login': {'username': "example_login", 'password': "Ex4mpL3_P4ssw0rD"}}] @@ -50,7 +50,7 @@ class RBW: def get_key( self, key_bitwarden: str, - multiple: bool, + leader: bool, ): keys = [] items = run_commandline(["rbw", "search", key_bitwarden]).strip() @@ -63,14 +63,14 @@ class RBW: keys.append(item.split("@", 1)[-1]) else: keys.append(item.rsplit("/", 1)[-1]) - if not multiple: + if not leader: if not items: return [] if len(items) > 1: return [{"name": key} for key in keys] keys = [key_bitwarden] datas = [] - for key in keys: + for key in sorted(keys): data = loads( run_commandline( ["rbw", "get", key, "--raw", "--ignorecase"] @@ -108,6 +108,15 @@ class BW: ) +def run_commandline(cmd) -> str: + cpe = run(cmd, capture_output=True) + returncode = cpe.returncode + err = cpe.stderr.decode("utf8") + if returncode != 0 or err: + raise ExtensionError("{0} ({1})".format(err, returncode)) + return cpe.stdout.decode("utf8") + + class RougailUserDataBitwarden: def __init__( self, @@ -132,7 +141,7 @@ class RougailUserDataBitwarden: raise ExtensionError(_('"bitwarden" is not set in step.user_data')) self.errors = [] self.warnings = [] - self.leader_informations = {} + self.cache = {} self.commands = {'rbw': RBW(), 'bw': BW(), } @@ -176,69 +185,54 @@ class RougailUserDataBitwarden: if option.isoptiondescription(): self.set_passwords(option, values) elif option.information.get("bitwarden", False): - values[option.path(uncalculated=True)] = (set_password, self.leader_informations, self.command) + values[option.path(uncalculated=True)] = (set_password, self.cache, self.command) -def set_password(leader_informations, command, *, option): - path = option.path() - leader_key = None - if option.isfollower(): - leader_path = option.parent().leader().path() - if leader_path in leader_informations: - leader_key = leader_informations[leader_path][option.index()] - if not option.isleader(): - return get_values(command, option, leader_key=leader_key) - leader_informations[path], option_values = get_values( - command, option, multiple=True, leader_key=option.value.default()[0] - ) - return option_values - - -def get_values(command, option, *, multiple=False, leader_key=None): - if leader_key: - key = leader_key - else: - key = option.value.default() - type_ = option.information.get("type") - if "validator" not in option.property.get(): - option.property.add("validator") +def set_password(cache, command, *, option): + key = option.value.default() + leader = option.isleader() + if leader: + key = key[0] if not isinstance(key, str): raise ConfigError( _('the default value must be the Bitwarden item name') ) - try: - data = command.get_key(key, multiple) - except Exception as exc: - raise ConfigError( - _( - 'cannot execute the "{0}" commandline from Bitwarden: {1}' - ).format(command, exc) - ) + if key in cache: + data = cache[key] + if option.isfollower(): + data = [data[option.index()]] + key = data[0]["name"] + else: + try: + data = command.get_key(key, leader) + except Exception as exc: + raise ConfigError( + _( + 'cannot execute the "{0}" commandline from Bitwarden: {1}' + ).format(command, exc) + ) + cache[key] = data.copy() if not data: raise ConfigError( _('item "{0}" in Bitwarden is not found"').format( key ) ) - if len(data) != 1: - names = [d["name"] for d in data] - if multiple: - ret = [] - return names, [ - get_value(key, type_, d) for d in data - ] + type_ = option.information.get("type") + if leader: + return [ + get_value(key, type_, d) for d in data + ] + elif len(data) != 1: raise ConfigError( _( 'several items found with name "{0}" in Bitwarden: "{1}"' - ).format(key, '", "'.join(names)) + ).format(key, '", "'.join([d["name"] for d in data])) ) - value = get_value(key, type_, data[0]) - if multiple: - return [data[0]["name"]], [value] - return value + return get_value(key, type_, data[0]) -def get_value( key_bitwarden: str, type_: str, data: dict) -> str: +def get_value(key_bitwarden: str, type_: str, data: dict) -> str: try: if type_ == "secret": value = data["login"]["password"] @@ -246,28 +240,18 @@ def get_value( key_bitwarden: str, type_: str, data: dict) -> str: value = data["login"]["username"] except Exception as exc: raise ConfigError( - _('unexpected datas "{0}" from Bitwarden: {2}').format( + _('unexpected datas "{0}" from Bitwarden: {1}').format( key_bitwarden, exc ) ) - else: - if value is None: - if type_ == "secret": - bw_type = _("password") - else: - bw_type = _("username") - raise ConfigError( - _('item "{0}" in Bitwarden has no {1}').format( - key_bitwarden, bw_type - ) + if value is None: + if type_ == "secret": + bw_type = _("password") + else: + bw_type = _("username") + raise ConfigError( + _('item "{0}" in Bitwarden has no {1}').format( + key_bitwarden, bw_type ) + ) return value - - -def run_commandline(cmd) -> str: - cpe = run(cmd, capture_output=True) - returncode = cpe.returncode - err = cpe.stderr.decode("utf8") - if returncode != 0 or err: - raise ExtensionError("{0} ({1})".format(err, returncode)) - return cpe.stdout.decode("utf8") diff --git a/src/rougail/user_data_bitwarden/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.mo b/src/rougail/user_data_bitwarden/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.mo index 9726694..120ca1b 100644 Binary files a/src/rougail/user_data_bitwarden/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.mo and b/src/rougail/user_data_bitwarden/locale/fr/LC_MESSAGES/rougail_user_data_bitwarden.mo differ diff --git a/tests/results/1_secret_unknown/errors/bitwarden.json b/tests/results/1_secret_unknown/errors/bitwarden.json index 47dd894..217db0a 100644 --- a/tests/results/1_secret_unknown/errors/bitwarden.json +++ b/tests/results/1_secret_unknown/errors/bitwarden.json @@ -1,6 +1,4 @@ { - "errors": [ - "item \"1_secret_unknown - environment - service - user\" in Bitwarden is not found for \"rougail.secret\"" - ], + "errors": [], "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 index fd8daba..4a47530 100644 --- a/tests/results/1_secret_unknown/makedict/bitwarden.json +++ b/tests/results/1_secret_unknown/makedict/bitwarden.json @@ -1,3 +1 @@ -{ - "rougail.secret": null -} \ No newline at end of file +"item \"1_secret_unknown - environment - service - user\" in Bitwarden is not found\"" \ 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 deleted file mode 100644 index 549b257..0000000 --- a/tests/results/3_leadership_secret_several/makedict/bitwarden.json.rbw +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rougail.leader.username": [ - { - "rougail.leader.username": "bitwarden_username_2", - "rougail.leader.secret": "bitwarden_password_2" - }, - { - "rougail.leader.username": "bitwarden_username", - "rougail.leader.secret": "bitwarden_password" - } - ] -} diff --git a/tests/results/4_several_secrets/errors/bitwarden.json b/tests/results/4_several_secrets/errors/bitwarden.json index 9a92834..217db0a 100644 --- a/tests/results/4_several_secrets/errors/bitwarden.json +++ b/tests/results/4_several_secrets/errors/bitwarden.json @@ -1,6 +1,4 @@ { - "errors": [ - "several items found with name \"4_several_secrets - environment - service - user\" in Bitwarden for \"rougail.secret\": \"4_several_secrets - environment - service - user_1\", \"4_several_secrets - environment - service - user_2\"" - ], + "errors": [], "warnings": [] } \ No newline at end of file diff --git a/tests/results/4_several_secrets/makedict/bitwarden.json b/tests/results/4_several_secrets/makedict/bitwarden.json index fd8daba..a491127 100644 --- a/tests/results/4_several_secrets/makedict/bitwarden.json +++ b/tests/results/4_several_secrets/makedict/bitwarden.json @@ -1,3 +1 @@ -{ - "rougail.secret": null -} \ No newline at end of file +"several items found with name \"4_several_secrets - environment - service - user\" in Bitwarden: \"4_several_secrets - environment - service - user_1\", \"4_several_secrets - environment - service - user_2\"" \ No newline at end of file diff --git a/tests/results/4_several_secrets_upper/errors/bitwarden.json b/tests/results/4_several_secrets_upper/errors/bitwarden.json index 1bc2b94..217db0a 100644 --- a/tests/results/4_several_secrets_upper/errors/bitwarden.json +++ b/tests/results/4_several_secrets_upper/errors/bitwarden.json @@ -1,6 +1,4 @@ { - "errors": [ - "several items found with name \"3_leadership_secret - ENVIRONMENT - SERVICE - USER\" in Bitwarden for \"rougail.secret\": \"3_leadership_secret - environment - service - user_1\", \"3_leadership_secret - environment - service - user_2\"" - ], + "errors": [], "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 index fd8daba..581c8e0 100644 --- a/tests/results/4_several_secrets_upper/makedict/bitwarden.json +++ b/tests/results/4_several_secrets_upper/makedict/bitwarden.json @@ -1,3 +1 @@ -{ - "rougail.secret": null -} \ No newline at end of file +"several items found with name \"3_leadership_secret - ENVIRONMENT - SERVICE - USER\" in Bitwarden: \"3_leadership_secret - environment - service - user_1\", \"3_leadership_secret - environment - service - user_2\"" \ No newline at end of file diff --git a/tests/results/5_secret_calc_other_user_data3/makedict/bitwarden.json b/tests/results/5_secret_calc_other_user_data3/makedict/bitwarden.json index eaf33a5..baa35d9 100644 --- a/tests/results/5_secret_calc_other_user_data3/makedict/bitwarden.json +++ b/tests/results/5_secret_calc_other_user_data3/makedict/bitwarden.json @@ -1,4 +1,8 @@ { + "rougail.host": "test", + "rougail.project": "5_secret_calc", + "rougail.environment": "environment", + "rougail.service": "service", "rougail.modified_variable_single": "user_2", "rougail.secret": "bitwarden_password_2" } \ No newline at end of file diff --git a/tests/results/6_no_username/errors/bitwarden.json b/tests/results/6_no_username/errors/bitwarden.json index 2de794e..217db0a 100644 --- a/tests/results/6_no_username/errors/bitwarden.json +++ b/tests/results/6_no_username/errors/bitwarden.json @@ -1,6 +1,4 @@ { - "errors": [ - "item \"6_no_username - environment - service - user_1\" in Bitwarden has no username for \"rougail.username\"" - ], + "errors": [], "warnings": [] } \ No newline at end of file diff --git a/tests/results/6_no_username/makedict/bitwarden.json b/tests/results/6_no_username/makedict/bitwarden.json index e9d227d..c6687f2 100644 --- a/tests/results/6_no_username/makedict/bitwarden.json +++ b/tests/results/6_no_username/makedict/bitwarden.json @@ -1,4 +1 @@ -{ - "rougail.username": null, - "rougail.secret": "ET6RrpdZv5bNZu" -} \ No newline at end of file +"item \"6_no_username - environment - service - user_1\" in Bitwarden has no username" \ No newline at end of file diff --git a/tests/results/7_secret_hidden/errors/bitwarden_env.json b/tests/results/7_secret_hidden/errors/bitwarden_env.json new file mode 100644 index 0000000..217db0a --- /dev/null +++ b/tests/results/7_secret_hidden/errors/bitwarden_env.json @@ -0,0 +1,4 @@ +{ + "errors": [], + "warnings": [] +} \ No newline at end of file diff --git a/tests/results/7_secret_hidden/makedict/bitwarden_env.json b/tests/results/7_secret_hidden/makedict/bitwarden_env.json new file mode 100644 index 0000000..44799da --- /dev/null +++ b/tests/results/7_secret_hidden/makedict/bitwarden_env.json @@ -0,0 +1,4 @@ +{ + "rougail.username": "example_login", + "rougail.secret": "Ex4mpL3_P4ssw0rD" +} \ No newline at end of file diff --git a/tests/structures/7_secret_hidden/00-base.yml b/tests/structures/7_secret_hidden/00-base.yml new file mode 100644 index 0000000..602aac3 --- /dev/null +++ b/tests/structures/7_secret_hidden/00-base.yml @@ -0,0 +1,23 @@ +--- +version: 1.1 + +username: + description: the username + type: unix_user + secret_manager: + host: test + project: 2_username_secret + environment: environment + service: service + user: user_1 + +secret: + description: the secret + type: secret + secret_manager: + host: test + project: 2_username_secret + environment: environment + service: service + user: user_1 + hidden: true diff --git a/tests/test_load.py b/tests/test_load.py index 08c4a4e..ad34040 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -8,6 +8,7 @@ from json import load, dump ######################### from pytest import raises from unittest import mock +from tiramisu.error import ConfigError from rougail_tests.utils import config_to_dict @@ -33,7 +34,10 @@ def _test_structural_files(test_dir, command, *, env=False, modified=False, mock generated_user_data.insert(0, {'source': 'By Hand', 'errors': [], 'warnings': [], 'values': {'rougail.modified_variable': ['user_1', 'user_2'], 'rougail.modified_variable_single': 'user_2'}}) errors = rougail.user_data(generated_user_data) #expected output - config_dict = dict(config_to_dict(config.value.get())) + try: + config_dict = dict(config_to_dict(config.value.get())) + except (ConfigError, ValueError) as err: + config_dict = str(err) if not env: base_filename = 'bitwarden.json' else: @@ -50,6 +54,10 @@ def _test_structural_files(test_dir, command, *, env=False, modified=False, mock errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / (base_filename + '.' + command) if not errors_file.is_file(): errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / base_filename + for l, data in errors.items(): + for i, d in enumerate(data): + if isinstance(d, dict): + data[i] = list(d) if not errors_file.is_file(): errors_file.parent.mkdir(parents=True, exist_ok=True) with open(errors_file, 'a') as json_file: @@ -186,6 +194,14 @@ def test_structural_files_6_no_username_bw(): _test_structural_files(test_dir / '6_no_username', 'bw') +def test_structural_files_7_secret_calc_rbw(): + _test_structural_files(test_dir / '7_secret_hidden', 'rbw', env=True, mock=True) + + +def test_structural_files_7_secret_hidden_bw(): + _test_structural_files(test_dir / '7_secret_hidden', 'bw', env=True, mock=True) + + def test_structural_files_8_multi_variable_rbw(): "tests the output" with raises(DictConsistencyError) as err: