From d28663d5abf40bd0908e00a0aa8c997cfc44b2a0 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 19 Aug 2019 14:54:23 -0400 Subject: [PATCH] Draw the passes needed for protocol operations from the database --- src/_zkapauthorizer/_plugin.py | 46 ++++++++++-- src/_zkapauthorizer/tests/test_plugin.py | 89 ++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py index e400e87..5db7928 100644 --- a/src/_zkapauthorizer/_plugin.py +++ b/src/_zkapauthorizer/_plugin.py @@ -17,6 +17,14 @@ The Twisted plugin that glues the Zero-Knowledge Access Pass system into Tahoe-LAFS. """ +from weakref import ( + WeakValueDictionary, +) + +from functools import ( + partial, +) + import attr from zope.interface import ( @@ -37,8 +45,8 @@ from .api import ( ZKAPAuthorizerStorageClient, ) -from ._storage_server import ( - TOKEN_LENGTH, +from .model import ( + VoucherStore, ) from .resource import ( @@ -56,14 +64,37 @@ class AnnounceableStorageServer(object): storage_server = attr.ib() - +@attr.s @implementer(IFoolscapStoragePlugin) class ZKAPAuthorizer(object): """ A storage plugin which provides a token-based access control mechanism on top of the Tahoe-LAFS built-in storage server interface. + + :ivar WeakValueDictionary _stores: A mapping from node directories to this + plugin's database connections for those nodes. The existence of any + kind of attribute to reference database connections (not so much the + fact that it is a WeakValueDictionary; if it were just a weakref the + same would be true) probably reflects an error in the interface which + forces different methods to use instance state to share a database + connection. """ - name = u"privatestorageio-zkapauthz-v1" + name = attr.ib(default=u"privatestorageio-zkapauthz-v1") + _stores = attr.ib(default=attr.Factory(WeakValueDictionary)) + + def _get_store(self, node_config): + """ + :return VoucherStore: The database for the given node. At most one + connection is made to the database per ``ZKAPAuthorizer`` instance. + """ + key = node_config.get_config_path() + try: + s = self._stores[key] + except KeyError: + s = VoucherStore.from_node_config(node_config) + self._stores[key] = s + return s + def get_storage_server(self, configuration, get_anonymous_storage_server): announcement = {} @@ -79,11 +110,13 @@ class ZKAPAuthorizer(object): ) - def get_storage_client(self, configuration, announcement, get_rref): + def get_storage_client(self, node_config, announcement, get_rref): return succeed( ZKAPAuthorizerStorageClient( get_rref, - lambda: [b"x" * TOKEN_LENGTH], + # TODO: Make the caller figure out the correct number of + # passes to extract. + partial(self._get_store(node_config).extract_passes, 1), ) ) @@ -91,5 +124,6 @@ class ZKAPAuthorizer(object): def get_client_resource(self, node_config): return resource_from_configuration( node_config, + store=self._get_store(node_config), redeemer=DummyRedeemer(), ) diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 72c017a..25fefb5 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -31,10 +31,16 @@ from testtools.matchers import ( Always, Contains, AfterPreprocessing, + Equals, ) from testtools.twistedsupport import ( succeeded, ) +from testtools.twistedsupport._deferred import ( + # I'd rather use https://twistedmatrix.com/trac/ticket/8900 but efforts + # there appear to have stalled. + extract_result, +) from hypothesis import ( given, @@ -47,6 +53,9 @@ from foolscap.ipb import ( IReferenceable, IRemotelyCallable, ) +from foolscap.referenceable import ( + LocalReferenceable, +) from allmydata.interfaces import ( IFoolscapStoragePlugin, @@ -68,10 +77,19 @@ from twisted.plugins.zkapauthorizer import ( storage_server, ) +from ..model import ( + VoucherStore, +) + from .strategies import ( tahoe_configs, configurations, announcements, + vouchers, + random_tokens, + zkaps, + storage_indexes, + lease_renew_secrets, ) from .matchers import ( Provides, @@ -88,7 +106,7 @@ def get_anonymous_storage_server(): def get_rref(): - return None + return LocalReferenceable(None) class PluginTests(TestCase): @@ -214,14 +232,20 @@ class ClientPluginTests(TestCase): Tests for the plugin's implementation of ``IFoolscapStoragePlugin.get_storage_client``. """ - @given(configurations(), announcements()) - def test_interface(self, configuration, announcement): + @given(tahoe_configs(), announcements()) + def test_interface(self, get_config, announcement): """ ``get_storage_client`` returns a ``Deferred`` that fires with an object which provides ``IStorageServer``. """ + tempdir = self.useFixture(TempDir()) + node_config = get_config( + tempdir.join(b"node"), + b"tub.port", + ) + storage_client_deferred = storage_server.get_storage_client( - configuration, + node_config, announcement, get_rref, ) @@ -232,6 +256,63 @@ class ClientPluginTests(TestCase): ) + @given( + tahoe_configs(), + announcements(), + vouchers(), + random_tokens(), + zkaps(), + storage_indexes(), + lease_renew_secrets(), + ) + def test_passes_extracted( + self, + get_config, + announcement, + voucher, + token, + zkap, + storage_index, + renew_secret, + ): + """ + The ``ZKAPAuthorizerStorageServer`` returned by ``get_storage_client`` + extracts passes from the plugin database. + """ + tempdir = self.useFixture(TempDir()) + node_config = get_config( + tempdir.join(b"node"), + b"tub.port", + ) + + store = VoucherStore.from_node_config(node_config) + store.add(voucher, [token]) + store.insert_passes_for_voucher(voucher, [zkap]) + + storage_client_deferred = storage_server.get_storage_client( + node_config, + announcement, + get_rref, + ) + + storage_client = extract_result(storage_client_deferred) + + # This is hooked up to a garbage reference. We don't care about its + # _result_, anyway, right now. + d = storage_client.renew_lease( + storage_index, + renew_secret, + ) + d.addBoth(lambda ignored: None) + + # There should be no passes left to extract. + remaining = store.extract_passes(1) + self.assertThat( + remaining, + Equals([]), + ) + + class ClientResourceTests(TestCase): """ Tests for the plugin's implementation of -- GitLab