Add password confirmation dialog

This commit is contained in:
Peter Schmidbauer 2019-10-31 21:22:50 +01:00
parent 70c946bc13
commit 8eb185f8a2
6 changed files with 66 additions and 25 deletions

View file

@ -3,29 +3,55 @@
Given we use the config "encrypted.yaml" Given we use the config "encrypted.yaml"
When we run "jrnl -n 1" and enter "bad doggie no biscuit" When we run "jrnl -n 1" and enter "bad doggie no biscuit"
Then the output should contain "Password" 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 Scenario: Decrypting a journal
Given we use the config "encrypted.yaml" Given we use the config "encrypted.yaml"
When we run "jrnl --decrypt" and enter "bad doggie no biscuit" 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 the config for journal "default" should have "encrypt" set to "bool:False"
Then we should see the message "Journal decrypted" 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 Scenario: Encrypting a journal
Given we use the config "basic.yaml" 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" 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" When we run "jrnl -n 1" and enter "swordfish"
Then the output should contain "Password" 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 Scenario: Storing a password in Keychain
Given we use the config "multiple.yaml" 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" When we set the keychain password of "simple" to "sabertooth"
Then the config for journal "simple" should have "encrypt" set to "bool:True" Then the config for journal "simple" should have "encrypt" set to "bool:True"
When we run "jrnl simple -n 1" When we run "jrnl simple -n 1"
Then we should not see the message "Password" Then 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"

View file

@ -42,5 +42,10 @@ Feature: Multiple journals
Scenario: Don't crash if no file exists for a configured encrypted journal Scenario: Don't crash if no file exists for a configured encrypted journal
Given we use the config "multiple.yaml" 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" Then we should see the message "Journal 'new_encrypted' created"

View file

@ -33,7 +33,7 @@ class TestKeyring(keyring.backend.KeyringBackend):
def get_password(self, servicename, username): def get_password(self, servicename, username):
return self.keys[servicename].get(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 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 ""') @when('we run "{command}" and enter ""')
@when('we run "{command}" and enter "{inputs1}"') @when('we run "{command}" and enter "{inputs}"')
@when('we run "{command}" and enter "{inputs1}" and "{inputs2}"') def run_with_input(context, command, inputs=""):
def run_with_input(context, command, inputs1="", inputs2=""):
# create an iterator through all inputs. These inputs will be fed one by one # 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()' # to the mocked calls for 'input()', 'util.getpass()' and 'sys.stdin.read()'
if inputs1: if context.text:
text = iter((inputs1, inputs2))
elif context.text:
text = iter(context.text.split("\n")) text = iter(context.text.split("\n"))
else: else:
text = iter(("", "")) text = iter([inputs])
args = ushlex(command)[1:] args = ushlex(command)[1:]
with patch("builtins.input", side_effect=_mock_input(text)) as mock_input: 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: patch("getpass.getpass", side_effect=_mock_getpass(text)) as mock_getpass,\
with patch("sys.stdin.read", side_effect=text) as mock_read: patch("sys.stdin.read", side_effect=text) as mock_read:
try: try:
cli.run(args or []) cli.run(args or [])
context.exit_status = 0 context.exit_status = 0
except SystemExit as e: except SystemExit as e:
context.exit_status = e.code 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 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

View file

@ -38,7 +38,7 @@ class EncryptedJournal(Journal.Journal):
filename = filename or self.config['journal'] filename = filename or self.config['journal']
if not os.path.exists(filename): if not os.path.exists(filename):
password = util.getpass("Enter password for new journal: ") password = util.create_password()
if password: if password:
if util.yesno("Do you want to store the password in your keychain?", default=True): if util.yesno("Do you want to store the password in your keychain?", default=True):
util.set_keychain(self.name, password) util.set_keychain(self.name, password)

View file

@ -78,7 +78,7 @@ def encrypt(journal, filename=None):
""" Encrypt into new file. If filename is not set, we encrypt the journal file itself. """ """ Encrypt into new file. If filename is not set, we encrypt the journal file itself. """
from . import EncryptedJournal from . import EncryptedJournal
journal.config['password'] = util.getpass("Enter new password: ") journal.config['password'] = util.create_password()
journal.config['encrypt'] = True journal.config['encrypt'] = True
new_journal = EncryptedJournal.EncryptedJournal(None, **journal.config) new_journal = EncryptedJournal.EncryptedJournal(None, **journal.config)

View file

@ -37,12 +37,18 @@ class UserAbort(Exception):
pass 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): def get_password(validator, keychain=None, max_attempts=3):
pwd_from_keychain = keychain and get_keychain(keychain) pwd_from_keychain = keychain and get_keychain(keychain)
password = pwd_from_keychain or getpass() password = pwd_from_keychain or gp.getpass()
result = validator(password) result = validator(password)
# Password is bad: # Password is bad:
if result is None and pwd_from_keychain: if result is None and pwd_from_keychain: