Make IdentityRepository a KeyRing
- 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.
This commit is contained in:
parent
5efef3c9cb
commit
7c2d32bf3c
7 changed files with 54 additions and 79 deletions
|
@ -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."""
|
||||
|
|
|
@ -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})
|
|
@ -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.
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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'))
|
14
test/modules/test_lacre_repositories.py
Normal file
14
test/modules/test_lacre_repositories.py
Normal file
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue