mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-20 21:18:32 +02:00
Merge pull request #708 from pspeter/simplify-password-logic
Refactor password logic to prevent accidental password leakage
This commit is contained in:
commit
a9e4e09547
15 changed files with 79 additions and 97 deletions
|
@ -6,7 +6,6 @@ highlight: true
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/bug153.dayone
|
default: features/journals/bug153.dayone
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
template: false
|
template: false
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
|
|
|
@ -7,7 +7,6 @@ highlight: true
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/dayone.dayone
|
default: features/journals/dayone.dayone
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ highlight: true
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/empty_folder
|
default: features/journals/empty_folder
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ highlight: true
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/encrypted.journal
|
default: features/journals/encrypted.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ template: false
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/markdown-headings-335.journal
|
default: features/journals/markdown-headings-335.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -13,7 +13,6 @@ journals:
|
||||||
encrypt: true
|
encrypt: true
|
||||||
journal: features/journals/new_encrypted.journal
|
journal: features/journals/new_encrypted.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ template: false
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/tags-216.journal
|
default: features/journals/tags-216.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ template: false
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/tags-237.journal
|
default: features/journals/tags-237.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -7,7 +7,6 @@ template: false
|
||||||
journals:
|
journals:
|
||||||
default: features/journals/tags.journal
|
default: features/journals/tags.journal
|
||||||
linewrap: 80
|
linewrap: 80
|
||||||
password: ''
|
|
||||||
tagsymbols: '@'
|
tagsymbols: '@'
|
||||||
timeformat: '%Y-%m-%d %H:%M'
|
timeformat: '%Y-%m-%d %H:%M'
|
||||||
indent_character: "|"
|
indent_character: "|"
|
||||||
|
|
|
@ -48,4 +48,4 @@ Feature: Multiple journals
|
||||||
these three eyes
|
these three eyes
|
||||||
n
|
n
|
||||||
"""
|
"""
|
||||||
Then we should see the message "Journal 'new_encrypted' created"
|
Then we should see the message "Encrypted journal 'new_encrypted' created"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from . import Journal, util
|
from . import util
|
||||||
|
from .Journal import Journal, LegacyJournal
|
||||||
from cryptography.fernet import Fernet, InvalidToken
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
from cryptography.hazmat.primitives import hashes, padding
|
from cryptography.hazmat.primitives import hashes, padding
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
@ -9,6 +10,8 @@ import sys
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
@ -27,10 +30,11 @@ def make_key(password):
|
||||||
return base64.urlsafe_b64encode(key)
|
return base64.urlsafe_b64encode(key)
|
||||||
|
|
||||||
|
|
||||||
class EncryptedJournal(Journal.Journal):
|
class EncryptedJournal(Journal):
|
||||||
def __init__(self, name='default', **kwargs):
|
def __init__(self, name='default', **kwargs):
|
||||||
super().__init__(name, **kwargs)
|
super().__init__(name, **kwargs)
|
||||||
self.config['encrypt'] = True
|
self.config['encrypt'] = True
|
||||||
|
self.password = None
|
||||||
|
|
||||||
def open(self, filename=None):
|
def open(self, filename=None):
|
||||||
"""Opens the journal file defined in the config and parses it into a list of Entries.
|
"""Opens the journal file defined in the config and parses it into a list of Entries.
|
||||||
|
@ -38,27 +42,17 @@ 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.create_password()
|
self.create_file(filename)
|
||||||
if password:
|
self.password = util.create_password(self.name)
|
||||||
if util.yesno("Do you want to store the password in your keychain?", default=True):
|
print(f"Encrypted journal '{self.name}' created at {filename}", file=sys.stderr)
|
||||||
util.set_keychain(self.name, password)
|
|
||||||
else:
|
text = self._load(filename)
|
||||||
util.set_keychain(self.name, None)
|
|
||||||
self.config['password'] = password
|
|
||||||
text = ""
|
|
||||||
self._store(filename, text)
|
|
||||||
print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print("No password supplied for encrypted journal", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
text = self._load(filename)
|
|
||||||
self.entries = self._parse(text)
|
self.entries = self._parse(text)
|
||||||
self.sort()
|
self.sort()
|
||||||
log.debug("opened %s with %d entries", self.__class__.__name__, len(self))
|
log.debug("opened %s with %d entries", self.__class__.__name__, len(self))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _load(self, filename, password=None):
|
def _load(self, filename):
|
||||||
"""Loads an encrypted journal from a file and tries to decrypt it.
|
"""Loads an encrypted journal from a file and tries to decrypt it.
|
||||||
If password is not provided, will look for password in the keychain
|
If password is not provided, will look for password in the keychain
|
||||||
and otherwise ask the user to enter a password up to three times.
|
and otherwise ask the user to enter a password up to three times.
|
||||||
|
@ -67,50 +61,52 @@ class EncryptedJournal(Journal.Journal):
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
journal_encrypted = f.read()
|
journal_encrypted = f.read()
|
||||||
|
|
||||||
def validate_password(password):
|
def decrypt_journal(password):
|
||||||
key = make_key(password)
|
key = make_key(password)
|
||||||
try:
|
try:
|
||||||
plain = Fernet(key).decrypt(journal_encrypted).decode('utf-8')
|
plain = Fernet(key).decrypt(journal_encrypted).decode('utf-8')
|
||||||
self.config['password'] = password
|
self.password = password
|
||||||
return plain
|
return plain
|
||||||
except (InvalidToken, IndexError):
|
except (InvalidToken, IndexError):
|
||||||
return None
|
return None
|
||||||
if password:
|
|
||||||
return validate_password(password)
|
if self.password:
|
||||||
return util.get_password(keychain=self.name, validator=validate_password)
|
return decrypt_journal(self.password)
|
||||||
|
|
||||||
|
return util.decrypt_content(keychain=self.name, decrypt_func=decrypt_journal)
|
||||||
|
|
||||||
def _store(self, filename, text):
|
def _store(self, filename, text):
|
||||||
key = make_key(self.config['password'])
|
key = make_key(self.password)
|
||||||
journal = Fernet(key).encrypt(text.encode('utf-8'))
|
journal = Fernet(key).encrypt(text.encode('utf-8'))
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, 'wb') as f:
|
||||||
f.write(journal)
|
f.write(journal)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create(cls, filename, password):
|
def from_journal(cls, other: Journal):
|
||||||
key = make_key(password)
|
new_journal = super().from_journal(other)
|
||||||
dummy = Fernet(key).encrypt(b"")
|
new_journal.password = other.password if hasattr(other, "password") else util.create_password(other.name)
|
||||||
with open(filename, 'wb') as f:
|
return new_journal
|
||||||
f.write(dummy)
|
|
||||||
|
|
||||||
|
|
||||||
class LegacyEncryptedJournal(Journal.LegacyJournal):
|
class LegacyEncryptedJournal(LegacyJournal):
|
||||||
"""Legacy class to support opening journals encrypted with the jrnl 1.x
|
"""Legacy class to support opening journals encrypted with the jrnl 1.x
|
||||||
standard. You'll not be able to save these journals anymore."""
|
standard. You'll not be able to save these journals anymore."""
|
||||||
def __init__(self, name='default', **kwargs):
|
def __init__(self, name='default', **kwargs):
|
||||||
super().__init__(name, **kwargs)
|
super().__init__(name, **kwargs)
|
||||||
self.config['encrypt'] = True
|
self.config['encrypt'] = True
|
||||||
|
self.password = None
|
||||||
|
|
||||||
def _load(self, filename, password=None):
|
def _load(self, filename):
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
journal_encrypted = f.read()
|
journal_encrypted = f.read()
|
||||||
iv, cipher = journal_encrypted[:16], journal_encrypted[16:]
|
iv, cipher = journal_encrypted[:16], journal_encrypted[16:]
|
||||||
|
|
||||||
def validate_password(password):
|
def decrypt_journal(password):
|
||||||
decryption_key = hashlib.sha256(password.encode('utf-8')).digest()
|
decryption_key = hashlib.sha256(password.encode('utf-8')).digest()
|
||||||
decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()).decryptor()
|
decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()).decryptor()
|
||||||
try:
|
try:
|
||||||
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
||||||
self.config['password'] = password
|
self.password = password
|
||||||
if plain_padded[-1] in (" ", 32):
|
if plain_padded[-1] in (" ", 32):
|
||||||
# Ancient versions of jrnl. Do not judge me.
|
# Ancient versions of jrnl. Do not judge me.
|
||||||
return plain_padded.decode('utf-8').rstrip(" ")
|
return plain_padded.decode('utf-8').rstrip(" ")
|
||||||
|
@ -120,6 +116,6 @@ class LegacyEncryptedJournal(Journal.LegacyJournal):
|
||||||
return plain.decode('utf-8')
|
return plain.decode('utf-8')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
if password:
|
if self.password:
|
||||||
return validate_password(password)
|
return decrypt_journal(self.password)
|
||||||
return util.get_password(keychain=self.name, validator=validate_password)
|
return util.decrypt_content(keychain=self.name, decrypt_func=decrypt_journal)
|
||||||
|
|
|
@ -41,6 +41,7 @@ class Journal:
|
||||||
# Set up date parser
|
# Set up date parser
|
||||||
self.search_tags = None # Store tags we're highlighting
|
self.search_tags = None # Store tags we're highlighting
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.entries = []
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Returns the number of entries"""
|
"""Returns the number of entries"""
|
||||||
|
@ -69,9 +70,9 @@ class 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):
|
||||||
|
self.create_file(filename)
|
||||||
print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr)
|
print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr)
|
||||||
self._create(filename)
|
|
||||||
|
|
||||||
text = self._load(filename)
|
text = self._load(filename)
|
||||||
self.entries = self._parse(text)
|
self.entries = self._parse(text)
|
||||||
self.sort()
|
self.sort()
|
||||||
|
@ -92,6 +93,11 @@ class Journal:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_file(filename):
|
||||||
|
with open(filename, "w"):
|
||||||
|
pass
|
||||||
|
|
||||||
def _to_text(self):
|
def _to_text(self):
|
||||||
return "\n".join([str(e) for e in self.entries])
|
return "\n".join([str(e) for e in self.entries])
|
||||||
|
|
||||||
|
@ -101,10 +107,6 @@ class Journal:
|
||||||
def _store(self, filename, text):
|
def _store(self, filename, text):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _create(cls, filename):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _parse(self, journal_txt):
|
def _parse(self, journal_txt):
|
||||||
"""Parses a journal that's stored in a string and returns a list of entries"""
|
"""Parses a journal that's stored in a string and returns a list of entries"""
|
||||||
|
|
||||||
|
@ -274,11 +276,6 @@ class Journal:
|
||||||
|
|
||||||
|
|
||||||
class PlainJournal(Journal):
|
class PlainJournal(Journal):
|
||||||
@classmethod
|
|
||||||
def _create(cls, filename):
|
|
||||||
with open(filename, "a", encoding="utf-8"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _load(self, filename):
|
def _load(self, filename):
|
||||||
with open(filename, "r", encoding="utf-8") as f:
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
25
jrnl/cli.py
25
jrnl/cli.py
|
@ -6,7 +6,8 @@
|
||||||
license: MIT, see LICENSE for more details.
|
license: MIT, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import Journal
|
from .Journal import PlainJournal, open_journal
|
||||||
|
from .EncryptedJournal import EncryptedJournal
|
||||||
from . import util
|
from . import util
|
||||||
from . import install
|
from . import install
|
||||||
from . import plugins
|
from . import plugins
|
||||||
|
@ -77,28 +78,19 @@ def guess_mode(args, config):
|
||||||
|
|
||||||
def encrypt(journal, filename=None):
|
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
|
|
||||||
|
|
||||||
journal.config['password'] = util.create_password()
|
|
||||||
journal.config['encrypt'] = True
|
journal.config['encrypt'] = True
|
||||||
|
|
||||||
new_journal = EncryptedJournal.EncryptedJournal(None, **journal.config)
|
new_journal = EncryptedJournal.from_journal(journal)
|
||||||
new_journal.entries = journal.entries
|
|
||||||
new_journal.write(filename)
|
new_journal.write(filename)
|
||||||
|
|
||||||
if util.yesno("Do you want to store the password in your keychain?", default=True):
|
|
||||||
util.set_keychain(journal.name, journal.config['password'])
|
|
||||||
|
|
||||||
print("Journal encrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr)
|
print("Journal encrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def decrypt(journal, filename=None):
|
def decrypt(journal, filename=None):
|
||||||
""" Decrypts into new file. If filename is not set, we encrypt the journal file itself. """
|
""" Decrypts into new file. If filename is not set, we encrypt the journal file itself. """
|
||||||
journal.config['encrypt'] = False
|
journal.config['encrypt'] = False
|
||||||
journal.config['password'] = ""
|
|
||||||
|
|
||||||
new_journal = Journal.PlainJournal(filename, **journal.config)
|
new_journal = PlainJournal.from_journal(journal)
|
||||||
new_journal.entries = journal.entries
|
|
||||||
new_journal.write(filename)
|
new_journal.write(filename)
|
||||||
print("Journal decrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr)
|
print("Journal decrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr)
|
||||||
|
|
||||||
|
@ -156,11 +148,12 @@ def run(manual_args=None):
|
||||||
|
|
||||||
# If the first textual argument points to a journal file,
|
# If the first textual argument points to a journal file,
|
||||||
# use this!
|
# use this!
|
||||||
journal_name = args.text[0] if (args.text and args.text[0] in config['journals']) else 'default'
|
|
||||||
|
|
||||||
if journal_name != 'default':
|
journal_name = install.DEFAULT_JOURNAL_KEY
|
||||||
|
if args.text and args.text[0] in config['journals']:
|
||||||
|
journal_name = args.text[0]
|
||||||
args.text = args.text[1:]
|
args.text = args.text[1:]
|
||||||
elif "default" not in config['journals']:
|
elif install.DEFAULT_JOURNAL_KEY not in config['journals']:
|
||||||
print("No default journal configured.", file=sys.stderr)
|
print("No default journal configured.", file=sys.stderr)
|
||||||
print(list_journals(config), file=sys.stderr)
|
print(list_journals(config), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -211,7 +204,7 @@ def run(manual_args=None):
|
||||||
|
|
||||||
# This is where we finally open the journal!
|
# This is where we finally open the journal!
|
||||||
try:
|
try:
|
||||||
journal = Journal.open_journal(journal_name, config)
|
journal = open_journal(journal_name, config)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print(f"[Interrupted while opening journal]", file=sys.stderr)
|
print(f"[Interrupted while opening journal]", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -19,6 +19,7 @@ if "win32" not in sys.platform:
|
||||||
|
|
||||||
DEFAULT_CONFIG_NAME = 'jrnl.yaml'
|
DEFAULT_CONFIG_NAME = 'jrnl.yaml'
|
||||||
DEFAULT_JOURNAL_NAME = 'journal.txt'
|
DEFAULT_JOURNAL_NAME = 'journal.txt'
|
||||||
|
DEFAULT_JOURNAL_KEY = 'default'
|
||||||
XDG_RESOURCE = 'jrnl'
|
XDG_RESOURCE = 'jrnl'
|
||||||
|
|
||||||
USER_HOME = os.path.expanduser('~')
|
USER_HOME = os.path.expanduser('~')
|
||||||
|
@ -45,7 +46,7 @@ def module_exists(module_name):
|
||||||
default_config = {
|
default_config = {
|
||||||
'version': __version__,
|
'version': __version__,
|
||||||
'journals': {
|
'journals': {
|
||||||
"default": JOURNAL_FILE_PATH
|
DEFAULT_JOURNAL_KEY: JOURNAL_FILE_PATH
|
||||||
},
|
},
|
||||||
'editor': os.getenv('VISUAL') or os.getenv('EDITOR') or "",
|
'editor': os.getenv('VISUAL') or os.getenv('EDITOR') or "",
|
||||||
'encrypt': False,
|
'encrypt': False,
|
||||||
|
@ -118,32 +119,23 @@ def install():
|
||||||
# Where to create the journal?
|
# Where to create the journal?
|
||||||
path_query = f'Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): '
|
path_query = f'Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): '
|
||||||
journal_path = input(path_query).strip() or JOURNAL_FILE_PATH
|
journal_path = input(path_query).strip() or JOURNAL_FILE_PATH
|
||||||
default_config['journals']['default'] = os.path.expanduser(os.path.expandvars(journal_path))
|
default_config['journals'][DEFAULT_JOURNAL_KEY] = os.path.expanduser(os.path.expandvars(journal_path))
|
||||||
|
|
||||||
path = os.path.split(default_config['journals']['default'])[0] # If the folder doesn't exist, create it
|
path = os.path.split(default_config['journals'][DEFAULT_JOURNAL_KEY])[0] # If the folder doesn't exist, create it
|
||||||
try:
|
try:
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Encrypt it?
|
# Encrypt it?
|
||||||
password = getpass.getpass("Enter password for journal (leave blank for no encryption): ")
|
encrypt = util.yesno("Do you want to encrypt your journal? You can always change this later", default=False)
|
||||||
if password:
|
if encrypt:
|
||||||
default_config['encrypt'] = True
|
default_config['encrypt'] = True
|
||||||
if util.yesno("Do you want to store the password in your keychain?", default=True):
|
|
||||||
util.set_keychain("default", password)
|
|
||||||
else:
|
|
||||||
util.set_keychain("default", None)
|
|
||||||
EncryptedJournal._create(default_config['journals']['default'], password)
|
|
||||||
print("Journal will be encrypted.", file=sys.stderr)
|
print("Journal will be encrypted.", file=sys.stderr)
|
||||||
else:
|
|
||||||
PlainJournal._create(default_config['journals']['default'])
|
|
||||||
|
|
||||||
config = default_config
|
save_config(default_config)
|
||||||
save_config(config)
|
return default_config
|
||||||
if password:
|
|
||||||
config['password'] = password
|
|
||||||
return config
|
|
||||||
|
|
||||||
def autocomplete(text, state):
|
def autocomplete(text, state):
|
||||||
expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + '*')
|
expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + '*')
|
||||||
|
|
27
jrnl/util.py
27
jrnl/util.py
|
@ -4,8 +4,10 @@ import sys
|
||||||
import os
|
import os
|
||||||
import getpass as gp
|
import getpass as gp
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
if "win32" in sys.platform:
|
if "win32" in sys.platform:
|
||||||
import colorama
|
import colorama
|
||||||
|
|
||||||
colorama.init()
|
colorama.init()
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -13,6 +15,7 @@ import subprocess
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import shlex
|
import shlex
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional, Callable
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,19 +40,29 @@ class UserAbort(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_password():
|
def create_password(journal_name: str, prompt: str = "Enter password for new journal: ") -> str:
|
||||||
while True:
|
while True:
|
||||||
pw = gp.getpass("Enter password for new journal: ")
|
pw = gp.getpass(prompt)
|
||||||
if pw == gp.getpass("Enter password again: "):
|
if not pw:
|
||||||
return pw
|
print("Password can't be an empty string!", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
elif pw == gp.getpass("Enter password again: "):
|
||||||
|
break
|
||||||
|
|
||||||
print("Passwords did not match, please try again", file=sys.stderr)
|
print("Passwords did not match, please try again", file=sys.stderr)
|
||||||
|
|
||||||
|
if yesno("Do you want to store the password in your keychain?", default=True):
|
||||||
|
set_keychain(journal_name, pw)
|
||||||
|
else:
|
||||||
|
set_keychain(journal_name, None)
|
||||||
|
|
||||||
def get_password(validator, keychain=None, max_attempts=3):
|
return pw
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_content(decrypt_func: Callable[[str], Optional[str]], keychain: str = None, max_attempts: int = 3) -> str:
|
||||||
pwd_from_keychain = keychain and get_keychain(keychain)
|
pwd_from_keychain = keychain and get_keychain(keychain)
|
||||||
password = pwd_from_keychain or gp.getpass()
|
password = pwd_from_keychain or gp.getpass()
|
||||||
result = validator(password)
|
result = decrypt_func(password)
|
||||||
# Password is bad:
|
# Password is bad:
|
||||||
if result is None and pwd_from_keychain:
|
if result is None and pwd_from_keychain:
|
||||||
set_keychain(keychain, None)
|
set_keychain(keychain, None)
|
||||||
|
@ -57,7 +70,7 @@ def get_password(validator, keychain=None, max_attempts=3):
|
||||||
while result is None and attempt < max_attempts:
|
while result is None and attempt < max_attempts:
|
||||||
print("Wrong password, try again.", file=sys.stderr)
|
print("Wrong password, try again.", file=sys.stderr)
|
||||||
password = gp.getpass()
|
password = gp.getpass()
|
||||||
result = validator(password)
|
result = decrypt_func(password)
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
|
|
Loading…
Add table
Reference in a new issue