feat: add reencrypt command
This commit is contained in:
parent
02ca346eae
commit
654d996a53
7 changed files with 60 additions and 6 deletions
|
|
@ -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")
|
||||||
|
|
|
||||||
17
halig/commands/reencrypt.py
Normal file
17
halig/commands/reencrypt.py
Normal 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)
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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():
|
||||||
|
|
|
||||||
16
tests/commands/test_reencrypt.py
Normal file
16
tests/commands/test_reencrypt.py
Normal 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""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue