From bea4cf18a0d7d91dece9fb4a45bb39c5b41b8e9d Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 11:19:29 -0500 Subject: [PATCH 01/28] News file. --- newsfragments/3843.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3843.minor diff --git a/newsfragments/3843.minor b/newsfragments/3843.minor new file mode 100644 index 000000000..e69de29bb From e7a5d14c0e8c0077880e2a9ffbd1e3db3738dd93 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 11:25:10 -0500 Subject: [PATCH 02/28] New requirements. --- nix/tahoe-lafs.nix | 2 +- setup.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nix/tahoe-lafs.nix b/nix/tahoe-lafs.nix index e42afc57f..f691677f6 100644 --- a/nix/tahoe-lafs.nix +++ b/nix/tahoe-lafs.nix @@ -4,7 +4,7 @@ , setuptools, setuptoolsTrial, pyasn1, zope_interface , service-identity, pyyaml, magic-wormhole, treq, appdirs , beautifulsoup4, eliot, autobahn, cryptography, netifaces -, html5lib, pyutil, distro, configparser +, html5lib, pyutil, distro, configparser, klein, treq }: python.pkgs.buildPythonPackage rec { # Most of the time this is not exactly the release version (eg 1.16.0). diff --git a/setup.py b/setup.py index 8c6396937..3d9f5a509 100644 --- a/setup.py +++ b/setup.py @@ -140,6 +140,10 @@ install_requires = [ # For the RangeMap datastructure. "collections-extended", + + # HTTP server and client + "klein", + "treq", ] setup_requires = [ @@ -397,7 +401,6 @@ setup(name="tahoe-lafs", # also set in __init__.py # Python 2.7. "decorator < 5", "hypothesis >= 3.6.1", - "treq", "towncrier", "testtools", "fixtures", From 777d630f481e3010c399d0cc2e872bacd572e700 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 12:00:07 -0500 Subject: [PATCH 03/28] Another dependency. --- nix/tahoe-lafs.nix | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/tahoe-lafs.nix b/nix/tahoe-lafs.nix index f691677f6..a6a8a69ec 100644 --- a/nix/tahoe-lafs.nix +++ b/nix/tahoe-lafs.nix @@ -4,7 +4,7 @@ , setuptools, setuptoolsTrial, pyasn1, zope_interface , service-identity, pyyaml, magic-wormhole, treq, appdirs , beautifulsoup4, eliot, autobahn, cryptography, netifaces -, html5lib, pyutil, distro, configparser, klein, treq +, html5lib, pyutil, distro, configparser, klein, treq, cbor2 }: python.pkgs.buildPythonPackage rec { # Most of the time this is not exactly the release version (eg 1.16.0). diff --git a/setup.py b/setup.py index 3d9f5a509..7e7a955c6 100644 --- a/setup.py +++ b/setup.py @@ -144,6 +144,7 @@ install_requires = [ # HTTP server and client "klein", "treq", + "cbor2" ] setup_requires = [ From a32c6be978f0c857ee0465cf123b56058178a21e Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 12:02:58 -0500 Subject: [PATCH 04/28] A sketch of what the HTTP server will look like. --- src/allmydata/storage/http_server.py | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/allmydata/storage/http_server.py diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py new file mode 100644 index 000000000..87edda999 --- /dev/null +++ b/src/allmydata/storage/http_server.py @@ -0,0 +1,66 @@ +""" +HTTP server for storage. +""" + +from functools import wraps + +from klein import Klein +from twisted.web import http + +# Make sure to use pure Python versions: +from cbor2.encoder import dumps +from cbor2.decoder import loads + +from .server import StorageServer + + +def _authorization_decorator(f): + """ + Check the ``Authorization`` header, and (TODO: in later revision of code) + extract ``X-Tahoe-Authorization`` headers and pass them in. + """ + + @wraps(f) + def route(self, request, *args, **kwargs): + if request.headers["Authorization"] != self._swissnum: + request.setResponseCode(http.NOT_ALLOWED) + return b"" + # authorization = request.headers.getRawHeaders("X-Tahoe-Authorization", []) + # For now, just a placeholder: + authorization = None + return f(self, request, authorization, *args, **kwargs) + + +def _route(app, *route_args, **route_kwargs): + """ + Like Klein's @route, but with additional support for checking the + ``Authorization`` header as well as ``X-Tahoe-Authorization`` headers. The + latter will (TODO: in later revision of code) get passed in as second + argument to wrapped functions. + """ + + def decorator(f): + @app.route(*route_args, **route_kwargs) + @_authorization_decorator + def handle_route(*args, **kwargs): + return f(*args, **kwargs) + + return handle_route + + return decorator + + +class HTTPServer(object): + """ + A HTTP interface to the storage server. + """ + + _app = Klein() + + def __init__(self, storage_server: StorageServer, swissnum): + self._storage_server = storage_server + self._swissnum = swissnum + + @_route(_app, "/v1/version", methods=["GET"]) + def version(self, request, authorization): + return dumps(self._storage_server.remote_get_version()) From ddd2780bd243436d3630fdcee8b0340480736e27 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 12:51:52 -0500 Subject: [PATCH 05/28] First sketch of HTTP client. --- src/allmydata/storage/http_client.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/allmydata/storage/http_client.py diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py new file mode 100644 index 000000000..ca80b704e --- /dev/null +++ b/src/allmydata/storage/http_client.py @@ -0,0 +1,36 @@ +""" +HTTP client that talks to the HTTP storage server. +""" + +# Make sure to import Python version: +from cbor2.encoder import loads +from cbor2.decoder import loads + +from twisted.internet.defer import inlineCallbacks, returnValue +from hyperlink import DecodedURL +import treq + + +def _decode_cbor(response): + """Given HTTP response, return decoded CBOR body.""" + return treq.content(response).addCallback(loads) + + +class StorageClient(object): + """ + HTTP client that talks to the HTTP storage server. + """ + + def __init__(self, url: DecodedURL, swissnum, treq=treq): + self._base_url = url + self._swissnum = swissnum + self._treq = treq + + @inlineCallbacks + def get_version(self): + """ + Return the version metadata for the server. + """ + url = self._base_url.child("v1", "version") + response = _decode_cbor((yield self._treq.get(url))) + returnValue(response) From 12cbf8a90109548aaba570d977863bacc2e8fdad Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 13:03:53 -0500 Subject: [PATCH 06/28] First sketch of HTTP testing infrastructure. --- src/allmydata/test/test_storage_http.py | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/allmydata/test/test_storage_http.py diff --git a/src/allmydata/test/test_storage_http.py b/src/allmydata/test/test_storage_http.py new file mode 100644 index 000000000..589cfdddf --- /dev/null +++ b/src/allmydata/test/test_storage_http.py @@ -0,0 +1,38 @@ +""" +Tests for HTTP storage client + server. +""" + +from twisted.trial.unittest import TestCase +from twisted.internet.defer import inlineCallbacks + +from treq.testing import StubTreq +from hyperlink import DecodedURL + +from ..storage.server import StorageServer +from ..storage.http_server import HTTPServer +from ..storage.http_client import StorageClient + + +class HTTPTests(TestCase): + """ + Tests of HTTP client talking to the HTTP server. + """ + + def setUp(self): + self.storage_server = StorageServer(self.mktemp(), b"\x00" * 20) + # TODO what should the swissnum _actually_ be? + self._http_server = HTTPServer(self._storage_server, b"abcd") + self.client = StorageClient( + DecodedURL.from_text("http://example.com"), + b"abcd", + treq=StubTreq(self._http_server.get_resource()), + ) + + @inlineCallbacks + def test_version(self): + """ + The client can return the version. + """ + version = yield self.client.get_version() + expected_version = self.storage_server.remote_get_version() + self.assertEqual(version, expected_version) From c101dd4dc9e33190da63daedd1963a1fb0e9f7cf Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 12 Nov 2021 13:13:19 -0500 Subject: [PATCH 07/28] Closer to first passing test. --- src/allmydata/storage/http_client.py | 20 +++++++++++++------- src/allmydata/storage/http_server.py | 20 +++++++++++++++----- src/allmydata/test/test_storage_http.py | 2 +- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index ca80b704e..e593fd379 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -2,18 +2,23 @@ HTTP client that talks to the HTTP storage server. """ -# Make sure to import Python version: -from cbor2.encoder import loads -from cbor2.decoder import loads +# TODO Make sure to import Python version? +from cbor2 import loads, dumps -from twisted.internet.defer import inlineCallbacks, returnValue +from twisted.internet.defer import inlineCallbacks, returnValue, fail from hyperlink import DecodedURL import treq +class ClientException(Exception): + """An unexpected error.""" + + def _decode_cbor(response): """Given HTTP response, return decoded CBOR body.""" - return treq.content(response).addCallback(loads) + if response.code > 199 and response.code < 300: + return treq.content(response).addCallback(loads) + return fail(ClientException(response.code, response.phrase)) class StorageClient(object): @@ -32,5 +37,6 @@ class StorageClient(object): Return the version metadata for the server. """ url = self._base_url.child("v1", "version") - response = _decode_cbor((yield self._treq.get(url))) - returnValue(response) + response = yield self._treq.get(url) + decoded_response = yield _decode_cbor(response) + returnValue(decoded_response) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 87edda999..b862fe7b1 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -7,9 +7,8 @@ from functools import wraps from klein import Klein from twisted.web import http -# Make sure to use pure Python versions: -from cbor2.encoder import dumps -from cbor2.decoder import loads +# TODO Make sure to use pure Python versions? +from cbor2 import loads, dumps from .server import StorageServer @@ -22,14 +21,19 @@ def _authorization_decorator(f): @wraps(f) def route(self, request, *args, **kwargs): - if request.headers["Authorization"] != self._swissnum: + if ( + request.requestHeaders.getRawHeaders("Authorization", [None])[0] + != self._swissnum + ): request.setResponseCode(http.NOT_ALLOWED) return b"" - # authorization = request.headers.getRawHeaders("X-Tahoe-Authorization", []) + # authorization = request.requestHeaders.getRawHeaders("X-Tahoe-Authorization", []) # For now, just a placeholder: authorization = None return f(self, request, authorization, *args, **kwargs) + return route + def _route(app, *route_args, **route_kwargs): """ @@ -53,6 +57,8 @@ def _route(app, *route_args, **route_kwargs): class HTTPServer(object): """ A HTTP interface to the storage server. + + TODO returning CBOR should set CBOR content-type """ _app = Klein() @@ -61,6 +67,10 @@ class HTTPServer(object): self._storage_server = storage_server self._swissnum = swissnum + def get_resource(self): + """Return twisted.web Resource for this object.""" + return self._app.resource() + @_route(_app, "/v1/version", methods=["GET"]) def version(self, request, authorization): return dumps(self._storage_server.remote_get_version()) diff --git a/src/allmydata/test/test_storage_http.py b/src/allmydata/test/test_storage_http.py index 589cfdddf..663675f40 100644 --- a/src/allmydata/test/test_storage_http.py +++ b/src/allmydata/test/test_storage_http.py @@ -21,7 +21,7 @@ class HTTPTests(TestCase): def setUp(self): self.storage_server = StorageServer(self.mktemp(), b"\x00" * 20) # TODO what should the swissnum _actually_ be? - self._http_server = HTTPServer(self._storage_server, b"abcd") + self._http_server = HTTPServer(self.storage_server, b"abcd") self.client = StorageClient( DecodedURL.from_text("http://example.com"), b"abcd", From 41ec63f7586124eaaf9ca65bb4d6c4884e16b48f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 10:56:21 -0500 Subject: [PATCH 08/28] Passing first tests. --- src/allmydata/storage/http_client.py | 22 ++++++++++++++++++++-- src/allmydata/storage/http_server.py | 8 ++++---- src/allmydata/test/test_storage_http.py | 19 +++++++++++++++++-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index e593fd379..412bf9cec 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -2,9 +2,13 @@ HTTP client that talks to the HTTP storage server. """ +import base64 + # TODO Make sure to import Python version? from cbor2 import loads, dumps + +from twisted.web.http_headers import Headers from twisted.internet.defer import inlineCallbacks, returnValue, fail from hyperlink import DecodedURL import treq @@ -21,6 +25,11 @@ def _decode_cbor(response): return fail(ClientException(response.code, response.phrase)) +def swissnum_auth_header(swissnum): + """Return value for ``Authentication`` header.""" + return b"Tahoe-LAFS " + base64.encodestring(swissnum).strip() + + class StorageClient(object): """ HTTP client that talks to the HTTP storage server. @@ -31,12 +40,21 @@ class StorageClient(object): self._swissnum = swissnum self._treq = treq + def _get_headers(self): + """Return the basic headers to be used by default.""" + headers = Headers() + headers.addRawHeader( + "Authorization", + swissnum_auth_header(self._swissnum), + ) + return headers + @inlineCallbacks def get_version(self): """ Return the version metadata for the server. """ - url = self._base_url.child("v1", "version") - response = yield self._treq.get(url) + url = self._base_url.click("/v1/version") + response = yield self._treq.get(url, headers=self._get_headers()) decoded_response = yield _decode_cbor(response) returnValue(decoded_response) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index b862fe7b1..2d6308baf 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -11,6 +11,7 @@ from twisted.web import http from cbor2 import loads, dumps from .server import StorageServer +from .http_client import swissnum_auth_header def _authorization_decorator(f): @@ -21,11 +22,10 @@ def _authorization_decorator(f): @wraps(f) def route(self, request, *args, **kwargs): - if ( - request.requestHeaders.getRawHeaders("Authorization", [None])[0] - != self._swissnum + if request.requestHeaders.getRawHeaders("Authorization", [None])[0] != str( + swissnum_auth_header(self._swissnum), "ascii" ): - request.setResponseCode(http.NOT_ALLOWED) + request.setResponseCode(http.UNAUTHORIZED) return b"" # authorization = request.requestHeaders.getRawHeaders("X-Tahoe-Authorization", []) # For now, just a placeholder: diff --git a/src/allmydata/test/test_storage_http.py b/src/allmydata/test/test_storage_http.py index 663675f40..b659a6ace 100644 --- a/src/allmydata/test/test_storage_http.py +++ b/src/allmydata/test/test_storage_http.py @@ -10,7 +10,7 @@ from hyperlink import DecodedURL from ..storage.server import StorageServer from ..storage.http_server import HTTPServer -from ..storage.http_client import StorageClient +from ..storage.http_client import StorageClient, ClientException class HTTPTests(TestCase): @@ -23,11 +23,26 @@ class HTTPTests(TestCase): # TODO what should the swissnum _actually_ be? self._http_server = HTTPServer(self.storage_server, b"abcd") self.client = StorageClient( - DecodedURL.from_text("http://example.com"), + DecodedURL.from_text("http://127.0.0.1"), b"abcd", treq=StubTreq(self._http_server.get_resource()), ) + @inlineCallbacks + def test_bad_authentication(self): + """ + If the wrong swissnum is used, an ``Unauthorized`` response code is + returned. + """ + client = StorageClient( + DecodedURL.from_text("http://127.0.0.1"), + b"something wrong", + treq=StubTreq(self._http_server.get_resource()), + ) + with self.assertRaises(ClientException) as e: + yield client.get_version() + self.assertEqual(e.exception.args[0], 401) + @inlineCallbacks def test_version(self): """ From 671b670154f62cb6c7876c707f254a6c7b3a2f4f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:09:08 -0500 Subject: [PATCH 09/28] Some type annotations. --- src/allmydata/storage/http_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 412bf9cec..8e14d1137 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -25,7 +25,7 @@ def _decode_cbor(response): return fail(ClientException(response.code, response.phrase)) -def swissnum_auth_header(swissnum): +def swissnum_auth_header(swissnum): # type: (bytes) -> bytes """Return value for ``Authentication`` header.""" return b"Tahoe-LAFS " + base64.encodestring(swissnum).strip() @@ -40,7 +40,7 @@ class StorageClient(object): self._swissnum = swissnum self._treq = treq - def _get_headers(self): + def _get_headers(self): # type: () -> Headers """Return the basic headers to be used by default.""" headers = Headers() headers.addRawHeader( From 171d1053ec803f2d2de57f0970fbad049d49f2da Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:09:17 -0500 Subject: [PATCH 10/28] CBOR content-type on responses. --- src/allmydata/storage/http_server.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 2d6308baf..91387c58f 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -57,8 +57,6 @@ def _route(app, *route_args, **route_kwargs): class HTTPServer(object): """ A HTTP interface to the storage server. - - TODO returning CBOR should set CBOR content-type """ _app = Klein() @@ -71,6 +69,12 @@ class HTTPServer(object): """Return twisted.web Resource for this object.""" return self._app.resource() + def _cbor(self, request, data): + """Return CBOR-encoded data.""" + request.setHeader("Content-Type", "application/cbor") + # TODO if data is big, maybe want to use a temporary file eventually... + return dumps(data) + @_route(_app, "/v1/version", methods=["GET"]) def version(self, request, authorization): - return dumps(self._storage_server.remote_get_version()) + return self._cbor(request, self._storage_server.remote_get_version()) From c195f895db7bd3ec7a8618956a71e67152e32df7 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:16:26 -0500 Subject: [PATCH 11/28] Python 2 support. --- src/allmydata/storage/http_client.py | 19 ++++++++++++++++++- src/allmydata/storage/http_server.py | 18 ++++++++++++++++-- src/allmydata/test/test_storage_http.py | 12 ++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 8e14d1137..4a143a60b 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -2,6 +2,21 @@ HTTP client that talks to the HTTP storage server. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 + +if PY2: + # fmt: off + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + # fmt: on +else: + from typing import Union + from treq.testing import StubTreq + import base64 # TODO Make sure to import Python version? @@ -35,7 +50,9 @@ class StorageClient(object): HTTP client that talks to the HTTP storage server. """ - def __init__(self, url: DecodedURL, swissnum, treq=treq): + def __init__( + self, url, swissnum, treq=treq + ): # type: (DecodedURL, bytes, Union[treq,StubTreq]) -> None self._base_url = url self._swissnum = swissnum self._treq = treq diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 91387c58f..373d31e2e 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -2,6 +2,18 @@ HTTP server for storage. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 + +if PY2: + # fmt: off + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + # fmt: on + from functools import wraps from klein import Klein @@ -61,12 +73,14 @@ class HTTPServer(object): _app = Klein() - def __init__(self, storage_server: StorageServer, swissnum): + def __init__( + self, storage_server, swissnum + ): # type: (StorageServer, bytes) -> None self._storage_server = storage_server self._swissnum = swissnum def get_resource(self): - """Return twisted.web Resource for this object.""" + """Return twisted.web ``Resource`` for this object.""" return self._app.resource() def _cbor(self, request, data): diff --git a/src/allmydata/test/test_storage_http.py b/src/allmydata/test/test_storage_http.py index b659a6ace..9ba8adf21 100644 --- a/src/allmydata/test/test_storage_http.py +++ b/src/allmydata/test/test_storage_http.py @@ -2,6 +2,18 @@ Tests for HTTP storage client + server. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 + +if PY2: + # fmt: off + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + # fmt: on + from twisted.trial.unittest import TestCase from twisted.internet.defer import inlineCallbacks From a64778ddb0fb774ea43fa8a3c59be67b84e957ff Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:28:13 -0500 Subject: [PATCH 12/28] Flakes. --- src/allmydata/storage/http_client.py | 2 +- src/allmydata/storage/http_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 4a143a60b..d5ca6caec 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -20,7 +20,7 @@ else: import base64 # TODO Make sure to import Python version? -from cbor2 import loads, dumps +from cbor2 import loads from twisted.web.http_headers import Headers diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 373d31e2e..3baa336fa 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -20,7 +20,7 @@ from klein import Klein from twisted.web import http # TODO Make sure to use pure Python versions? -from cbor2 import loads, dumps +from cbor2 import dumps from .server import StorageServer from .http_client import swissnum_auth_header From e5b5b50602268314e035c89e56b740c745b85c84 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:28:19 -0500 Subject: [PATCH 13/28] Duplicate package. --- nix/tahoe-lafs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tahoe-lafs.nix b/nix/tahoe-lafs.nix index a6a8a69ec..8092dfaa7 100644 --- a/nix/tahoe-lafs.nix +++ b/nix/tahoe-lafs.nix @@ -95,7 +95,7 @@ EOF propagatedBuildInputs = with python.pkgs; [ twisted foolscap zfec appdirs setuptoolsTrial pyasn1 zope_interface - service-identity pyyaml magic-wormhole treq + service-identity pyyaml magic-wormhole eliot autobahn cryptography netifaces setuptools future pyutil distro configparser collections-extended ]; From a1424e90e18ae1dfbed245277120fdf3f0aaedc8 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:34:44 -0500 Subject: [PATCH 14/28] Another duplicate. --- nix/tahoe-lafs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tahoe-lafs.nix b/nix/tahoe-lafs.nix index 8092dfaa7..df12f21d4 100644 --- a/nix/tahoe-lafs.nix +++ b/nix/tahoe-lafs.nix @@ -4,7 +4,7 @@ , setuptools, setuptoolsTrial, pyasn1, zope_interface , service-identity, pyyaml, magic-wormhole, treq, appdirs , beautifulsoup4, eliot, autobahn, cryptography, netifaces -, html5lib, pyutil, distro, configparser, klein, treq, cbor2 +, html5lib, pyutil, distro, configparser, klein, cbor2 }: python.pkgs.buildPythonPackage rec { # Most of the time this is not exactly the release version (eg 1.16.0). From f549488bb508a8377d968d16addb07a98559d8fd Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 16 Nov 2021 11:47:09 -0500 Subject: [PATCH 15/28] Don't use a deprecated API. --- src/allmydata/storage/http_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index d5ca6caec..e1743343d 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -42,7 +42,7 @@ def _decode_cbor(response): def swissnum_auth_header(swissnum): # type: (bytes) -> bytes """Return value for ``Authentication`` header.""" - return b"Tahoe-LAFS " + base64.encodestring(swissnum).strip() + return b"Tahoe-LAFS " + base64.b64encode(swissnum).strip() class StorageClient(object): From 6c514dfda57bfc2ede45719db24acecfbfee3ed1 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Nov 2021 10:33:45 -0500 Subject: [PATCH 16/28] Add klein. --- nix/klein.nix | 18 ++++++++++++++++++ nix/overlays.nix | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 nix/klein.nix diff --git a/nix/klein.nix b/nix/klein.nix new file mode 100644 index 000000000..aa109e3d1 --- /dev/null +++ b/nix/klein.nix @@ -0,0 +1,18 @@ +{ lib, buildPythonPackage, fetchPypi }: +buildPythonPackage rec { + pname = "klein"; + version = "21.8.0"; + + src = fetchPypi { + sha256 = "09i1x5ppan3kqsgclbz8xdnlvzvp3amijbmdzv0kik8p5l5zswxa"; + inherit pname version; + }; + + doCheck = false; + + meta = with lib; { + homepage = https://github.com/twisted/klein; + description = "Nicer web server for Twisted"; + license = licenses.mit; + }; +} diff --git a/nix/overlays.nix b/nix/overlays.nix index fbd0ce3bb..011d8dd6b 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -28,6 +28,9 @@ self: super: { packageOverrides = python-self: python-super: { # collections-extended is not part of nixpkgs at this time. collections-extended = python-super.pythonPackages.callPackage ./collections-extended.nix { }; + + # klein is not in nixpkgs 21.05, at least: + klein = python-super.pythonPackages.callPackage ./klein.nix { }; }; }; } From c921b153f4990e98a32dde1286d3a9c11d5fd2e4 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Nov 2021 10:39:15 -0500 Subject: [PATCH 17/28] A better name for the API. --- src/allmydata/storage/http_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 3baa336fa..327892ecd 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -47,7 +47,7 @@ def _authorization_decorator(f): return route -def _route(app, *route_args, **route_kwargs): +def _authorized_route(app, *route_args, **route_kwargs): """ Like Klein's @route, but with additional support for checking the ``Authorization`` header as well as ``X-Tahoe-Authorization`` headers. The @@ -89,6 +89,6 @@ class HTTPServer(object): # TODO if data is big, maybe want to use a temporary file eventually... return dumps(data) - @_route(_app, "/v1/version", methods=["GET"]) + @_authorized_route(_app, "/v1/version", methods=["GET"]) def version(self, request, authorization): return self._cbor(request, self._storage_server.remote_get_version()) From a593095dc935b6719e266e0bf3a996b39047d9c0 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Nov 2021 10:39:53 -0500 Subject: [PATCH 18/28] Explain why it's a conditional import. --- src/allmydata/storage/http_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index e1743343d..f8a7590aa 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -14,6 +14,8 @@ if PY2: from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 # fmt: on else: + # typing module not available in Python 2, and we only do type checking in + # Python 3 anyway. from typing import Union from treq.testing import StubTreq From 8abc1ad8f4e43a244d0bcead201a133f3cf8b0c1 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Nov 2021 10:44:45 -0500 Subject: [PATCH 19/28] cbor2 for Python 2 on Nix. --- nix/cbor2.nix | 18 ++++++++++++++++++ nix/overlays.nix | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 nix/cbor2.nix diff --git a/nix/cbor2.nix b/nix/cbor2.nix new file mode 100644 index 000000000..02c810e1e --- /dev/null +++ b/nix/cbor2.nix @@ -0,0 +1,18 @@ +{ lib, buildPythonPackage, fetchPypi }: +buildPythonPackage rec { + pname = "cbor2"; + version = "5.2.0"; + + src = fetchPypi { + sha256 = "1mmmncfbsx7cbdalcrsagp9hx7wqfawaz9361gjkmsk3lp6chd5w"; + inherit pname version; + }; + + doCheck = false; + + meta = with lib; { + homepage = https://github.com/agronholm/cbor2; + description = "CBOR encoder/decoder"; + license = licenses.mit; + }; +} diff --git a/nix/overlays.nix b/nix/overlays.nix index 011d8dd6b..5cfab200c 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -21,6 +21,9 @@ self: super: { # collections-extended is not part of nixpkgs at this time. collections-extended = python-super.pythonPackages.callPackage ./collections-extended.nix { }; + + # cbor2 is not part of nixpkgs at this time. + cbor2 = python-super.pythonPackages.callPackage ./cbor2.nix { }; }; }; From 30511ea8502dc04848f9ed3715b5517c51444c96 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Nov 2021 11:39:51 -0500 Subject: [PATCH 20/28] Add more build inputs. --- nix/tahoe-lafs.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/tahoe-lafs.nix b/nix/tahoe-lafs.nix index df12f21d4..59864d36d 100644 --- a/nix/tahoe-lafs.nix +++ b/nix/tahoe-lafs.nix @@ -98,6 +98,7 @@ EOF service-identity pyyaml magic-wormhole eliot autobahn cryptography netifaces setuptools future pyutil distro configparser collections-extended + klein cbor2 treq ]; checkInputs = with python.pkgs; [ From b01478659ea9868164aa9f7b7368f295f2d47921 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:18:18 -0500 Subject: [PATCH 21/28] Apparently I generated wrong hashes. --- nix/cbor2.nix | 2 +- nix/klein.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/cbor2.nix b/nix/cbor2.nix index 02c810e1e..1bd9920e6 100644 --- a/nix/cbor2.nix +++ b/nix/cbor2.nix @@ -4,7 +4,7 @@ buildPythonPackage rec { version = "5.2.0"; src = fetchPypi { - sha256 = "1mmmncfbsx7cbdalcrsagp9hx7wqfawaz9361gjkmsk3lp6chd5w"; + sha256 = "1gwlgjl70vlv35cgkcw3cg7b5qsmws36hs4mmh0l9msgagjs4fm3"; inherit pname version; }; diff --git a/nix/klein.nix b/nix/klein.nix index aa109e3d1..0bb025cf8 100644 --- a/nix/klein.nix +++ b/nix/klein.nix @@ -4,7 +4,7 @@ buildPythonPackage rec { version = "21.8.0"; src = fetchPypi { - sha256 = "09i1x5ppan3kqsgclbz8xdnlvzvp3amijbmdzv0kik8p5l5zswxa"; + sha256 = "1mpydmz90d0n9dwa7mr6pgj5v0kczfs05ykssrasdq368dssw7ch"; inherit pname version; }; From 1fc77504aeec738acac315d65b68b4a7e01db095 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:39:42 -0500 Subject: [PATCH 22/28] List dependencies. --- nix/klein.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/klein.nix b/nix/klein.nix index 0bb025cf8..196f95e88 100644 --- a/nix/klein.nix +++ b/nix/klein.nix @@ -10,6 +10,8 @@ buildPythonPackage rec { doCheck = false; + propagatedBuildInputs = [ attrs hyperlink incremental Tubes Twisted typing_extensions Werkzeug zope.interface ]; + meta = with lib; { homepage = https://github.com/twisted/klein; description = "Nicer web server for Twisted"; From c65a13e63228fada255a94b99ca4e61a1e9e58dc Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:47:28 -0500 Subject: [PATCH 23/28] Rip out klein, maybe not necessary. --- nix/klein.nix | 20 -------------------- nix/overlays.nix | 3 --- 2 files changed, 23 deletions(-) delete mode 100644 nix/klein.nix diff --git a/nix/klein.nix b/nix/klein.nix deleted file mode 100644 index 196f95e88..000000000 --- a/nix/klein.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ lib, buildPythonPackage, fetchPypi }: -buildPythonPackage rec { - pname = "klein"; - version = "21.8.0"; - - src = fetchPypi { - sha256 = "1mpydmz90d0n9dwa7mr6pgj5v0kczfs05ykssrasdq368dssw7ch"; - inherit pname version; - }; - - doCheck = false; - - propagatedBuildInputs = [ attrs hyperlink incremental Tubes Twisted typing_extensions Werkzeug zope.interface ]; - - meta = with lib; { - homepage = https://github.com/twisted/klein; - description = "Nicer web server for Twisted"; - license = licenses.mit; - }; -} diff --git a/nix/overlays.nix b/nix/overlays.nix index 5cfab200c..92f36e93e 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -31,9 +31,6 @@ self: super: { packageOverrides = python-self: python-super: { # collections-extended is not part of nixpkgs at this time. collections-extended = python-super.pythonPackages.callPackage ./collections-extended.nix { }; - - # klein is not in nixpkgs 21.05, at least: - klein = python-super.pythonPackages.callPackage ./klein.nix { }; }; }; } From 2f4d1079aa3b0621e4ad5991f810a5baf32c23db Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:51:36 -0500 Subject: [PATCH 24/28] Needs setuptools_scm --- nix/cbor2.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/cbor2.nix b/nix/cbor2.nix index 1bd9920e6..4d9734a8b 100644 --- a/nix/cbor2.nix +++ b/nix/cbor2.nix @@ -1,4 +1,4 @@ -{ lib, buildPythonPackage, fetchPypi }: +{ lib, buildPythonPackage, fetchPypi , setuptools_scm }: buildPythonPackage rec { pname = "cbor2"; version = "5.2.0"; @@ -10,6 +10,8 @@ buildPythonPackage rec { doCheck = false; + nativeBuildInputs = [ setuptools_scm ]; + meta = with lib; { homepage = https://github.com/agronholm/cbor2; description = "CBOR encoder/decoder"; From 136bf95bdfcf0819285f7c4ed937f4de64a99125 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:58:02 -0500 Subject: [PATCH 25/28] Simpler way. --- nix/cbor2.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/cbor2.nix b/nix/cbor2.nix index 4d9734a8b..ace5e13c6 100644 --- a/nix/cbor2.nix +++ b/nix/cbor2.nix @@ -1,4 +1,4 @@ -{ lib, buildPythonPackage, fetchPypi , setuptools_scm }: +{ lib, buildPythonPackage, fetchPypi }: buildPythonPackage rec { pname = "cbor2"; version = "5.2.0"; @@ -10,7 +10,7 @@ buildPythonPackage rec { doCheck = false; - nativeBuildInputs = [ setuptools_scm ]; + buildInputs = [ setuptools_scm ]; meta = with lib; { homepage = https://github.com/agronholm/cbor2; From f2b52f368d63059ebe559109b4dbe8c4720bdd2f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 30 Nov 2021 13:58:22 -0500 Subject: [PATCH 26/28] Another way. --- nix/cbor2.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/cbor2.nix b/nix/cbor2.nix index ace5e13c6..0544b1eb1 100644 --- a/nix/cbor2.nix +++ b/nix/cbor2.nix @@ -10,7 +10,7 @@ buildPythonPackage rec { doCheck = false; - buildInputs = [ setuptools_scm ]; + propagatedBuildInputs = [ setuptools_scm ]; meta = with lib; { homepage = https://github.com/agronholm/cbor2; From d985d1062295e2f816214bcbedb6746838d7a67d Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 1 Dec 2021 09:24:03 -0500 Subject: [PATCH 27/28] Update nix/cbor2.nix Co-authored-by: Jean-Paul Calderone --- nix/cbor2.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/cbor2.nix b/nix/cbor2.nix index 0544b1eb1..16ca8ff63 100644 --- a/nix/cbor2.nix +++ b/nix/cbor2.nix @@ -1,4 +1,4 @@ -{ lib, buildPythonPackage, fetchPypi }: +{ lib, buildPythonPackage, fetchPypi, setuptools_scm }: buildPythonPackage rec { pname = "cbor2"; version = "5.2.0"; From 18a5966f1d27791d3129690926791a623957472c Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 1 Dec 2021 09:38:56 -0500 Subject: [PATCH 28/28] Don't bother running HTTP server tests on Python 2, since it's going away any day now. --- src/allmydata/test/test_storage_http.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/test/test_storage_http.py b/src/allmydata/test/test_storage_http.py index 9ba8adf21..442e154a0 100644 --- a/src/allmydata/test/test_storage_http.py +++ b/src/allmydata/test/test_storage_http.py @@ -14,6 +14,8 @@ if PY2: from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 # fmt: on +from unittest import SkipTest + from twisted.trial.unittest import TestCase from twisted.internet.defer import inlineCallbacks @@ -31,6 +33,8 @@ class HTTPTests(TestCase): """ def setUp(self): + if PY2: + raise SkipTest("Not going to bother supporting Python 2") self.storage_server = StorageServer(self.mktemp(), b"\x00" * 20) # TODO what should the swissnum _actually_ be? self._http_server = HTTPServer(self.storage_server, b"abcd")