diff --git a/features/encryption.feature b/features/encryption.feature index d30c48b3..787fa850 100644 --- a/features/encryption.feature +++ b/features/encryption.feature @@ -3,29 +3,55 @@ Given we use the config "encrypted.yaml" When we run "jrnl -n 1" and enter "bad doggie no biscuit" Then the output should contain "Password" - and the output should contain "2013-06-10 15:40 Life is good" + And the output should contain "2013-06-10 15:40 Life is good" Scenario: Decrypting a journal Given we use the config "encrypted.yaml" When we run "jrnl --decrypt" and enter "bad doggie no biscuit" Then the config for journal "default" should have "encrypt" set to "bool:False" Then we should see the message "Journal decrypted" - and the journal should have 2 entries + And the journal should have 2 entries Scenario: Encrypting a journal Given we use the config "basic.yaml" - When we run "jrnl --encrypt" and enter "swordfish" and "n" + When we run "jrnl --encrypt" and enter + """ + swordfish + swordfish + n + """ Then we should see the message "Journal encrypted" - and the config for journal "default" should have "encrypt" set to "bool:True" + And the config for journal "default" should have "encrypt" set to "bool:True" When we run "jrnl -n 1" and enter "swordfish" Then the output should contain "Password" - and the output should contain "2013-06-10 15:40 Life is good" + And the output should contain "2013-06-10 15:40 Life is good" + + Scenario: Mistyping your password + Given we use the config "basic.yaml" + When we run "jrnl --encrypt" and enter + """ + swordfish + sordfish + swordfish + swordfish + n + """ + Then we should see the message "Passwords did not match" + And we should see the message "Journal encrypted" + And the config for journal "default" should have "encrypt" set to "bool:True" + When we run "jrnl -n 1" and enter "swordfish" + Then the output should contain "Password" + And the output should contain "2013-06-10 15:40 Life is good" Scenario: Storing a password in Keychain Given we use the config "multiple.yaml" - When we run "jrnl simple --encrypt" and enter "sabertooth" and "y" + When we run "jrnl simple --encrypt" and enter + """ + sabertooth + sabertooth + y + """ When we set the keychain password of "simple" to "sabertooth" Then the config for journal "simple" should have "encrypt" set to "bool:True" When we run "jrnl simple -n 1" - Then we should not see the message "Password" - and the output should contain "2013-06-10 15:40 Life is good" + Then the output should contain "2013-06-10 15:40 Life is good" diff --git a/features/multiple_journals.feature b/features/multiple_journals.feature index 28587f96..56c4b3a7 100644 --- a/features/multiple_journals.feature +++ b/features/multiple_journals.feature @@ -42,5 +42,10 @@ Feature: Multiple journals Scenario: Don't crash if no file exists for a configured encrypted journal Given we use the config "multiple.yaml" - When we run "jrnl new_encrypted Adding first entry" and enter "these three eyes" and "y" + When we run "jrnl new_encrypted Adding first entry" and enter + """ + these three eyes + these three eyes + n + """ Then we should see the message "Journal 'new_encrypted' created" diff --git a/features/steps/core.py b/features/steps/core.py index 5e8e3ea3..57b958f8 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -33,7 +33,7 @@ class TestKeyring(keyring.backend.KeyringBackend): def get_password(self, servicename, username): return self.keys[servicename].get(username) - def delete_password(self, servicename, username, password): + def delete_password(self, servicename, username): self.keys[servicename][username] = None @@ -109,29 +109,33 @@ def _mock_input(inputs): @when('we run "{command}" and enter') @when('we run "{command}" and enter ""') -@when('we run "{command}" and enter "{inputs1}"') -@when('we run "{command}" and enter "{inputs1}" and "{inputs2}"') -def run_with_input(context, command, inputs1="", inputs2=""): +@when('we run "{command}" and enter "{inputs}"') +def run_with_input(context, command, inputs=""): # create an iterator through all inputs. These inputs will be fed one by one # to the mocked calls for 'input()', 'util.getpass()' and 'sys.stdin.read()' - if inputs1: - text = iter((inputs1, inputs2)) - elif context.text: + if context.text: text = iter(context.text.split("\n")) else: - text = iter(("", "")) + text = iter([inputs]) + args = ushlex(command)[1:] - with patch("builtins.input", side_effect=_mock_input(text)) as mock_input: - with patch("jrnl.util.getpass", side_effect=_mock_getpass(text)) as mock_getpass: - with patch("sys.stdin.read", side_effect=text) as mock_read: + with patch("builtins.input", side_effect=_mock_input(text)) as mock_input,\ + patch("getpass.getpass", side_effect=_mock_getpass(text)) as mock_getpass,\ + patch("sys.stdin.read", side_effect=text) as mock_read: try: cli.run(args or []) context.exit_status = 0 except SystemExit as e: context.exit_status = e.code - # assert at least one of the mocked input methods got called + # at least one of the mocked input methods got called assert mock_input.called or mock_getpass.called or mock_read.called + # all inputs were used + try: + next(text) + assert False, "Not all inputs were consumed" + except StopIteration: + pass diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 1cce66b8..f3ff63c6 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -38,7 +38,7 @@ class EncryptedJournal(Journal.Journal): filename = filename or self.config['journal'] if not os.path.exists(filename): - password = util.getpass("Enter password for new journal: ") + password = util.create_password() if password: if util.yesno("Do you want to store the password in your keychain?", default=True): util.set_keychain(self.name, password) diff --git a/jrnl/cli.py b/jrnl/cli.py index 738087e4..945a728f 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -78,7 +78,7 @@ def encrypt(journal, filename=None): """ Encrypt into new file. If filename is not set, we encrypt the journal file itself. """ from . import EncryptedJournal - journal.config['password'] = util.getpass("Enter new password: ") + journal.config['password'] = util.create_password() journal.config['encrypt'] = True new_journal = EncryptedJournal.EncryptedJournal(None, **journal.config) diff --git a/jrnl/util.py b/jrnl/util.py index fc917ae9..f70b2df9 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -37,12 +37,18 @@ class UserAbort(Exception): pass -getpass = gp.getpass +def create_password(): + while True: + pw = gp.getpass("Enter password for new journal: ") + if pw == gp.getpass("Enter password again: "): + return pw + + print("Passwords did not match, please try again", file=sys.stderr) def get_password(validator, keychain=None, max_attempts=3): pwd_from_keychain = keychain and get_keychain(keychain) - password = pwd_from_keychain or getpass() + password = pwd_from_keychain or gp.getpass() result = validator(password) # Password is bad: if result is None and pwd_from_keychain: