From 1de3eda7acd9e42787002713f219aea452e21e7f Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Tue, 12 May 2020 09:27:04 -0400
Subject: [PATCH] Add a test for MorePassesRequired making it across the
 network

Meant this to be in PR#152 but somehow forgot to even commit it...
---
 src/_zkapauthorizer/_storage_server.py        | 21 +-------
 src/_zkapauthorizer/api.py                    |  4 +-
 src/_zkapauthorizer/storage_common.py         | 20 ++++++++
 .../tests/test_storage_protocol.py            | 50 +++++++++++++++++++
 4 files changed, 74 insertions(+), 21 deletions(-)

diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py
index 73753fe..bb89df0 100644
--- a/src/_zkapauthorizer/_storage_server.py
+++ b/src/_zkapauthorizer/_storage_server.py
@@ -88,6 +88,7 @@ from .foolscap import (
     RIPrivacyPassAuthorizedStorageServer,
 )
 from .storage_common import (
+    MorePassesRequired,
     pass_value_attribute,
     required_passes,
     allocate_buckets_message,
@@ -102,26 +103,6 @@ from .storage_common import (
 SLOT_HEADER_SIZE = 468
 LEASE_TRAILER_SIZE = 4
 
-@attr.s
-class MorePassesRequired(Exception):
-    """
-    Storage operations fail with ``MorePassesRequired`` when they are not
-    accompanied by a sufficient number of valid passes.
-
-    :ivar int valid_count: The number of valid passes presented in the
-        operation.
-
-    ivar int required_count: The number of valid passes which must be
-        presented for the operation to be authorized.
-
-    :ivar list[int] signature_check_failed: Indices into the supplied list of
-        passes indicating passes which failed the signature check.
-    """
-    valid_count = attr.ib()
-    required_count = attr.ib()
-    signature_check_failed = attr.ib()
-
-
 @attr.s
 class _ValidationResult(object):
     """
diff --git a/src/_zkapauthorizer/api.py b/src/_zkapauthorizer/api.py
index 365e39a..70e4725 100644
--- a/src/_zkapauthorizer/api.py
+++ b/src/_zkapauthorizer/api.py
@@ -20,8 +20,10 @@ __all__ = [
     "ZKAPAuthorizer",
 ]
 
-from ._storage_server import (
+from .storage_common import (
     MorePassesRequired,
+)
+from ._storage_server import (
     LeaseRenewalRequired,
     ZKAPAuthorizerStorageServer,
 )
diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py
index f00997b..1a52b4d 100644
--- a/src/_zkapauthorizer/storage_common.py
+++ b/src/_zkapauthorizer/storage_common.py
@@ -30,6 +30,26 @@ from .validators import (
     greater_than,
 )
 
+@attr.s
+class MorePassesRequired(Exception):
+    """
+    Storage operations fail with ``MorePassesRequired`` when they are not
+    accompanied by a sufficient number of valid passes.
+
+    :ivar int valid_count: The number of valid passes presented in the
+        operation.
+
+    ivar int required_count: The number of valid passes which must be
+        presented for the operation to be authorized.
+
+    :ivar list[int] signature_check_failed: Indices into the supplied list of
+        passes indicating passes which failed the signature check.
+    """
+    valid_count = attr.ib()
+    required_count = attr.ib()
+    signature_check_failed = attr.ib()
+
+
 def _message_maker(label):
     def make_message(storage_index):
         return u"{label} {storage_index}".format(
diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py
index a267c06..612a853 100644
--- a/src/_zkapauthorizer/tests/test_storage_protocol.py
+++ b/src/_zkapauthorizer/tests/test_storage_protocol.py
@@ -111,6 +111,7 @@ from .foolscap import (
     LocalRemote,
 )
 from ..api import (
+    MorePassesRequired,
     ZKAPAuthorizerStorageServer,
     ZKAPAuthorizerStorageClient,
 )
@@ -215,6 +216,55 @@ class ShareTests(TestCase):
             succeeded(matches_version_dictionary()),
         )
 
+    @given(
+        storage_index=storage_indexes(),
+        renew_secret=lease_renew_secrets(),
+        cancel_secret=lease_cancel_secrets(),
+        sharenums=sharenum_sets(),
+        size=sizes(),
+    )
+    def test_rejected_passes_reported(self, storage_index, renew_secret, cancel_secret, sharenums, size):
+        """
+        Any passes rejected by the storage server are reported with a
+        ``MorePassesRequired`` exception sent to the client.
+        """
+        # Hypothesis causes our storage server to be used many times.  Clean
+        # up between iterations.
+        cleanup_storage_server(self.anonymous_storage_server)
+
+        # Break our infinite pass factory by replacing the expected key with a
+        # new one.  Now the passes are mis-signed as far as the server is
+        # concerned.  The clunky way we control pass generation means it's
+        # hard to have anything but an all-or-nothing test.  Perhaps some
+        # future refactoring will let us exercise a mix of passes with valid
+        # and invalid signatures.
+        self.signing_key = random_signing_key()
+
+        num_passes = required_passes(self.pass_value, [size] * len(sharenums))
+
+        self.assertThat(
+            self.client.allocate_buckets(
+                storage_index,
+                renew_secret,
+                cancel_secret,
+                sharenums,
+                size,
+                canary=self.canary,
+            ),
+            failed(
+                AfterPreprocessing(
+                    lambda f: f.value,
+                    Equals(
+                        MorePassesRequired(
+                            valid_count=0,
+                            required_count=num_passes,
+                            signature_check_failed=range(num_passes),
+                        ),
+                    ),
+                ),
+            ),
+        )
+
     @given(
         storage_index=storage_indexes(),
         renew_secret=lease_renew_secrets(),
-- 
GitLab