mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 08:38:32 +02:00
Introduce legacy classes
This commit is contained in:
parent
df82ad1f4d
commit
a5f08e6081
6 changed files with 77 additions and 52 deletions
|
@ -36,6 +36,7 @@
|
|||
"""
|
||||
Y
|
||||
bad doggie no biscuit
|
||||
bad doggie no biscuit
|
||||
"""
|
||||
Then we should see the message "Password"
|
||||
and the output should contain "2013-06-10 15:40 Life is good"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from . import Journal, util
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import hashes, padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
import hashlib
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import base64
|
||||
|
@ -38,7 +40,9 @@ class EncryptedJournal(Journal.Journal):
|
|||
def validate_password(password):
|
||||
key = make_key(password)
|
||||
try:
|
||||
return Fernet(key).decrypt(journal_encrypted).decode('utf-8')
|
||||
plain = Fernet(key).decrypt(journal_encrypted).decode('utf-8')
|
||||
self.config['password'] = password
|
||||
return plain
|
||||
except (InvalidToken, IndexError):
|
||||
return None
|
||||
if password:
|
||||
|
@ -57,3 +61,33 @@ class EncryptedJournal(Journal.Journal):
|
|||
dummy = Fernet(key).encrypt("")
|
||||
with open(filename, 'w') as f:
|
||||
f.write(dummy)
|
||||
|
||||
|
||||
class LegacyEncryptedJournal(Journal.Journal):
|
||||
def __init__(self, name='default', **kwargs):
|
||||
super(LegacyEncryptedJournal, self).__init__(name, **kwargs)
|
||||
self.config['encrypt'] = True
|
||||
|
||||
def _load(self, filename, password=None):
|
||||
with open(filename) as f:
|
||||
journal_encrypted = f.read()
|
||||
iv, cipher = journal_encrypted[:16], journal_encrypted[16:]
|
||||
|
||||
def validate_password(password):
|
||||
decryption_key = hashlib.sha256(password.encode('utf-8')).digest()
|
||||
decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()).decryptor()
|
||||
try:
|
||||
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
||||
self.config['password'] = password
|
||||
if plain_padded[-1] == " ":
|
||||
# Ancient versions of jrnl. Do not judge me.
|
||||
plain = plain_padded.rstrip(" ")
|
||||
else:
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
plain = unpadder.update(plain_padded) + unpadder.finalize()
|
||||
return plain.decode('utf-8')
|
||||
except ValueError:
|
||||
return None
|
||||
if password:
|
||||
return validate_password(password)
|
||||
return util.get_password(keychain=self.name, validator=validate_password)
|
||||
|
|
|
@ -10,6 +10,9 @@ import sys
|
|||
import codecs
|
||||
import re
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("jrnl")
|
||||
|
||||
|
||||
class Journal(object):
|
||||
|
@ -33,6 +36,15 @@ class Journal(object):
|
|||
"""Returns the number of entries"""
|
||||
return len(self.entries)
|
||||
|
||||
@classmethod
|
||||
def from_journal(cls, other):
|
||||
"""Creates a new journal by copying configuration and entries from
|
||||
another journal object"""
|
||||
new_journal = cls(other.name, **other.config)
|
||||
new_journal.entries = other.entries
|
||||
log.debug("Imported %d entries from %s to %s", len(new_journal), other.__class__.__name__, cls.__name__)
|
||||
return new_journal
|
||||
|
||||
def import_(self, other_journal_txt):
|
||||
self.entries = list(frozenset(self.entries) | frozenset(self._parse(other_journal_txt)))
|
||||
self.sort()
|
||||
|
@ -44,6 +56,7 @@ class Journal(object):
|
|||
text = self._load(filename)
|
||||
self.entries = self._parse(text)
|
||||
self.sort()
|
||||
log.debug("opened %s with %d entries", self.__class__.__name__, len(self))
|
||||
return self
|
||||
|
||||
def write(self, filename=None):
|
||||
|
@ -72,7 +85,6 @@ class Journal(object):
|
|||
# Initialise our current entry
|
||||
entries = []
|
||||
current_entry = None
|
||||
|
||||
for line in journal_txt.splitlines():
|
||||
line = line.rstrip()
|
||||
try:
|
||||
|
@ -94,7 +106,7 @@ class Journal(object):
|
|||
# Happens when we can't parse the start of the line as an date.
|
||||
# In this case, just append line to our body.
|
||||
if current_entry:
|
||||
current_entry.body += line + "\n"
|
||||
current_entry.body += line + u"\n"
|
||||
|
||||
# Append last entry
|
||||
if current_entry:
|
||||
|
@ -243,10 +255,21 @@ class PlainJournal(Journal):
|
|||
f.write(text)
|
||||
|
||||
|
||||
def open_journal(name, config):
|
||||
def open_journal(name, config, legacy=False):
|
||||
"""
|
||||
Creates a normal, encrypted or DayOne journal based on the passed config.
|
||||
If legacy is True, it will open Journals with legacy classes build for
|
||||
backwards compatibility with jrnl 1.x
|
||||
"""
|
||||
config = config.copy()
|
||||
journal_conf = config['journals'].get(name)
|
||||
if type(journal_conf) is dict: # We can override the default config on a by-journal basis
|
||||
log.debug('Updating configuration with specific journal overrides %s', journal_conf)
|
||||
config.update(journal_conf)
|
||||
else: # But also just give them a string to point to the journal file
|
||||
config['journal'] = journal_conf
|
||||
config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
|
||||
|
||||
if os.path.isdir(config['journal']):
|
||||
if config['journal'].strip("/").endswith(".dayone") or "entries" in os.listdir(config['journal']):
|
||||
from . import DayOneJournal
|
||||
|
@ -259,4 +282,6 @@ def open_journal(name, config):
|
|||
return PlainJournal(name, **config).open()
|
||||
else:
|
||||
from . import EncryptedJournal
|
||||
if legacy:
|
||||
return EncryptedJournal.LegacyEncryptedJournal(name, **config).open()
|
||||
return EncryptedJournal.EncryptedJournal(name, **config).open()
|
||||
|
|
14
jrnl/cli.py
14
jrnl/cli.py
|
@ -18,7 +18,7 @@ import argparse
|
|||
import sys
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger("jrnl")
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
|
@ -147,7 +147,6 @@ def run(manual_args=None):
|
|||
sys.exit(0)
|
||||
|
||||
config = install.load_or_install_jrnl()
|
||||
|
||||
if args.ls:
|
||||
util.prnt(u"Journals defined in {}".format(install.CONFIG_FILE_PATH))
|
||||
ml = min(max(len(k) for k in config['journals']), 20)
|
||||
|
@ -163,6 +162,7 @@ def run(manual_args=None):
|
|||
journal_name = args.text[0] if (args.text and args.text[0] in config['journals']) else 'default'
|
||||
if journal_name is not 'default':
|
||||
args.text = args.text[1:]
|
||||
|
||||
# If the first remaining argument looks like e.g. '-3', interpret that as a limiter
|
||||
if not args.limit and args.text and args.text[0].startswith("-"):
|
||||
try:
|
||||
|
@ -172,16 +172,7 @@ def run(manual_args=None):
|
|||
pass
|
||||
|
||||
log.debug('Using journal "%s"', journal_name)
|
||||
journal_conf = config['journals'].get(journal_name)
|
||||
if type(journal_conf) is dict: # We can override the default config on a by-journal basis
|
||||
log.debug('Updating configuration with specific jourlnal overrides %s', journal_conf)
|
||||
config.update(journal_conf)
|
||||
else: # But also just give them a string to point to the journal file
|
||||
config['journal'] = journal_conf
|
||||
config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
|
||||
touch_journal(config['journal'])
|
||||
mode_compose, mode_export, mode_import = guess_mode(args, config)
|
||||
log.debug('Using journal path %(journal)s', config)
|
||||
|
||||
# How to quit writing?
|
||||
if "win32" in sys.platform:
|
||||
|
@ -206,6 +197,7 @@ def run(manual_args=None):
|
|||
else:
|
||||
mode_compose = False
|
||||
|
||||
# This is where we finally open the journal!
|
||||
journal = Journal.open_journal(journal_name, config)
|
||||
|
||||
# Import mode
|
||||
|
|
|
@ -65,7 +65,7 @@ def upgrade_config(config):
|
|||
for key in missing_keys:
|
||||
config[key] = default_config[key]
|
||||
save_config(config)
|
||||
print("[.jrnl_conf updated to newest version at {}]".format(CONFIG_FILE_PATH))
|
||||
print("[Configuration updated to newest version at {}]".format(CONFIG_FILE_PATH))
|
||||
|
||||
|
||||
def save_config(config):
|
||||
|
|
|
@ -1,37 +1,8 @@
|
|||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
import hashlib
|
||||
import util
|
||||
from . import __version__
|
||||
from . import EncryptedJournal
|
||||
from . import Journal
|
||||
import sys
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
|
||||
def upgrade_encrypted_journal(filename, key_plain):
|
||||
"""Decrypts a journal in memory using the jrnl 1.x encryption scheme
|
||||
and returns it in plain text."""
|
||||
with open(filename) as f:
|
||||
iv_cipher = f.read()
|
||||
iv, cipher = iv_cipher[:16], iv_cipher[16:]
|
||||
decryption_key = hashlib.sha256(key_plain.encode('utf-8')).digest()
|
||||
decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()).decryptor()
|
||||
try:
|
||||
plain_padded = decryptor.update(cipher) + decryptor.finalize()
|
||||
if plain_padded[-1] == " ":
|
||||
# Ancient versions of jrnl. Do not judge me.
|
||||
plain = plain_padded.rstrip(" ")
|
||||
else:
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
plain = unpadder.update(plain_padded) + unpadder.finalize()
|
||||
except ValueError:
|
||||
return None
|
||||
key = EncryptedJournal.make_key(key_plain)
|
||||
journal = Fernet(key).encrypt(plain)
|
||||
with open(filename, 'w') as f:
|
||||
f.write(journal)
|
||||
return plain
|
||||
from .EncryptedJournal import EncryptedJournal
|
||||
|
||||
|
||||
def upgrade_jrnl_if_necessary(config_path):
|
||||
|
@ -57,7 +28,6 @@ Please note that jrnl 1.x is NOT forward compatible with this version of jrnl.
|
|||
If you choose to proceed, you will not be able to use your journals with
|
||||
older versions of jrnl anymore.
|
||||
""".format(__version__))
|
||||
|
||||
encrypted_journals = {}
|
||||
plain_journals = {}
|
||||
for journal, journal_conf in config['journals'].items():
|
||||
|
@ -86,9 +56,12 @@ older versions of jrnl anymore.
|
|||
util.prompt("jrnl NOT upgraded, exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
for journal, path in encrypted_journals.items():
|
||||
util.prompt("Enter password for {} journal (stored in {}).".format(journal, path))
|
||||
util.get_password(keychain=journal, validator=lambda pwd: upgrade_encrypted_journal(path, pwd))
|
||||
for journal_name, path in encrypted_journals.items():
|
||||
util.prompt("Upgrading {} journal (stored in {}).".format(journal_name, path))
|
||||
old_journal = Journal.open_journal(journal_name, config, legacy=True)
|
||||
new_journal = EncryptedJournal.from_journal(old_journal)
|
||||
new_journal.write()
|
||||
# util.get_password(keychain=journal, validator=lambda pwd: upgrade_encrypted_journal(path, pwd))
|
||||
|
||||
with open(config_path + ".backup", 'w') as config_backup:
|
||||
config_backup.write(config_file)
|
||||
|
|
Loading…
Add table
Reference in a new issue