feat: add reencrypt command

This commit is contained in:
cătălin 2023-07-24 19:52:25 +02:00
commit 654d996a53
Signed by: catalin
GPG key ID: 0178DF42F43E5FD2
7 changed files with 60 additions and 6 deletions

View file

@ -1,5 +1,4 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import Callable
from halig.settings import Settings from halig.settings import Settings
@ -14,5 +13,5 @@ class BaseCommand(ICommand):
def __init__(self, settings: Settings, *args, **kwargs): def __init__(self, settings: Settings, *args, **kwargs):
self.settings = settings self.settings = settings
def traverse_notebooks(self, callback_on_item: Callable): def traverse(self):
"""Traverse root_path""" return self.settings.notebooks_root_path.glob("./**/*.age")

View file

@ -0,0 +1,17 @@
from halig.commands.base import BaseCommand
from halig.encryption import Encryptor
from halig.settings import Settings
class ReencryptCommand(BaseCommand):
def __init__(self, settings: Settings, *args, **kwargs):
super().__init__(settings, *args, **kwargs)
self.encryptor = Encryptor(settings)
def run(self):
for note_path in self.traverse():
with note_path.open("rb") as fr:
orig_data = self.encryptor.decrypt(fr.read())
new_data = self.encryptor.encrypt(orig_data)
with note_path.open("wb") as fw:
fw.write(new_data)

View file

@ -76,7 +76,7 @@ class SearchCommand(BaseCommand):
) )
def _index_notebooks(self): def _index_notebooks(self):
for note_path in self.settings.notebooks_root_path.glob("./**/*.age"): for note_path in self.traverse():
updated_at = note_path.stat().st_mtime updated_at = note_path.stat().st_mtime
with note_path.open("rb") as f: with note_path.open("rb") as f:
body = self.encryptor.decrypt(f.read()) body = self.encryptor.decrypt(f.read())

View file

@ -35,4 +35,6 @@ class Encryptor:
return rage_encrypt(data, self.recipients) # type: ignore[no-any-return] return rage_encrypt(data, self.recipients) # type: ignore[no-any-return]
def decrypt(self, data: bytes) -> bytes: def decrypt(self, data: bytes) -> bytes:
if not len(data):
return data
return rage_decrypt(data, self.identities) # type: ignore[no-any-return] return rage_decrypt(data, self.identities) # type: ignore[no-any-return]

View file

@ -4,6 +4,12 @@ COMMANDS_EDIT_HELP = "Edit or add a note into a notebook"
COMMANDS_SHOW_HELP = "Show a note's contents" COMMANDS_SHOW_HELP = "Show a note's contents"
COMMANDS_VERSION = "Show halig's version" COMMANDS_VERSION = "Show halig's version"
COMMANDS_IMPORT_HELP = "Encrypt existing unencrypted files" COMMANDS_IMPORT_HELP = "Encrypt existing unencrypted files"
COMMANDS_SEARCH_HELP = """Perform a full-text search against all your notes,
which are indexed into a SQLite FTS5 database located at `~/.cache/halig/halig.db`
"""
COMMANDS_REENCRYPT_HELP = """Reencrypt all available notes. This operation is useful
when new public keys have been added to the config file and you want the notes
to be seen by the new pairing private keys"""
# OPTIONS # OPTIONS
OPTION_CONFIG_HELP = "Configuration file. Must be YAML and schema compatible" OPTION_CONFIG_HELP = "Configuration file. Must be YAML and schema compatible"
@ -13,6 +19,10 @@ OPTION_LEVEL_HELP = (
OPTION_UNLINK_HELP = """Setting this will remove the original markdown files; OPTION_UNLINK_HELP = """Setting this will remove the original markdown files;
only the newly encrypted .age files will be preserved. Backup your data first only the newly encrypted .age files will be preserved. Backup your data first
""" """
OPTION_INDEX_HELP = """Index the SQLite database with your notes contents. The first
time you perform a search, this flag should be set. Afterwards, you should only index
when new notes have been added or older ones have been changed, since it's a slow
operation"""
# ARGUMENTS # ARGUMENTS
ARGUMENT_EDIT_NOTE_HELP = """A valid, settings-relative path. ARGUMENT_EDIT_NOTE_HELP = """A valid, settings-relative path.
Be aware that valid can also mean implicit notes, that is, pointing to a Be aware that valid can also mean implicit notes, that is, pointing to a

View file

@ -11,6 +11,7 @@ from halig.__version__ import __version__
from halig.commands.edit import EditCommand from halig.commands.edit import EditCommand
from halig.commands.import_unencrypted import ImportCommand from halig.commands.import_unencrypted import ImportCommand
from halig.commands.notebooks import NotebooksCommand from halig.commands.notebooks import NotebooksCommand
from halig.commands.reencrypt import ReencryptCommand
from halig.commands.search import SearchCommand from halig.commands.search import SearchCommand
from halig.commands.show import ShowCommand from halig.commands.show import ShowCommand
from halig.settings import load_from_file from halig.settings import load_from_file
@ -115,10 +116,10 @@ def import_unencrypted(
command.run() command.run()
@app.command() @app.command(help=literals.COMMANDS_SEARCH_HELP)
def search( def search(
term: str, term: str,
index: bool = False, index: bool = Option(False, help=literals.OPTION_INDEX_HELP), # noqa: B008
): ):
settings = load_from_file() settings = load_from_file()
command = SearchCommand( command = SearchCommand(
@ -129,6 +130,15 @@ def search(
command.run() command.run()
@app.command(help=literals.COMMANDS_REENCRYPT_HELP)
def reencrypt():
settings = load_from_file()
command = ReencryptCommand(
settings=settings,
)
command.run()
@app.command(help=literals.COMMANDS_VERSION) @app.command(help=literals.COMMANDS_VERSION)
@capture @capture
def version(): def version():

View file

@ -0,0 +1,16 @@
import pytest
from halig.commands.reencrypt import ReencryptCommand
@pytest.fixture()
def reencrypt_command(settings):
return ReencryptCommand(settings)
@pytest.mark.usefixtures('notes')
def test_reencrypt(reencrypt_command):
reencrypt_command.run()
for note_path in reencrypt_command.traverse():
with note_path.open("rb") as f:
assert reencrypt_command.encryptor.decrypt(f.read()) == b""