I went through and removed a bunch of cruft that was lying around for compatibility with old Python versions. This PR also will now prevent Synapse from starting unless you're running Python 3.6+.tags/v1.33.0rc1
@@ -0,0 +1 @@ | |||||
Remove backwards-compatibility code for Python versions < 3.6. |
@@ -41,7 +41,6 @@ files = | |||||
synapse/push, | synapse/push, | ||||
synapse/replication, | synapse/replication, | ||||
synapse/rest, | synapse/rest, | ||||
synapse/secrets.py, | |||||
synapse/server.py, | synapse/server.py, | ||||
synapse/server_notices, | synapse/server_notices, | ||||
synapse/spam_checker_api, | synapse/spam_checker_api, | ||||
@@ -21,8 +21,8 @@ import os | |||||
import sys | import sys | ||||
# Check that we're not running on an unsupported Python version. | # Check that we're not running on an unsupported Python version. | ||||
if sys.version_info < (3, 5): | |||||
print("Synapse requires Python 3.5 or above.") | |||||
if sys.version_info < (3, 6): | |||||
print("Synapse requires Python 3.6 or above.") | |||||
sys.exit(1) | sys.exit(1) | ||||
# Twisted and canonicaljson will fail to import when this file is executed to | # Twisted and canonicaljson will fail to import when this file is executed to | ||||
@@ -85,7 +85,7 @@ REQUIREMENTS = [ | |||||
"typing-extensions>=3.7.4", | "typing-extensions>=3.7.4", | ||||
# We enforce that we have a `cryptography` version that bundles an `openssl` | # We enforce that we have a `cryptography` version that bundles an `openssl` | ||||
# with the latest security patches. | # with the latest security patches. | ||||
"cryptography>=3.4.7;python_version>='3.6'", | |||||
"cryptography>=3.4.7", | |||||
] | ] | ||||
CONDITIONAL_REQUIREMENTS = { | CONDITIONAL_REQUIREMENTS = { | ||||
@@ -100,14 +100,9 @@ CONDITIONAL_REQUIREMENTS = { | |||||
# that use the protocol, such as Let's Encrypt. | # that use the protocol, such as Let's Encrypt. | ||||
"acme": [ | "acme": [ | ||||
"txacme>=0.9.2", | "txacme>=0.9.2", | ||||
# txacme depends on eliot. Eliot 1.8.0 is incompatible with | |||||
# python 3.5.2, as per https://github.com/itamarst/eliot/issues/418 | |||||
"eliot<1.8.0;python_version<'3.5.3'", | |||||
], | ], | ||||
"saml2": [ | "saml2": [ | ||||
# pysaml2 6.4.0 is incompatible with Python 3.5 (see https://github.com/IdentityPython/pysaml2/issues/749) | |||||
"pysaml2>=4.5.0,<6.4.0;python_version<'3.6'", | |||||
"pysaml2>=4.5.0;python_version>='3.6'", | |||||
"pysaml2>=4.5.0", | |||||
], | ], | ||||
"oidc": ["authlib>=0.14.0"], | "oidc": ["authlib>=0.14.0"], | ||||
# systemd-python is necessary for logging to the systemd journal via | # systemd-python is necessary for logging to the systemd journal via | ||||
@@ -14,6 +14,7 @@ | |||||
import hashlib | import hashlib | ||||
import hmac | import hmac | ||||
import logging | import logging | ||||
import secrets | |||||
from http import HTTPStatus | from http import HTTPStatus | ||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple | from typing import TYPE_CHECKING, Dict, List, Optional, Tuple | ||||
@@ -375,7 +376,7 @@ class UserRegisterServlet(RestServlet): | |||||
""" | """ | ||||
self._clear_old_nonces() | self._clear_old_nonces() | ||||
nonce = self.hs.get_secrets().token_hex(64) | |||||
nonce = secrets.token_hex(64) | |||||
self.nonces[nonce] = int(self.reactor.seconds()) | self.nonces[nonce] = int(self.reactor.seconds()) | ||||
return 200, {"nonce": nonce} | return 200, {"nonce": nonce} | ||||
@@ -32,14 +32,6 @@ TEMPLATE_LANGUAGE = "en" | |||||
logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||
# use hmac.compare_digest if we have it (python 2.7.7), else just use equality | |||||
if hasattr(hmac, "compare_digest"): | |||||
compare_digest = hmac.compare_digest | |||||
else: | |||||
def compare_digest(a, b): | |||||
return a == b | |||||
class ConsentResource(DirectServeHtmlResource): | class ConsentResource(DirectServeHtmlResource): | ||||
"""A twisted Resource to display a privacy policy and gather consent to it | """A twisted Resource to display a privacy policy and gather consent to it | ||||
@@ -209,5 +201,5 @@ class ConsentResource(DirectServeHtmlResource): | |||||
.encode("ascii") | .encode("ascii") | ||||
) | ) | ||||
if not compare_digest(want_mac, userhmac): | |||||
if not hmac.compare_digest(want_mac, userhmac): | |||||
raise SynapseError(HTTPStatus.FORBIDDEN, "HMAC incorrect") | raise SynapseError(HTTPStatus.FORBIDDEN, "HMAC incorrect") |
@@ -21,7 +21,7 @@ from typing import Callable, List | |||||
NEW_FORMAT_ID_RE = re.compile(r"^\d\d\d\d-\d\d-\d\d") | NEW_FORMAT_ID_RE = re.compile(r"^\d\d\d\d-\d\d-\d\d") | ||||
def _wrap_in_base_path(func: "Callable[..., str]") -> "Callable[..., str]": | |||||
def _wrap_in_base_path(func: Callable[..., str]) -> Callable[..., str]: | |||||
"""Takes a function that returns a relative path and turns it into an | """Takes a function that returns a relative path and turns it into an | ||||
absolute path based on the location of the primary media store | absolute path based on the location of the primary media store | ||||
""" | """ | ||||
@@ -1,44 +0,0 @@ | |||||
# Copyright 2018 New Vector Ltd | |||||
# | |||||
# Licensed under the Apache License, Version 2.0 (the "License"); | |||||
# you may not use this file except in compliance with the License. | |||||
# You may obtain a copy of the License at | |||||
# | |||||
# http://www.apache.org/licenses/LICENSE-2.0 | |||||
# | |||||
# Unless required by applicable law or agreed to in writing, software | |||||
# distributed under the License is distributed on an "AS IS" BASIS, | |||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
# See the License for the specific language governing permissions and | |||||
# limitations under the License. | |||||
""" | |||||
Injectable secrets module for Synapse. | |||||
See https://docs.python.org/3/library/secrets.html#module-secrets for the API | |||||
used in Python 3.6, and the API emulated in Python 2.7. | |||||
""" | |||||
import sys | |||||
# secrets is available since python 3.6 | |||||
if sys.version_info[0:2] >= (3, 6): | |||||
import secrets | |||||
class Secrets: | |||||
def token_bytes(self, nbytes: int = 32) -> bytes: | |||||
return secrets.token_bytes(nbytes) | |||||
def token_hex(self, nbytes: int = 32) -> str: | |||||
return secrets.token_hex(nbytes) | |||||
else: | |||||
import binascii | |||||
import os | |||||
class Secrets: | |||||
def token_bytes(self, nbytes: int = 32) -> bytes: | |||||
return os.urandom(nbytes) | |||||
def token_hex(self, nbytes: int = 32) -> str: | |||||
return binascii.hexlify(self.token_bytes(nbytes)).decode("ascii") |
@@ -126,7 +126,6 @@ from synapse.rest.media.v1.media_repository import ( | |||||
MediaRepository, | MediaRepository, | ||||
MediaRepositoryResource, | MediaRepositoryResource, | ||||
) | ) | ||||
from synapse.secrets import Secrets | |||||
from synapse.server_notices.server_notices_manager import ServerNoticesManager | from synapse.server_notices.server_notices_manager import ServerNoticesManager | ||||
from synapse.server_notices.server_notices_sender import ServerNoticesSender | from synapse.server_notices.server_notices_sender import ServerNoticesSender | ||||
from synapse.server_notices.worker_server_notices_sender import ( | from synapse.server_notices.worker_server_notices_sender import ( | ||||
@@ -641,10 +640,6 @@ class HomeServer(metaclass=abc.ABCMeta): | |||||
def get_groups_attestation_renewer(self) -> GroupAttestionRenewer: | def get_groups_attestation_renewer(self) -> GroupAttestionRenewer: | ||||
return GroupAttestionRenewer(self) | return GroupAttestionRenewer(self) | ||||
@cache_in_self | |||||
def get_secrets(self) -> Secrets: | |||||
return Secrets() | |||||
@cache_in_self | @cache_in_self | ||||
def get_stats_handler(self) -> StatsHandler: | def get_stats_handler(self) -> StatsHandler: | ||||
return StatsHandler(self) | return StatsHandler(self) | ||||
@@ -114,7 +114,7 @@ def db_to_json(db_content: Union[memoryview, bytes, bytearray, str]) -> Any: | |||||
db_content = db_content.tobytes() | db_content = db_content.tobytes() | ||||
# Decode it to a Unicode string before feeding it to the JSON decoder, since | # Decode it to a Unicode string before feeding it to the JSON decoder, since | ||||
# Python 3.5 does not support deserializing bytes. | |||||
# it only supports handling strings | |||||
if isinstance(db_content, (bytes, bytearray)): | if isinstance(db_content, (bytes, bytearray)): | ||||
db_content = db_content.decode("utf8") | db_content = db_content.decode("utf8") | ||||
@@ -171,10 +171,7 @@ class LoggingDatabaseConnection: | |||||
# The type of entry which goes on our after_callbacks and exception_callbacks lists. | # The type of entry which goes on our after_callbacks and exception_callbacks lists. | ||||
# | |||||
# Python 3.5.2 doesn't support Callable with an ellipsis, so we wrap it in quotes so | |||||
# that mypy sees the type but the runtime python doesn't. | |||||
_CallbackListEntry = Tuple["Callable[..., None]", Iterable[Any], Dict[str, Any]] | |||||
_CallbackListEntry = Tuple[Callable[..., None], Iterable[Any], Dict[str, Any]] | |||||
R = TypeVar("R") | R = TypeVar("R") | ||||
@@ -221,7 +218,7 @@ class LoggingTransaction: | |||||
self.after_callbacks = after_callbacks | self.after_callbacks = after_callbacks | ||||
self.exception_callbacks = exception_callbacks | self.exception_callbacks = exception_callbacks | ||||
def call_after(self, callback: "Callable[..., None]", *args: Any, **kwargs: Any): | |||||
def call_after(self, callback: Callable[..., None], *args: Any, **kwargs: Any): | |||||
"""Call the given callback on the main twisted thread after the | """Call the given callback on the main twisted thread after the | ||||
transaction has finished. Used to invalidate the caches on the | transaction has finished. Used to invalidate the caches on the | ||||
correct thread. | correct thread. | ||||
@@ -233,7 +230,7 @@ class LoggingTransaction: | |||||
self.after_callbacks.append((callback, args, kwargs)) | self.after_callbacks.append((callback, args, kwargs)) | ||||
def call_on_exception( | def call_on_exception( | ||||
self, callback: "Callable[..., None]", *args: Any, **kwargs: Any | |||||
self, callback: Callable[..., None], *args: Any, **kwargs: Any | |||||
): | ): | ||||
# if self.exception_callbacks is None, that means that whatever constructed the | # if self.exception_callbacks is None, that means that whatever constructed the | ||||
# LoggingTransaction isn't expecting there to be any callbacks; assert that | # LoggingTransaction isn't expecting there to be any callbacks; assert that | ||||
@@ -485,7 +482,7 @@ class DatabasePool: | |||||
desc: str, | desc: str, | ||||
after_callbacks: List[_CallbackListEntry], | after_callbacks: List[_CallbackListEntry], | ||||
exception_callbacks: List[_CallbackListEntry], | exception_callbacks: List[_CallbackListEntry], | ||||
func: "Callable[..., R]", | |||||
func: Callable[..., R], | |||||
*args: Any, | *args: Any, | ||||
**kwargs: Any, | **kwargs: Any, | ||||
) -> R: | ) -> R: | ||||
@@ -618,7 +615,7 @@ class DatabasePool: | |||||
async def runInteraction( | async def runInteraction( | ||||
self, | self, | ||||
desc: str, | desc: str, | ||||
func: "Callable[..., R]", | |||||
func: Callable[..., R], | |||||
*args: Any, | *args: Any, | ||||
db_autocommit: bool = False, | db_autocommit: bool = False, | ||||
**kwargs: Any, | **kwargs: Any, | ||||
@@ -678,7 +675,7 @@ class DatabasePool: | |||||
async def runWithConnection( | async def runWithConnection( | ||||
self, | self, | ||||
func: "Callable[..., R]", | |||||
func: Callable[..., R], | |||||
*args: Any, | *args: Any, | ||||
db_autocommit: bool = False, | db_autocommit: bool = False, | ||||
**kwargs: Any, | **kwargs: Any, | ||||
@@ -110,7 +110,7 @@ class ResponseCache(Generic[T]): | |||||
return result.observe() | return result.observe() | ||||
def wrap( | def wrap( | ||||
self, key: T, callback: "Callable[..., Any]", *args: Any, **kwargs: Any | |||||
self, key: T, callback: Callable[..., Any], *args: Any, **kwargs: Any | |||||
) -> defer.Deferred: | ) -> defer.Deferred: | ||||
"""Wrap together a *get* and *set* call, taking care of logcontexts | """Wrap together a *get* and *set* call, taking care of logcontexts | ||||
@@ -18,7 +18,7 @@ import json | |||||
import urllib.parse | import urllib.parse | ||||
from binascii import unhexlify | from binascii import unhexlify | ||||
from typing import List, Optional | from typing import List, Optional | ||||
from unittest.mock import Mock | |||||
from unittest.mock import Mock, patch | |||||
import synapse.rest.admin | import synapse.rest.admin | ||||
from synapse.api.constants import UserTypes | from synapse.api.constants import UserTypes | ||||
@@ -54,8 +54,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase): | |||||
self.datastore = Mock(return_value=Mock()) | self.datastore = Mock(return_value=Mock()) | ||||
self.datastore.get_current_state_deltas = Mock(return_value=(0, [])) | self.datastore.get_current_state_deltas = Mock(return_value=(0, [])) | ||||
self.secrets = Mock() | |||||
self.hs = self.setup_test_homeserver() | self.hs = self.setup_test_homeserver() | ||||
self.hs.config.registration_shared_secret = "shared" | self.hs.config.registration_shared_secret = "shared" | ||||
@@ -84,14 +82,13 @@ class UserRegisterTestCase(unittest.HomeserverTestCase): | |||||
Calling GET on the endpoint will return a randomised nonce, using the | Calling GET on the endpoint will return a randomised nonce, using the | ||||
homeserver's secrets provider. | homeserver's secrets provider. | ||||
""" | """ | ||||
secrets = Mock() | |||||
secrets.token_hex = Mock(return_value="abcd") | |||||
self.hs.get_secrets = Mock(return_value=secrets) | |||||
with patch("secrets.token_hex") as token_hex: | |||||
# Patch secrets.token_hex for the duration of this context | |||||
token_hex.return_value = "abcd" | |||||
channel = self.make_request("GET", self.url) | |||||
channel = self.make_request("GET", self.url) | |||||
self.assertEqual(channel.json_body, {"nonce": "abcd"}) | |||||
self.assertEqual(channel.json_body, {"nonce": "abcd"}) | |||||
def test_expired_nonce(self): | def test_expired_nonce(self): | ||||
""" | """ | ||||
@@ -13,6 +13,7 @@ | |||||
# See the License for the specific language governing permissions and | # See the License for the specific language governing permissions and | ||||
# limitations under the License. | # limitations under the License. | ||||
import secrets | |||||
from tests import unittest | from tests import unittest | ||||
@@ -21,7 +22,7 @@ class UpsertManyTests(unittest.HomeserverTestCase): | |||||
def prepare(self, reactor, clock, hs): | def prepare(self, reactor, clock, hs): | ||||
self.storage = hs.get_datastore() | self.storage = hs.get_datastore() | ||||
self.table_name = "table_" + hs.get_secrets().token_hex(6) | |||||
self.table_name = "table_" + secrets.token_hex(6) | |||||
self.get_success( | self.get_success( | ||||
self.storage.db_pool.runInteraction( | self.storage.db_pool.runInteraction( | ||||
"create", | "create", | ||||
@@ -18,6 +18,7 @@ import hashlib | |||||
import hmac | import hmac | ||||
import inspect | import inspect | ||||
import logging | import logging | ||||
import secrets | |||||
import time | import time | ||||
from typing import Callable, Dict, Iterable, Optional, Tuple, Type, TypeVar, Union | from typing import Callable, Dict, Iterable, Optional, Tuple, Type, TypeVar, Union | ||||
from unittest.mock import Mock, patch | from unittest.mock import Mock, patch | ||||
@@ -626,7 +627,6 @@ class HomeserverTestCase(TestCase): | |||||
str: The new event's ID. | str: The new event's ID. | ||||
""" | """ | ||||
event_creator = self.hs.get_event_creation_handler() | event_creator = self.hs.get_event_creation_handler() | ||||
secrets = self.hs.get_secrets() | |||||
requester = create_requester(user) | requester = create_requester(user) | ||||
event, context = self.get_success( | event, context = self.get_success( | ||||
@@ -21,13 +21,11 @@ deps = | |||||
# installed on that). | # installed on that). | ||||
# | # | ||||
# anyway, make sure that we have a recent enough setuptools. | # anyway, make sure that we have a recent enough setuptools. | ||||
setuptools>=18.5 ; python_version >= '3.6' | |||||
setuptools>=18.5,<51.0.0 ; python_version < '3.6' | |||||
setuptools>=18.5 | |||||
# we also need a semi-recent version of pip, because old ones fail to | # we also need a semi-recent version of pip, because old ones fail to | ||||
# install the "enum34" dependency of cryptography. | # install the "enum34" dependency of cryptography. | ||||
pip>=10 ; python_version >= '3.6' | |||||
pip>=10,<21.0 ; python_version < '3.6' | |||||
pip>=10 | |||||
# directories/files we run the linters on. | # directories/files we run the linters on. | ||||
# if you update this list, make sure to do the same in scripts-dev/lint.sh | # if you update this list, make sure to do the same in scripts-dev/lint.sh | ||||
@@ -168,8 +166,7 @@ skip_install = true | |||||
usedevelop = false | usedevelop = false | ||||
deps = | deps = | ||||
coverage | coverage | ||||
pip>=10 ; python_version >= '3.6' | |||||
pip>=10,<21.0 ; python_version < '3.6' | |||||
pip>=10 | |||||
commands= | commands= | ||||
coverage combine | coverage combine | ||||
coverage report | coverage report | ||||