Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for MSC4108 delegation #17070

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions rust/src/rendezvous/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ use std::{collections::HashMap, time::Duration};

use bytes::Bytes;
use headers::{
AccessControlAllowOrigin, AccessControlExposeHeaders, ContentLength, ContentType, HeaderMapExt,
IfMatch, IfNoneMatch, Pragma,
AccessControlAllowHeaders, AccessControlAllowOrigin, AccessControlExposeHeaders, CacheControl,
ContentLength, ContentType, HeaderMapExt, IfMatch, IfNoneMatch, Pragma,
};
use http::{
header::ETAG, header::IF_MATCH, header::IF_NONE_MATCH, HeaderMap, Response, StatusCode, Uri,
};
use http::{header::ETAG, HeaderMap, Response, StatusCode, Uri};
use log::info;
use mime::Mime;
use pyo3::{
Expand All @@ -40,7 +42,12 @@ const MAX_CONTENT_LENGTH: u64 = 1024 * 100;
fn prepare_headers(headers: &mut HeaderMap, session: &Session) {
headers.typed_insert(AccessControlAllowOrigin::ANY);
headers.typed_insert(AccessControlExposeHeaders::from_iter([ETAG]));
headers.typed_insert(AccessControlAllowHeaders::from_iter([
IF_MATCH,
IF_NONE_MATCH,
]));
hughns marked this conversation as resolved.
Show resolved Hide resolved
headers.typed_insert(Pragma::no_cache());
headers.typed_insert(CacheControl::new().with_no_store());
headers.typed_insert(session.etag());
headers.typed_insert(session.expires());
headers.typed_insert(session.last_modified());
Expand All @@ -60,13 +67,13 @@ fn check_input_headers(headers: &HeaderMap) -> PyResult<Mime> {

// TODO: handle eviction
#[pyclass]
struct RendezVousHandler {
struct RendezvousHandler {
base: Uri,
sessions: HashMap<Ulid, Session>,
}

#[pymethods]
impl RendezVousHandler {
impl RendezvousHandler {
#[new]
fn new(py: Python<'_>, homeserver: &PyAny) -> PyResult<Py<Self>> {
let base: String = homeserver
Expand Down Expand Up @@ -228,7 +235,7 @@ impl RendezVousHandler {
pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
let child_module = PyModule::new(py, "rendezvous")?;

child_module.add_class::<RendezVousHandler>()?;
child_module.add_class::<RendezvousHandler>()?;

m.add_submodule(child_module)?;

Expand Down
3 changes: 3 additions & 0 deletions synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ def __init__(
additional_fields={"approval_notice_medium": approval_notice_medium},
)


class InvalidHeaderError(SynapseError):
"""An error raised when a request has an invalid header"""

Expand All @@ -669,6 +670,7 @@ def __init__(self, header: str):
errcode=Codes.INVALID_PARAM,
)


class MissingHeaderError(SynapseError):
"""An error raised when a request is missing a required header"""

Expand All @@ -679,6 +681,7 @@ def __init__(self, header: str):
errcode=Codes.MISSING_PARAM,
)


class PayloadTooLargeError(SynapseError):
"""An error raised when a request is too large"""

Expand Down
16 changes: 16 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,19 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
"MSC4108 requires MSC3861 to be enabled",
("experimental", "msc4108_enabled"),
)

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"),
)

if self.msc4108_delegation_endpoint is not None and self.msc4108_enabled:
raise ConfigError(
"You cannot have MSC4108 both enabled and delegated at the same time",
("experimental", "msc4108_delegation_endpoint"),
)
13 changes: 12 additions & 1 deletion synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 25 additions & 0 deletions synapse/rest/client/rendezvous.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ async def on_POST(self, request: SynapseRequest) -> None:
# 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
)


class MSC4108RendezvousServlet(RestServlet):
PATTERNS = client_patterns(
"/org.matrix.msc4108/rendezvous$", releases=[], v1=False, unstable=True
Expand All @@ -88,6 +109,7 @@ def __init__(self, hs: "HomeServer") -> None:
def on_POST(self, request: SynapseRequest) -> None:
self._handler.handle_post(request)


class MSC4108RendezvousSessionServlet(RestServlet):
# TODO: this should probably be mounted on the _synapse/client namespace
PATTERNS = client_patterns(
Expand Down Expand Up @@ -118,3 +140,6 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
if hs.config.experimental.msc4108_enabled:
MSC4108RendezvousServlet(hs).register(http_server)
MSC4108RendezvousSessionServlet(hs).register(http_server)

if hs.config.experimental.msc4108_delegation_endpoint is not None:
MSC4108DelegationRendezvousServlet(hs).register(http_server)
6 changes: 3 additions & 3 deletions synapse/rest/client/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
"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,

# TODO
"org.matrix.msc4108": True,
# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code
"org.matrix.msc4108": self.config.experimental.msc4108_enabled
or self.config.experimental.msc4108_delegation_endpoint is not None,
},
},
)
Expand Down
6 changes: 3 additions & 3 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@
from synapse.storage import Databases
from synapse.storage.controllers import StorageControllers
from synapse.streams.events import EventSources
from synapse.synapse_rust.rendezvous import RendezvousHandler
from synapse.types import DomainSpecificString, ISynapseReactor
from synapse.util import Clock
from synapse.util.distributor import Distributor
from synapse.util.macaroons import MacaroonGenerator
from synapse.util.ratelimitutils import FederationRateLimiter
from synapse.util.stringutils import random_string
from synapse.util.task_scheduler import TaskScheduler
from synapse.synapse_rust.rendezvous import RendezVousHandler

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -861,8 +861,8 @@ def get_room_forgetter_handler(self) -> RoomForgetterHandler:
return RoomForgetterHandler(self)

@cache_in_self
def get_rendezvous_handler(self) -> RendezVousHandler:
return RendezVousHandler(self)
def get_rendezvous_handler(self) -> RendezvousHandler:
return RendezvousHandler(self)

@cache_in_self
def get_outbound_redis_connection(self) -> "ConnectionHandler":
Expand Down
5 changes: 3 additions & 2 deletions synapse/synapse_rust/rendezvous.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.

from synapse.server import HomeServer
from twisted.web.iweb import IRequest

class RendezVousHandler:
from synapse.server import HomeServer

class RendezvousHandler:
def __init__(self, homeserver: HomeServer): ...
def handle_post(self, request: IRequest): ...
def handle_get(self, request: IRequest, session_id: str): ...
Expand Down
32 changes: 28 additions & 4 deletions tests/rest/client/test_rendezvous.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
from tests import unittest
from tests.unittest import override_config

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):
Expand All @@ -41,11 +42,34 @@ def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
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"])

@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"])
Loading