diff --git a/src/_zkapauthorizer/_json.py b/src/_zkapauthorizer/_json.py new file mode 100644 index 0000000000000000000000000000000000000000..ff8c0c2070e275b3761038c868540552b8c11146 --- /dev/null +++ b/src/_zkapauthorizer/_json.py @@ -0,0 +1,34 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +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 + +def dumps(o): + return ensure_binary(_dumps(o)) diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py index f6105e631f0f151ecf0677f9d26dc2ab912cb811..5deab0ddb9351e5ba45d2c22b29e7579c152b497 100644 --- a/src/_zkapauthorizer/_plugin.py +++ b/src/_zkapauthorizer/_plugin.py @@ -291,7 +291,7 @@ def _create_maintenance_service(reactor, node_config, client_node): get_now=get_now, ) last_run_path = FilePath( - node_config.get_private_path(b"last-lease-maintenance-run") + node_config.get_private_path(u"last-lease-maintenance-run") ) # Create the service to periodically run the lease maintenance operation. return lease_maintenance_service( diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py index 28670659bb8fde7d0b2cda20a2770f8a24b175b6..b47c5b3bc6c8cfc040476b978026c7d6e18258ce 100644 --- a/src/_zkapauthorizer/_storage_client.py +++ b/src/_zkapauthorizer/_storage_client.py @@ -20,7 +20,34 @@ This is the client part of a storage access protocol. The server part is implemented in ``_storage_server.py``. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals + +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 functools import partial, wraps diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py index 5d94598ff08f6fbd0d17db9db524a5bd2144e7ae..6e0ea94e4917ddd27d679d17fa4ef24cb6da3e5f 100644 --- a/src/_zkapauthorizer/_storage_server.py +++ b/src/_zkapauthorizer/_storage_server.py @@ -149,13 +149,13 @@ class _ValidationResult(object): """ Cryptographically check the validity of a single pass. - :param str message: The shared message for pass validation. + :param bytes message: The shared message for pass validation. :param Pass pass_: The pass to validate. :return bool: ``False`` (invalid) if the pass includes a valid signature, ``True`` (valid) otherwise. """ - assert isinstance(message, str), "message %r not str" % (message,) + assert isinstance(message, bytes), "message %r not bytes" % (message,) assert isinstance(pass_, Pass), "pass %r not a Pass" % (pass_,) try: preimage = TokenPreimage.decode_base64(pass_.preimage) @@ -163,7 +163,7 @@ class _ValidationResult(object): unblinded_token = signing_key.rederive_unblinded_token(preimage) verification_key = unblinded_token.derive_verification_key_sha512() invalid_pass = verification_key.invalid_sha512( - proposed_signature, message.encode("utf-8") + proposed_signature, message, ) return invalid_pass except Exception: @@ -175,7 +175,7 @@ class _ValidationResult(object): """ Check all of the given passes for validity. - :param str message: The shared message for pass validation. + :param bytes message: The shared message for pass validation. :param list[bytes] passes: The encoded passes to validate. :param SigningKey signing_key: The signing key to use to check the passes. @@ -315,7 +315,7 @@ class ZKAPAuthorizerStorageServer(Referenceable): storage for immutable shares if they present valid passes. """ validation = _ValidationResult.validate_passes( - allocate_buckets_message(storage_index), + allocate_buckets_message(storage_index).encode("utf-8"), passes, self._signing_key, ) @@ -398,7 +398,7 @@ class ZKAPAuthorizerStorageServer(Referenceable): duration of share storage if they present valid passes. """ validation = _ValidationResult.validate_passes( - add_lease_message(storage_index), + add_lease_message(storage_index).encode("utf-8"), passes, self._signing_key, ) @@ -506,7 +506,7 @@ class ZKAPAuthorizerStorageServer(Referenceable): # Check passes for cryptographic validity. validation = _ValidationResult.validate_passes( - slot_testv_and_readv_and_writev_message(storage_index), + slot_testv_and_readv_and_writev_message(storage_index).encode("utf-8"), passes, self._signing_key, ) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index f9536b26b812cb423fa299c92e9195188c78a09d..e11fd5f8b6429b01bedff57ad140175e4717ab04 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -22,7 +22,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera from future.utils import PY2 if PY2: - from future.builtins import ( + from future.builtins import ( # noqa: F401 filter, map, zip, @@ -44,7 +44,7 @@ if PY2: str, max, min, - ) # noqa: F401 + ) from base64 import b64decode, b64encode from datetime import timedelta @@ -54,6 +54,7 @@ from json import dumps, loads from operator import delitem, setitem from sys import exc_info +from six import ensure_text import attr import challenge_bypass_ristretto from treq import content @@ -105,7 +106,7 @@ class Unpaid(Exception): """ -@attr.s(frozen=True) +@attr.s class UnrecognizedFailureReason(Exception): """ An attempt was made to redeem a voucher and the response contained an unknown reason. @@ -121,15 +122,18 @@ class RedemptionResult(object): """ Contain the results of an attempt to redeem a voucher for ZKAP material. - :ivar list[UnblindedToken] unblinded_tokens: The tokens which resulted - from the redemption. + :ivar unblinded_tokens: The tokens which resulted from the redemption. - :ivar str public_key: The public key which the server proved was - involved in the redemption process. + :ivar public_key: The public key which the server proved was involved in + the redemption process. """ - unblinded_tokens = attr.ib() - public_key = attr.ib() + unblinded_tokens = attr.ib( # type: List[UnblindedToken] + validator=attr.validators.instance_of(list), + ) + public_key = attr.ib( # type: str + validator=attr.validators.instance_of(str), + ) class IRedeemer(Interface): @@ -269,10 +273,10 @@ class ErrorRedeemer(object): @classmethod def make(cls, section_name, node_config, announcement, reactor): - details = node_config.get_config( + details = ensure_text(node_config.get_config( section=section_name, option="details", - ).decode("ascii") + )) return cls(details) def random_tokens_for_voucher(self, voucher, counter, count): @@ -384,7 +388,7 @@ class DummyRedeemer(object): node_config.get_config( section=section_name, option="issuer-public-key", - ).decode("utf-8"), + ), ) def random_tokens_for_voucher(self, voucher, counter, count): @@ -486,10 +490,10 @@ class RistrettoRedeemer(object): @classmethod def make(cls, section_name, node_config, announcement, reactor): - configured_issuer = node_config.get_config( + configured_issuer = ensure_text(node_config.get_config( section=section_name, option="ristretto-issuer-root-url", - ).decode("ascii") + )) if announcement is not None: # Don't let us talk to a storage server that has a different idea # about who issues ZKAPs. We should lift this limitation (that is, we @@ -531,7 +535,7 @@ class RistrettoRedeemer(object): "redeemVoucher": voucher.number.decode("ascii"), "redeemCounter": counter, "redeemTokens": list( - token.encode_base64() for token in blinded_tokens + ensure_text(token.encode_base64()) for token in blinded_tokens ), } ), @@ -965,7 +969,7 @@ class PaymentController(object): ) self._error[voucher] = model_Error( finished=self.store.now(), - details=reason.getErrorMessage().decode("utf-8", "replace"), + details=ensure_text(reason.getErrorMessage()), ) return False @@ -1053,7 +1057,9 @@ def bracket(first, last, between): except: info = exc_info() yield last() - raise info[0], info[1], info[2] + if PY2: + exec("raise info[0], info[1], info[2]") + raise else: yield last() returnValue(result) diff --git a/src/_zkapauthorizer/foolscap.py b/src/_zkapauthorizer/foolscap.py index 20ba99fde74bfcf6f0a3f92281867887864c0d1c..c2d814eccc4ee3cf8a93aae4a140b82f8d4242a0 100644 --- a/src/_zkapauthorizer/foolscap.py +++ b/src/_zkapauthorizer/foolscap.py @@ -17,14 +17,15 @@ Definitions related to the Foolscap-based protocol used by ZKAPAuthorizer to communicate between storage clients and servers. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals +from future.utils import PY2 import attr from allmydata.interfaces import Offset, RIStorageServer, StorageIndex from foolscap.api import Any, Copyable, DictOf, ListOf, RemoteCopy from foolscap.constraint import ByteStringConstraint from foolscap.remoteinterface import RemoteInterface, RemoteMethodSchema - +from six import ensure_str @attr.s class ShareStat(Copyable, RemoteCopy): @@ -37,7 +38,7 @@ class ShareStat(Copyable, RemoteCopy): lease on this share expires, or None if there is no lease. """ - typeToCopy = copytype = "ShareStat" + typeToCopy = copytype = ensure_str("ShareStat") # To be a RemoteCopy it must be possible to instantiate this with no # arguments. :/ So supply defaults for these attributes. @@ -90,7 +91,7 @@ def add_passes(schema): :return foolscap.remoteinterface.RemoteMethodSchema: A schema like ``schema`` but with one additional required argument. """ - return add_arguments(schema, [(b"passes", _PassList)]) + return add_arguments(schema, [("passes", _PassList)]) def add_arguments(schema, kwargs): @@ -136,7 +137,9 @@ class RIPrivacyPassAuthorizedStorageServer(RemoteInterface): validated is service provided. """ - __remote_name__ = "RIPrivacyPassAuthorizedStorageServer.tahoe.privatestorage.io" + __remote_name__ = ensure_str( + "RIPrivacyPassAuthorizedStorageServer.tahoe.privatestorage.io" + ) get_version = RIStorageServer["get_version"] diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py index 49adebf5253260cbf214a0671d284cb698c061dd..4916768ba53a3fce6720de63b299c0f3232a2251 100644 --- a/src/_zkapauthorizer/lease_maintenance.py +++ b/src/_zkapauthorizer/lease_maintenance.py @@ -17,6 +17,35 @@ This module implements a service which periodically spends ZKAPs to refresh leases on all shares reachable from a root. """ +from __future__ import absolute_import, division, print_function, unicode_literals + +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 datetime import datetime, timedelta from errno import ENOENT from functools import partial @@ -26,6 +55,7 @@ try: except ImportError: pass +from six import ensure_binary import attr from allmydata.interfaces import IDirectoryNode, IFilesystemNode from allmydata.util.hashutil import ( @@ -395,7 +425,7 @@ def lease_maintenance_service( """ interval_mean = lease_maint_config.crawl_interval_mean interval_range = lease_maint_config.crawl_interval_range - halfrange = interval_range / 2 + halfrange = interval_range // 2 def sample_interval_distribution(): return timedelta( @@ -507,7 +537,7 @@ def write_time_to_path(path, when): :param datetime when: The datetime to write. """ - path.setContent(when.isoformat()) + path.setContent(ensure_binary(when.isoformat())) def read_time_from_path(path): @@ -526,7 +556,7 @@ def read_time_from_path(path): return None raise else: - return parse_datetime(when) + return parse_datetime(when.decode("ascii")) def visit_storage_indexes_from_root(visitor, get_root_nodes): diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 7caa8efa5b11357dc1cec9475f20cd3e8ba63957..fba2112dcb9146082f723930a7188b27ccc068ee 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -48,7 +48,7 @@ if PY2: from datetime import datetime from functools import wraps -from json import dumps, loads +from json import loads from sqlite3 import OperationalError from sqlite3 import connect as _connect @@ -58,7 +58,9 @@ from past.builtins import long from twisted.logger import Logger from twisted.python.filepath import FilePath from zope.interface import Interface, implementer +from six import ensure_text +from ._json import dumps from ._base64 import urlsafe_b64decode from .schema import get_schema_upgrades, get_schema_version, run_schema_upgrades from .storage_common import ( @@ -69,16 +71,19 @@ from .storage_common import ( from .validators import greater_than, has_length, is_base64_encoded -def parse_datetime(s, **kw): - """ - Like ``aniso8601.parse_datetime`` but accept str as well. - """ - if isinstance(s, str): - s = s.encode("utf-8") - assert isinstance(s, bytes) - if "delimiter" in kw and isinstance(kw["delimiter"], str): - kw["delimiter"] = kw["delimiter"].encode("utf-8") - return _parse_datetime(s, **kw) +if PY2: + def parse_datetime(s, **kw): + """ + Like ``aniso8601.parse_datetime`` but accept str as well. + """ + if isinstance(s, str): + s = s.encode("utf-8") + assert isinstance(s, bytes) + if "delimiter" in kw and isinstance(kw["delimiter"], str): + kw["delimiter"] = kw["delimiter"].encode("utf-8") + return _parse_datetime(s, **kw) +else: + parse_datetime = _parse_datetime class ILeaseMaintenanceObserver(Interface): @@ -975,7 +980,7 @@ class Redeeming(object): def to_json_v1(self): return { "name": "redeeming", - "started": self.started.isoformat(), + "started": ensure_text(self.started.isoformat()), "counter": self.counter, } @@ -1000,7 +1005,7 @@ class Redeemed(object): def to_json_v1(self): return { "name": "redeemed", - "finished": self.finished.isoformat(), + "finished": ensure_text(self.finished.isoformat()), "token-count": self.token_count, } @@ -1015,7 +1020,7 @@ class DoubleSpend(object): def to_json_v1(self): return { "name": "double-spend", - "finished": self.finished.isoformat(), + "finished": ensure_text(self.finished.isoformat()), } @@ -1035,7 +1040,7 @@ class Unpaid(object): def to_json_v1(self): return { "name": "unpaid", - "finished": self.finished.isoformat(), + "finished": ensure_text(self.finished.isoformat()), } @@ -1056,7 +1061,7 @@ class Error(object): def to_json_v1(self): return { "name": "error", - "finished": self.finished.isoformat(), + "finished": ensure_text(self.finished.isoformat()), "details": self.details, } @@ -1204,7 +1209,7 @@ class Voucher(object): return { "number": self.number.decode("ascii"), "expected-tokens": self.expected_tokens, - "created": None if self.created is None else self.created.isoformat(), + "created": None if self.created is None else ensure_text(self.created.isoformat()), "state": state, "version": 1, } diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py index 9dbb51e2fe57385510b5cfe19d74605a9a5c2b7f..82d787e545129aff07609e02894a72d1022df621 100644 --- a/src/_zkapauthorizer/resource.py +++ b/src/_zkapauthorizer/resource.py @@ -51,9 +51,10 @@ if PY2: ) from itertools import islice -from json import dumps, load, loads -from sys import maxint +from json import load, loads +from sys import maxsize +from six import ensure_str, ensure_binary from past.builtins import long from twisted.logger import Logger from twisted.web.http import BAD_REQUEST @@ -61,6 +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 . import __version__ as _zkapauthorizer_version from ._base64 import urlsafe_b64decode from .config import get_configured_lease_duration @@ -170,7 +172,7 @@ def from_configuration( ) root = create_private_tree( - lambda: node_config.get_private_config(b"api_auth_token"), + lambda: ensure_binary(node_config.get_private_config(ensure_str("api_auth_token"))), authorizationless_resource_tree( store, controller, @@ -373,7 +375,7 @@ class _UnblindedTokenCollection(Resource): limit = request.args.get(b"limit", [None])[0] if limit is not None: - limit = min(maxint, int(limit)) + limit = min(maxsize, int(limit)) position = request.args.get(b"position", [b""])[0].decode("utf-8") @@ -432,7 +434,7 @@ class _VoucherCollection(Resource): payload = loads(request.content.read()) except Exception: return bad_request("json request body required").render(request) - if payload.keys() != ["voucher"]: + if set(payload) != {"voucher"}: return bad_request( "request object must have exactly one key: 'voucher'" ).render(request) diff --git a/src/_zkapauthorizer/spending.py b/src/_zkapauthorizer/spending.py index 19b96ecb1f597e0ddbe26e1840853c6af7b0ef64..49983f4eafbc0ddb6c1eebcd650142fb0608c358 100644 --- a/src/_zkapauthorizer/spending.py +++ b/src/_zkapauthorizer/spending.py @@ -16,6 +16,35 @@ A module for logic controlling the manner in which ZKAPs are spent. """ +from __future__ import absolute_import, division, print_function, unicode_literals + +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, + ) + import attr from zope.interface import Attribute, Interface, implementer @@ -101,7 +130,7 @@ class PassGroup(object): """ Track the state of a group of passes intended as payment for an operation. - :ivar unicode _message: The request binding message for this group of + :ivar _message: The request binding message for this group of passes. :ivar IPassFactory _factory: The factory which created this pass group. @@ -109,19 +138,22 @@ class PassGroup(object): :ivar list[Pass] passes: The passes of which this group consists. """ - _message = attr.ib() - _factory = attr.ib() - _tokens = attr.ib() + _message = attr.ib(validator=attr.validators.instance_of(bytes)) # type: bytes + _factory = attr.ib(validator=attr.validators.provides(IPassFactory)) # type: IPassFactory + _tokens = attr.ib(validator=attr.validators.instance_of(list)) # type: List[(UnblinidedToken, Pass)] @property def passes(self): + # type: () -> List[Pass] return list(pass_ for (unblinded_token, pass_) in self._tokens) @property def unblinded_tokens(self): + # type: () -> List[UnblindedToken] return list(unblinded_token for (unblinded_token, pass_) in self._tokens) def split(self, select_indices): + # type: (List[int]) -> (PassGroup, PassGroup) selected = [] unselected = [] for idx, t in enumerate(self._tokens): @@ -135,18 +167,22 @@ class PassGroup(object): ) def expand(self, by_amount): + # type: (int) -> PassGroup return attr.evolve( self, tokens=self._tokens + self._factory.get(self._message, by_amount)._tokens, ) def mark_spent(self): + # type: () -> None self._factory._mark_spent(self.unblinded_tokens) def mark_invalid(self, reason): + # type: () -> None self._factory._mark_invalid(reason, self.unblinded_tokens) def reset(self): + # tye: () -> None self._factory._reset(self.unblinded_tokens) @@ -158,12 +194,12 @@ class SpendingController(object): attempts when necessary. """ - get_unblinded_tokens = attr.ib() - discard_unblinded_tokens = attr.ib() - invalidate_unblinded_tokens = attr.ib() - reset_unblinded_tokens = attr.ib() + get_unblinded_tokens = attr.ib() # type: (int) -> [UnblindedToken] + discard_unblinded_tokens = attr.ib() # type: ([UnblindedTokens]) -> None + invalidate_unblinded_tokens = attr.ib() # type: ([UnblindedTokens]) -> None + reset_unblinded_tokens = attr.ib() # type: ([UnblindedTokens]) -> None - tokens_to_passes = attr.ib() + tokens_to_passes = attr.ib() # type: (bytes, [UnblindedTokens]) -> [Pass] @classmethod def for_store(cls, tokens_to_passes, store): @@ -179,10 +215,10 @@ class SpendingController(object): unblinded_tokens = self.get_unblinded_tokens(num_passes) passes = self.tokens_to_passes(message, unblinded_tokens) GET_PASSES.log( - message=message, + message=message.decode("utf-8"), count=num_passes, ) - return PassGroup(message, self, zip(unblinded_tokens, passes)) + return PassGroup(message, self, list(zip(unblinded_tokens, passes))) def _mark_spent(self, unblinded_tokens): SPENT_PASSES.log( diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py index b847073a5cc7a1c857ff2d9874b087ceae2397c0..b9c4b2cf6880d722066f46447264af90566c6dac 100644 --- a/src/_zkapauthorizer/storage_common.py +++ b/src/_zkapauthorizer/storage_common.py @@ -55,7 +55,7 @@ from .eliot import MUTABLE_PASSES_REQUIRED from .validators import greater_than -@attr.s(frozen=True, str=True) +@attr.s(str=True) class MorePassesRequired(Exception): """ Storage operations fail with ``MorePassesRequired`` when they are not @@ -80,7 +80,7 @@ def _message_maker(label): def make_message(storage_index): return "{label} {storage_index}".format( label=label, - storage_index=b64encode(storage_index), + storage_index=b64encode(storage_index).decode("utf-8"), ) return make_message diff --git a/src/_zkapauthorizer/tests/fixtures.py b/src/_zkapauthorizer/tests/fixtures.py index e017eadd6b42f10f6f4255caf5c110d64cfca7de..2bc62c8e6c35f35beefac24b31514499e975c83e 100644 --- a/src/_zkapauthorizer/tests/fixtures.py +++ b/src/_zkapauthorizer/tests/fixtures.py @@ -16,7 +16,34 @@ Common fixtures to let the test suite focus on application logic. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals + +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 base64 import b64encode @@ -49,9 +76,9 @@ class AnonymousStorageServer(Fixture): clock = attr.ib() def _setUp(self): - self.tempdir = FilePath(self.useFixture(TempDir()).join(b"storage")) + self.tempdir = FilePath(self.useFixture(TempDir()).join(u"storage")) self.storage_server = StorageServer( - self.tempdir.asBytesMode().path, + self.tempdir.asTextMode().path, b"x" * 20, clock=self.clock, ) @@ -75,7 +102,7 @@ class TemporaryVoucherStore(Fixture): def _setUp(self): self.tempdir = self.useFixture(TempDir()) - self.config = self.get_config(self.tempdir.join(b"node"), b"tub.port") + self.config = self.get_config(self.tempdir.join(u"node"), u"tub.port") self.store = VoucherStore.from_node_config( self.config, self.get_now, diff --git a/src/_zkapauthorizer/tests/foolscap.py b/src/_zkapauthorizer/tests/foolscap.py index 3a984bea163fd4c567812556f8229508c0cb8a2d..e2d1c43f32324f9895b83324634c2f426b6463f7 100644 --- a/src/_zkapauthorizer/tests/foolscap.py +++ b/src/_zkapauthorizer/tests/foolscap.py @@ -121,7 +121,7 @@ class LocalRemote(object): schema = self._referenceable.getInterface()[methname] if self.check_args: schema.checkAllArgs(args, kwargs, inbound=True) - _check_copyables(list(args) + kwargs.values()) + _check_copyables(list(args) + list(kwargs.values())) result = self._referenceable.doRemoteCall( methname, args, diff --git a/src/_zkapauthorizer/tests/privacypass.py b/src/_zkapauthorizer/tests/privacypass.py index a2c29cc4b9daee16bfb86f1f311f1b1c4b45e4f6..0f4287730b66b021c41dc549b535b6e06bdfc5f0 100644 --- a/src/_zkapauthorizer/tests/privacypass.py +++ b/src/_zkapauthorizer/tests/privacypass.py @@ -30,7 +30,7 @@ def make_passes(signing_key, for_message, random_tokens): :param challenge_bypass_ristretto.SigningKey signing_key: The key to use to sign the passes. - :param unicode for_message: The request-binding message with which to + :param bytes for_message: The request-binding message with which to associate the passes. :param list[challenge_bypass_ristretto.RandomToken] random_tokens: The @@ -62,7 +62,7 @@ def make_passes(signing_key, for_message, random_tokens): for unblinded_signature in unblinded_signatures ) message_signatures = list( - verification_key.sign_sha512(for_message.encode("utf-8")) + verification_key.sign_sha512(for_message) for verification_key in verification_keys ) passes = list( diff --git a/src/_zkapauthorizer/tests/storage_common.py b/src/_zkapauthorizer/tests/storage_common.py index db95a71c78308c472b7ab60f96cb09e50a4e9ac4..61c061c55d71dd240aa37b8095d6597f8973d3b5 100644 --- a/src/_zkapauthorizer/tests/storage_common.py +++ b/src/_zkapauthorizer/tests/storage_common.py @@ -154,7 +154,7 @@ def integer_passes(limit): def get_passes(message, count, signing_key): """ - :param unicode message: Request-binding message for PrivacyPass. + :param bytes message: Request-binding message for PrivacyPass. :param int count: The number of passes to get. @@ -163,6 +163,7 @@ def get_passes(message, count, signing_key): :return list[Pass]: ``count`` new random passes signed with the given key and bound to the given message. """ + assert isinstance(message, bytes) return make_passes( signing_key, message, @@ -200,33 +201,33 @@ class _PassFactory(object): """ A stateful pass issuer. - :ivar (unicode -> int -> [bytes]) _get_passes: A function for getting - passes. + :ivar _get_passes: A function for getting passes. - :ivar set[int] in_use: All of the passes given out without a confirmed + :ivar in_use: All of the passes given out without a confirmed terminal state. - :ivar dict[int, unicode] invalid: All of the passes given out and returned - using ``IPassGroup.invalid`` mapped to the reason given. + :ivar invalid: All of the passes given out and returned using + ``IPassGroup.invalid`` mapped to the reason given. - :ivar set[int] spent: All of the passes given out and returned via + :ivar spent: All of the passes given out and returned via ``IPassGroup.mark_spent``. - :ivar set[int] issued: All of the passes ever given out. + :ivar issued: All of the passes ever given out. - :ivar list[int] returned: A list of passes which were given out but then - returned via ``IPassGroup.reset``. + :ivar returned: A list of passes which were given out but then returned + via ``IPassGroup.reset``. """ - _get_passes = attr.ib() + _get_passes = attr.ib() # type: (bytes, int) -> List[bytes] - returned = attr.ib(default=attr.Factory(list), init=False) - in_use = attr.ib(default=attr.Factory(set), init=False) - invalid = attr.ib(default=attr.Factory(dict), init=False) - spent = attr.ib(default=attr.Factory(set), init=False) - issued = attr.ib(default=attr.Factory(set), init=False) + returned = attr.ib(default=attr.Factory(list), init=False) # type: List[int] + in_use = attr.ib(default=attr.Factory(set), init=False) # type: Set[int] + invalid = attr.ib(default=attr.Factory(dict), init=False) # type: Dict[int, unicode] + spent = attr.ib(default=attr.Factory(set), init=False) # type: Set[int] + issued = attr.ib(default=attr.Factory(set), init=False) # type: Set[int] def get(self, message, num_passes): + # type: (bytes, int) -> PassGroup passes = [] if self.returned: passes.extend(self.returned[:num_passes]) @@ -235,7 +236,7 @@ class _PassFactory(object): passes.extend(self._get_passes(message, num_passes)) self.issued.update(passes) self.in_use.update(passes) - return PassGroup(message, self, zip(passes, passes)) + return PassGroup(message, self, list(zip(passes, passes))) def _clear(self): """ diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 7f29860e6046ce6e4bdab528e844210bc1193329..54306ea102d22e38a8a6029276d96aec2d4a8840 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -47,7 +47,6 @@ if PY2: from base64 import b64encode, urlsafe_b64encode from datetime import datetime, timedelta -from urllib import quote import attr from allmydata.client import config_from_string @@ -70,6 +69,8 @@ from hypothesis.strategies import ( text, tuples, ) +from six.moves.urllib.parse import quote +from six import ensure_binary from twisted.internet.defer import succeed from twisted.internet.task import Clock from twisted.web.test.requesthelper import DummyRequest @@ -554,7 +555,7 @@ def tahoe_configs( def path_setter(config): def set_paths(basedir, portnumfile): - config._basedir = basedir.decode("ascii") + config._basedir = basedir config.portnum_fname = portnumfile return config @@ -757,7 +758,13 @@ def request_paths(): :see: ``requests`` """ - return lists(text().map(lambda x: quote(x.encode("utf-8"), safe=b""))) + def quote_segment(seg): + if PY2: + return quote(seg.encode("utf-8"), safe=b"") + else: + return quote(seg, safe="").encode("utf-8") + + return lists(text().map(quote_segment)) def requests(paths=request_paths()): diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index f559b3e135bb34db7289ce8186bd88dac9fa84ff..471a41a7d5f28feed3a5238bb5c4990fbe43e386 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -48,8 +48,8 @@ if PY2: from datetime import datetime from io import BytesIO -from json import dumps -from urllib import quote +from six.moves.urllib.parse import quote +from six import ensure_binary, ensure_text import attr from allmydata.client import config_from_string @@ -98,6 +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 .. import __version__ as zkapauthorizer_version from .._base64 import urlsafe_b64decode from ..configutil import config_string_from_sections @@ -164,17 +165,17 @@ def is_not_json(bytestring): def not_vouchers(): """ - Builds text strings which are not legal vouchers. + Builds byte strings which are not legal vouchers. """ return one_of( text().filter( lambda t: (not is_urlsafe_base64(t)), - ), + ).map(lambda t: t.encode("utf-8")), vouchers().map( # Turn a valid voucher into a voucher that is invalid only by # containing a character from the base64 alphabet in place of one # from the urlsafe-base64 alphabet. - lambda voucher: "/" + lambda voucher: b"/" + voucher[1:], ), ) @@ -201,7 +202,7 @@ def invalid_bodies(): # The wrong key but the right kind of value. fixed_dictionaries( { - "some-key": vouchers(), + "some-key": vouchers().map(ensure_text), } ).map(dumps), # The right key but the wrong kind of value. @@ -209,7 +210,7 @@ def invalid_bodies(): { "voucher": one_of( integers(), - not_vouchers(), + not_vouchers().map(ensure_text), ), } ).map(dumps), @@ -270,7 +271,7 @@ def authorized_request(api_auth_token, agent, method, uri, headers=None, data=No headers = Headers(headers) headers.setRawHeaders( "authorization", - [b"tahoe-lafs {}".format(api_auth_token)], + [b"tahoe-lafs " + api_auth_token], ) return agent.request( method, @@ -294,8 +295,8 @@ def get_config_with_api_token(tempdir, get_config, api_auth_token): :param bytes api_auth_token: The HTTP API authorization token to write to the node directory. """ - basedir = tempdir.join(b"tahoe") - config = get_config(basedir, b"tub.port") + basedir = tempdir.join(u"tahoe") + config = get_config(basedir, u"tub.port") add_api_token_to_config( basedir, config, @@ -309,9 +310,9 @@ def add_api_token_to_config(basedir, config, api_auth_token): Create a private directory beneath the given base directory, point the given config at it, and write the given API auth token to it. """ - FilePath(basedir).child(b"private").makedirs() + FilePath(basedir).child(u"private").makedirs() config._basedir = basedir - config.write_private_config(b"api_auth_token", api_auth_token) + config.write_private_config(u"api_auth_token", api_auth_token) class FromConfigurationTests(TestCase): @@ -326,7 +327,7 @@ class FromConfigurationTests(TestCase): the public keys found in the configuration. """ tempdir = self.useFixture(TempDir()) - config = get_config(tempdir.join(b"tahoe"), b"tub.port") + config = get_config(tempdir.join("tahoe"), "tub.port") allowed_public_keys = get_configured_allowed_public_keys(config) # root_from_config is just an easier way to call from_configuration @@ -366,7 +367,7 @@ class GetTokenCountTests(TestCase): ] ) node_config = config_from_string( - self.useFixture(TempDir()).join(b"tahoe"), + self.useFixture(TempDir()).join("tahoe"), "tub.port", config_text.encode("utf-8"), ) @@ -391,7 +392,7 @@ class ResourceTests(TestCase): receives a 401 response. """ tempdir = self.useFixture(TempDir()) - config = get_config(tempdir.join(b"tahoe"), b"tub.port") + config = get_config(tempdir.join("tahoe"), "tub.port") root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) requesting = agent.request( @@ -429,7 +430,7 @@ class ResourceTests(TestCase): ``from_configuration``. """ tempdir = self.useFixture(TempDir()) - config = get_config(tempdir.join(b"tahoe"), b"tub.port") + config = get_config(tempdir.join("tahoe"), "tub.port") root = root_from_config(config, datetime.now) self.assertThat( getChildForRequest(root, request), @@ -545,7 +546,7 @@ class UnblindedTokenTests(TestCase): self.assertThat( stored_tokens, - Equals(list(token.unblinded_token for token in unblinded_tokens)), + Equals(list(token.unblinded_token.decode("ascii") for token in unblinded_tokens)), ) @given( @@ -632,7 +633,7 @@ class UnblindedTokenTests(TestCase): api_auth_token, agent, b"GET", - b"http://127.0.0.1/unblinded-token?limit={}".format(limit), + u"http://127.0.0.1/unblinded-token?limit={}".format(limit).encode("utf-8"), ) self.addDetail( "requesting result", @@ -685,9 +686,9 @@ class UnblindedTokenTests(TestCase): api_auth_token, agent, b"GET", - b"http://127.0.0.1/unblinded-token?position={}".format( + u"http://127.0.0.1/unblinded-token?position={}".format( quote(position.encode("utf-8"), safe=b""), - ), + ).encode("utf-8"), ) self.addDetail( "requesting result", @@ -1033,11 +1034,11 @@ class VoucherTests(TestCase): ) root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) - url = "http://127.0.0.1/voucher/{}".format( + url = u"http://127.0.0.1/voucher/{}".format( quote( - not_voucher.encode("utf-8"), + not_voucher, safe=b"", - ).decode("utf-8"), + ), ).encode("ascii") requesting = authorized_request( api_auth_token, @@ -1070,7 +1071,7 @@ class VoucherTests(TestCase): api_auth_token, agent, b"GET", - "http://127.0.0.1/voucher/{}".format(voucher).encode("ascii"), + b"http://127.0.0.1/voucher/" + voucher, ) self.assertThat( requesting, @@ -1251,7 +1252,7 @@ class VoucherTests(TestCase): to be returned by the ``GET``. """ add_api_token_to_config( - self.useFixture(TempDir()).join(b"tahoe"), + self.useFixture(TempDir()).join("tahoe"), config, api_auth_token, ) @@ -1275,11 +1276,11 @@ class VoucherTests(TestCase): api_auth_token, agent, b"GET", - "http://127.0.0.1/voucher/{}".format( + u"http://127.0.0.1/voucher/{}".format( quote( - voucher.encode("utf-8"), - safe=b"", - ).decode("utf-8"), + voucher, + safe=u"", + ), ).encode("ascii"), ) self.assertThat( @@ -1379,7 +1380,7 @@ class VoucherTests(TestCase): # times between setUp and tearDown. Avoid re-using the same # temporary directory for every Hypothesis iteration because this # test leaves state behind that invalidates future iterations. - self.useFixture(TempDir()).join(b"tahoe"), + self.useFixture(TempDir()).join("tahoe"), config, api_auth_token, ) @@ -1431,9 +1432,10 @@ def mime_types(blacklist=None): """ Build MIME types as b"major/minor" byte strings. - :param set|None blacklist: If not ``None``, MIME types to exclude from the - result. + :param blacklist: If not ``None``, MIME types to + exclude from the result. """ + # type: Optional[Set[unicode]] -> Strategy[unicode] if blacklist is None: blacklist = set() return ( @@ -1442,7 +1444,7 @@ def mime_types(blacklist=None): text(), ) .map( - b"/".join, + u"/".join, ) .filter( lambda content_type: content_type not in blacklist, @@ -1482,7 +1484,7 @@ def bad_calculate_price_requests(): bad_headers = fixed_dictionaries( { b"content-type": mime_types(blacklist={b"application/json"},).map( - lambda content_type: [content_type], + lambda content_type: [content_type.encode("utf-8")], ), } ) @@ -1645,7 +1647,7 @@ class CalculatePriceTests(TestCase): (encoding_params, min_time_remaining), config = encoding_params_and_config shares_needed, shares_happy, shares_total = encoding_params add_api_token_to_config( - self.useFixture(TempDir()).join(b"tahoe"), + self.useFixture(TempDir()).join("tahoe"), config, api_auth_token, ) @@ -1699,7 +1701,7 @@ def json_content(response): def ok_response(headers=None): - return match_response(OK, headers) + return match_response(OK, headers, phrase=Equals(b"OK")) def not_found_response(headers=None): @@ -1710,12 +1712,13 @@ def bad_request_response(headers=None): return match_response(BAD_REQUEST, headers) -def match_response(code, headers): +def match_response(code, headers, phrase=Always()): if headers is None: headers = Always() return _MatchResponse( code=Equals(code), headers=headers, + phrase=phrase, ) @@ -1723,6 +1726,7 @@ def match_response(code, headers): class _MatchResponse(object): code = attr.ib() headers = attr.ib() + phrase = attr.ib() _details = attr.ib(default=attr.Factory(dict)) def match(self, response): @@ -1735,6 +1739,7 @@ class _MatchResponse(object): return MatchesStructure( code=self.code, headers=self.headers, + phrase=self.phrase, ).match(response) def get_details(self): diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 4742a1462882b9f49639931d9b531be84a08ebf6..4b4f399b1ff7965a43b700d066bce888a719cd0d 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -20,9 +20,10 @@ from __future__ import absolute_import, division from datetime import datetime, timedelta from functools import partial -from json import dumps, loads +from json import loads import attr +from six import ensure_text from challenge_bypass_ristretto import ( BatchDLEQProof, BlindedToken, @@ -49,7 +50,7 @@ from testtools.matchers import ( ) from testtools.twistedsupport import failed, has_no_result, succeeded from treq.testing import StubTreq -from twisted.internet.defer import fail +from twisted.internet.defer import fail, succeed from twisted.internet.task import Clock from twisted.python.url import URL from twisted.web.http import BAD_REQUEST, INTERNAL_SERVER_ERROR, UNSUPPORTED_MEDIA_TYPE @@ -58,6 +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 ..controller import ( AlreadySpent, DoubleSpendRedeemer, @@ -794,7 +796,7 @@ class RistrettoRedeemerTests(TestCase): HasLength(num_tokens), ), public_key=Equals( - PublicKey.from_signing_key(signing_key).encode_base64(), + ensure_text(PublicKey.from_signing_key(signing_key).encode_base64()), ), ), ), @@ -1156,9 +1158,9 @@ class RistrettoRedemption(Resource): return dumps( { u"success": True, - u"public-key": self.public_key.encode_base64(), - u"signatures": marshaled_signed_tokens, - u"proof": marshaled_proof, + u"public-key": ensure_text(self.public_key.encode_base64()), + u"signatures": list(ensure_text(t) for t in marshaled_signed_tokens), + u"proof": ensure_text(marshaled_proof), } ) @@ -1250,7 +1252,7 @@ def check_redemption_request(request): Verify that the given request conforms to the redemption server's public interface. """ - if request.requestHeaders.getRawHeaders(b"content-type") != ["application/json"]: + if request.requestHeaders.getRawHeaders(b"content-type") != [b"application/json"]: return bad_content_type(request) p = request.content.tell() @@ -1293,10 +1295,15 @@ def bad_content_type(request): ).render(request) -class BracketTests(TestCase): +class _BracketTestMixin: """ Tests for ``bracket``. """ + def wrap_success(self, result): + raise NotImplemented() + + def wrap_failure(self, result): + raise NotImplemented() def test_success(self): """ @@ -1309,7 +1316,7 @@ class BracketTests(TestCase): def between(): actions.append("between") - return result + return self.wrap_success(result) last = partial(actions.append, "last") self.assertThat( @@ -1337,7 +1344,7 @@ class BracketTests(TestCase): def between(): actions.append("between") - raise SomeException() + return self.wrap_failure(SomeException()) last = partial(actions.append, "last") self.assertThat( @@ -1349,3 +1356,121 @@ class BracketTests(TestCase): ), ), ) + self.assertThat( + actions, + Equals(["first", "between", "last"]), + ) + + def test_success_with_failing_last(self): + """ + If the ``between`` action succeeds and the ``last`` action fails then + ``bracket`` fails the same way as the ``last`` action. + """ + + class SomeException(Exception): + pass + + actions = [] + first = partial(actions.append, "first") + def between(): + actions.append("between") + return self.wrap_success(None) + + def last(): + actions.append("last") + return self.wrap_failure(SomeException()) + + self.assertThat( + bracket(first, last, between), + failed( + AfterPreprocessing( + lambda failure: failure.value, + IsInstance(SomeException), + ), + ), + ) + self.assertThat( + actions, + Equals(["first", "between", "last"]), + ) + + def test_failure_with_failing_last(self): + """ + If both the ``between`` and ``last`` actions fail then ``bracket`` fails + the same way as the ``last`` action. + """ + + class SomeException(Exception): + pass + + class AnotherException(Exception): + pass + + actions = [] + first = partial(actions.append, "first") + + def between(): + actions.append("between") + return self.wrap_failure(SomeException()) + + def last(): + actions.append("last") + return self.wrap_failure(AnotherException()) + + self.assertThat( + bracket(first, last, between), + failed( + AfterPreprocessing( + lambda failure: failure.value, + IsInstance(AnotherException), + ), + ), + ) + self.assertThat( + actions, + Equals(["first", "between", "last"]), + ) + + def test_first_failure(self): + """ + If the ``first`` action fails then ``bracket`` fails the same way and + runs neither the ``between`` nor ``last`` actions. + """ + + class SomeException(Exception): + pass + + actions = [] + + def first(): + actions.append("first") + return self.wrap_failure(SomeException()) + + between = partial(actions.append, "between") + last = partial(actions.append, "last") + + self.assertThat( + bracket(first, last, between), + failed( + AfterPreprocessing( + lambda failure: failure.value, + IsInstance(SomeException), + ), + ), + ) + self.assertThat( + actions, + Equals(["first"]), + ) + +class BracketTests(_BracketTestMixin, TestCase): + def wrap_success(self, result): + return result + def wrap_failure(self, exception): + raise exception + +class SynchronousDeferredBracketTests(_BracketTestMixin, TestCase): + def wrap_success(self, result): + return succeed(result) + def wrap_failure(self, exception): + return fail(exception) diff --git a/src/_zkapauthorizer/tests/test_foolscap.py b/src/_zkapauthorizer/tests/test_foolscap.py index 3a313b879aa720caf31b20ce97b191d9289e1424..1dee261cd41fc4867b0f7e963a7418ce8d737d4e 100644 --- a/src/_zkapauthorizer/tests/test_foolscap.py +++ b/src/_zkapauthorizer/tests/test_foolscap.py @@ -16,8 +16,36 @@ Tests for Foolscap-related test helpers. """ -from __future__ import absolute_import - +from __future__ import absolute_import, division, print_function, unicode_literals + +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_str from fixtures import Fixture from foolscap.api import Any, RemoteInterface, Violation from foolscap.furl import decode_furl @@ -55,7 +83,7 @@ class IHasSchema(RemoteInterface): def remote_reference(): tub = Tub() tub.setLocation("127.0.0.1:12345") - url = tub.buildURL(b"efgh") + url = tub.buildURL(ensure_str("efgh")) # Ugh ugh ugh. Skip over the extra correctness checking in # RemoteReferenceTracker.__init__ that requires having a broker by passing @@ -85,12 +113,9 @@ class LocalRemoteTests(TestCase): """ self.assertThat( ref.tracker.getURL(), - MatchesAll( - IsInstance(bytes), - AfterPreprocessing( - decode_furl, - Always(), - ), + AfterPreprocessing( + decode_furl, + Always(), ), ) diff --git a/src/_zkapauthorizer/tests/test_lease_maintenance.py b/src/_zkapauthorizer/tests/test_lease_maintenance.py index 02e2230c2ae2b6ad9b4d97e7c9149aeceb928722..15d9ca9765dae9dc21af780a0cee9ff4fa3503a9 100644 --- a/src/_zkapauthorizer/tests/test_lease_maintenance.py +++ b/src/_zkapauthorizer/tests/test_lease_maintenance.py @@ -16,7 +16,34 @@ Tests for ``_zkapauthorizer.lease_maintenance``. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, division, print_function, unicode_literals + +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 datetime import datetime, timedelta @@ -37,6 +64,7 @@ from hypothesis.strategies import ( randoms, sets, ) +from six import ensure_binary from testtools import TestCase from testtools.matchers import ( AfterPreprocessing, @@ -284,8 +312,8 @@ class LeaseMaintenanceServiceTests(TestCase): [maintenance_call] = clock.getDelayedCalls() datetime_now = datetime.utcfromtimestamp(clock.seconds()) - low = datetime_now + mean - (range_ / 2) - high = datetime_now + mean + (range_ / 2) + low = datetime_now + mean - (range_ // 2) + high = datetime_now + mean + (range_ // 2) self.assertThat( datetime.utcfromtimestamp(maintenance_call.getTime()), between(low, high), @@ -313,7 +341,7 @@ class LeaseMaintenanceServiceTests(TestCase): # Figure out the absolute last run time. last_run = datetime_now - since_last_run last_run_path = FilePath(self.useFixture(TempDir()).join("last-run")) - last_run_path.setContent(last_run.isoformat()) + last_run_path.setContent(ensure_binary(last_run.isoformat())) service = lease_maintenance_service( dummy_maintain_leases, @@ -331,14 +359,14 @@ class LeaseMaintenanceServiceTests(TestCase): low = datetime_now + max( timedelta(0), - mean - (range_ / 2) - since_last_run, + mean - (range_ // 2) - since_last_run, ) high = max( # If since_last_run is one microsecond (precision of timedelta) # then the range is indivisible. Avoid putting the expected high # below the expected low. low, - datetime_now + mean + (range_ / 2) - since_last_run, + datetime_now + mean + (range_ // 2) - since_last_run, ) note( diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index 6c0e360cdd0ac810604fcb997fc394f4ee978d5b..0102ee70b46b69d95d1b45d08a522c7d14af1ac5 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -17,7 +17,34 @@ Tests for ``_zkapauthorizer.model``. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals + +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 datetime import datetime, timedelta from errno import EACCES @@ -141,8 +168,8 @@ class VoucherStoreTests(TestCase): """ counter_a = counters[0] counter_b = counters[1] - tokens_a = tokens[: len(tokens) / 2] - tokens_b = tokens[len(tokens) / 2 :] + tokens_a = tokens[: len(tokens) // 2] + tokens_b = tokens[len(tokens) // 2 :] store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store # We only have to get the expected_tokens value (len(tokens)) right on @@ -253,13 +280,13 @@ class VoucherStoreTests(TestCase): then ``VoucherStore.from_node_config`` raises ``StoreOpenError``. """ tempdir = self.useFixture(TempDir()) - nodedir = tempdir.join(b"node") + nodedir = tempdir.join(u"node") # Create the node directory without permission to create the # underlying directory. mkdir(nodedir, 0o500) - config = get_config(nodedir, b"tub.port") + config = get_config(nodedir, u"tub.port") self.assertThat( lambda: VoucherStore.from_node_config( @@ -295,9 +322,9 @@ class VoucherStoreTests(TestCase): ``VoucherStore.from_node_config`` raises ``StoreOpenError``. """ tempdir = self.useFixture(TempDir()) - nodedir = tempdir.join(b"node") + nodedir = tempdir.join(u"node") - config = get_config(nodedir, b"tub.port") + config = get_config(nodedir, u"tub.port") # Create the underlying database file. store = VoucherStore.from_node_config(config, lambda: now) @@ -358,9 +385,9 @@ class VoucherStoreTests(TestCase): :return: A three-tuple of (backed up tokens, extracted tokens, inserted tokens). """ tempdir = self.useFixture(TempDir()) - nodedir = tempdir.join(b"node") + nodedir = tempdir.join(u"node") - config = get_config(nodedir, b"tub.port") + config = get_config(nodedir, u"tub.port") # Create the underlying database file. store = VoucherStore.from_node_config(config, lambda: now) @@ -384,14 +411,14 @@ class VoucherStoreTests(TestCase): while tokens_remaining > 0: to_spend = data.draw(integers(min_value=1, max_value=tokens_remaining)) extracted_tokens.extend( - token.unblinded_token for token in store.get_unblinded_tokens(to_spend) + token.unblinded_token.decode("ascii") for token in store.get_unblinded_tokens(to_spend) ) tokens_remaining -= to_spend return ( backed_up_tokens, extracted_tokens, - list(token.unblinded_token for token in unblinded_tokens), + list(token.unblinded_token.decode("ascii") for token in unblinded_tokens), ) @@ -946,7 +973,7 @@ def store_for_test(testcase, get_config, get_now): :return VoucherStore: A newly created temporary store. """ tempdir = testcase.useFixture(TempDir()) - config = get_config(tempdir.join(b"node"), b"tub.port") + config = get_config(tempdir.join(u"node"), u"tub.port") store = VoucherStore.from_node_config( config, get_now, diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 831f4236734cbce2c2300c612881137bd111709b..0baa11245d08f93ad0ba0d32dfbdde655f0d43a1 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -48,6 +48,7 @@ if PY2: from datetime import timedelta from functools import partial from os import makedirs +import os.path from allmydata.client import config_from_string, create_client_from_config from allmydata.interfaces import ( @@ -66,7 +67,8 @@ from hypothesis import given, settings from hypothesis.strategies import datetimes, just, sampled_from, timedeltas from prometheus_client import Gauge from prometheus_client.parser import text_string_to_metric_families -from StringIO import StringIO +from six.moves import StringIO +from six import ensure_binary from testtools import TestCase from testtools.content import text_content from testtools.matchers import ( @@ -389,8 +391,8 @@ class ClientPluginTests(TestCase): """ tempdir = self.useFixture(TempDir()) node_config = get_config( - tempdir.join(b"node"), - b"tub.port", + tempdir.join(u"node"), + u"tub.port", ) storage_client = storage_server.get_storage_client( @@ -412,8 +414,8 @@ class ClientPluginTests(TestCase): """ tempdir = self.useFixture(TempDir()) node_config = config_from_string( - tempdir.join(b"node"), - b"tub.port", + tempdir.join(u"node"), + u"tub.port", config_text.encode("utf-8"), ) # On Tahoe-LAFS <1.16, the config is written as bytes. @@ -466,8 +468,8 @@ class ClientPluginTests(TestCase): """ tempdir = self.useFixture(TempDir()) node_config = get_config( - tempdir.join(b"node"), - b"tub.port", + tempdir.join(u"node"), + u"tub.port", ) storage_client = storage_server.get_storage_client( @@ -516,8 +518,8 @@ class ClientPluginTests(TestCase): """ tempdir = self.useFixture(TempDir()) node_config = get_config( - tempdir.join(b"node"), - b"tub.port", + tempdir.join(u"node"), + u"tub.port", ) store = VoucherStore.from_node_config(node_config, lambda: now) @@ -548,12 +550,12 @@ class ClientPluginTests(TestCase): # tests, at least until creating a real server doesn't involve so much # complex setup. So avoid using any of the client APIs that make a # remote call ... which is all of them. - pass_group = storage_client._get_passes("request binding message", num_passes) + pass_group = storage_client._get_passes(b"request binding message", num_passes) pass_group.mark_spent() # There should be no unblinded tokens left to extract. self.assertThat( - lambda: storage_client._get_passes("request binding message", 1), + lambda: storage_client._get_passes(b"request binding message", 1), raises(NotEnoughTokens), ) @@ -567,7 +569,7 @@ class ClientPluginTests(TestCase): lambda logged_message: logged_message.message, ContainsDict( { - "message": Equals("request binding message"), + "message": Equals(u"request binding message"), "count": Equals(num_passes), } ), @@ -589,8 +591,8 @@ class ClientResourceTests(TestCase): ``get_client_resource`` returns an object that provides ``IResource``. """ tempdir = self.useFixture(TempDir()) - nodedir = tempdir.join(b"node") - config = get_config(nodedir, b"tub.port") + nodedir = tempdir.join(u"node") + config = get_config(nodedir, u"tub.port") self.assertThat( storage_server.get_client_resource( config, @@ -652,20 +654,28 @@ class LeaseMaintenanceServiceTests(TestCase): file, ``False`` otherwise. """ tempdir = self.useFixture(TempDir()) - nodedir = tempdir.join(b"node") - privatedir = tempdir.join(b"node", b"private") + nodedir = tempdir.join(u"node") + privatedir = tempdir.join(u"node", u"private") makedirs(privatedir) - config = get_config(nodedir, b"tub.port") + config = get_config(nodedir, u"tub.port") + + # In Tahoe-LAFS 1.17 write_private_config is broken. It mixes bytes + # and unicode in an os.path.join() call that always fails with a + # TypeError. + def write_private_config(name, value): + privname = os.path.join(config._basedir, u"private", name) + with open(privname, "wb") as f: + f.write(value) if servers_yaml is not None: # Provide it a statically configured server to connect to. - config.write_private_config( - b"servers.yaml", + write_private_config( + u"servers.yaml", servers_yaml, ) if rootcap: config.write_private_config( - b"rootcap", + u"rootcap", b"dddddddd", ) @@ -780,7 +790,7 @@ class LoadSigningKeyTests(TestCase): :param bytes key: A base64-encoded Ristretto signing key. """ - p = FilePath(self.useFixture(TempDir()).join(b"key")) + p = FilePath(self.useFixture(TempDir()).join(u"key")) p.setContent(key_bytes) key = load_signing_key(p) self.assertThat(key, IsInstance(SigningKey)) diff --git a/src/_zkapauthorizer/tests/test_private.py b/src/_zkapauthorizer/tests/test_private.py index 568cc1eb1baf613c17ee3336874b08b9aed4b17c..effc6dc73021ae53fa56c612896c109cb1f4d89c 100644 --- a/src/_zkapauthorizer/tests/test_private.py +++ b/src/_zkapauthorizer/tests/test_private.py @@ -10,6 +10,32 @@ Tests for ``_zkapauthorizer.private``. """ from __future__ import absolute_import, division, print_function, unicode_literals +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 allmydata.test.web.matchers import has_response_code from testtools import TestCase @@ -39,7 +65,9 @@ class PrivacyTests(TestCase): def _authorization(self, scheme, value): return Headers( { - "authorization": ["{} {}".format(scheme, value)], + u"authorization": [ + u"{} {}".format(scheme.decode("ascii"), value.decode("ascii")), + ], } ) @@ -60,7 +88,7 @@ class PrivacyTests(TestCase): self.assertThat( self.client.head( b"http:///foo/bar", - headers=self._authorization("basic", self.token), + headers=self._authorization(b"basic", self.token), ), succeeded(has_response_code(Equals(UNAUTHORIZED))), ) @@ -73,7 +101,7 @@ class PrivacyTests(TestCase): self.assertThat( self.client.head( b"http:///foo/bar", - headers=self._authorization(SCHEME, "foo bar"), + headers=self._authorization(SCHEME, b"foo bar"), ), succeeded(has_response_code(Equals(UNAUTHORIZED))), ) diff --git a/src/_zkapauthorizer/tests/test_spending.py b/src/_zkapauthorizer/tests/test_spending.py index 6833e0895e5d8f4ec38310eec5c25456ccc8e601..55f9aa9276eff0a3ab3ec3062cd743637a264743 100644 --- a/src/_zkapauthorizer/tests/test_spending.py +++ b/src/_zkapauthorizer/tests/test_spending.py @@ -16,6 +16,35 @@ Tests for ``_zkapauthorizer.spending``. """ +from __future__ import absolute_import, division, print_function, unicode_literals + +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 hypothesis import given from hypothesis.strategies import data, integers, randoms from testtools import TestCase @@ -62,7 +91,7 @@ class PassGroupTests(TestCase): store=configless.store, ) - group = pass_factory.get(u"message", num_passes) + group = pass_factory.get(b"message", num_passes) self.assertThat( group, MatchesAll( @@ -97,7 +126,7 @@ class PassGroupTests(TestCase): # Figure out some subset, maybe empty, of passes from the group that # we will try to operate on. group_size = data.draw(integers(min_value=0, max_value=num_passes)) - indices = range(num_passes) + indices = list(range(num_passes)) random.shuffle(indices) spent_indices = indices[:group_size] @@ -106,7 +135,7 @@ class PassGroupTests(TestCase): tokens_to_passes=configless.redeemer.tokens_to_passes, store=configless.store, ) - group = pass_factory.get(u"message", num_passes) + group = pass_factory.get(b"message", num_passes) spent, rest = group.split(spent_indices) operation(spent) diff --git a/src/_zkapauthorizer/tests/test_storage_client.py b/src/_zkapauthorizer/tests/test_storage_client.py index 1075884f570d7b15b220f5d33843c5105ecb522f..8da2975c2fb89abb482d4eaee04cbb99be846b0e 100644 --- a/src/_zkapauthorizer/tests/test_storage_client.py +++ b/src/_zkapauthorizer/tests/test_storage_client.py @@ -16,7 +16,34 @@ Tests for ``_zkapauthorizer._storage_client``. """ -from __future__ import division +from __future__ import absolute_import, division, print_function, unicode_literals + +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 functools import partial @@ -180,7 +207,7 @@ class CallWithPassesTests(TestCase): call_with_passes( lambda group: succeed(result), num_passes, - partial(pass_factory(integer_passes(num_passes)).get, u"message"), + partial(pass_factory(integer_passes(num_passes)).get, b"message"), ), succeeded(Is(result)), ) @@ -197,7 +224,7 @@ class CallWithPassesTests(TestCase): call_with_passes( lambda group: fail(result), num_passes, - partial(pass_factory(integer_passes(num_passes)).get, u"message"), + partial(pass_factory(integer_passes(num_passes)).get, b"message"), ), failed( AfterPreprocessing( @@ -220,7 +247,7 @@ class CallWithPassesTests(TestCase): call_with_passes( lambda group: succeed(group.passes), num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), succeeded( Equals( @@ -241,7 +268,7 @@ class CallWithPassesTests(TestCase): call_with_passes( lambda group: None, num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), succeeded(Always()), ) @@ -261,7 +288,7 @@ class CallWithPassesTests(TestCase): call_with_passes( lambda group: fail(Exception("Anything")), num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), failed(Always()), ) @@ -301,7 +328,7 @@ class CallWithPassesTests(TestCase): call_with_passes( reject_even_pass_values, num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), succeeded(Always()), ) @@ -342,7 +369,7 @@ class CallWithPassesTests(TestCase): call_with_passes( reject_passes, num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), failed( AfterPreprocessing( @@ -406,7 +433,7 @@ class CallWithPassesTests(TestCase): # out of passes no matter how many we start with. reject_half_passes, num_passes, - partial(passes.get, u"message"), + partial(passes.get, b"message"), ), failed( AfterPreprocessing( @@ -458,7 +485,7 @@ class PassFactoryTests(TestCase): ``IPassGroup.reset`` makes passes available to be returned by ``IPassGroup.get`` again. """ - message = u"message" + message = b"message" min_passes = min(num_passes_a, num_passes_b) max_passes = max(num_passes_a, num_passes_b) @@ -486,7 +513,7 @@ class PassFactoryTests(TestCase): :param (IPassGroup -> None) invalid_op: Some follow-up operation to perform with the pass group and to assert raises an exception. """ - message = u"message" + message = b"message" factory = pass_factory(integer_passes(num_passes)) group = factory.get(message, num_passes) setup_op(group) diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py index 89654633b6b9176ec98fb71492571b0467eb42b7..ffae2fff58e545382b3ca2b701ce2f1cd10ac993 100644 --- a/src/_zkapauthorizer/tests/test_storage_protocol.py +++ b/src/_zkapauthorizer/tests/test_storage_protocol.py @@ -235,14 +235,14 @@ class ShareTests(TestCase): # Make some passes with a key untrusted by the server. bad_passes = get_passes( - allocate_buckets_message(storage_index), + allocate_buckets_message(storage_index).encode("utf-8"), len(bad_pass_indexes), random_signing_key(), ) # Make some passes with a key trusted by the server. good_passes = get_passes( - allocate_buckets_message(storage_index), + allocate_buckets_message(storage_index).encode("utf-8"), num_passes - len(bad_passes), self.signing_key, ) diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py index 7e334d79e6f8fca00f16170d46dca9b2bd1f1513..b40e82633a4fa45772bd48a34e607c0614807289 100644 --- a/src/_zkapauthorizer/tests/test_storage_server.py +++ b/src/_zkapauthorizer/tests/test_storage_server.py @@ -81,7 +81,7 @@ class ValidationResultTests(TestCase): ``validate_passes`` returns a ``_ValidationResult`` instance which describes the valid and invalid passes. """ - message = u"hello world" + message = b"hello world" valid_passes = get_passes( message, valid_count, @@ -136,7 +136,7 @@ class ValidationResultTests(TestCase): AfterPreprocessing( str, Equals( - "MorePassesRequired(valid_count=4, required_count=10, signature_check_failed=frozenset([4]))" + "MorePassesRequired(valid_count=4, required_count=10, signature_check_failed={})".format(str(frozenset([4]))), ), ), ), @@ -243,7 +243,7 @@ class PassValidationTests(TestCase): renew_secret = b"x" * 32 cancel_secret = b"y" * 32 valid_passes = get_passes( - allocate_buckets_message(storage_index), + allocate_buckets_message(storage_index).encode("utf-8"), required_passes - 1, self.signing_key, ) @@ -345,7 +345,7 @@ class PassValidationTests(TestCase): ) valid_passes = get_passes( - slot_testv_and_readv_and_writev_message(storage_index), + slot_testv_and_readv_and_writev_message(storage_index).encode("utf-8"), required_pass_count, self.signing_key, ) @@ -481,7 +481,7 @@ class PassValidationTests(TestCase): tw_vectors, ) valid_passes = get_passes( - slot_testv_and_readv_and_writev_message(storage_index), + slot_testv_and_readv_and_writev_message(storage_index).encode("utf-8"), required_pass_count, self.signing_key, ) @@ -550,7 +550,7 @@ class PassValidationTests(TestCase): # Attempt the lease operation with one fewer pass than is required. passes = get_passes( - add_lease_message(storage_index), + add_lease_message(storage_index).encode("utf-8"), required_count - 1, self.signing_key, ) @@ -614,7 +614,7 @@ class PassValidationTests(TestCase): tw_vectors, ) valid_passes = get_passes( - slot_testv_and_readv_and_writev_message(slot), + slot_testv_and_readv_and_writev_message(slot).encode("utf-8"), required_pass_count, self.signing_key, ) @@ -678,7 +678,7 @@ class PassValidationTests(TestCase): tw_vectors, ) valid_passes = get_passes( - slot_testv_and_readv_and_writev_message(storage_index), + slot_testv_and_readv_and_writev_message(storage_index).encode("utf-8"), num_passes, self.signing_key, ) @@ -739,7 +739,7 @@ class PassValidationTests(TestCase): tw_vectors, ) valid_passes = get_passes( - slot_testv_and_readv_and_writev_message(storage_index), + slot_testv_and_readv_and_writev_message(storage_index).encode("utf-8"), num_passes, self.signing_key, ) @@ -820,7 +820,7 @@ class PassValidationTests(TestCase): [size] * len(new_sharenums - existing_sharenums), ) valid_passes = get_passes( - allocate_buckets_message(storage_index), + allocate_buckets_message(storage_index).encode("utf-8"), num_passes, self.signing_key, ) @@ -887,7 +887,7 @@ class PassValidationTests(TestCase): self.storage_server._pass_value, [allocated_size] * len(sharenums) ) valid_passes = get_passes( - add_lease_message(storage_index), + add_lease_message(storage_index).encode("utf-8"), num_passes, self.signing_key, ) @@ -948,7 +948,7 @@ class PassValidationTests(TestCase): self.storage_server._pass_value, [allocated_size] * len(sharenums) ) valid_passes = get_passes( - add_lease_message(storage_index), + add_lease_message(storage_index).encode("utf-8"), num_passes, self.signing_key, ) diff --git a/src/_zkapauthorizer/tests/test_strategies.py b/src/_zkapauthorizer/tests/test_strategies.py index b046450cc8289561a141b1e1e62b3979d4638692..da9757050a89655d1545218b150ab69b3c73fdc8 100644 --- a/src/_zkapauthorizer/tests/test_strategies.py +++ b/src/_zkapauthorizer/tests/test_strategies.py @@ -16,7 +16,34 @@ Tests for our custom Hypothesis strategies. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals + +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 allmydata.client import config_from_string from fixtures import TempDir @@ -49,7 +76,7 @@ class TahoeConfigsTests(TestCase): ) note(config_text) config_from_string( - tempdir.join(b"tahoe.ini"), - b"tub.port", + tempdir.join(u"tahoe.ini"), + u"tub.port", config_text.encode("utf-8"), )