From c8e0bed4269106c49ffd733eaad5cdb3576f55d2 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 17 Apr 2024 16:47:35 +0200 Subject: [PATCH] Support for MSC4108 via delegation (#17086) This adds support for MSC4108 via delegation, similar to what has been done for MSC3886 --------- Co-authored-by: Hugh Nimmo-Smith --- changelog.d/17086.feature | 1 + synapse/config/experimental.py | 11 +++++++++ synapse/http/server.py | 13 ++++++++++- synapse/rest/client/rendezvous.py | 30 +++++++++++++++++++++--- synapse/rest/client/versions.py | 3 +++ tests/rest/client/test_rendezvous.py | 34 ++++++++++++++++++++++++---- 6 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 changelog.d/17086.feature diff --git a/changelog.d/17086.feature b/changelog.d/17086.feature new file mode 100644 index 000000000..08b407d31 --- /dev/null +++ b/changelog.d/17086.feature @@ -0,0 +1 @@ +Support delegating the rendezvous mechanism described MSC4108 to an external implementation. diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index fcc78d2d8..353ae23f9 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -411,3 +411,14 @@ class ExperimentalConfig(Config): self.msc4069_profile_inhibit_propagation = experimental.get( "msc4069_profile_inhibit_propagation", False ) + + # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code + self.msc4108_delegation_endpoint: Optional[str] = experimental.get( + "msc4108_delegation_endpoint", None + ) + + if self.msc4108_delegation_endpoint is not None and not self.msc3861.enabled: + raise ConfigError( + "MSC4108 requires MSC3861 to be enabled", + ("experimental", "msc4108_delegation_endpoint"), + ) diff --git a/synapse/http/server.py b/synapse/http/server.py index c76500e14..45b2cbffc 100644 --- a/synapse/http/server.py +++ b/synapse/http/server.py @@ -909,7 +909,18 @@ def set_cors_headers(request: "SynapseRequest") -> None: request.setHeader( b"Access-Control-Allow-Methods", b"GET, HEAD, POST, PUT, DELETE, OPTIONS" ) - if request.experimental_cors_msc3886: + if request.path is not None and request.path.startswith( + b"/_matrix/client/unstable/org.matrix.msc4108/rendezvous" + ): + request.setHeader( + b"Access-Control-Allow-Headers", + b"Content-Type, If-Match, If-None-Match", + ) + request.setHeader( + b"Access-Control-Expose-Headers", + b"Synapse-Trace-Id, Server, ETag", + ) + elif request.experimental_cors_msc3886: request.setHeader( b"Access-Control-Allow-Headers", b"X-Requested-With, Content-Type, Authorization, Date, If-Match, If-None-Match", diff --git a/synapse/rest/client/rendezvous.py b/synapse/rest/client/rendezvous.py index dee7c37ec..ed06a2998 100644 --- a/synapse/rest/client/rendezvous.py +++ b/synapse/rest/client/rendezvous.py @@ -2,7 +2,7 @@ # This file is licensed under the Affero General Public License (AGPL) version 3. # # Copyright 2022 The Matrix.org Foundation C.I.C. -# Copyright (C) 2023 New Vector, Ltd +# Copyright (C) 2023-2024 New Vector, Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -34,7 +34,7 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -class RendezvousServlet(RestServlet): +class MSC3886RendezvousServlet(RestServlet): """ This is a placeholder implementation of [MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886) simple client rendezvous capability that is used by the "Sign in with QR" functionality. @@ -76,6 +76,30 @@ class RendezvousServlet(RestServlet): # PUT, GET and DELETE are not implemented as they should be fulfilled by the redirect target. +class MSC4108DelegationRendezvousServlet(RestServlet): + PATTERNS = client_patterns( + "/org.matrix.msc4108/rendezvous$", releases=[], v1=False, unstable=True + ) + + def __init__(self, hs: "HomeServer"): + super().__init__() + redirection_target: Optional[str] = ( + hs.config.experimental.msc4108_delegation_endpoint + ) + assert ( + redirection_target is not None + ), "Servlet is only registered if there is a delegation target" + self.endpoint = redirection_target.encode("utf-8") + + async def on_POST(self, request: SynapseRequest) -> None: + respond_with_redirect( + request, self.endpoint, statusCode=TEMPORARY_REDIRECT, cors=True + ) + + def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: if hs.config.experimental.msc3886_endpoint is not None: - RendezvousServlet(hs).register(http_server) + MSC3886RendezvousServlet(hs).register(http_server) + + if hs.config.experimental.msc4108_delegation_endpoint is not None: + MSC4108DelegationRendezvousServlet(hs).register(http_server) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index c46d4fe8c..638d4c45a 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -140,6 +140,9 @@ class VersionsRestServlet(RestServlet): "org.matrix.msc4069": self.config.experimental.msc4069_profile_inhibit_propagation, # Allows clients to handle push for encrypted events. "org.matrix.msc4028": self.config.experimental.msc4028_push_encrypted_events, + # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code + "org.matrix.msc4108": self.config.experimental.msc4108_delegation_endpoint + is not None, }, }, ) diff --git a/tests/rest/client/test_rendezvous.py b/tests/rest/client/test_rendezvous.py index 294b39f17..c84704c09 100644 --- a/tests/rest/client/test_rendezvous.py +++ b/tests/rest/client/test_rendezvous.py @@ -27,8 +27,10 @@ from synapse.util import Clock from tests import unittest from tests.unittest import override_config +from tests.utils import HAS_AUTHLIB -endpoint = "/_matrix/client/unstable/org.matrix.msc3886/rendezvous" +msc3886_endpoint = "/_matrix/client/unstable/org.matrix.msc3886/rendezvous" +msc4108_endpoint = "/_matrix/client/unstable/org.matrix.msc4108/rendezvous" class RendezvousServletTestCase(unittest.HomeserverTestCase): @@ -41,11 +43,35 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase): return self.hs def test_disabled(self) -> None: - channel = self.make_request("POST", endpoint, {}, access_token=None) + channel = self.make_request("POST", msc3886_endpoint, {}, access_token=None) + self.assertEqual(channel.code, 404) + channel = self.make_request("POST", msc4108_endpoint, {}, access_token=None) self.assertEqual(channel.code, 404) @override_config({"experimental_features": {"msc3886_endpoint": "/asd"}}) - def test_redirect(self) -> None: - channel = self.make_request("POST", endpoint, {}, access_token=None) + def test_msc3886_redirect(self) -> None: + channel = self.make_request("POST", msc3886_endpoint, {}, access_token=None) self.assertEqual(channel.code, 307) self.assertEqual(channel.headers.getRawHeaders("Location"), ["/asd"]) + + @unittest.skip_unless(HAS_AUTHLIB, "requires authlib") + @override_config( + { + "disable_registration": True, + "experimental_features": { + "msc4108_delegation_endpoint": "https://asd", + "msc3861": { + "enabled": True, + "issuer": "https://issuer", + "client_id": "client_id", + "client_auth_method": "client_secret_post", + "client_secret": "client_secret", + "admin_token": "admin_token_value", + }, + }, + } + ) + def test_msc4108_delegation(self) -> None: + channel = self.make_request("POST", msc4108_endpoint, {}, access_token=None) + self.assertEqual(channel.code, 307) + self.assertEqual(channel.headers.getRawHeaders("Location"), ["https://asd"])