mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-20 13:08:31 +02:00
clean up some code around keyrings, clean up more tests
This commit is contained in:
parent
764227d2f2
commit
6ada26d629
2 changed files with 106 additions and 38 deletions
|
@ -1,8 +1,9 @@
|
||||||
Feature: Encrypted journals
|
Feature: Encrypting and decrypting journals
|
||||||
|
|
||||||
Scenario: Loading an encrypted journal
|
Scenario: Loading an encrypted journal
|
||||||
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 we should be prompted for a 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
|
||||||
|
@ -12,6 +13,13 @@
|
||||||
And we should see the message "Journal decrypted"
|
And we should see the message "Journal decrypted"
|
||||||
And the journal should have 2 entries
|
And the journal should have 2 entries
|
||||||
|
|
||||||
|
Scenario: Trying to decrypt an unencrypted journal
|
||||||
|
Given we use the config "basic.yaml"
|
||||||
|
When we run "jrnl --decrypt"
|
||||||
|
Then the config for journal "default" should have "encrypt" set to "bool:False"
|
||||||
|
And we should get no error
|
||||||
|
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
|
When we run "jrnl --encrypt" and enter
|
||||||
|
@ -23,10 +31,22 @@
|
||||||
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 we should be prompted for a 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
|
Scenario: Mistyping your password
|
||||||
|
Given we use the config "basic.yaml"
|
||||||
|
When we run "jrnl --encrypt" and enter
|
||||||
|
"""
|
||||||
|
swordfish
|
||||||
|
sordfish
|
||||||
|
"""
|
||||||
|
Then we should be prompted for a password
|
||||||
|
And we should see the message "Passwords did not match"
|
||||||
|
And the config for journal "default" should not have "encrypt" set
|
||||||
|
And the journal should have 2 entries
|
||||||
|
|
||||||
|
Scenario: Mistyping your password, then getting it right
|
||||||
Given we use the config "basic.yaml"
|
Given we use the config "basic.yaml"
|
||||||
When we run "jrnl --encrypt" and enter
|
When we run "jrnl --encrypt" and enter
|
||||||
"""
|
"""
|
||||||
|
@ -36,22 +56,23 @@
|
||||||
swordfish
|
swordfish
|
||||||
n
|
n
|
||||||
"""
|
"""
|
||||||
Then we should see the message "Passwords did not match"
|
Then we should be prompted for a password
|
||||||
|
And we should see the message "Passwords did not match"
|
||||||
And we should see the message "Journal encrypted"
|
And 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 we should be prompted for a 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: Storing a password in Keychain
|
Scenario: Storing a password in keyring
|
||||||
Given we use the config "multiple.yaml"
|
Given we use the config "multiple.yaml"
|
||||||
|
And we have a keyring
|
||||||
When we run "jrnl simple --encrypt" and enter
|
When we run "jrnl simple --encrypt" and enter
|
||||||
"""
|
"""
|
||||||
sabertooth
|
sabertooth
|
||||||
sabertooth
|
sabertooth
|
||||||
y
|
y
|
||||||
"""
|
"""
|
||||||
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 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"
|
||||||
|
@ -59,8 +80,8 @@
|
||||||
|
|
||||||
Scenario: Encrypt journal with no keyring backend and do not store in keyring
|
Scenario: Encrypt journal with no keyring backend and do not store in keyring
|
||||||
Given we use the config "basic.yaml"
|
Given we use the config "basic.yaml"
|
||||||
When we disable the keychain
|
And we do not have a keyring
|
||||||
And we run "jrnl test entry"
|
When we run "jrnl test entry"
|
||||||
And we run "jrnl --encrypt" and enter
|
And we run "jrnl --encrypt" and enter
|
||||||
"""
|
"""
|
||||||
password
|
password
|
||||||
|
@ -71,8 +92,8 @@
|
||||||
|
|
||||||
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 "basic.yaml"
|
Given we use the config "basic.yaml"
|
||||||
When we disable the keychain
|
And we do not have a keyring
|
||||||
And we run "jrnl test entry"
|
When we run "jrnl test entry"
|
||||||
And we run "jrnl --encrypt" and enter
|
And we run "jrnl --encrypt" and enter
|
||||||
"""
|
"""
|
||||||
password
|
password
|
||||||
|
|
|
@ -64,8 +64,24 @@ class NoKeyring(keyring.backend.KeyringBackend):
|
||||||
raise keyring.errors.NoKeyringError
|
raise keyring.errors.NoKeyringError
|
||||||
|
|
||||||
|
|
||||||
# set the keyring for keyring lib
|
class FailedKeyring(keyring.backend.KeyringBackend):
|
||||||
keyring.set_keyring(TestKeyring())
|
"""
|
||||||
|
A keyring that simulates an environment with a keyring that has passwords, but fails
|
||||||
|
to return them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
priority = 2
|
||||||
|
keys = defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
|
def set_password(self, servicename, username, password):
|
||||||
|
self.keys[servicename][username] = password
|
||||||
|
|
||||||
|
def get_password(self, servicename, username):
|
||||||
|
raise keyring.errors.NoKeyringError
|
||||||
|
|
||||||
|
def delete_password(self, servicename, username):
|
||||||
|
self.keys[servicename][username] = None
|
||||||
|
|
||||||
|
|
||||||
def ushlex(command):
|
def ushlex(command):
|
||||||
|
@ -93,6 +109,18 @@ def open_journal(journal_name="default"):
|
||||||
return Journal.open_journal(journal_name, config)
|
return Journal.open_journal(journal_name, config)
|
||||||
|
|
||||||
|
|
||||||
|
def read_value_from_string(string):
|
||||||
|
if string[0] == "{":
|
||||||
|
# Handle value being a dictionary
|
||||||
|
return ast.literal_eval(string)
|
||||||
|
|
||||||
|
# Takes strings like "bool:true" or "int:32" and coerces them into proper type
|
||||||
|
t, value = string.split(":")
|
||||||
|
value = {"bool": lambda v: v.lower() == "true", "int": int, "str": str}[t](
|
||||||
|
value
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
@given('we use the config "{config_file}"')
|
@given('we use the config "{config_file}"')
|
||||||
def set_config(context, config_file):
|
def set_config(context, config_file):
|
||||||
full_path = os.path.join("features/configs", config_file)
|
full_path = os.path.join("features/configs", config_file)
|
||||||
|
@ -103,6 +131,16 @@ def set_config(context, config_file):
|
||||||
cf.write("version: {}".format(__version__))
|
cf.write("version: {}".format(__version__))
|
||||||
|
|
||||||
|
|
||||||
|
@given('we have a keyring')
|
||||||
|
def set_keyring(context):
|
||||||
|
keyring.set_keyring(TestKeyring())
|
||||||
|
|
||||||
|
|
||||||
|
@given('we do not have a keyring')
|
||||||
|
def disable_keyring(context):
|
||||||
|
keyring.core.set_keyring(NoKeyring())
|
||||||
|
|
||||||
|
|
||||||
@when('we change directory to "{path}"')
|
@when('we change directory to "{path}"')
|
||||||
def move_up_dir(context, path):
|
def move_up_dir(context, path):
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
|
@ -175,16 +213,22 @@ def matches_editor_arg(context, regex):
|
||||||
def _mock_getpass(inputs):
|
def _mock_getpass(inputs):
|
||||||
def prompt_return(prompt="Password: "):
|
def prompt_return(prompt="Password: "):
|
||||||
print(prompt)
|
print(prompt)
|
||||||
return next(inputs)
|
try:
|
||||||
|
return next(inputs)
|
||||||
|
except StopIteration:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
return prompt_return
|
return prompt_return
|
||||||
|
|
||||||
|
|
||||||
def _mock_input(inputs):
|
def _mock_input(inputs):
|
||||||
def prompt_return(prompt=""):
|
def prompt_return(prompt=""):
|
||||||
val = next(inputs)
|
try:
|
||||||
print(prompt, val)
|
val = next(inputs)
|
||||||
return val
|
print(prompt, val)
|
||||||
|
return val
|
||||||
|
except StopIteration:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
return prompt_return
|
return prompt_return
|
||||||
|
|
||||||
|
@ -247,11 +291,16 @@ def inputs_were_called(context):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@then("we were prompted for a password")
|
@then("we should be prompted for a password")
|
||||||
def password_was_called(context, method):
|
def password_was_called(context):
|
||||||
assert context.getpass.called
|
assert context.getpass.called
|
||||||
|
|
||||||
|
|
||||||
|
@then("we should not be prompted for a password")
|
||||||
|
def password_was_not_called(context):
|
||||||
|
assert not context.getpass.called
|
||||||
|
|
||||||
|
|
||||||
@then("all input was used")
|
@then("all input was used")
|
||||||
def all_input_was_used(context):
|
def all_input_was_used(context):
|
||||||
# all inputs were used (ignore if empty string)
|
# all inputs were used (ignore if empty string)
|
||||||
|
@ -292,16 +341,11 @@ def load_template(context, filename):
|
||||||
plugins.__exporter_types[exporter.names[0]] = exporter
|
plugins.__exporter_types[exporter.names[0]] = exporter
|
||||||
|
|
||||||
|
|
||||||
@when('we set the keychain password of "{journal}" to "{password}"')
|
@when('we set the keyring password of "{journal}" to "{password}"')
|
||||||
def set_keychain(context, journal, password):
|
def set_keyring(context, journal, password):
|
||||||
keyring.set_password("jrnl", journal, password)
|
keyring.set_password("jrnl", journal, password)
|
||||||
|
|
||||||
|
|
||||||
@when("we disable the keychain")
|
|
||||||
def disable_keychain(context):
|
|
||||||
keyring.core.set_keyring(NoKeyring())
|
|
||||||
|
|
||||||
|
|
||||||
@then("we should get an error")
|
@then("we should get an error")
|
||||||
def has_error(context):
|
def has_error(context):
|
||||||
assert context.exit_status != 0, context.exit_status
|
assert context.exit_status != 0, context.exit_status
|
||||||
|
@ -397,8 +441,8 @@ def check_not_journal_content(context, text, journal_name="default"):
|
||||||
|
|
||||||
@then('journal "{journal_name}" should not exist')
|
@then('journal "{journal_name}" should not exist')
|
||||||
def journal_doesnt_exist(context, journal_name="default"):
|
def journal_doesnt_exist(context, journal_name="default"):
|
||||||
with open(install.CONFIG_FILE_PATH) as config_file:
|
config = load_config(install.CONFIG_FILE_PATH)
|
||||||
config = yaml.load(config_file, Loader=yaml.FullLoader)
|
|
||||||
journal_path = config["journals"][journal_name]
|
journal_path = config["journals"][journal_name]
|
||||||
assert not os.path.exists(journal_path)
|
assert not os.path.exists(journal_path)
|
||||||
|
|
||||||
|
@ -407,23 +451,26 @@ def journal_doesnt_exist(context, journal_name="default"):
|
||||||
@then('the config should have "{key}" set to "{value}"')
|
@then('the config should have "{key}" set to "{value}"')
|
||||||
@then('the config for journal "{journal}" should have "{key}" set to "{value}"')
|
@then('the config for journal "{journal}" should have "{key}" set to "{value}"')
|
||||||
def config_var(context, key, value="", journal=None):
|
def config_var(context, key, value="", journal=None):
|
||||||
value = value or context.text or ""
|
value = read_value_from_string(value or context.text or "")
|
||||||
if not value[0] == "{":
|
|
||||||
t, value = value.split(":")
|
|
||||||
value = {"bool": lambda v: v.lower() == "true", "int": int, "str": str}[t](
|
|
||||||
value
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Handle value being a dictionary
|
|
||||||
value = ast.literal_eval(value)
|
|
||||||
|
|
||||||
config = load_config(install.CONFIG_FILE_PATH)
|
config = load_config(install.CONFIG_FILE_PATH)
|
||||||
|
|
||||||
if journal:
|
if journal:
|
||||||
config = config["journals"][journal]
|
config = config["journals"][journal]
|
||||||
|
|
||||||
assert key in config
|
assert key in config
|
||||||
assert config[key] == value
|
assert config[key] == value
|
||||||
|
|
||||||
|
|
||||||
|
@then('the config for journal "{journal}" should not have "{key}" set')
|
||||||
|
def config_var(context, key, value="", journal=None):
|
||||||
|
config = load_config(install.CONFIG_FILE_PATH)
|
||||||
|
|
||||||
|
if journal:
|
||||||
|
config = config["journals"][journal]
|
||||||
|
|
||||||
|
assert key not in config
|
||||||
|
|
||||||
|
|
||||||
@then("the journal should have {number:d} entries")
|
@then("the journal should have {number:d} entries")
|
||||||
@then("the journal should have {number:d} entry")
|
@then("the journal should have {number:d} entry")
|
||||||
@then('journal "{journal_name}" should have {number:d} entries')
|
@then('journal "{journal_name}" should have {number:d} entries')
|
||||||
|
|
Loading…
Add table
Reference in a new issue