Fix keyring error handling (#1138)

* Capture KeyringLocked exception and allow manual password entry
* Create LockedKeyring for steps
* Support types in set_keyring step
* Clarify LockedKeyring docs
* Deal with locked exceptions elsewhere too
* Create behave tests for locked keyring
* Fix linting
* Fix keyring step to allow no type
* Handle all keyring retrieval errors
* Better keyring error handling
* Remove locked keyring for steps; generalize failed keyring
* Finalize tests for keyring handling
* Update set_keyring step
* Make password of failed keyring encryption test more semantic
This commit is contained in:
Karim Rahal 2021-01-02 23:26:45 +02:00 committed by GitHub
parent 9f1bef7fde
commit 57de4f9ba4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 20 deletions

View file

@ -24,6 +24,7 @@ Feature: Using the installed keyring
n n
""" """
Then we should get no error Then we should get no error
And we should not see the message "Failed to retrieve keyring"
Scenario: Encrypt journal with no keyring backend and do store in keyring Scenario: Encrypt journal with no keyring backend and do store in keyring
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
@ -36,25 +37,53 @@ Feature: Using the installed keyring
y y
""" """
Then we should get no error Then we should get no error
And we should not see the message "Failed to retrieve keyring"
# @todo add step to check contents of keyring # @todo add step to check contents of keyring
@todo @todo
Scenario: Open an encrypted journal with wrong password in keyring Scenario: Open an encrypted journal with wrong password in keyring
# This should ask the user for the password after the keyring fails # This should ask the user for the password after the keyring fails
@todo
Scenario: Open encrypted journal when keyring exists but fails
# This should ask the user for the password after the keyring fails
@todo @todo
Scenario: Decrypt journal with password in keyring Scenario: Decrypt journal with password in keyring
@todo @todo
Scenario: Decrypt journal without a keyring Scenario: Decrypt journal without a keyring
@todo Scenario: Encrypt journal when keyring exists but fails
Given we use the config "simple.yaml"
And we have a failed keyring
When we run "jrnl --encrypt" and enter
"""
this password will not be saved in keyring
this password will not be saved in keyring
y
"""
Then we should see the message "Failed to retrieve keyring"
And we should get no error
And we should be prompted for a password
And the config for journal "default" should have "encrypt" set to "bool:True"
Scenario: Decrypt journal when keyring exists but fails Scenario: Decrypt journal when keyring exists but fails
Given we use the config "encrypted.yaml"
And we have a failed keyring
When we run "jrnl --decrypt" and enter "bad doggie no biscuit"
Then we should see the message "Failed to retrieve keyring"
And we should get no error
And we should be prompted for a password
And we should see the message "Journal decrypted"
And the config for journal "default" should have "encrypt" set to "bool:False"
And the journal should have 2 entries
Scenario: Open encrypted journal when keyring exists but fails
# This should ask the user for the password after the keyring fails # This should ask the user for the password after the keyring fails
Given we use the config "encrypted.yaml"
And we have a failed keyring
When we run "jrnl -n 1" and enter "bad doggie no biscuit"
Then we should see the message "Failed to retrieve keyring"
And we should get no error
And we should be prompted for a password
And the output should contain "2013-06-10 15:40 Life is good"
Scenario: Mistyping your password Scenario: Mistyping your password
Given we use the config "simple.yaml" Given we use the config "simple.yaml"

View file

@ -69,21 +69,19 @@ class NoKeyring(keyring.backend.KeyringBackend):
class FailedKeyring(keyring.backend.KeyringBackend): class FailedKeyring(keyring.backend.KeyringBackend):
""" """
A keyring that simulates an environment with a keyring that has passwords, but fails A keyring that cannot be retrieved.
to return them.
""" """
priority = 2 priority = 2
keys = defaultdict(dict)
def set_password(self, servicename, username, password): def set_password(self, servicename, username, password):
self.keys[servicename][username] = password raise keyring.errors.KeyringError
def get_password(self, servicename, username): def get_password(self, servicename, username):
raise keyring.errors.NoKeyringError raise keyring.errors.KeyringError
def delete_password(self, servicename, username): def delete_password(self, servicename, username):
self.keys[servicename][username] = None raise keyring.errors.KeyringError
# set a default keyring # set a default keyring
@ -148,8 +146,12 @@ def use_password(context, password, num=1):
@given("we have a keyring") @given("we have a keyring")
def set_keyring(context): @given("we have a {type} keyring")
keyring.set_keyring(TestKeyring()) def set_keyring(context, type=""):
if type == "failed":
keyring.set_keyring(FailedKeyring())
else:
keyring.set_keyring(TestKeyring())
@given("we do not have a keyring") @given("we do not have a keyring")

View file

@ -176,7 +176,9 @@ def get_keychain(journal_name):
try: try:
return keyring.get_password("jrnl", journal_name) return keyring.get_password("jrnl", journal_name)
except RuntimeError: except keyring.errors.KeyringError as e:
if not isinstance(e, keyring.errors.NoKeyringError):
print("Failed to retrieve keyring", file=sys.stderr)
return "" return ""
@ -186,13 +188,16 @@ def set_keychain(journal_name, password):
if password is None: if password is None:
try: try:
keyring.delete_password("jrnl", journal_name) keyring.delete_password("jrnl", journal_name)
except keyring.errors.PasswordDeleteError: except keyring.errors.KeyringError:
pass pass
else: else:
try: try:
keyring.set_password("jrnl", journal_name, password) keyring.set_password("jrnl", journal_name, password)
except keyring.errors.NoKeyringError: except keyring.errors.KeyringError as e:
print( if isinstance(e, keyring.errors.NoKeyringError):
"Keyring backend not found. Please install one of the supported backends by visiting: https://pypi.org/project/keyring/", print(
file=sys.stderr, "Keyring backend not found. Please install one of the supported backends by visiting: https://pypi.org/project/keyring/",
) file=sys.stderr,
)
else:
print("Failed to retrieve keyring", file=sys.stderr)