From 3ec56b80917b650b15760d6d160826dc68060fc3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 6 Sep 2022 09:29:59 +0100 Subject: [PATCH 01/13] Implementation of MSC3882 login token request --- synapse/config/experimental.py | 5 + synapse/rest/__init__.py | 2 + synapse/rest/client/login_token_request.py | 101 +++++++++++++++++++++ synapse/rest/client/versions.py | 2 + 4 files changed, 110 insertions(+) create mode 100644 synapse/rest/client/login_token_request.py diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 702b81e636c9..d85ef752c956 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -93,3 +93,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None: # MSC3852: Expose last seen user agent field on /_matrix/client/v3/devices. self.msc3852_enabled: bool = experimental.get("msc3852_enabled", False) + + # MSC3882: Allow an existing session to sign in a new session + self.msc3882_enabled: bool = experimental.get("msc3882_enabled", False) + self.msc3882_ui_auth: bool = experimental.get("msc3882_ui_auth", True) + self.msc3882_token_timeout = self.parse_duration(experimental.get("msc3882_token_timeout", "5m")) diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index b71221511209..9a2ab99ede87 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -30,6 +30,7 @@ keys, knock, login as v1_login, + login_token_request, logout, mutual_rooms, notifications, @@ -130,3 +131,4 @@ def register_servlets(client_resource: HttpServer, hs: "HomeServer") -> None: # unstable mutual_rooms.register_servlets(hs, client_resource) + login_token_request.register_servlets(hs, client_resource) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py new file mode 100644 index 000000000000..8cf6802fb713 --- /dev/null +++ b/synapse/rest/client/login_token_request.py @@ -0,0 +1,101 @@ +# Copyright 2022 The Matrix.org Foundation C.I.C. +# +# 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. + +import logging +from typing import TYPE_CHECKING, Tuple + +from synapse.http.server import HttpServer +from synapse.http.servlet import RestServlet, parse_json_object_from_request +from synapse.http.site import SynapseRequest +from synapse.types import JsonDict +from synapse.util.stringutils import random_string + +from ._base import client_patterns, interactive_auth_handler + +if TYPE_CHECKING: + from synapse.server import HomeServer + +logger = logging.getLogger(__name__) + + +class LoginTokenRequestServlet(RestServlet): + """ + Get a token that can be used with `m.login.token` to log in a second device. + + Request: + + POST /login/token?access_token=... HTTP/1.1 + + {} + + Response: + + HTTP/1.1 200 OK + { + "login_token": "ABDEFGH", + "expires_in": 3600, + } + """ + + PATTERNS = client_patterns("/login/token$") + + def __init__(self, hs: "HomeServer"): + super().__init__() + self.auth = hs.get_auth() + self.store = hs.get_datastores().main + self.clock = hs.get_clock() + self.server_name = hs.config.server.server_name + self.macaroon_gen = hs.get_macaroon_generator() + self.auth_handler = hs.get_auth_handler() + self.token_timeout = hs.config.experimental.msc3882_token_timeout + self.ui_auth = hs.config.experimental.msc3882_ui_auth + + @interactive_auth_handler + async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: + requester = await self.auth.get_user_by_req(request) + user_id = requester.user.to_string() + body = parse_json_object_from_request(request) + + if self.ui_auth: + await self.auth_handler.validate_user_via_ui_auth( + requester, + request, + body, + "issue a new access token for your account", + can_skip_ui_auth=False, # Don't allow skipping of UI auth + ) + + token = random_string(24) + ts_valid_until_ms = self.clock.time_msec() + self.token_timeout + + login_token = self.macaroon_gen.generate_short_term_login_token( + user_id=requester.user.to_string(), + auth_provider_id="org.matrix.msc3882.login_token_request", + duration_in_ms=self.token_timeout, + ) + + await self.store.insert_open_id_token(token, ts_valid_until_ms, user_id) + + return ( + 200, + { + "login_token": login_token, + "expires_in": self.token_timeout // 1000, + }, + ) + + +def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: + if hs.config.experimental.msc3882_enabled: + LoginTokenRequestServlet(hs).register(http_server) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index c516cda95d5c..c3488f4330bc 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -105,6 +105,8 @@ def on_GET(self, request: Request) -> Tuple[int, JsonDict]: "org.matrix.msc3440.stable": True, # TODO: remove when "v1.3" is added above # Allows moderators to fetch redacted event content as described in MSC2815 "fi.mau.msc2815": self.config.experimental.msc2815_enabled, + # Adds support for login token requests as per MSC3882 + "org.matrix.msc3882": self.config.experimental.msc3882_enabled, }, }, ) From fef89d17f7fece118015208ca6980bab6ff038f5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 6 Sep 2022 09:36:26 +0100 Subject: [PATCH 02/13] Lint fixes --- synapse/config/experimental.py | 4 +++- synapse/rest/client/login_token_request.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index d85ef752c956..2ccd2cf1383b 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -97,4 +97,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None: # MSC3882: Allow an existing session to sign in a new session self.msc3882_enabled: bool = experimental.get("msc3882_enabled", False) self.msc3882_ui_auth: bool = experimental.get("msc3882_ui_auth", True) - self.msc3882_token_timeout = self.parse_duration(experimental.get("msc3882_token_timeout", "5m")) + self.msc3882_token_timeout = self.parse_duration( + experimental.get("msc3882_token_timeout", "5m") + ) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index 8cf6802fb713..c16fcd586fa5 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -73,7 +73,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: request, body, "issue a new access token for your account", - can_skip_ui_auth=False, # Don't allow skipping of UI auth + can_skip_ui_auth=False, # Don't allow skipping of UI auth ) token = random_string(24) From e7babaabd335de760ce03cbb24d296cabae48061 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 6 Sep 2022 09:39:34 +0100 Subject: [PATCH 03/13] Changelog --- changelog.d/13722.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13722.misc diff --git a/changelog.d/13722.misc b/changelog.d/13722.misc new file mode 100644 index 000000000000..588d143c0fe4 --- /dev/null +++ b/changelog.d/13722.misc @@ -0,0 +1 @@ +Experimental implementation of MSC3882 to allow an existing device/session to generate a login token for use on a new device/session. From 54b7ffa717f269eee0473d12d037429d23a9d1cd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 16:53:48 +0100 Subject: [PATCH 04/13] Revise change type to feature --- changelog.d/{13722.misc => 13722.feature} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{13722.misc => 13722.feature} (100%) diff --git a/changelog.d/13722.misc b/changelog.d/13722.feature similarity index 100% rename from changelog.d/13722.misc rename to changelog.d/13722.feature From daa42c3a55bc2d0adec829624ed08e66733bf6c0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 16:57:11 +0100 Subject: [PATCH 05/13] Fix request documentation --- synapse/rest/client/login_token_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index c16fcd586fa5..6eb5dfa2866b 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -35,7 +35,8 @@ class LoginTokenRequestServlet(RestServlet): Request: - POST /login/token?access_token=... HTTP/1.1 + POST /login/token HTTP/1.1 + Content-Type: application/json {} From e2125abc62abb2de5c6d9b8507b0fc529591c6ec Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 17:13:48 +0100 Subject: [PATCH 06/13] Remove unnecessary/unused code --- synapse/rest/client/login_token_request.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index 6eb5dfa2866b..e26a98d92610 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -65,7 +65,6 @@ def __init__(self, hs: "HomeServer"): @interactive_auth_handler async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) - user_id = requester.user.to_string() body = parse_json_object_from_request(request) if self.ui_auth: @@ -77,16 +76,12 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: can_skip_ui_auth=False, # Don't allow skipping of UI auth ) - token = random_string(24) - ts_valid_until_ms = self.clock.time_msec() + self.token_timeout - login_token = self.macaroon_gen.generate_short_term_login_token( user_id=requester.user.to_string(), auth_provider_id="org.matrix.msc3882.login_token_request", duration_in_ms=self.token_timeout, ) - await self.store.insert_open_id_token(token, ts_valid_until_ms, user_id) return ( 200, From 087ff043a5710a939184c8e48d12d33f35eb05fe Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 17:15:26 +0100 Subject: [PATCH 07/13] Clean up imports --- synapse/rest/client/login_token_request.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index e26a98d92610..64f77f3b8bd3 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -18,10 +18,8 @@ from synapse.http.server import HttpServer from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.site import SynapseRequest +from synapse.rest.client._base import client_patterns, interactive_auth_handler from synapse.types import JsonDict -from synapse.util.stringutils import random_string - -from ._base import client_patterns, interactive_auth_handler if TYPE_CHECKING: from synapse.server import HomeServer From 7287e36717db3398455940673dfb03d24d9b2559 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 18:12:21 +0100 Subject: [PATCH 08/13] Return 404 if not enabled instead of not loading the servlet --- synapse/rest/client/login_token_request.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index 64f77f3b8bd3..ea249755b0d1 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -57,11 +57,15 @@ def __init__(self, hs: "HomeServer"): self.server_name = hs.config.server.server_name self.macaroon_gen = hs.get_macaroon_generator() self.auth_handler = hs.get_auth_handler() + self.enabled = hs.config.experimental.msc3882_enabled self.token_timeout = hs.config.experimental.msc3882_token_timeout self.ui_auth = hs.config.experimental.msc3882_ui_auth @interactive_auth_handler async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: + if not self.enabled: + return (404, {"errcode": "M_NOT_FOUND", "error": "Not found"}) + requester = await self.auth.get_user_by_req(request) body = parse_json_object_from_request(request) @@ -80,7 +84,6 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: duration_in_ms=self.token_timeout, ) - return ( 200, { @@ -91,5 +94,4 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - if hs.config.experimental.msc3882_enabled: - LoginTokenRequestServlet(hs).register(http_server) + LoginTokenRequestServlet(hs).register(http_server) From f2f7bb82cf72fb30be6ac891ed9b4d59f73f233e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 20 Sep 2022 18:19:26 +0100 Subject: [PATCH 09/13] Unit tests --- tests/rest/client/test_login_token_request.py | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/rest/client/test_login_token_request.py diff --git a/tests/rest/client/test_login_token_request.py b/tests/rest/client/test_login_token_request.py new file mode 100644 index 000000000000..4134a758c479 --- /dev/null +++ b/tests/rest/client/test_login_token_request.py @@ -0,0 +1,128 @@ +# Copyright 2022 The Matrix.org Foundation C.I.C. +# +# 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. + +from twisted.test.proto_helpers import MemoryReactor + +from synapse.rest import admin +from synapse.rest.client import login, login_token_request +from synapse.server import HomeServer +from synapse.util import Clock + +from tests import unittest +from tests.unittest import override_config + + +class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase): + + servlets = [ + login.register_servlets, + admin.register_servlets, + login_token_request.register_servlets, + ] + + def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: + self.hs = self.setup_test_homeserver() + self.hs.config.registration.enable_registration = True + self.hs.config.registration.registrations_require_3pid = [] + self.hs.config.registration.auto_join_rooms = [] + self.hs.config.captcha.enable_registration_captcha = False + + return self.hs + + def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: + self.user = "user123" + self.password = "password" + + def test_disabled(self) -> None: + self.register_user(self.user, self.password) + token = self.login(self.user, self.password) + + channel = self.make_request("POST", "/login/token", {}, access_token=token) + self.assertEqual(channel.code, 404) + + @override_config({"experimental_features": {"msc3882_enabled": True}}) + def test_require_auth(self) -> None: + channel = self.make_request("POST", "/login/token", {}, access_token=None) + self.assertEqual(channel.code, 401) + + @override_config({"experimental_features": {"msc3882_enabled": True}}) + def test_uia_on(self) -> None: + self.register_user(self.user, self.password) + token = self.login(self.user, self.password) + + channel = self.make_request("POST", "/login/token", {}, access_token=token) + self.assertEqual(channel.code, 401) + self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"]) + + session = channel.json_body["session"] + + uia = { + "auth": { + "type": "m.login.password", + "identifier": {"type": "m.id.user", "user": self.user}, + "password": self.password, + "session": session, + }, + } + + channel = self.make_request("POST", "/login/token", uia, access_token=token) + self.assertEqual(channel.code, 200) + self.assertEqual(channel.json_body["expires_in"], 300) + + login_token = channel.json_body["login_token"] + + channel = self.make_request( + "POST", + "/login", + content={"type": "m.login.token", "token": login_token}, + ) + self.assertEqual(channel.code, 200, channel.result) + self.assertEqual(channel.json_body["user_id"], "@" + self.user + ":test") + + @override_config( + {"experimental_features": {"msc3882_enabled": True, "msc3882_ui_auth": False}} + ) + def test_uia_off(self) -> None: + self.register_user(self.user, self.password) + token = self.login(self.user, self.password) + + channel = self.make_request("POST", "/login/token", {}, access_token=token) + self.assertEqual(channel.code, 200) + self.assertEqual(channel.json_body["expires_in"], 300) + + login_token = channel.json_body["login_token"] + + channel = self.make_request( + "POST", + "/login", + content={"type": "m.login.token", "token": login_token}, + ) + self.assertEqual(channel.code, 200, channel.result) + self.assertEqual(channel.json_body["user_id"], "@" + self.user + ":test") + + @override_config( + { + "experimental_features": { + "msc3882_enabled": True, + "msc3882_ui_auth": False, + "msc3882_token_timeout": "15s", + } + } + ) + def test_expires_in(self) -> None: + self.register_user(self.user, self.password) + token = self.login(self.user, self.password) + + channel = self.make_request("POST", "/login/token", {}, access_token=token) + self.assertEqual(channel.code, 200) From 5323ed8b7e51258b11f955c810e2fcedd1187152 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 21 Sep 2022 09:54:38 +0100 Subject: [PATCH 10/13] Revert "Return 404 if not enabled instead of not loading the servlet" This reverts commit 7287e36717db3398455940673dfb03d24d9b2559. --- synapse/rest/client/login_token_request.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index ea249755b0d1..64f77f3b8bd3 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -57,15 +57,11 @@ def __init__(self, hs: "HomeServer"): self.server_name = hs.config.server.server_name self.macaroon_gen = hs.get_macaroon_generator() self.auth_handler = hs.get_auth_handler() - self.enabled = hs.config.experimental.msc3882_enabled self.token_timeout = hs.config.experimental.msc3882_token_timeout self.ui_auth = hs.config.experimental.msc3882_ui_auth @interactive_auth_handler async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: - if not self.enabled: - return (404, {"errcode": "M_NOT_FOUND", "error": "Not found"}) - requester = await self.auth.get_user_by_req(request) body = parse_json_object_from_request(request) @@ -84,6 +80,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: duration_in_ms=self.token_timeout, ) + return ( 200, { @@ -94,4 +91,5 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - LoginTokenRequestServlet(hs).register(http_server) + if hs.config.experimental.msc3882_enabled: + LoginTokenRequestServlet(hs).register(http_server) From 8c14c2bb3bb2eb31fcfe28a7b01c582690981688 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 21 Sep 2022 09:55:15 +0100 Subject: [PATCH 11/13] Simplify user_id checks --- tests/rest/client/test_login_token_request.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/rest/client/test_login_token_request.py b/tests/rest/client/test_login_token_request.py index 4134a758c479..27b80b4a0940 100644 --- a/tests/rest/client/test_login_token_request.py +++ b/tests/rest/client/test_login_token_request.py @@ -58,7 +58,7 @@ def test_require_auth(self) -> None: @override_config({"experimental_features": {"msc3882_enabled": True}}) def test_uia_on(self) -> None: - self.register_user(self.user, self.password) + user_id = self.register_user(self.user, self.password) token = self.login(self.user, self.password) channel = self.make_request("POST", "/login/token", {}, access_token=token) @@ -88,13 +88,13 @@ def test_uia_on(self) -> None: content={"type": "m.login.token", "token": login_token}, ) self.assertEqual(channel.code, 200, channel.result) - self.assertEqual(channel.json_body["user_id"], "@" + self.user + ":test") + self.assertEqual(channel.json_body["user_id"], user_id) @override_config( {"experimental_features": {"msc3882_enabled": True, "msc3882_ui_auth": False}} ) def test_uia_off(self) -> None: - self.register_user(self.user, self.password) + user_id = self.register_user(self.user, self.password) token = self.login(self.user, self.password) channel = self.make_request("POST", "/login/token", {}, access_token=token) @@ -109,7 +109,7 @@ def test_uia_off(self) -> None: content={"type": "m.login.token", "token": login_token}, ) self.assertEqual(channel.code, 200, channel.result) - self.assertEqual(channel.json_body["user_id"], "@" + self.user + ":test") + self.assertEqual(channel.json_body["user_id"], user_id) @override_config( { @@ -126,3 +126,4 @@ def test_expires_in(self) -> None: channel = self.make_request("POST", "/login/token", {}, access_token=token) self.assertEqual(channel.code, 200) + self.assertEqual(channel.json_body["expires_in"], 15) From 7b3df6f17c50862e8f10ad47d8a0e94b64cd22e4 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 21 Sep 2022 09:57:33 +0100 Subject: [PATCH 12/13] Whitespace --- synapse/rest/client/login_token_request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/rest/client/login_token_request.py b/synapse/rest/client/login_token_request.py index 64f77f3b8bd3..ca5c54bf17a0 100644 --- a/synapse/rest/client/login_token_request.py +++ b/synapse/rest/client/login_token_request.py @@ -80,7 +80,6 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: duration_in_ms=self.token_timeout, ) - return ( 200, { From 112a13344a7220a8d6d2b630b529c22d19b9c8a4 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 21 Sep 2022 09:57:51 +0100 Subject: [PATCH 13/13] Revised test for case of feature disabled --- tests/rest/client/test_login_token_request.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/rest/client/test_login_token_request.py b/tests/rest/client/test_login_token_request.py index 27b80b4a0940..d5bb16c98daa 100644 --- a/tests/rest/client/test_login_token_request.py +++ b/tests/rest/client/test_login_token_request.py @@ -45,11 +45,14 @@ def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: self.password = "password" def test_disabled(self) -> None: + channel = self.make_request("POST", "/login/token", {}, access_token=None) + self.assertEqual(channel.code, 400) + self.register_user(self.user, self.password) token = self.login(self.user, self.password) channel = self.make_request("POST", "/login/token", {}, access_token=token) - self.assertEqual(channel.code, 404) + self.assertEqual(channel.code, 400) @override_config({"experimental_features": {"msc3882_enabled": True}}) def test_require_auth(self) -> None: