diff --git a/src/_zkapauthorizer/_json.py b/src/_zkapauthorizer/_json.py index ff8c0c2070e275b3761038c868540552b8c11146..898f6b5601e0647e62a262e55272946d514228c6 100644 --- a/src/_zkapauthorizer/_json.py +++ b/src/_zkapauthorizer/_json.py @@ -1,34 +1,22 @@ -from __future__ import absolute_import, division, print_function, unicode_literals +# Copyright 2022 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from future.utils import PY2 - -if PY2: - from future.builtins import ( # noqa: F401 - filter, - map, - zip, - ascii, - chr, - hex, - input, - next, - oct, - open, - pow, - round, - super, - bytes, - dict, - list, - object, - range, - str, - max, - min, - ) - -from six import ensure_binary from json import dumps as _dumps +from typing import Any -def dumps(o): - return ensure_binary(_dumps(o)) +def dumps_utf8(o: Any) -> bytes: + """ + Serialize an object to a UTF-8-encoded JSON byte string. + """ + return _dumps(o).encode("utf-8") diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index e11fd5f8b6429b01bedff57ad140175e4717ab04..4be61aa6a6d9058cb3a4cbf77705f485e32fb979 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -50,7 +50,7 @@ from base64 import b64decode, b64encode from datetime import timedelta from functools import partial from hashlib import sha256 -from json import dumps, loads +from json import loads from operator import delitem, setitem from sys import exc_info @@ -67,6 +67,7 @@ from twisted.python.url import URL from twisted.web.client import Agent from zope.interface import Interface, implementer +from ._json import dumps_utf8 from ._base64 import urlsafe_b64decode from ._stack import less_limited_stack from .model import Error as model_Error @@ -530,7 +531,7 @@ class RistrettoRedeemer(object): blinded_tokens = list(token.blind() for token in random_tokens) response = yield self._treq.post( self._api_root.child("v1", "redeem").to_text(), - dumps( + dumps_utf8( { "redeemVoucher": voucher.number.decode("ascii"), "redeemCounter": counter, diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index fba2112dcb9146082f723930a7188b27ccc068ee..0aec9d3885a790c847642efc141d692fd0c81f0e 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -60,7 +60,7 @@ from twisted.python.filepath import FilePath from zope.interface import Interface, implementer from six import ensure_text -from ._json import dumps +from ._json import dumps_utf8 from ._base64 import urlsafe_b64decode from .schema import get_schema_upgrades, get_schema_version, run_schema_upgrades from .storage_common import ( @@ -1199,7 +1199,7 @@ class Voucher(object): ) def to_json(self): - return dumps(self.marshal()) + return dumps_utf8(self.marshal()) def marshal(self): return self.to_json_v1() diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py index 82d787e545129aff07609e02894a72d1022df621..b2b97520feec4b99da866f4b947d500b704e6c01 100644 --- a/src/_zkapauthorizer/resource.py +++ b/src/_zkapauthorizer/resource.py @@ -62,7 +62,7 @@ from twisted.web.resource import ErrorPage, IResource, NoResource, Resource from twisted.web.server import NOT_DONE_YET from zope.interface import Attribute -from ._json import dumps +from ._json import dumps_utf8 from . import __version__ as _zkapauthorizer_version from ._base64 import urlsafe_b64decode from .config import get_configured_lease_duration @@ -260,7 +260,7 @@ class _CalculatePrice(Resource): body_object = loads(payload) except ValueError: request.setResponseCode(BAD_REQUEST) - return dumps( + return dumps_utf8( { "error": "could not parse request body", } @@ -271,7 +271,7 @@ class _CalculatePrice(Resource): sizes = body_object["sizes"] except (TypeError, KeyError): request.setResponseCode(BAD_REQUEST) - return dumps( + return dumps_utf8( { "error": "could not read `version` and `sizes` properties", } @@ -279,7 +279,7 @@ class _CalculatePrice(Resource): if version != 1: request.setResponseCode(BAD_REQUEST) - return dumps( + return dumps_utf8( { "error": "did not find required version number 1 in request", } @@ -289,7 +289,7 @@ class _CalculatePrice(Resource): isinstance(size, (int, long)) and size >= 0 for size in sizes ): request.setResponseCode(BAD_REQUEST) - return dumps( + return dumps_utf8( { "error": "did not find required positive integer sizes list in request", } @@ -298,7 +298,7 @@ class _CalculatePrice(Resource): application_json(request) price = self._price_calculator.calculate(sizes) - return dumps( + return dumps_utf8( { "price": price, "period": self._lease_period, @@ -345,7 +345,7 @@ class _ProjectVersion(Resource): def render_GET(self, request): application_json(request) - return dumps( + return dumps_utf8( { "version": _zkapauthorizer_version, } @@ -379,7 +379,7 @@ class _UnblindedTokenCollection(Resource): position = request.args.get(b"position", [b""])[0].decode("utf-8") - return dumps( + return dumps_utf8( { "total": len(unblinded_tokens), "spendable": self._store.count_unblinded_tokens(), @@ -399,7 +399,7 @@ class _UnblindedTokenCollection(Resource): application_json(request) unblinded_tokens = load(request.content)["unblinded-tokens"] self._store.insert_unblinded_tokens(unblinded_tokens, group_id=0) - return dumps({}) + return dumps_utf8({}) def _lease_maintenance_activity(self): activity = self._store.get_latest_lease_maintenance_activity() @@ -452,7 +452,7 @@ class _VoucherCollection(Resource): def render_GET(self, request): application_json(request) - return dumps( + return dumps_utf8( { "vouchers": list( self._controller.incorporate_transient_state(voucher).marshal() diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index 471a41a7d5f28feed3a5238bb5c4990fbe43e386..63acf4c4bc59e486ff23f2a7fb4b302d90599e29 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -98,7 +98,7 @@ from twisted.web.http import BAD_REQUEST, NOT_FOUND, NOT_IMPLEMENTED, OK, UNAUTH from twisted.web.http_headers import Headers from twisted.web.resource import IResource, getChildForRequest -from .. _json import dumps +from .. _json import dumps_utf8 from .. import __version__ as zkapauthorizer_version from .._base64 import urlsafe_b64decode from ..configutil import config_string_from_sections @@ -204,7 +204,7 @@ def invalid_bodies(): { "some-key": vouchers().map(ensure_text), } - ).map(dumps), + ).map(dumps_utf8), # The right key but the wrong kind of value. fixed_dictionaries( { @@ -213,7 +213,7 @@ def invalid_bodies(): not_vouchers().map(ensure_text), ), } - ).map(dumps), + ).map(dumps_utf8), # Not even JSON binary().filter(is_not_json), ) @@ -518,7 +518,7 @@ class UnblindedTokenTests(TestCase): root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) data = BytesIO( - dumps( + dumps_utf8( { "unblinded-tokens": list( token.unblinded_token.decode("ascii") @@ -970,7 +970,7 @@ class VoucherTests(TestCase): ) root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) - data = BytesIO(dumps({"voucher": voucher.decode("ascii")})) + data = BytesIO(dumps_utf8({"voucher": voucher.decode("ascii")})) requesting = authorized_request( api_auth_token, agent, @@ -1263,7 +1263,7 @@ class VoucherTests(TestCase): agent, b"PUT", b"http://127.0.0.1/voucher", - data=BytesIO(dumps({"voucher": voucher.decode("ascii")})), + data=BytesIO(dumps_utf8({"voucher": voucher.decode("ascii")})), ) self.assertThat( putting, @@ -1390,7 +1390,7 @@ class VoucherTests(TestCase): note("{} vouchers".format(len(vouchers))) for voucher in vouchers: - data = BytesIO(dumps({"voucher": voucher.decode("ascii")})) + data = BytesIO(dumps_utf8({"voucher": voucher.decode("ascii")})) putting = authorized_request( api_auth_token, agent, @@ -1511,26 +1511,26 @@ def bad_calculate_price_requests(): "version": good_version, "sizes": good_sizes, } - ).map(dumps) + ).map(dumps_utf8) bad_data_version = fixed_dictionaries( { "version": bad_version, "sizes": good_sizes, } - ).map(dumps) + ).map(dumps_utf8) bad_data_sizes = fixed_dictionaries( { "version": good_version, "sizes": bad_sizes, } - ).map(dumps) + ).map(dumps_utf8) bad_data_other = dictionaries( text(), integers(), - ).map(dumps) + ).map(dumps_utf8) bad_data_junk = binary() @@ -1667,7 +1667,7 @@ class CalculatePriceTests(TestCase): b"POST", self.url, headers={b"content-type": [b"application/json"]}, - data=BytesIO(dumps({"version": 1, "sizes": sizes})), + data=BytesIO(dumps_utf8({"version": 1, "sizes": sizes})), ), succeeded( matches_response( diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 4b4f399b1ff7965a43b700d066bce888a719cd0d..1ffac55a631a43011875c820d2b140bf2b5d4b33 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -59,7 +59,7 @@ from twisted.web.iweb import IAgent from twisted.web.resource import ErrorPage, Resource from zope.interface import implementer -from .._json import dumps +from .._json import dumps_utf8 from ..controller import ( AlreadySpent, DoubleSpendRedeemer, @@ -1155,7 +1155,7 @@ class RistrettoRedemption(Resource): finally: servers_proof.destroy() - return dumps( + return dumps_utf8( { u"success": True, u"public-key": ensure_text(self.public_key.encode_base64()), @@ -1233,7 +1233,7 @@ class CheckRedemptionRequestTests(TestCase): treq = treq_for_loopback_ristretto(issuer) d = treq.post( NOWHERE.child(u"v1", u"redeem").to_text().encode("ascii"), - dumps(dict.fromkeys(properties)), + dumps_utf8(dict.fromkeys(properties)), headers=Headers({u"content-type": [u"application/json"]}), ) self.assertThat( @@ -1283,7 +1283,7 @@ def check_redemption_request(request): def bad_request(request, body_object): request.setResponseCode(BAD_REQUEST) request.setHeader(b"content-type", b"application/json") - request.write(dumps(body_object)) + request.write(dumps_utf8(body_object)) return b""