def _test_create_installs(self, datadir): """ This is a bootstrap for one-time use creating lockers to be tested so we know nothing in our encryption has changed """ for crypt_id in list_crypts(): data_root = datadir['.'] new_loc = Path(data_root / crypt_id) new_loc.mkdir() conf = ConfigModel(store_path=new_loc, editor='vim') write_config_file(new_loc, conf) new_lock = Locker.create(self.test_name, self.test_pw, crypt_id) for name, pt in plain_texts.items(): ni = new_lock.create_item(name) ni.content = ( f"{pt}\nsite:www.zombo.com\npassword: YouCanD0NEthing!") new_lock.add_item(ni) # after lockers are stored, stub out the store_path conf._store_path = Path("/") conf.store = { 'store_type': StoreType.FileSystem.name, 'store_path': "/" } write_config_file(new_loc, conf, update=True, bypass_validation=True)
def test_multiple_cycles(self): """ Ciphers can only be used once - CryptImpl is supposed to have a workaround that refreshes the cipher after each use :return: """ crypt = create_crypt(self.pw) for tt in plain_texts: ct = crypt.encrypt(tt) pt = crypt.decrypt(ct) assert pt == tt for crypt_id in list_crypts(): crypt = create_crypt(self.pw, crypt_id) crypt_id = crypt.crypt_id pw_hash = crypt.pw_hash salt = crypt.salt for tt in plain_texts: ct = crypt.encrypt(tt) pt = crypt.decrypt(ct) assert pt == tt crypt = get_crypt(crypt_id, self.pw, pw_hash, salt) for tt in plain_texts: ct = crypt.encrypt(tt) pt = crypt.decrypt(ct) assert pt == tt
def custom_setup(self, tmp_path): super(EmptyLocker, self).custom_setup(tmp_path) for name in self.lockers: try: Locker.delete(name, self.password) except PhibesNotFoundError: pass self.lockers = {} try: Locker.delete(self.locker_name, self.password) except PhibesNotFoundError: pass finally: self.my_locker = Locker.create( password=self.password, crypt_id=crypto.default_id, locker_name=self.locker_name ) # create a locker for each registered crypt instance for crypt_id in crypto.list_crypts(): # dedupe the names with random numbers wart = str(random.randint(1000, 9999)) # but make sure there isn't a freak collision while self.locker_name + str(wart) in self.lockers: wart = str(random.randint(1000, 9999)) locker_name = self.locker_name + wart self.lockers[locker_name] = Locker.create( password=self.password, crypt_id=crypt_id, locker_name=locker_name ) return
def test_create_encrypt_decrypt(self, plaintext): for crypt_id in list_crypts(): crypt = create_crypt(self.pw, crypt_id) step1 = crypt.encrypt(plaintext) step2 = crypt.decrypt(step1) assert step2 == plaintext, (f"{crypt=}\n" f"{plaintext=}\n" f"{step1=}\n" f"{step2=}\n")
def test_validate_installs(self, datadir): # Get the list of install directories under `tests/data` data_root = datadir['.'] installs = data_root.listdir() # Each directory is named for a crypt_id, representing an installation # Each installation has its own .phibes.cfg and one test locker installed_paths = set() crypt_dirs = set() for install in installs: installed_paths.add(Path(install)) crypt_dirs.add(install.basename) # The directory listing should exactly match registered crypt_ids warnings = "" for item in set(list_crypts()) - crypt_dirs: warnings += f"Registered crypt {item} missing test install\n" for item in crypt_dirs - set(list_crypts()): warnings += f"No crypt registered matching test install {item}\n" assert not warnings for pt in installed_paths: validate_install(pt, self.test_name, self.test_pw)
def custom_setup(self, tmp_path): super(TestDeleteLocker, self).custom_setup(tmp_path) try: Locker.delete(password=self.pw, locker_name=self.locker_name) except PhibesNotFoundError: pass crypt_id = crypto.list_crypts()[0] Locker.create( password=self.pw, crypt_id=crypt_id, locker_name=self.locker_name ) self.setup_command()
class TestNoName(ConfigLoadingTestClass, MixinItemsList): password = "******" test_item_name = 'gonna_getchall' test_content = f"here is some stuff\npassword: HardHat\nsome name\n" def custom_setup(self, tmp_path): super(TestNoName, self).custom_setup(tmp_path) self.setup_command() def custom_teardown(self, tmp_path): super(TestNoName, self).custom_teardown(tmp_path) def invoke(self, arg_dict: dict): """ Helper method for often repeated code in test methods :return: click test-runner result """ args = [ "--password", arg_dict.get('password', self.password), "--path", arg_dict.get('path', self.test_path), "--verbose", True ] return CliRunner().invoke(cli=self.target_cmd, args=args) def prep_and_run(self, arg_dict): self.my_locker = Locker.create( password=self.password, crypt_id=arg_dict['crypt_id'] ) new_item = self.my_locker.create_item( item_name=self.test_item_name ) new_item.content = self.test_content self.my_locker.add_item(item=new_item) update_config_option_default(self.target_cmd, self.test_path) return self.invoke(arg_dict=arg_dict) @pytest.mark.parametrize("crypt_id", list_crypts()) @pytest.mark.positive def test_found(self, crypt_id, setup_and_teardown): result = self.prep_and_run({'crypt_id': crypt_id}) assert result assert result.exit_code == 0, ( f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n" ) for part in [self.test_content, self.test_item_name]: assert part in result.output, ( f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n" )
def setup_method(self): self.pw = "s00p3rsekrit" self.crypts = {} for cid in list_crypts(): crypt_impl = create_crypt(self.pw, crypt_id=cid) self.crypts[cid] = { 'password': self.pw, 'pw_hash': crypt_impl.pw_hash, 'salt': crypt_impl.salt, 'crypt_impl': crypt_impl } return
class TestNoName(ConfigLoadingTestClass, MixinItemDelete): password = "******" test_item_name = 'gonna_deletecha' test_content = f"here is some stuff\npassword: HardHat\nsome name\n" def custom_setup(self, tmp_path): super(TestNoName, self).custom_setup(tmp_path) self.setup_command() def custom_teardown(self, tmp_path): super(TestNoName, self).custom_teardown(tmp_path) def invoke(self, arg_dict: dict): """ Helper method for often repeated code in test methods :return: click test-runner result """ args = [ "--password", arg_dict.get('password', self.password), "--path", arg_dict.get('path', self.test_path), "--item", arg_dict.get('item', self.test_item_name) ] return CliRunner().invoke(self.target_cmd, args) def prep_and_run(self, arg_dict): self.my_locker = Locker.create(password=self.password, crypt_id=arg_dict['crypt_id']) new_item = self.my_locker.create_item(item_name=self.test_item_name) new_item.content = self.test_content self.my_locker.add_item(item=new_item) # change the configured working path to the test directory update_config_option_default(self.target_cmd, self.test_path) return self.invoke(arg_dict=arg_dict) @pytest.mark.parametrize("crypt_id", list_crypts()) @pytest.mark.positive def test_success(self, crypt_id, setup_and_teardown): result = self.prep_and_run({'crypt_id': crypt_id}) assert result assert result.exit_code == 0, (f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n") with pytest.raises(PhibesNotFoundError): self.my_locker.get_item(self.test_item_name)
class TestNoName(ConfigLoadingTestClass, MixinLockerGet): password = "******" def custom_setup(self, tmp_path): super(TestNoName, self).custom_setup(tmp_path) self.setup_command() def custom_teardown(self, tmp_path): super(TestNoName, self).custom_teardown(tmp_path) def prep_and_run(self, arg_dict): self.my_locker = Locker.create( password=self.password, crypt_id=arg_dict['crypt_id'], locker_name=None ) # change the configured working path to the test directory update_config_option_default(self.target_cmd, self.test_path) arg_list = [ "--path", arg_dict.get('path', self.test_path), "--password", arg_dict.get('password', self.password) ] return CliRunner().invoke(cli=self.target_cmd, args=arg_list) @pytest.mark.parametrize("crypt_id", list_crypts()) @pytest.mark.positive def test_found(self, crypt_id, setup_and_teardown): result = self.prep_and_run({'crypt_id': crypt_id}) assert result assert result.exit_code == 0, ( f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n" ) assert f'Crypt ID {crypt_id}' in result.output inst = Locker.get(password=self.password, locker_name=None) assert ( inst.data_model.storage.locker_file == self.test_path / LOCKER_FILE )
# Related third party imports import pytest # Local application/library specific imports from phibes import crypto from phibes.lib.errors import PhibesAuthError from phibes.lib.errors import PhibesExistsError from phibes.lib.errors import PhibesNotFoundError from phibes.model import Locker # Local test imports from tests.lib.test_helpers import ConfigLoadingTestClass from tests.lib.test_helpers import EmptyLocker, plain_texts, PopulatedLocker crypt_list = crypto.list_crypts() class TestLocker(EmptyLocker): my_locker = None locker_name = "my_locker" password = "******" def custom_setup(self, tmp_path): super(TestLocker, self).custom_setup(tmp_path) try: if Locker.get( password=self.password, locker_name=self.locker_name ): Locker.delete(
from phibes.crypto import list_crypts from phibes.lib.config import ConfigModel from phibes.lib.errors import PhibesAuthError from phibes.model import Locker, LOCKER_FILE # Local test imports from tests.cli.click_test_helpers import GroupProvider from tests.cli.click_test_helpers import update_config_option_default from tests.lib.test_helpers import ConfigLoadingTestClass from tests.lib.test_helpers import PopulatedLocker params = "crypt_id,config_arg" include_config_arg = [False, True] matrix_params = [] for element in itertools.product(list_crypts(), include_config_arg): matrix_params.append(element) class MixinLockerGet(GroupProvider): target = Target.Locker action = Action.Get class TestNoName(ConfigLoadingTestClass, MixinLockerGet): password = "******" def custom_setup(self, tmp_path): super(TestNoName, self).custom_setup(tmp_path)
class TestNoName(ConfigLoadingTestClass, MixinItemEdit): password = "******" test_item_name = 'gonna_editecha' start_content = f"replace this\n" edit_content = f"unique" def custom_setup(self, tmp_path): super(TestNoName, self).custom_setup(tmp_path) self.setup_command() def custom_teardown(self, tmp_path): super(TestNoName, self).custom_teardown(tmp_path) def invoke(self, arg_dict: dict): """ Helper method for often repeated code in test methods :return: click test-runner result """ args = [ "--password", arg_dict.get('password', self.password), "--path", arg_dict.get('path', self.test_path), "--item", arg_dict.get('item', self.test_item_name) ] if 'editor' in arg_dict: args += ["--editor", arg_dict['editor']] return CliRunner().invoke(self.target_cmd, args) def prep_and_run(self, arg_dict): return self.invoke(arg_dict=arg_dict) @pytest.mark.parametrize("crypt_id", list_crypts()) @pytest.mark.positive def test_success(self, crypt_id, tmp_path, setup_and_teardown): self.my_locker = Locker.create(password=self.password, crypt_id=crypt_id) new_item = self.my_locker.create_item(item_name=self.test_item_name) new_item.content = self.start_content self.my_locker.add_item(item=new_item) conf = CliConfig() conf.store = { 'store_type': conf.store['store_type'], 'store_path': tmp_path } conf.editor = f'echo {self.edit_content}> ' write_config_file(tmp_path, update=True) load_config_file(tmp_path) # change the configured working path to the test directory update_config_option_default(self.target_cmd, self.test_path) result = self.prep_and_run({'crypt_id': crypt_id}) assert result assert result.exit_code == 0, (f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n") assert self.start_content not in result.output inst = self.my_locker.get_item(self.test_item_name) assert self.edit_content == inst.content.strip() @pytest.mark.parametrize("crypt_id", list_crypts()) @pytest.mark.positive def test_editor_option(self, crypt_id, tmp_path, setup_and_teardown): self.my_locker = Locker.create(password=self.password, crypt_id=crypt_id) new_item = self.my_locker.create_item(item_name=self.test_item_name) new_item.content = self.start_content self.my_locker.add_item(item=new_item) conf = CliConfig() conf.store = { 'store_type': conf.store['store_type'], 'store_path': tmp_path } conf.editor = f'echo {self.edit_content}> ' write_config_file(tmp_path, update=True) load_config_file(tmp_path) # change the configured working path to the test directory update_config_option_default(self.target_cmd, self.test_path) replacement_content = "emacswhatwhat" cli_editor_option = f"echo {replacement_content}> " result = self.prep_and_run({ 'crypt_id': crypt_id, 'editor': cli_editor_option }) assert result assert result.exit_code == 0, (f"{crypt_id=}\n" f"{result.exception=}\n" f"{result.output=}\n") assert self.start_content not in result.output assert self.edit_content not in result.output inst = self.my_locker.get_item(self.test_item_name) assert replacement_content == inst.content.strip()
if len(value) < 3: raise click.BadParameter("password must be a least 3 characters") return value def validate_item_name(ctx: click.Context, param: click.Option, value: str): """ Custom validation rule for item_name_option """ # Possibly enforce max length, character set, or something return value crypt_choices = MappedChoices( prompt='Crypt ID (accept default)\n', choices=list_crypts(), default_val=default_id, help="Encryption type, usually best to accept default") crypt_option = crypt_choices.get_click_option("--crypt_id") item_name_option = click.option( '--item', prompt='Item name', callback=validate_item_name, help='Name of item on which to perform this action', ) template_name_option = click.option( '--template', prompt='Template name', help='Name of item to start with, can also be a text file', default='Empty') locker_name_option = click.option(