diff options
| author | Simeon Simeonov | 2024-04-30 15:39:29 +0200 |
|---|---|---|
| committer | Simeon Simeonov | 2024-04-30 15:39:29 +0200 |
| commit | bb9a844e22134a2537652ea14f93e82acb4ee380 (patch) | |
| tree | bdc8921ba20bf7c9f3d31e8cad4dd4b2abe86df8 | |
| parent | c31fa58c85e866b3a5ab04882c7aff655f0b5477 (diff) | |
Improve tests
| -rw-r--r-- | src/etoolkit/__main__.py | 14 | ||||
| -rw-r--r-- | src/etoolkit/etoolkit.py | 19 | ||||
| -rw-r--r-- | tests/conftest.py | 35 | ||||
| -rw-r--r-- | tests/test_cli.py | 67 | ||||
| -rw-r--r-- | tests/test_envtoolkit_instance.py | 16 | ||||
| -rw-r--r-- | tests/test_envtoolkit_instance_static.py | 83 |
6 files changed, 151 insertions, 83 deletions
diff --git a/src/etoolkit/__main__.py b/src/etoolkit/__main__.py index fba9dc7..8b5bd55 100644 --- a/src/etoolkit/__main__.py +++ b/src/etoolkit/__main__.py | |||
| @@ -319,7 +319,13 @@ def main(inargs=None): | |||
| 319 | sys.exit(0) | 319 | sys.exit(0) |
| 320 | 320 | ||
| 321 | inst = etoolkit.EtoolkitInstance(args.instance, config_dict) | 321 | inst = etoolkit.EtoolkitInstance(args.instance, config_dict) |
| 322 | inst.prompt_func = etoolkit.EtoolkitInstance.confirm_password_prompt | 322 | if ( |
| 323 | args.master_password_prompt | ||
| 324 | or os.environ.get('ETOOLKIT_MASTER_PASSWORD') is None | ||
| 325 | ): | ||
| 326 | inst.prompt_func = ( | ||
| 327 | etoolkit.EtoolkitInstance.confirm_password_prompt | ||
| 328 | ) | ||
| 323 | env = inst.get_environ() | 329 | env = inst.get_environ() |
| 324 | 330 | ||
| 325 | if args.dump_output: | 331 | if args.dump_output: |
| @@ -328,9 +334,11 @@ def main(inargs=None): | |||
| 328 | os.environ.update(env) | 334 | os.environ.update(env) |
| 329 | 335 | ||
| 330 | if args.spawn: | 336 | if args.spawn: |
| 331 | subprocess.run(args.spawn.split(), check=True) | 337 | subprocess.run(args.spawn.split(), check=False) |
| 332 | else: | 338 | else: |
| 333 | subprocess.run(os.environ.get('SHELL', 'bash').split(), check=True) | 339 | subprocess.run( |
| 340 | os.environ.get('SHELL', 'bash').split(), check=False | ||
| 341 | ) | ||
| 334 | except KeyboardInterrupt: | 342 | except KeyboardInterrupt: |
| 335 | logger.debug('KeyboardInterrupt') | 343 | logger.debug('KeyboardInterrupt') |
| 336 | print(os.linesep) | 344 | print(os.linesep) |
diff --git a/src/etoolkit/etoolkit.py b/src/etoolkit/etoolkit.py index 870a4be..a2e0d7a 100644 --- a/src/etoolkit/etoolkit.py +++ b/src/etoolkit/etoolkit.py | |||
| @@ -385,7 +385,7 @@ class EtoolkitInstance: | |||
| 385 | if self._parent is not None | 385 | if self._parent is not None |
| 386 | else '' | 386 | else '' |
| 387 | ) | 387 | ) |
| 388 | if isinstance(value, str) and value.startswith('enc-val$1$'): | 388 | if isinstance(value, str) and value.startswith('enc-val$'): |
| 389 | value = self._decrypt_value(value) | 389 | value = self._decrypt_value(value) |
| 390 | if isinstance(value, str) and value.endswith(':'): | 390 | if isinstance(value, str) and value.endswith(':'): |
| 391 | # if 'value' ends with ':', append the existing value of | 391 | # if 'value' ends with ':', append the existing value of |
| @@ -440,11 +440,16 @@ class EtoolkitInstance: | |||
| 440 | """ | 440 | """ |
| 441 | if self._master_password is None: | 441 | if self._master_password is None: |
| 442 | if self._prompt_func is None: | 442 | if self._prompt_func is None: |
| 443 | raise EtoolkitInstanceError( | 443 | if ( |
| 444 | 'Neither password or prompt function set' | 444 | mp_from_env := os.environ.get('ETOOLKIT_MASTER_PASSWORD') |
| 445 | ) is None: | ||
| 446 | raise EtoolkitInstanceError( | ||
| 447 | 'Neither password or prompt function set' | ||
| 448 | ) | ||
| 449 | self.master_password = mp_from_env | ||
| 450 | else: | ||
| 451 | # use master_password setter in order to propagate to parent | ||
| 452 | self.master_password = self._prompt_func( | ||
| 453 | self._master_password_hash, confirm=False | ||
| 445 | ) | 454 | ) |
| 446 | # use master_password setter in order to propagate to parent | ||
| 447 | self.master_password = self._prompt_func( | ||
| 448 | self._master_password_hash, confirm=False | ||
| 449 | ) | ||
| 450 | return EtoolkitInstance.decrypt(self._master_password, evalue) | 455 | return EtoolkitInstance.decrypt(self._master_password, evalue) |
diff --git a/tests/conftest.py b/tests/conftest.py index e555a49..cbf7152 100644 --- a/tests/conftest.py +++ b/tests/conftest.py | |||
| @@ -27,26 +27,27 @@ def config_data(): | |||
| 27 | return { | 27 | return { |
| 28 | 'general': { | 28 | 'general': { |
| 29 | 'MASTER_PASSWORD_HASH': ( | 29 | 'MASTER_PASSWORD_HASH': ( |
| 30 | 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4m' | 30 | 'pbkdf2_sha256$500000$UY3o78KUM1Btzxk3k3JCsijnwtJ2lx+hH9Newp' |
| 31 | 'n80o=$h3PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo=' | 31 | 'VKxo8=$tHwDm8OVKanC4DoYTigTCb0R3lQIa/CbBYj0B3TZtHg=' |
| 32 | ) | 32 | ) |
| 33 | }, | 33 | }, |
| 34 | 'instances': { | 34 | 'instances': { |
| 35 | '_default': { | 35 | '_default': { |
| 36 | 'ETOOLKIT_PROMPT': '(%i)', | 36 | 'ETOOLKIT_PROMPT': '(%i)', |
| 37 | 'PYTHONPATH': '/home/foo/%i/python', | 37 | 'ETOOLKIT_TEST_PYTHONPATH': '/home/foo/%i/python', |
| 38 | }, | 38 | }, |
| 39 | 'dev': { | 39 | 'dev': { |
| 40 | 'ETOOLKIT_PARENT': '_default', | 40 | 'ETOOLKIT_PARENT': '_default', |
| 41 | 'PYTHONPATH': '%p:/home/user/%i/.pythonpath', | 41 | 'ETOOLKIT_TEST_PYTHONPATH': '%p:/home/user/%i/.pythonpath', |
| 42 | }, | 42 | }, |
| 43 | 'secret': { | 43 | 'secret': { |
| 44 | 'ETOOLKIT_PARENT': '_default', | 44 | 'ETOOLKIT_PARENT': '_default', |
| 45 | 'ETOOLKIT_SENSITIVE': ['PASSWORD'], | 45 | 'ETOOLKIT_SENSITIVE': ['ETOOLKIT_TEST_PASSWORD'], |
| 46 | 'GNUPGHOME': '%h/private/.gnupg', | 46 | 'GNUPGHOME': '%h/private/.gnupg', |
| 47 | 'PASSWORD': ( | 47 | 'ETOOLKIT_TEST_PASSWORD': ( |
| 48 | 'enc-val$1$vIBcoCNiYrsDLtF41uLuSEnppBjhliD0B8jwcBJcj/c=$Kw' | 48 | 'enc-val$2$v6F2M7LeUDbQWNLg6WW5mUcbuYYo7aGynSxzWAENVBI=$' |
| 49 | 'OGe/y1dlxktDaCnJPIVNuaQ4Q7yNo=' | 49 | 'ZcyWzf9Kp0aYI8N+biKMSmu4RGGi199ayq' |
| 50 | 'EYdJl+qdq7b1HSutwYlC7UR2GsSofu4Xo=' | ||
| 50 | ), | 51 | ), |
| 51 | }, | 52 | }, |
| 52 | }, | 53 | }, |
| @@ -55,7 +56,7 @@ def config_data(): | |||
| 55 | 56 | ||
| 56 | @pytest.fixture() | 57 | @pytest.fixture() |
| 57 | def config_file(tmp_path, config_data): | 58 | def config_file(tmp_path, config_data): |
| 58 | """temporary config file for testing that includes config_data""" | 59 | """Temporary config file for testing that includes config_data""" |
| 59 | 60 | ||
| 60 | cf = tmp_path / 'etoolkit.json' | 61 | cf = tmp_path / 'etoolkit.json' |
| 61 | cf.write_text(json.dumps(config_data)) | 62 | cf.write_text(json.dumps(config_data)) |
| @@ -63,6 +64,12 @@ def config_file(tmp_path, config_data): | |||
| 63 | 64 | ||
| 64 | 65 | ||
| 65 | @pytest.fixture() | 66 | @pytest.fixture() |
| 67 | def master_password(): | ||
| 68 | """Master passord""" | ||
| 69 | return 'The very secret passwd' | ||
| 70 | |||
| 71 | |||
| 72 | @pytest.fixture() | ||
| 66 | def non_random_bytes_32(): | 73 | def non_random_bytes_32(): |
| 67 | """always use the same bytes instead of os.urandom(32)""" | 74 | """always use the same bytes instead of os.urandom(32)""" |
| 68 | 75 | ||
| @@ -95,6 +102,12 @@ def password_hash(): | |||
| 95 | """password hash for testing, corresponding to 'The very secret passwd'""" | 102 | """password hash for testing, corresponding to 'The very secret passwd'""" |
| 96 | 103 | ||
| 97 | return ( | 104 | return ( |
| 98 | 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$h3' | 105 | 'pbkdf2_sha256$500000$UY3o78KUM1Btzxk3k3JCsijnwtJ2lx+hH9NewpVKxo8=$' |
| 99 | 'PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo=' | 106 | 'tHwDm8OVKanC4DoYTigTCb0R3lQIa/CbBYj0B3TZtHg=' |
| 100 | ) | 107 | ) |
| 108 | |||
| 109 | |||
| 110 | @pytest.fixture() | ||
| 111 | def wrong_master_password(): | ||
| 112 | """Wrong master passord""" | ||
| 113 | return 'the very secret passwd' | ||
diff --git a/tests/test_cli.py b/tests/test_cli.py index e5db244..1693a2b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py | |||
| @@ -26,14 +26,15 @@ from etoolkit.__main__ import main | |||
| 26 | 26 | ||
| 27 | 27 | ||
| 28 | @unittest.mock.patch('builtins.input') | 28 | @unittest.mock.patch('builtins.input') |
| 29 | def test_decrypt_v1(binput, capsys, config_file): | 29 | def test_decrypt_v1(binput, capsys, config_file, master_password): |
| 30 | """Tests v1 decryption via the CLI interface""" | 30 | """Tests v1 decryption via the CLI interface""" |
| 31 | |||
| 31 | binput.return_value = ( | 32 | binput.return_value = ( |
| 32 | 'enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+' | 33 | 'enc-val$1$rye0sMGEnd35gOWyISE1FQa6dzS+8/jf6aopMO5tPr4=$' |
| 33 | 'hIFEgx+F4mn80o=$xdF/1S+R2MGlEQMCOLG6OjEuzw==' | 34 | 'RjnRY0bUJWFOiejTlM3OhKNimQ==' |
| 34 | ) | 35 | ) |
| 35 | with unittest.mock.patch.dict( | 36 | with unittest.mock.patch.dict( |
| 36 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} | 37 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password} |
| 37 | ): | 38 | ): |
| 38 | with pytest.raises(SystemExit) as exit_info: | 39 | with pytest.raises(SystemExit) as exit_info: |
| 39 | main(['-c', f'{config_file}', '-d']) | 40 | main(['-c', f'{config_file}', '-d']) |
| @@ -43,15 +44,16 @@ def test_decrypt_v1(binput, capsys, config_file): | |||
| 43 | 44 | ||
| 44 | 45 | ||
| 45 | @unittest.mock.patch('builtins.input') | 46 | @unittest.mock.patch('builtins.input') |
| 46 | def test_decrypt_v2(binput, capsys, config_file): | 47 | def test_decrypt_v2(binput, capsys, config_file, master_password): |
| 47 | """Tests v2 decryption via the CLI interface""" | 48 | """Tests v2 decryption via the CLI interface""" |
| 49 | |||
| 48 | binput.return_value = ( | 50 | binput.return_value = ( |
| 49 | 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' | 51 | 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' |
| 50 | 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' | 52 | 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E' |
| 51 | '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' | 53 | 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' |
| 52 | ) | 54 | ) |
| 53 | with unittest.mock.patch.dict( | 55 | with unittest.mock.patch.dict( |
| 54 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} | 56 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password} |
| 55 | ): | 57 | ): |
| 56 | with pytest.raises(SystemExit) as exit_info: | 58 | with pytest.raises(SystemExit) as exit_info: |
| 57 | main(['-c', f'{config_file}', '-d']) | 59 | main(['-c', f'{config_file}', '-d']) |
| @@ -61,12 +63,16 @@ def test_decrypt_v2(binput, capsys, config_file): | |||
| 61 | 63 | ||
| 62 | 64 | ||
| 63 | @unittest.mock.patch('os.urandom') | 65 | @unittest.mock.patch('os.urandom') |
| 64 | @unittest.mock.patch('builtins.input', lambda *args: 'bar') | 66 | @unittest.mock.patch('builtins.input') |
| 65 | def test_encrypt_with_echo(urandom, capsys, non_random_bytes_61, config_file): | 67 | def test_encrypt_with_echo( |
| 68 | binput, urandom, capsys, non_random_bytes_61, config_file, master_password | ||
| 69 | ): | ||
| 66 | """Tests encryption via the CLI interface""" | 70 | """Tests encryption via the CLI interface""" |
| 71 | |||
| 72 | binput.return_value = 'bar' | ||
| 67 | urandom.return_value = non_random_bytes_61 | 73 | urandom.return_value = non_random_bytes_61 |
| 68 | with unittest.mock.patch.dict( | 74 | with unittest.mock.patch.dict( |
| 69 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} | 75 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password} |
| 70 | ): | 76 | ): |
| 71 | with pytest.raises(SystemExit) as exit_info: | 77 | with pytest.raises(SystemExit) as exit_info: |
| 72 | main(['-c', f'{config_file}', '-e', '-E']) | 78 | main(['-c', f'{config_file}', '-e', '-E']) |
| @@ -74,18 +80,22 @@ def test_encrypt_with_echo(urandom, capsys, non_random_bytes_61, config_file): | |||
| 74 | assert exit_info.value.code == 0 | 80 | assert exit_info.value.code == 0 |
| 75 | assert capsys.readouterr().out.strip() == ( | 81 | assert capsys.readouterr().out.strip() == ( |
| 76 | 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' | 82 | 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' |
| 77 | 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' | 83 | 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E' |
| 78 | '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' | 84 | 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' |
| 79 | ) | 85 | ) |
| 80 | 86 | ||
| 81 | 87 | ||
| 82 | @unittest.mock.patch('os.urandom') | 88 | @unittest.mock.patch('os.urandom') |
| 83 | @unittest.mock.patch('getpass.getpass', lambda *args: 'bar') | 89 | @unittest.mock.patch('getpass.getpass') |
| 84 | def test_encrypt_without_echo(gpass, capsys, non_random_bytes_61, config_file): | 90 | def test_encrypt_without_echo( |
| 91 | getpass, urandom, capsys, non_random_bytes_61, config_file, master_password | ||
| 92 | ): | ||
| 85 | """Tests encryption via the CLI interface""" | 93 | """Tests encryption via the CLI interface""" |
| 86 | gpass.return_value = non_random_bytes_61 | 94 | |
| 95 | getpass.return_value = 'bar' | ||
| 96 | urandom.return_value = non_random_bytes_61 | ||
| 87 | with unittest.mock.patch.dict( | 97 | with unittest.mock.patch.dict( |
| 88 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} | 98 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password} |
| 89 | ): | 99 | ): |
| 90 | with pytest.raises(SystemExit) as exit_info: | 100 | with pytest.raises(SystemExit) as exit_info: |
| 91 | main(['-c', f'{config_file}', '-e']) | 101 | main(['-c', f'{config_file}', '-e']) |
| @@ -93,13 +103,25 @@ def test_encrypt_without_echo(gpass, capsys, non_random_bytes_61, config_file): | |||
| 93 | assert exit_info.value.code == 0 | 103 | assert exit_info.value.code == 0 |
| 94 | assert capsys.readouterr().out.strip() == ( | 104 | assert capsys.readouterr().out.strip() == ( |
| 95 | 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' | 105 | 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' |
| 96 | 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' | 106 | 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E' |
| 97 | '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' | 107 | 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' |
| 98 | ) | 108 | ) |
| 99 | 109 | ||
| 100 | 110 | ||
| 111 | def test_fetch_encrypted_value(config_file, master_password): | ||
| 112 | """Tests decryption of encrypted value""" | ||
| 113 | |||
| 114 | with unittest.mock.patch.dict( | ||
| 115 | os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password} | ||
| 116 | ): | ||
| 117 | assert os.environ.get('ETOOLKIT_TEST_PASSWORD') is None | ||
| 118 | main(['-c', f'{config_file}', '-q', '-s', '/bin/false', 'secret']) | ||
| 119 | assert os.environ.get('ETOOLKIT_TEST_PASSWORD') == 'bar' | ||
| 120 | |||
| 121 | |||
| 101 | def test_list(capsys, config_file, nonexistent_config_file): | 122 | def test_list(capsys, config_file, nonexistent_config_file): |
| 102 | """Tests list via the CLI interface""" | 123 | """Tests list via the CLI interface""" |
| 124 | |||
| 103 | with pytest.raises(SystemExit) as exit_info: | 125 | with pytest.raises(SystemExit) as exit_info: |
| 104 | main(['-c', nonexistent_config_file, '-l']) | 126 | main(['-c', nonexistent_config_file, '-l']) |
| 105 | assert exit_info.type == SystemExit | 127 | assert exit_info.type == SystemExit |
| @@ -113,6 +135,7 @@ def test_list(capsys, config_file, nonexistent_config_file): | |||
| 113 | 135 | ||
| 114 | def test_help(capsys): | 136 | def test_help(capsys): |
| 115 | """Dummy test checking if the CLI is available at all""" | 137 | """Dummy test checking if the CLI is available at all""" |
| 138 | |||
| 116 | with pytest.raises(SystemExit) as exit_info: | 139 | with pytest.raises(SystemExit) as exit_info: |
| 117 | main(['-h']) | 140 | main(['-h']) |
| 118 | assert exit_info.type == SystemExit | 141 | assert exit_info.type == SystemExit |
| @@ -123,11 +146,12 @@ def test_help(capsys): | |||
| 123 | @unittest.mock.patch('os.urandom') | 146 | @unittest.mock.patch('os.urandom') |
| 124 | @unittest.mock.patch('getpass.getpass') | 147 | @unittest.mock.patch('getpass.getpass') |
| 125 | def test_generate_master_password_hash( | 148 | def test_generate_master_password_hash( |
| 126 | gpass, urandom, capsys, non_random_bytes_32 | 149 | getpass, urandom, capsys, non_random_bytes_32 |
| 127 | ): | 150 | ): |
| 128 | """Tests master password hash generation via the CLI interface""" | 151 | """Tests master password hash generation via the CLI interface""" |
| 152 | |||
| 153 | getpass.return_value = 'The very secret passwd' | ||
| 129 | urandom.return_value = non_random_bytes_32 | 154 | urandom.return_value = non_random_bytes_32 |
| 130 | gpass.return_value = 'The very secret passwd' | ||
| 131 | with pytest.raises(SystemExit) as exit_info: | 155 | with pytest.raises(SystemExit) as exit_info: |
| 132 | main(['--generate-master-password-hash']) | 156 | main(['--generate-master-password-hash']) |
| 133 | assert exit_info.type == SystemExit | 157 | assert exit_info.type == SystemExit |
| @@ -140,6 +164,7 @@ def test_generate_master_password_hash( | |||
| 140 | 164 | ||
| 141 | def test_version(capsys): | 165 | def test_version(capsys): |
| 142 | """Dummy test checking if the CLI is available at all""" | 166 | """Dummy test checking if the CLI is available at all""" |
| 167 | |||
| 143 | with pytest.raises(SystemExit) as exit_info: | 168 | with pytest.raises(SystemExit) as exit_info: |
| 144 | main(['-v']) | 169 | main(['-v']) |
| 145 | assert exit_info.type == SystemExit | 170 | assert exit_info.type == SystemExit |
diff --git a/tests/test_envtoolkit_instance.py b/tests/test_envtoolkit_instance.py index 73faed6..0147c6f 100644 --- a/tests/test_envtoolkit_instance.py +++ b/tests/test_envtoolkit_instance.py | |||
| @@ -22,6 +22,7 @@ import etoolkit | |||
| 22 | 22 | ||
| 23 | def test_instantiation(config_data): | 23 | def test_instantiation(config_data): |
| 24 | """Tests for object instatiation""" | 24 | """Tests for object instatiation""" |
| 25 | |||
| 25 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 26 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 26 | instance = etoolkit.EtoolkitInstance('devv', config_data) | 27 | instance = etoolkit.EtoolkitInstance('devv', config_data) |
| 27 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 28 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| @@ -31,7 +32,7 @@ def test_instantiation(config_data): | |||
| 31 | assert 'ETOOLKIT_PARENT' not in instance.raw_env_variables | 32 | assert 'ETOOLKIT_PARENT' not in instance.raw_env_variables |
| 32 | assert 'ETOOLKIT_SENSITIVE' not in instance.raw_env_variables | 33 | assert 'ETOOLKIT_SENSITIVE' not in instance.raw_env_variables |
| 33 | assert 'DB_CONNECTION' not in instance.sensitive_env_variables | 34 | assert 'DB_CONNECTION' not in instance.sensitive_env_variables |
| 34 | assert 'PASSWORD' in instance.sensitive_env_variables | 35 | assert 'ETOOLKIT_TEST_PASSWORD' in instance.sensitive_env_variables |
| 35 | assert instance.name == 'secret' | 36 | assert instance.name == 'secret' |
| 36 | assert ( | 37 | assert ( |
| 37 | instance.master_password_hash | 38 | instance.master_password_hash |
| @@ -40,15 +41,16 @@ def test_instantiation(config_data): | |||
| 40 | assert instance.master_password is None | 41 | assert instance.master_password is None |
| 41 | 42 | ||
| 42 | 43 | ||
| 43 | def test_get_environ(config_data): | 44 | def test_get_environ(config_data, master_password, wrong_master_password): |
| 44 | """Tests the EtoolkitInstance.get_environ method""" | 45 | """Tests the EtoolkitInstance.get_environ method""" |
| 46 | |||
| 45 | instance = etoolkit.EtoolkitInstance('secret', config_data) | 47 | instance = etoolkit.EtoolkitInstance('secret', config_data) |
| 46 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 48 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 47 | env = instance.get_environ() | 49 | env = instance.get_environ() |
| 48 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 50 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| 49 | assert exc_info.value.args[0] == 'Neither password or prompt function set' | 51 | assert exc_info.value.args[0] == 'Neither password or prompt function set' |
| 50 | 52 | ||
| 51 | instance.master_password = 'The very secret passwd' # wrong passwd | 53 | instance.master_password = wrong_master_password |
| 52 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 54 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 53 | env = instance.get_environ() | 55 | env = instance.get_environ() |
| 54 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 56 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| @@ -56,22 +58,24 @@ def test_get_environ(config_data): | |||
| 56 | 'Invalid tag when decrypting: enc-val$' | 58 | 'Invalid tag when decrypting: enc-val$' |
| 57 | ) | 59 | ) |
| 58 | 60 | ||
| 59 | instance.master_password = 'the very secret passwd' # correct passwd | 61 | instance.master_password = master_password |
| 60 | env = instance.get_environ() | 62 | env = instance.get_environ() |
| 61 | assert isinstance(env, dict) | 63 | assert isinstance(env, dict) |
| 62 | assert env['PASSWORD'] == 'secret2' | 64 | assert env['ETOOLKIT_TEST_PASSWORD'] == 'bar' |
| 63 | 65 | ||
| 64 | 66 | ||
| 65 | def test_get_full_name(config_data): | 67 | def test_get_full_name(config_data): |
| 66 | """Tests the EtoolkitInstance.get_full_name method""" | 68 | """Tests the EtoolkitInstance.get_full_name method""" |
| 69 | |||
| 67 | instance = etoolkit.EtoolkitInstance('secret', config_data) | 70 | instance = etoolkit.EtoolkitInstance('secret', config_data) |
| 68 | assert instance.get_full_name('->') == '_default->secret' | 71 | assert instance.get_full_name('->') == '_default->secret' |
| 69 | 72 | ||
| 70 | 73 | ||
| 71 | def test_parent_vars(config_data): | 74 | def test_parent_vars(config_data): |
| 72 | """Tests the EtoolkitInstance.get_full_name method""" | 75 | """Tests the EtoolkitInstance.get_full_name method""" |
| 76 | |||
| 73 | instance = etoolkit.EtoolkitInstance('dev', config_data) | 77 | instance = etoolkit.EtoolkitInstance('dev', config_data) |
| 74 | assert instance.get_full_name('->') == '_default->dev' | 78 | assert instance.get_full_name('->') == '_default->dev' |
| 75 | assert instance.get_environ()['PYTHONPATH'] == ( | 79 | assert instance.get_environ()['ETOOLKIT_TEST_PYTHONPATH'] == ( |
| 76 | '/home/foo/_default/python:/home/user/dev/.pythonpath' | 80 | '/home/foo/_default/python:/home/user/dev/.pythonpath' |
| 77 | ) | 81 | ) |
diff --git a/tests/test_envtoolkit_instance_static.py b/tests/test_envtoolkit_instance_static.py index 5fb9451..b3b24f0 100644 --- a/tests/test_envtoolkit_instance_static.py +++ b/tests/test_envtoolkit_instance_static.py | |||
| @@ -23,24 +23,26 @@ import etoolkit | |||
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | @unittest.mock.patch('getpass.getpass') | 25 | @unittest.mock.patch('getpass.getpass') |
| 26 | def test_confirm_password_prompt(getpass, password_hash): | 26 | def test_confirm_password_prompt(getpass, password_hash, master_password): |
| 27 | """Tests the static EtoolkitInstance.confirm_password_prompt method""" | 27 | """Tests the static EtoolkitInstance.confirm_password_prompt method""" |
| 28 | getpass.return_value = 'The very secret passwd' | 28 | |
| 29 | getpass.return_value = master_password | ||
| 29 | assert ( | 30 | assert ( |
| 30 | etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash) | 31 | etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash) |
| 31 | == 'The very secret passwd' | 32 | == master_password |
| 32 | ) | 33 | ) |
| 33 | assert ( | 34 | assert ( |
| 34 | etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False) | 35 | etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False) |
| 35 | == 'The very secret passwd' | 36 | == master_password |
| 36 | ) | 37 | ) |
| 37 | 38 | ||
| 38 | 39 | ||
| 39 | def test_decrypt_v1(): | 40 | def test_decrypt_v1(master_password): |
| 40 | """Tests the static EtoolkitInstance.decrypt method""" | 41 | """Tests the static EtoolkitInstance.decrypt method""" |
| 42 | |||
| 41 | assert ( | 43 | assert ( |
| 42 | etoolkit.EtoolkitInstance.decrypt( | 44 | etoolkit.EtoolkitInstance.decrypt( |
| 43 | 'The very secret passwd', | 45 | master_password, |
| 44 | ( | 46 | ( |
| 45 | 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ4ZoAkurNgMYx' | 47 | 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ4ZoAkurNgMYx' |
| 46 | 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' | 48 | 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' |
| @@ -55,16 +57,17 @@ def test_decrypt_v1(): | |||
| 55 | 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' | 57 | 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' |
| 56 | ) | 58 | ) |
| 57 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 59 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 58 | etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) | 60 | etoolkit.EtoolkitInstance.decrypt(master_password, edata) |
| 59 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 61 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| 60 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' | 62 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' |
| 61 | 63 | ||
| 62 | 64 | ||
| 63 | def test_decrypt_v2_no_padding(): | 65 | def test_decrypt_v2_no_padding(master_password): |
| 64 | """Tests the static EtoolkitInstance.decrypt method for v2 - no padding""" | 66 | """Tests the static EtoolkitInstance.decrypt method for v2 - no padding""" |
| 67 | |||
| 65 | assert ( | 68 | assert ( |
| 66 | etoolkit.EtoolkitInstance.decrypt( | 69 | etoolkit.EtoolkitInstance.decrypt( |
| 67 | 'The very secret passwd', | 70 | master_password, |
| 68 | ( | 71 | ( |
| 69 | 'enc-val$2$Wer5lECGyeZhhYS58N18WVx5Zzy+rrC+BPlq3Dw89wQ=$' | 72 | 'enc-val$2$Wer5lECGyeZhhYS58N18WVx5Zzy+rrC+BPlq3Dw89wQ=$' |
| 70 | 'SQc0ox6Emf2m5rrumsiptpIZEujdpXXSR/' | 73 | 'SQc0ox6Emf2m5rrumsiptpIZEujdpXXSR/' |
| @@ -81,16 +84,17 @@ def test_decrypt_v2_no_padding(): | |||
| 81 | '1VcfEZeBz4+KDSagr9ID+bkc4R2yFdxHnhig1eqQ8=' | 84 | '1VcfEZeBz4+KDSagr9ID+bkc4R2yFdxHnhig1eqQ8=' |
| 82 | ) | 85 | ) |
| 83 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 86 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 84 | etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) | 87 | etoolkit.EtoolkitInstance.decrypt(master_password, edata) |
| 85 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 88 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| 86 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' | 89 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' |
| 87 | 90 | ||
| 88 | 91 | ||
| 89 | def test_decrypt_v2_with_padding(): | 92 | def test_decrypt_v2_with_padding(master_password): |
| 90 | """Tests the static EtoolkitInstance.decrypt method for v2 with padding""" | 93 | """Tests the static EtoolkitInstance.decrypt method for v2 with padding""" |
| 94 | |||
| 91 | assert ( | 95 | assert ( |
| 92 | etoolkit.EtoolkitInstance.decrypt( | 96 | etoolkit.EtoolkitInstance.decrypt( |
| 93 | 'The very secret passwd', | 97 | master_password, |
| 94 | ( | 98 | ( |
| 95 | 'enc-val$2$//kzyUbDEWNoPC5dyukhB8de8+IVaLR2ngx2HwkfOuM=$' | 99 | 'enc-val$2$//kzyUbDEWNoPC5dyukhB8de8+IVaLR2ngx2HwkfOuM=$' |
| 96 | 'rhRona4wP9nhnXjcHqwkjFDsiVVVjYanAs' | 100 | 'rhRona4wP9nhnXjcHqwkjFDsiVVVjYanAs' |
| @@ -106,39 +110,44 @@ def test_decrypt_v2_with_padding(): | |||
| 106 | 'rhRona4wP8nhnXjcHqwkjFDsiVVVjYanAsN4kknNkgC0ix4RtJQHYDeTzw1rrR1vb2w=' | 110 | 'rhRona4wP8nhnXjcHqwkjFDsiVVVjYanAsN4kknNkgC0ix4RtJQHYDeTzw1rrR1vb2w=' |
| 107 | ) | 111 | ) |
| 108 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: | 112 | with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: |
| 109 | etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) | 113 | etoolkit.EtoolkitInstance.decrypt(master_password, edata) |
| 110 | assert exc_info.type is etoolkit.EtoolkitInstanceError | 114 | assert exc_info.type is etoolkit.EtoolkitInstanceError |
| 111 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' | 115 | assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' |
| 112 | 116 | ||
| 113 | 117 | ||
| 114 | def test_encrypt_no_padding(): | 118 | def test_encrypt_no_padding(master_password): |
| 115 | """Tests the static EtoolkitInstance.encrypt method with a long string""" | 119 | """Tests the static EtoolkitInstance.encrypt method with a long string""" |
| 120 | |||
| 116 | edata = etoolkit.EtoolkitInstance.encrypt( | 121 | edata = etoolkit.EtoolkitInstance.encrypt( |
| 117 | 'foo', 'Nobody expects the Spanish inquisition' | 122 | master_password, 'Nobody expects the Spanish inquisition' |
| 118 | ) | 123 | ) |
| 119 | assert edata.startswith('enc-val$2$') | 124 | assert edata.startswith('enc-val$2$') |
| 120 | assert len(edata) == 131 | 125 | assert len(edata) == 131 |
| 121 | # the edata should always be different because of random salting | 126 | # the edata should always be different because of random salting |
| 122 | assert edata != etoolkit.EtoolkitInstance.encrypt( | 127 | assert edata != etoolkit.EtoolkitInstance.encrypt( |
| 123 | 'foo', 'Nobody expects the Spanish inquisition' | 128 | master_password, 'Nobody expects the Spanish inquisition' |
| 124 | ) | 129 | ) |
| 125 | 130 | ||
| 126 | 131 | ||
| 127 | def test_encrypt_with_padding(): | 132 | def test_encrypt_with_padding(master_password): |
| 128 | """Tests the static EtoolkitInstance.encrypt method with a short string""" | 133 | """Tests the static EtoolkitInstance.encrypt method with a short string""" |
| 129 | edata = etoolkit.EtoolkitInstance.encrypt('foo', 'bar') | 134 | |
| 135 | edata = etoolkit.EtoolkitInstance.encrypt(master_password, 'bar') | ||
| 130 | assert edata.startswith('enc-val$2$') | 136 | assert edata.startswith('enc-val$2$') |
| 131 | assert len(edata) == 123 | 137 | assert len(edata) == 123 |
| 132 | # the edata should always be different because of random salting | 138 | # the edata should always be different because of random salting |
| 133 | assert edata != etoolkit.EtoolkitInstance.encrypt('foo', 'bar') | 139 | assert edata != etoolkit.EtoolkitInstance.encrypt(master_password, 'bar') |
| 134 | 140 | ||
| 135 | 141 | ||
| 136 | @unittest.mock.patch('os.urandom') | 142 | @unittest.mock.patch('os.urandom') |
| 137 | def test_encrypt_staticly_no_padding(urandom, non_random_bytes_32): | 143 | def test_encrypt_staticly_no_padding( |
| 144 | urandom, master_password, non_random_bytes_32 | ||
| 145 | ): | ||
| 138 | """Tests the EtoolkitInstance.encrypt method always with the same salt""" | 146 | """Tests the EtoolkitInstance.encrypt method always with the same salt""" |
| 147 | |||
| 139 | urandom.return_value = non_random_bytes_32 | 148 | urandom.return_value = non_random_bytes_32 |
| 140 | edata = etoolkit.EtoolkitInstance.encrypt( | 149 | edata = etoolkit.EtoolkitInstance.encrypt( |
| 141 | 'The very secret passwd', 'Nobody expects the Spanish inquisition' | 150 | master_password, 'Nobody expects the Spanish inquisition' |
| 142 | ) | 151 | ) |
| 143 | assert edata == ( | 152 | assert edata == ( |
| 144 | 'enc-val$2$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$' | 153 | 'enc-val$2$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$' |
| @@ -147,51 +156,55 @@ def test_encrypt_staticly_no_padding(urandom, non_random_bytes_32): | |||
| 147 | ) | 156 | ) |
| 148 | assert len(edata) == 131 | 157 | assert len(edata) == 131 |
| 149 | assert edata == etoolkit.EtoolkitInstance.encrypt( | 158 | assert edata == etoolkit.EtoolkitInstance.encrypt( |
| 150 | 'The very secret passwd', 'Nobody expects the Spanish inquisition' | 159 | master_password, 'Nobody expects the Spanish inquisition' |
| 151 | ) | 160 | ) |
| 152 | 161 | ||
| 153 | 162 | ||
| 154 | @unittest.mock.patch('os.urandom') | 163 | @unittest.mock.patch('os.urandom') |
| 155 | def test_encrypt_staticly_with_padding(urandom, non_random_bytes_61): | 164 | def test_encrypt_staticly_with_padding( |
| 165 | urandom, master_password, non_random_bytes_61 | ||
| 166 | ): | ||
| 156 | """Tests the EtoolkitInstance.encrypt method always with the same salt""" | 167 | """Tests the EtoolkitInstance.encrypt method always with the same salt""" |
| 168 | |||
| 157 | urandom.return_value = non_random_bytes_61 | 169 | urandom.return_value = non_random_bytes_61 |
| 158 | edata = etoolkit.EtoolkitInstance.encrypt('The very secret passwd', 'bar') | 170 | edata = etoolkit.EtoolkitInstance.encrypt(master_password, 'bar') |
| 159 | assert edata == ( | 171 | assert edata == ( |
| 160 | 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviqrLTBxM=$' | 172 | 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviqrLTBxM=$' |
| 161 | '+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/ESvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' | 173 | '+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/ESvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' |
| 162 | ) | 174 | ) |
| 163 | assert edata == etoolkit.EtoolkitInstance.encrypt( | 175 | assert edata == etoolkit.EtoolkitInstance.encrypt(master_password, 'bar') |
| 164 | 'The very secret passwd', 'bar' | ||
| 165 | ) | ||
| 166 | 176 | ||
| 167 | 177 | ||
| 168 | def test_get_new_password_hash(): | 178 | def test_get_new_password_hash(master_password): |
| 169 | """Tests the static EtoolkitInstance.get_new_password_hash method""" | 179 | """Tests the static EtoolkitInstance.get_new_password_hash method""" |
| 170 | new_hash = etoolkit.EtoolkitInstance.get_new_password_hash( | 180 | |
| 171 | 'The very secret passwd' | 181 | new_hash = etoolkit.EtoolkitInstance.get_new_password_hash(master_password) |
| 172 | ) | ||
| 173 | # all pbkdf2 params are the same / hardcoded for the time being | 182 | # all pbkdf2 params are the same / hardcoded for the time being |
| 174 | assert new_hash.startswith('pbkdf2_sha256$500000$') | 183 | assert new_hash.startswith('pbkdf2_sha256$500000$') |
| 175 | assert len(new_hash) == 110 | 184 | assert len(new_hash) == 110 |
| 176 | # the hash should always be different because of random salting | 185 | # the hash should always be different because of random salting |
| 177 | assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash( | 186 | assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash( |
| 178 | 'The very secret passwd' | 187 | master_password |
| 179 | ) | 188 | ) |
| 180 | 189 | ||
| 181 | 190 | ||
| 182 | def test_parse_value(): | 191 | def test_parse_value(): |
| 183 | """Tests the static EtoolkitInstance.parse_value method""" | 192 | """Tests the static EtoolkitInstance.parse_value method""" |
| 193 | |||
| 184 | assert ( | 194 | assert ( |
| 185 | etoolkit.EtoolkitInstance.parse_value('t%bs%t', {'%b': 'e', '%t': 't'}) | 195 | etoolkit.EtoolkitInstance.parse_value('t%bs%t', {'%b': 'e', '%t': 't'}) |
| 186 | == 'test' | 196 | == 'test' |
| 187 | ) | 197 | ) |
| 188 | 198 | ||
| 189 | 199 | ||
| 190 | def test_password_matches(password_hash): | 200 | def test_password_matches( |
| 201 | password_hash, master_password, wrong_master_password | ||
| 202 | ): | ||
| 191 | """Tests the static EtoolkitInstance.password_matches method""" | 203 | """Tests the static EtoolkitInstance.password_matches method""" |
| 204 | |||
| 192 | assert etoolkit.EtoolkitInstance.password_matches( | 205 | assert etoolkit.EtoolkitInstance.password_matches( |
| 193 | 'The very secret passwd', password_hash | 206 | master_password, password_hash |
| 194 | ) | 207 | ) |
| 195 | assert not etoolkit.EtoolkitInstance.password_matches( | 208 | assert not etoolkit.EtoolkitInstance.password_matches( |
| 196 | 'The very secret passwdo', password_hash | 209 | wrong_master_password, password_hash |
| 197 | ) | 210 | ) |
