feat: improvment + Calculation with hidden

This commit is contained in:
egarette@silique.fr 2025-12-23 09:45:29 +01:00
parent 61fd155025
commit 7fe91e9eff
19 changed files with 159 additions and 155 deletions

View file

@ -5,8 +5,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"POT-Creation-Date: 2025-12-20 20:33+0100\n" "POT-Creation-Date: 2025-12-23 09:42+0100\n"
"PO-Revision-Date: 2025-12-20 20:33+0100\n" "PO-Revision-Date: 2025-12-23 09:44+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\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" 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" 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" msgid "\"bitwarden\" is not set in step.user_data"
msgstr "\"bitwarden\" n'est pas dans 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" msgid "please unlock Bitwarden password database with {0} command line"
msgstr "" msgstr ""
"veuillez déverrouiller la base de mot de passe Bitwarden avec la ligne de " "veuillez déverrouiller la base de mot de passe Bitwarden avec la ligne de "
"commande {0}" "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" msgid "cannot find Bitwarden command line {0} please install it"
msgstr "" msgstr ""
"ne peut trouver la ligne de commande Bitwarden {0} veuillez l'installer" "ne peut trouver la ligne de commande Bitwarden {0} veuillez l'installer"
#: src/rougail/user_data_bitwarden/data.py:208 #: src/rougail/user_data_bitwarden/data.py:198
msgid "the default value for \"{0}\" must be the Bitwarden item name" msgid "the default value must be the Bitwarden item name"
msgstr "" msgstr "la valeur par défaut doit être un nom d'élément de Bitwarden"
"la valeur par défaut pour \"{0}\" doit être un nom d'élément de Bitwarden"
#: src/rougail/user_data_bitwarden/data.py:219 #: src/rougail/user_data_bitwarden/data.py:210
msgid "cannot execute the \"{0}\" commandline from Bitwarden for \"{1}\": {2}" msgid "cannot execute the \"{0}\" commandline from Bitwarden: {1}"
msgstr "" msgstr "ne peut exécuter la ligne de commande \"{0}\" pour Bitwarden : {1}"
"ne peut exécuter la ligne de commande \"{0}\" pour Bitwarden pour \"{1}\": "
"{2}" #: 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 #: 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 "l'élément \"{0}\" dans Bitwarden n'est pas trouvé pour \"{1}\"" msgstr ""
"plusieurs éléments trouvés avec le nom \"{0}\" dans Bitwarden : \"{1}\""
#: src/rougail/user_data_bitwarden/data.py:243 #: 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 "" msgstr "données inattendues \"{0}\" pour Bitwarden : {1}"
"plusieurs éléments trouvés avec le nom \"{0}\" dans Bitwarden pour \"{1}\": "
"\"{2}\""
#: src/rougail/user_data_bitwarden/data.py:262 #: src/rougail/user_data_bitwarden/data.py:249
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
msgid "password" msgid "password"
msgstr "de mot de passe" msgstr "de mot de passe"
#: src/rougail/user_data_bitwarden/data.py:272 #: src/rougail/user_data_bitwarden/data.py:251
msgid "username" msgid "username"
msgstr "de nom d'utilisateur" msgstr "de nom d'utilisateur"
#: src/rougail/user_data_bitwarden/data.py:274 #: src/rougail/user_data_bitwarden/data.py:253
msgid "item \"{0}\" in Bitwarden has no {1} for \"{2}\"" msgid "item \"{0}\" in Bitwarden has no {1}"
msgstr "l'élément \"{0}\" dans Bitwarden n'a pas {1} for \"{2}\"" msgstr "l'élément \"{0}\" dans Bitwarden n'a pas {1}"
#~ msgid "Configuration rougail-user-data-bitwarden" #~ msgid "Configuration rougail-user-data-bitwarden"
#~ msgstr "Configuration de rougail-user-data-bitwarden" #~ msgstr "Configuration de rougail-user-data-bitwarden"

View file

@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -39,47 +39,47 @@ msgstr ""
msgid "Simulate password generation instead of retrieve them" msgid "Simulate password generation instead of retrieve them"
msgstr "" 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" msgid "\"bitwarden\" is not set in step.user_data"
msgstr "" 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" msgid "please unlock Bitwarden password database with {0} command line"
msgstr "" 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" msgid "cannot find Bitwarden command line {0} please install it"
msgstr "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:208 #: src/rougail/user_data_bitwarden/data.py:198
msgid "the default value for \"{0}\" must be the Bitwarden item name" msgid "the default value must be the Bitwarden item name"
msgstr "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:219 #: src/rougail/user_data_bitwarden/data.py:210
msgid "cannot execute the \"{0}\" commandline from Bitwarden for \"{1}\": {2}" 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 "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:228 #: 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 "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:243 #: 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 "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:262 #: src/rougail/user_data_bitwarden/data.py:249
msgid "unexpected datas \"{0}\" from Bitwarden for \"{1}\": {2}"
msgstr ""
#: src/rougail/user_data_bitwarden/data.py:270
msgid "password" msgid "password"
msgstr "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:272 #: src/rougail/user_data_bitwarden/data.py:251
msgid "username" msgid "username"
msgstr "" msgstr ""
#: src/rougail/user_data_bitwarden/data.py:274 #: src/rougail/user_data_bitwarden/data.py:253
msgid "item \"{0}\" in Bitwarden has no {1} for \"{2}\"" msgid "item \"{0}\" in Bitwarden has no {1}"
msgstr "" msgstr ""

View file

@ -44,5 +44,7 @@ class Annotator(Walk):
path = variable.path path = variable.path
self.objectspace.informations.add(path, "bitwarden", True) self.objectspace.informations.add(path, "bitwarden", True)
self.objectspace.informations.add(path, "default_value_makes_sense", False) 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) self.objectspace.properties.add(path, "novalidator", True)
variable.default = variable.secret_manager variable.default = variable.secret_manager

View file

@ -34,7 +34,7 @@ class FakeBW:
def get_key( def get_key(
self, self,
key_bitwarden: str, key_bitwarden: str,
multiple: bool, leader: bool,
): ):
return [{'name': key_bitwarden, 'login': {'username': "example_login", 'password': "Ex4mpL3_P4ssw0rD"}}] return [{'name': key_bitwarden, 'login': {'username': "example_login", 'password': "Ex4mpL3_P4ssw0rD"}}]
@ -50,7 +50,7 @@ class RBW:
def get_key( def get_key(
self, self,
key_bitwarden: str, key_bitwarden: str,
multiple: bool, leader: bool,
): ):
keys = [] keys = []
items = run_commandline(["rbw", "search", key_bitwarden]).strip() items = run_commandline(["rbw", "search", key_bitwarden]).strip()
@ -63,14 +63,14 @@ class RBW:
keys.append(item.split("@", 1)[-1]) keys.append(item.split("@", 1)[-1])
else: else:
keys.append(item.rsplit("/", 1)[-1]) keys.append(item.rsplit("/", 1)[-1])
if not multiple: if not leader:
if not items: if not items:
return [] return []
if len(items) > 1: if len(items) > 1:
return [{"name": key} for key in keys] return [{"name": key} for key in keys]
keys = [key_bitwarden] keys = [key_bitwarden]
datas = [] datas = []
for key in keys: for key in sorted(keys):
data = loads( data = loads(
run_commandline( run_commandline(
["rbw", "get", key, "--raw", "--ignorecase"] ["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: class RougailUserDataBitwarden:
def __init__( def __init__(
self, self,
@ -132,7 +141,7 @@ class RougailUserDataBitwarden:
raise ExtensionError(_('"bitwarden" is not set in step.user_data')) raise ExtensionError(_('"bitwarden" is not set in step.user_data'))
self.errors = [] self.errors = []
self.warnings = [] self.warnings = []
self.leader_informations = {} self.cache = {}
self.commands = {'rbw': RBW(), self.commands = {'rbw': RBW(),
'bw': BW(), 'bw': BW(),
} }
@ -176,69 +185,54 @@ class RougailUserDataBitwarden:
if option.isoptiondescription(): if option.isoptiondescription():
self.set_passwords(option, values) self.set_passwords(option, values)
elif option.information.get("bitwarden", False): 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): def set_password(cache, 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() key = option.value.default()
type_ = option.information.get("type") leader = option.isleader()
if "validator" not in option.property.get(): if leader:
option.property.add("validator") key = key[0]
if not isinstance(key, str): if not isinstance(key, str):
raise ConfigError( raise ConfigError(
_('the default value must be the Bitwarden item name') _('the default value must be the Bitwarden item name')
) )
if key in cache:
data = cache[key]
if option.isfollower():
data = [data[option.index()]]
key = data[0]["name"]
else:
try: try:
data = command.get_key(key, multiple) data = command.get_key(key, leader)
except Exception as exc: except Exception as exc:
raise ConfigError( raise ConfigError(
_( _(
'cannot execute the "{0}" commandline from Bitwarden: {1}' 'cannot execute the "{0}" commandline from Bitwarden: {1}'
).format(command, exc) ).format(command, exc)
) )
cache[key] = data.copy()
if not data: if not data:
raise ConfigError( raise ConfigError(
_('item "{0}" in Bitwarden is not found"').format( _('item "{0}" in Bitwarden is not found"').format(
key key
) )
) )
if len(data) != 1: type_ = option.information.get("type")
names = [d["name"] for d in data] if leader:
if multiple: return [
ret = []
return names, [
get_value(key, type_, d) for d in data get_value(key, type_, d) for d in data
] ]
elif len(data) != 1:
raise ConfigError( raise ConfigError(
_( _(
'several items found with name "{0}" in Bitwarden: "{1}"' '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]) return get_value(key, type_, data[0])
if multiple:
return [data[0]["name"]], [value]
return value
def get_value( key_bitwarden: str, type_: str, data: dict) -> str: def get_value(key_bitwarden: str, type_: str, data: dict) -> str:
try: try:
if type_ == "secret": if type_ == "secret":
value = data["login"]["password"] value = data["login"]["password"]
@ -246,11 +240,10 @@ def get_value( key_bitwarden: str, type_: str, data: dict) -> str:
value = data["login"]["username"] value = data["login"]["username"]
except Exception as exc: except Exception as exc:
raise ConfigError( raise ConfigError(
_('unexpected datas "{0}" from Bitwarden: {2}').format( _('unexpected datas "{0}" from Bitwarden: {1}').format(
key_bitwarden, exc key_bitwarden, exc
) )
) )
else:
if value is None: if value is None:
if type_ == "secret": if type_ == "secret":
bw_type = _("password") bw_type = _("password")
@ -262,12 +255,3 @@ def get_value( key_bitwarden: str, type_: str, data: dict) -> str:
) )
) )
return value 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")

View file

@ -1,6 +1,4 @@
{ {
"errors": [ "errors": [],
"item \"1_secret_unknown - environment - service - user\" in Bitwarden is not found for \"rougail.secret\""
],
"warnings": [] "warnings": []
} }

View file

@ -1,3 +1 @@
{ "item \"1_secret_unknown - environment - service - user\" in Bitwarden is not found\""
"rougail.secret": null
}

View file

@ -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"
}
]
}

View file

@ -1,6 +1,4 @@
{ {
"errors": [ "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\""
],
"warnings": [] "warnings": []
} }

View file

@ -1,3 +1 @@
{ "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\""
"rougail.secret": null
}

View file

@ -1,6 +1,4 @@
{ {
"errors": [ "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\""
],
"warnings": [] "warnings": []
} }

View file

@ -1,3 +1 @@
{ "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\""
"rougail.secret": null
}

View file

@ -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.modified_variable_single": "user_2",
"rougail.secret": "bitwarden_password_2" "rougail.secret": "bitwarden_password_2"
} }

View file

@ -1,6 +1,4 @@
{ {
"errors": [ "errors": [],
"item \"6_no_username - environment - service - user_1\" in Bitwarden has no username for \"rougail.username\""
],
"warnings": [] "warnings": []
} }

View file

@ -1,4 +1 @@
{ "item \"6_no_username - environment - service - user_1\" in Bitwarden has no username"
"rougail.username": null,
"rougail.secret": "ET6RrpdZv5bNZu"
}

View file

@ -0,0 +1,4 @@
{
"errors": [],
"warnings": []
}

View file

@ -0,0 +1,4 @@
{
"rougail.username": "example_login",
"rougail.secret": "Ex4mpL3_P4ssw0rD"
}

View file

@ -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

View file

@ -8,6 +8,7 @@ from json import load, dump
######################### #########################
from pytest import raises from pytest import raises
from unittest import mock from unittest import mock
from tiramisu.error import ConfigError
from rougail_tests.utils import config_to_dict 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'}}) 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) errors = rougail.user_data(generated_user_data)
#expected output #expected output
try:
config_dict = dict(config_to_dict(config.value.get())) config_dict = dict(config_to_dict(config.value.get()))
except (ConfigError, ValueError) as err:
config_dict = str(err)
if not env: if not env:
base_filename = 'bitwarden.json' base_filename = 'bitwarden.json'
else: 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) errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / (base_filename + '.' + command)
if not errors_file.is_file(): if not errors_file.is_file():
errors_file = Path('tests') / 'results' / test_dir.name / 'errors' / base_filename 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(): if not errors_file.is_file():
errors_file.parent.mkdir(parents=True, exist_ok=True) errors_file.parent.mkdir(parents=True, exist_ok=True)
with open(errors_file, 'a') as json_file: 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') _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(): def test_structural_files_8_multi_variable_rbw():
"tests the output" "tests the output"
with raises(DictConsistencyError) as err: with raises(DictConsistencyError) as err: