From 7c2d32bf3c4c47d60ca2483da76e026edc02dba9 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 24 Nov 2023 22:59:21 +0100 Subject: [PATCH] =?UTF-8?q?Make=20IdentityRepository=20a=C2=A0KeyRing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Keep only one class to provide access to identities stored in the database. - Remove old code and its tests. - Align KeyRing and IdentityRepository APIs. - Implement a (very) simple unit test for IdentityRepository. --- lacre/_keyringcommon.py | 8 ++--- lacre/dbkeyring.py | 48 ------------------------- lacre/keyring.py | 9 +++-- lacre/repositories.py | 35 ++++++++++++++++-- test/modules/test_dbkeyring.py | 16 --------- test/modules/test_lacre_repositories.py | 14 ++++++++ webgate-cron.py | 3 +- 7 files changed, 54 insertions(+), 79 deletions(-) delete mode 100644 lacre/dbkeyring.py delete mode 100644 test/modules/test_dbkeyring.py create mode 100644 test/modules/test_lacre_repositories.py diff --git a/lacre/_keyringcommon.py b/lacre/_keyringcommon.py index 503bd6c..b8f0ce1 100644 --- a/lacre/_keyringcommon.py +++ b/lacre/_keyringcommon.py @@ -40,17 +40,13 @@ class KeyCache: class KeyRing: """Contract to be implemented by a key-store (a.k.a. keyring).""" - def load(self): - """Load keyring, replacing any previous contents of the cache.""" - raise NotImplementedError('KeyRing.load not implemented') - def freeze_identities(self) -> KeyCache: """Return a static, async-safe copy of the identity map.""" raise NotImplementedError('KeyRing.load not implemented') - def register(self, email: str, key_id: str): + def register_or_update(self, email: str, key_id: str): """Add a new (email,key) pair to the keystore.""" - raise NotImplementedError('KeyRing.register not implemented') + raise NotImplementedError('KeyRing.register_or_update not implemented') def post_init_hook(self): """Lets the keyring perform additional operations following its initialisation.""" diff --git a/lacre/dbkeyring.py b/lacre/dbkeyring.py deleted file mode 100644 index 0f01102..0000000 --- a/lacre/dbkeyring.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Database-backed keyring implementation.""" - -from lacre._keyringcommon import KeyRing, KeyCache -from lacre.dbschema import init_identities_table, table_metadata -import logging -import sqlalchemy -from sqlalchemy.sql import select - -LOG = logging.getLogger(__name__) - - -class KeyRingSchema: - def __init__(self): - self._meta = table_metadata() - self._id_table = init_identities_table() - - def identities(self): - return self._id_table - - -class DatabaseKeyRing(KeyRing): - """Database-backed key storage.""" - - def __init__(self, database_url, schema: KeyRingSchema): - self._schema = schema - self._url = database_url - self._initialised = False - - def _ensure_initialised(self): - if not self._initialised: - self._engine = sqlalchemy.create_engine(self._url) - self._connection = self._engine.connect() - - def load(self): - """Do nothing, database contents doesn't need to be cached.""" - pass - - def freeze_identities(self) -> KeyCache: - """Return a static, async-safe copy of the identity map.""" - self._ensure_initialised() - return self._load_identities() - - def _load_identities(self) -> KeyCache: - identities = self._schema.identities() - all_identities = select(identities.c.fingerprint, identities.c.email) - result = self._connection.execute(all_identities) - LOG.debug('Retrieving all keys') - return KeyCache({key_id: email for key_id, email in result}) diff --git a/lacre/keyring.py b/lacre/keyring.py index 0d9cff1..a50df53 100644 --- a/lacre/keyring.py +++ b/lacre/keyring.py @@ -6,7 +6,8 @@ module. import lacre.config as conf from lacre._keyringcommon import KeyRing, KeyCache -import lacre.dbkeyring as dbk +from lacre.dbschema import GPGMW_IDENTITIES +from lacre.repositories import IdentityRepository import logging LOG = logging.getLogger(__name__) @@ -15,12 +16,10 @@ LOG = logging.getLogger(__name__) def init_keyring() -> KeyRing: """Initialise appropriate type of keyring.""" url = conf.get_item('database', 'url') - schema = dbk.KeyRingSchema() - LOG.info('Initialising database keyring from %s', url) - return dbk.DatabaseKeyRing(url, schema) + return IdentityRepository(GPGMW_IDENTITIES, db_url=url) -def freeze_and_load_keys(): +def freeze_and_load_keys() -> KeyCache: """Load and return keys. Doesn't refresh the keys when they change on disk. diff --git a/lacre/repositories.py b/lacre/repositories.py index 04cbdfa..3013df1 100644 --- a/lacre/repositories.py +++ b/lacre/repositories.py @@ -1,17 +1,20 @@ """Lacre identity and key repositories.""" -from sqlalchemy import select, delete, and_ +from sqlalchemy import create_engine, select, delete, and_ import logging +from lacre._keyringcommon import KeyRing, KeyCache import lacre.dbschema as db LOG = logging.getLogger(__name__) -class IdentityRepository: - def __init__(self, identity_table, connection): +class IdentityRepository(KeyRing): + def __init__(self, identity_table, connection=None, db_url=None): self._identities = identity_table self._conn = connection + self._url = db_url + self._initialised = connection is not None def register_or_update(self, email, fprint): assert email, "email is mandatory" @@ -23,18 +26,21 @@ class IdentityRepository: self._insert(email, fprint) def _exists(self, email: str) -> bool: + self._ensure_connected() selq = select(self._identities.c.email).where(self._identities.c.email == email) emails = [e for e in self._conn.execute(selq)] assert len(emails) == 1 return emails def _insert(self, email, fprint): + self._ensure_connected() insq = self._identities.insert().values(email=email, fingerprint=fprint) LOG.debug('Registering identity %s: %s', email, insq) self._conn.execute(insq) def _update(self, email, fprint): + self._ensure_connected() upq = self._identities.update() \ .values(fingerprint=fprint) \ .where(self._identities.c.email == email) @@ -42,6 +48,29 @@ class IdentityRepository: LOG.debug('Updating identity %s: %s', email, upq) self._conn.execute(upq) + def _ensure_connected(self): + if not self._initialised: + self._engine = create_engine(self._url) + LOG.debug('Connecting with %s', repr(self._engine)) + self._connection = self._engine.connect() + + def delete(self, email): + self._ensure_connected() + + delq = delete(self._identities).where(self._identities.c.email == email) + LOG.debug('Deleting keys assigned to %s', email) + + def freeze_identities(self) -> KeyCache: + """Return a static, async-safe copy of the identity map.""" + self._ensure_connected() + return self._load_identities() + + def _load_identities(self) -> KeyCache: + all_identities = select(self._identities.c.fingerprint, self._identities.c.email) + result = self._connection.execute(all_identities) + LOG.debug('Retrieving all keys') + return KeyCache({key_id: email for key_id, email in result}) + class KeyConfirmationQueue: """Encapsulates access to gpgmw_keys table.""" diff --git a/test/modules/test_dbkeyring.py b/test/modules/test_dbkeyring.py deleted file mode 100644 index 482f35d..0000000 --- a/test/modules/test_dbkeyring.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Tests for lacre.dbkeyring.""" - -import lacre.dbkeyring as dbk - -import unittest - -class LacreDbKeyringTest(unittest.TestCase): - def test_load_keys(self): - db_url = 'sqlite:///test/lacre.db' - schema = dbk.KeyRingSchema() - db = dbk.DatabaseKeyRing(db_url, schema) - - identities = db.freeze_identities() - - self.assertTrue('1CD245308F0963D038E88357973CF4D9387C44D7' in identities) - self.assertTrue(identities.has_email('alice@disposlab')) diff --git a/test/modules/test_lacre_repositories.py b/test/modules/test_lacre_repositories.py new file mode 100644 index 0000000..fc60f70 --- /dev/null +++ b/test/modules/test_lacre_repositories.py @@ -0,0 +1,14 @@ +"""Lacre identity and key repository tests.""" + +import unittest + +import lacre.repositories as r +import lacre.dbschema as s + +class IdentityRepositoryTest(unittest.TestCase): + + def test_x(self): + ir = r.IdentityRepository(s.GPGMW_IDENTITIES, db_url='sqlite:///test/lacre.db') + identities = ir.freeze_identities() + + self.assertTrue(identities) diff --git a/webgate-cron.py b/webgate-cron.py index cf84dc6..07fe616 100755 --- a/webgate-cron.py +++ b/webgate-cron.py @@ -73,7 +73,8 @@ if conf.flag_enabled('database', 'enabled') and conf.config_item_set('database', for armored_key, row_id, email in result_set: # delete any other public keys associated with this confirmed email address - key_queue.delete_keys(row_id, email) + key_queue.delete_keys(row_id, email=email) + identities.delete(email) GnuPG.delete_key(key_dir, email) LOG.info('Deleted key for <%s> via import request', email)