From 5111d2186266c39bbd0ff04360956c2b806623ad Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 18 Nov 2019 15:49:17 -0500 Subject: [PATCH] recognize unpaid and also double-spend --- src/_zkapauthorizer/controller.py | 8 +++- src/_zkapauthorizer/tests/test_controller.py | 41 +++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 1939478..21e0189 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -395,9 +395,13 @@ class RistrettoRedeemer(object): self._log.failure("Parsing redeem response failed", response=response) raise - if result.get(u"failed", False): - if result.get(u"reason", None) == u"double-spend": + success = result.get(u"success", False) + if not success: + reason = result.get(u"reason", None) + if reason == u"double-spend": raise AlreadySpent(voucher) + elif reason == u"unpaid": + raise Unpaid(voucher) self._log.info("Redeemed: {public-key} {proof} {signatures}", **result) diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 31be5bf..86bcd8f 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -100,6 +100,7 @@ from ..controller import ( RistrettoRedeemer, PaymentController, AlreadySpent, + Unpaid, ) from ..model import ( @@ -282,6 +283,32 @@ class RistrettoRedeemerTests(TestCase): ), ) + @given(voucher_objects(), integers(min_value=1, max_value=100)) + def test_redemption_denied_unpaid(self, voucher, num_tokens): + """ + If the issuer declines to allow the voucher to be redeemed and gives a + reason that the voucher has not been paid for, ``RistrettoRedeem`` + returns a ``Deferred`` that fires with a ``Failure`` wrapping + ``Unpaid``. + """ + issuer = UnpaidRedemption() + treq = treq_for_loopback_ristretto(issuer) + redeemer = RistrettoRedeemer(treq, NOWHERE) + random_tokens = redeemer.random_tokens_for_voucher(voucher, num_tokens) + d = redeemer.redeem( + voucher, + random_tokens, + ) + self.assertThat( + d, + failed( + AfterPreprocessing( + lambda f: f.value, + IsInstance(Unpaid), + ), + ), + ) + @given(voucher_objects(), integers(min_value=1, max_value=100)) def test_bad_ristretto_redemption(self, voucher, num_tokens): """ @@ -439,8 +466,20 @@ class AlreadySpentRedemption(Resource): if request.requestHeaders.getRawHeaders(b"content-type") != ["application/json"]: return bad_content_type(request) - return bad_request(request, {u"failed": True, u"reason": u"double-spend"}) + return bad_request(request, {u"success": False, u"reason": u"double-spend"}) + + +class UnpaidRedemption(Resource): + """ + An ``UnpaidRedemption`` simulates the Ristretto redemption server but + always refuses to allow vouchers to be redeemed and reports an error that + the voucher has not been paid for. + """ + def render_POST(self, request): + if request.requestHeaders.getRawHeaders(b"content-type") != ["application/json"]: + return bad_content_type(request) + return bad_request(request, {u"success": False, u"reason": u"unpaid"}) class RistrettoRedemption(Resource): -- GitLab