Implement stable support for MSC3882 to allow an existing device/session to generate a login token for use on a new device/session (#15388)
Implements stable support for MSC3882; this involves updating Synapse's support to match the MSC / the spec says. Continue to support the unstable version to allow clients to transition.
This commit is contained in:
parent
0b5f64ff09
commit
d1693f0362
|
@ -0,0 +1 @@
|
||||||
|
Stable support for [MSC3882](https://github.com/matrix-org/matrix-spec-proposals/pull/3882) to allow an existing device/session to generate a login token for use on a new device/session.
|
|
@ -2570,7 +2570,50 @@ Example configuration:
|
||||||
```yaml
|
```yaml
|
||||||
nonrefreshable_access_token_lifetime: 24h
|
nonrefreshable_access_token_lifetime: 24h
|
||||||
```
|
```
|
||||||
|
---
|
||||||
|
### `ui_auth`
|
||||||
|
|
||||||
|
The amount of time to allow a user-interactive authentication session to be active.
|
||||||
|
|
||||||
|
This defaults to 0, meaning the user is queried for their credentials
|
||||||
|
before every action, but this can be overridden to allow a single
|
||||||
|
validation to be re-used. This weakens the protections afforded by
|
||||||
|
the user-interactive authentication process, by allowing for multiple
|
||||||
|
(and potentially different) operations to use the same validation session.
|
||||||
|
|
||||||
|
This is ignored for potentially "dangerous" operations (including
|
||||||
|
deactivating an account, modifying an account password, adding a 3PID,
|
||||||
|
and minting additional login tokens).
|
||||||
|
|
||||||
|
Use the `session_timeout` sub-option here to change the time allowed for credential validation.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```yaml
|
||||||
|
ui_auth:
|
||||||
|
session_timeout: "15s"
|
||||||
|
```
|
||||||
|
---
|
||||||
|
### `login_via_existing_session`
|
||||||
|
|
||||||
|
Matrix supports the ability of an existing session to mint a login token for
|
||||||
|
another client.
|
||||||
|
|
||||||
|
Synapse disables this by default as it has security ramifications -- a malicious
|
||||||
|
client could use the mechanism to spawn more than one session.
|
||||||
|
|
||||||
|
The duration of time the generated token is valid for can be configured with the
|
||||||
|
`token_timeout` sub-option.
|
||||||
|
|
||||||
|
User-interactive authentication is required when this is enabled unless the
|
||||||
|
`require_ui_auth` sub-option is set to `False`.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```yaml
|
||||||
|
login_via_existing_session:
|
||||||
|
enabled: true
|
||||||
|
require_ui_auth: false
|
||||||
|
token_timeout: "5m"
|
||||||
|
```
|
||||||
---
|
---
|
||||||
## Metrics
|
## Metrics
|
||||||
Config options related to metrics.
|
Config options related to metrics.
|
||||||
|
@ -3415,28 +3458,6 @@ password_config:
|
||||||
require_uppercase: true
|
require_uppercase: true
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
### `ui_auth`
|
|
||||||
|
|
||||||
The amount of time to allow a user-interactive authentication session to be active.
|
|
||||||
|
|
||||||
This defaults to 0, meaning the user is queried for their credentials
|
|
||||||
before every action, but this can be overridden to allow a single
|
|
||||||
validation to be re-used. This weakens the protections afforded by
|
|
||||||
the user-interactive authentication process, by allowing for multiple
|
|
||||||
(and potentially different) operations to use the same validation session.
|
|
||||||
|
|
||||||
This is ignored for potentially "dangerous" operations (including
|
|
||||||
deactivating an account, modifying an account password, and
|
|
||||||
adding a 3PID).
|
|
||||||
|
|
||||||
Use the `session_timeout` sub-option here to change the time allowed for credential validation.
|
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
```yaml
|
|
||||||
ui_auth:
|
|
||||||
session_timeout: "15s"
|
|
||||||
```
|
|
||||||
---
|
|
||||||
## Push
|
## Push
|
||||||
Configuration settings related to push notifications
|
Configuration settings related to push notifications
|
||||||
|
|
||||||
|
|
|
@ -60,3 +60,13 @@ class AuthConfig(Config):
|
||||||
self.ui_auth_session_timeout = self.parse_duration(
|
self.ui_auth_session_timeout = self.parse_duration(
|
||||||
ui_auth.get("session_timeout", 0)
|
ui_auth.get("session_timeout", 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Logging in with an existing session.
|
||||||
|
login_via_existing = config.get("login_via_existing_session", {})
|
||||||
|
self.login_via_existing_enabled = login_via_existing.get("enabled", False)
|
||||||
|
self.login_via_existing_require_ui_auth = login_via_existing.get(
|
||||||
|
"require_ui_auth", True
|
||||||
|
)
|
||||||
|
self.login_via_existing_token_timeout = self.parse_duration(
|
||||||
|
login_via_existing.get("token_timeout", "5m")
|
||||||
|
)
|
||||||
|
|
|
@ -192,10 +192,10 @@ class MSC3861:
|
||||||
("captcha", "enable_registration_captcha"),
|
("captcha", "enable_registration_captcha"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if root.experimental.msc3882_enabled:
|
if root.auth.login_via_existing_enabled:
|
||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
"MSC3882 cannot be enabled when OAuth delegation is enabled",
|
"Login via existing session cannot be enabled when OAuth delegation is enabled",
|
||||||
("experimental_features", "msc3882_enabled"),
|
("login_via_existing_session", "enabled"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if root.registration.refresh_token_lifetime:
|
if root.registration.refresh_token_lifetime:
|
||||||
|
@ -319,13 +319,6 @@ class ExperimentalConfig(Config):
|
||||||
# MSC3881: Remotely toggle push notifications for another client
|
# MSC3881: Remotely toggle push notifications for another client
|
||||||
self.msc3881_enabled: bool = experimental.get("msc3881_enabled", False)
|
self.msc3881_enabled: bool = experimental.get("msc3881_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")
|
|
||||||
)
|
|
||||||
|
|
||||||
# MSC3874: Filtering /messages with rel_types / not_rel_types.
|
# MSC3874: Filtering /messages with rel_types / not_rel_types.
|
||||||
self.msc3874_enabled: bool = experimental.get("msc3874_enabled", False)
|
self.msc3874_enabled: bool = experimental.get("msc3874_enabled", False)
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,9 @@ class CapabilitiesRestServlet(RestServlet):
|
||||||
"m.3pid_changes": {
|
"m.3pid_changes": {
|
||||||
"enabled": self.config.registration.enable_3pid_changes
|
"enabled": self.config.registration.enable_3pid_changes
|
||||||
},
|
},
|
||||||
|
"m.get_login_token": {
|
||||||
|
"enabled": self.config.auth.login_via_existing_enabled,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,9 @@ class LoginRestServlet(RestServlet):
|
||||||
and hs.config.experimental.msc3866.require_approval_for_new_accounts
|
and hs.config.experimental.msc3866.require_approval_for_new_accounts
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Whether get login token is enabled.
|
||||||
|
self._get_login_token_enabled = hs.config.auth.login_via_existing_enabled
|
||||||
|
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
@ -142,6 +145,9 @@ class LoginRestServlet(RestServlet):
|
||||||
# to SSO.
|
# to SSO.
|
||||||
flows.append({"type": LoginRestServlet.CAS_TYPE})
|
flows.append({"type": LoginRestServlet.CAS_TYPE})
|
||||||
|
|
||||||
|
# The login token flow requires m.login.token to be advertised.
|
||||||
|
support_login_token_flow = self._get_login_token_enabled
|
||||||
|
|
||||||
if self.cas_enabled or self.saml2_enabled or self.oidc_enabled:
|
if self.cas_enabled or self.saml2_enabled or self.oidc_enabled:
|
||||||
flows.append(
|
flows.append(
|
||||||
{
|
{
|
||||||
|
@ -153,14 +159,23 @@ class LoginRestServlet(RestServlet):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# While it's valid for us to advertise this login type generally,
|
# SSO requires a login token to be generated, so we need to advertise that flow
|
||||||
# synapse currently only gives out these tokens as part of the
|
support_login_token_flow = True
|
||||||
# SSO login flow.
|
|
||||||
# Generally we don't want to advertise login flows that clients
|
# While it's valid for us to advertise this login type generally,
|
||||||
# don't know how to implement, since they (currently) will always
|
# synapse currently only gives out these tokens as part of the
|
||||||
# fall back to the fallback API if they don't understand one of the
|
# SSO login flow or as part of login via an existing session.
|
||||||
# login flow types returned.
|
#
|
||||||
flows.append({"type": LoginRestServlet.TOKEN_TYPE})
|
# Generally we don't want to advertise login flows that clients
|
||||||
|
# don't know how to implement, since they (currently) will always
|
||||||
|
# fall back to the fallback API if they don't understand one of the
|
||||||
|
# login flow types returned.
|
||||||
|
if support_login_token_flow:
|
||||||
|
tokenTypeFlow: Dict[str, Any] = {"type": LoginRestServlet.TOKEN_TYPE}
|
||||||
|
# If the login token flow is enabled advertise the get_login_token flag.
|
||||||
|
if self._get_login_token_enabled:
|
||||||
|
tokenTypeFlow["get_login_token"] = True
|
||||||
|
flows.append(tokenTypeFlow)
|
||||||
|
|
||||||
flows.extend({"type": t} for t in self.auth_handler.get_supported_login_types())
|
flows.extend({"type": t} for t in self.auth_handler.get_supported_login_types())
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Tuple
|
from typing import TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
|
from synapse.api.ratelimiting import Ratelimiter
|
||||||
from synapse.http.server import HttpServer
|
from synapse.http.server import HttpServer
|
||||||
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
|
@ -33,7 +34,7 @@ class LoginTokenRequestServlet(RestServlet):
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
||||||
POST /login/token HTTP/1.1
|
POST /login/get_token HTTP/1.1
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{}
|
{}
|
||||||
|
@ -43,30 +44,45 @@ class LoginTokenRequestServlet(RestServlet):
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
{
|
{
|
||||||
"login_token": "ABDEFGH",
|
"login_token": "ABDEFGH",
|
||||||
"expires_in": 3600,
|
"expires_in_ms": 3600000,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PATTERNS = client_patterns(
|
PATTERNS = [
|
||||||
"/org.matrix.msc3882/login/token$", releases=[], v1=False, unstable=True
|
*client_patterns(
|
||||||
)
|
"/login/get_token$", releases=["v1"], v1=False, unstable=False
|
||||||
|
),
|
||||||
|
# TODO: this is no longer needed once unstable MSC3882 does not need to be supported:
|
||||||
|
*client_patterns(
|
||||||
|
"/org.matrix.msc3882/login/token$", releases=[], v1=False, unstable=True
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
self.store = hs.get_datastores().main
|
self._main_store = hs.get_datastores().main
|
||||||
self.clock = hs.get_clock()
|
|
||||||
self.server_name = hs.config.server.server_name
|
|
||||||
self.auth_handler = hs.get_auth_handler()
|
self.auth_handler = hs.get_auth_handler()
|
||||||
self.token_timeout = hs.config.experimental.msc3882_token_timeout
|
self.token_timeout = hs.config.auth.login_via_existing_token_timeout
|
||||||
self.ui_auth = hs.config.experimental.msc3882_ui_auth
|
self._require_ui_auth = hs.config.auth.login_via_existing_require_ui_auth
|
||||||
|
|
||||||
|
# Ratelimit aggressively to a maxmimum of 1 request per minute.
|
||||||
|
#
|
||||||
|
# This endpoint can be used to spawn additional sessions and could be
|
||||||
|
# abused by a malicious client to create many sessions.
|
||||||
|
self._ratelimiter = Ratelimiter(
|
||||||
|
store=self._main_store,
|
||||||
|
clock=hs.get_clock(),
|
||||||
|
rate_hz=1 / 60,
|
||||||
|
burst_count=1,
|
||||||
|
)
|
||||||
|
|
||||||
@interactive_auth_handler
|
@interactive_auth_handler
|
||||||
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||||
requester = await self.auth.get_user_by_req(request)
|
requester = await self.auth.get_user_by_req(request)
|
||||||
body = parse_json_object_from_request(request)
|
body = parse_json_object_from_request(request)
|
||||||
|
|
||||||
if self.ui_auth:
|
if self._require_ui_auth:
|
||||||
await self.auth_handler.validate_user_via_ui_auth(
|
await self.auth_handler.validate_user_via_ui_auth(
|
||||||
requester,
|
requester,
|
||||||
request,
|
request,
|
||||||
|
@ -75,9 +91,12 @@ class LoginTokenRequestServlet(RestServlet):
|
||||||
can_skip_ui_auth=False, # Don't allow skipping of UI auth
|
can_skip_ui_auth=False, # Don't allow skipping of UI auth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ensure that this endpoint isn't being used too often. (Ensure this is
|
||||||
|
# done *after* UI auth.)
|
||||||
|
await self._ratelimiter.ratelimit(None, requester.user.to_string().lower())
|
||||||
|
|
||||||
login_token = await self.auth_handler.create_login_token_for_user_id(
|
login_token = await self.auth_handler.create_login_token_for_user_id(
|
||||||
user_id=requester.user.to_string(),
|
user_id=requester.user.to_string(),
|
||||||
auth_provider_id="org.matrix.msc3882.login_token_request",
|
|
||||||
duration_ms=self.token_timeout,
|
duration_ms=self.token_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,11 +104,13 @@ class LoginTokenRequestServlet(RestServlet):
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
"login_token": login_token,
|
"login_token": login_token,
|
||||||
|
# TODO: this is no longer needed once unstable MSC3882 does not need to be supported:
|
||||||
"expires_in": self.token_timeout // 1000,
|
"expires_in": self.token_timeout // 1000,
|
||||||
|
"expires_in_ms": self.token_timeout,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
||||||
if hs.config.experimental.msc3882_enabled:
|
if hs.config.auth.login_via_existing_enabled:
|
||||||
LoginTokenRequestServlet(hs).register(http_server)
|
LoginTokenRequestServlet(hs).register(http_server)
|
||||||
|
|
|
@ -113,8 +113,8 @@ class VersionsRestServlet(RestServlet):
|
||||||
"fi.mau.msc2815": self.config.experimental.msc2815_enabled,
|
"fi.mau.msc2815": self.config.experimental.msc2815_enabled,
|
||||||
# Adds a ping endpoint for appservices to check HS->AS connection
|
# Adds a ping endpoint for appservices to check HS->AS connection
|
||||||
"fi.mau.msc2659.stable": True, # TODO: remove when "v1.7" is added above
|
"fi.mau.msc2659.stable": True, # TODO: remove when "v1.7" is added above
|
||||||
# Adds support for login token requests as per MSC3882
|
# TODO: this is no longer needed once unstable MSC3882 does not need to be supported:
|
||||||
"org.matrix.msc3882": self.config.experimental.msc3882_enabled,
|
"org.matrix.msc3882": self.config.auth.login_via_existing_enabled,
|
||||||
# Adds support for remotely enabling/disabling pushers, as per MSC3881
|
# Adds support for remotely enabling/disabling pushers, as per MSC3881
|
||||||
"org.matrix.msc3881": self.config.experimental.msc3881_enabled,
|
"org.matrix.msc3881": self.config.experimental.msc3881_enabled,
|
||||||
# Adds support for filtering /messages by event relation.
|
# Adds support for filtering /messages by event relation.
|
||||||
|
|
|
@ -228,8 +228,8 @@ class MSC3861OAuthDelegation(TestCase):
|
||||||
with self.assertRaises(ConfigError):
|
with self.assertRaises(ConfigError):
|
||||||
self.parse_config()
|
self.parse_config()
|
||||||
|
|
||||||
def test_msc3882_auth_cannot_be_enabled(self) -> None:
|
def test_login_via_existing_session_cannot_be_enabled(self) -> None:
|
||||||
self.config_dict["experimental_features"]["msc3882_enabled"] = True
|
self.config_dict["login_via_existing_session"] = {"enabled": True}
|
||||||
with self.assertRaises(ConfigError):
|
with self.assertRaises(ConfigError):
|
||||||
self.parse_config()
|
self.parse_config()
|
||||||
|
|
||||||
|
|
|
@ -186,3 +186,31 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertGreater(len(details["support"]), 0)
|
self.assertGreater(len(details["support"]), 0)
|
||||||
for room_version in details["support"]:
|
for room_version in details["support"]:
|
||||||
self.assertTrue(room_version in KNOWN_ROOM_VERSIONS, str(room_version))
|
self.assertTrue(room_version in KNOWN_ROOM_VERSIONS, str(room_version))
|
||||||
|
|
||||||
|
def test_get_get_token_login_fields_when_disabled(self) -> None:
|
||||||
|
"""By default login via an existing session is disabled."""
|
||||||
|
access_token = self.get_success(
|
||||||
|
self.auth_handler.create_access_token_for_user_id(
|
||||||
|
self.user, device_id=None, valid_until_ms=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
channel = self.make_request("GET", self.url, access_token=access_token)
|
||||||
|
capabilities = channel.json_body["capabilities"]
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK)
|
||||||
|
self.assertFalse(capabilities["m.get_login_token"]["enabled"])
|
||||||
|
|
||||||
|
@override_config({"login_via_existing_session": {"enabled": True}})
|
||||||
|
def test_get_get_token_login_fields_when_enabled(self) -> None:
|
||||||
|
access_token = self.get_success(
|
||||||
|
self.auth_handler.create_access_token_for_user_id(
|
||||||
|
self.user, device_id=None, valid_until_ms=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
channel = self.make_request("GET", self.url, access_token=access_token)
|
||||||
|
capabilities = channel.json_body["capabilities"]
|
||||||
|
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK)
|
||||||
|
self.assertTrue(capabilities["m.get_login_token"]["enabled"])
|
||||||
|
|
|
@ -446,6 +446,29 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
|
||||||
ApprovalNoticeMedium.NONE, channel.json_body["approval_notice_medium"]
|
ApprovalNoticeMedium.NONE, channel.json_body["approval_notice_medium"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_login_flows_with_login_via_existing_disabled(self) -> None:
|
||||||
|
"""GET /login should return m.login.token without get_login_token"""
|
||||||
|
channel = self.make_request("GET", "/_matrix/client/r0/login")
|
||||||
|
self.assertEqual(channel.code, 200, channel.result)
|
||||||
|
|
||||||
|
flows = {flow["type"]: flow for flow in channel.json_body["flows"]}
|
||||||
|
self.assertNotIn("m.login.token", flows)
|
||||||
|
|
||||||
|
@override_config({"login_via_existing_session": {"enabled": True}})
|
||||||
|
def test_get_login_flows_with_login_via_existing_enabled(self) -> None:
|
||||||
|
"""GET /login should return m.login.token with get_login_token true"""
|
||||||
|
channel = self.make_request("GET", "/_matrix/client/r0/login")
|
||||||
|
self.assertEqual(channel.code, 200, channel.result)
|
||||||
|
|
||||||
|
self.assertCountEqual(
|
||||||
|
channel.json_body["flows"],
|
||||||
|
[
|
||||||
|
{"type": "m.login.token", "get_login_token": True},
|
||||||
|
{"type": "m.login.password"},
|
||||||
|
{"type": "m.login.application_service"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless(has_saml2 and HAS_OIDC, "Requires SAML2 and OIDC")
|
@skip_unless(has_saml2 and HAS_OIDC, "Requires SAML2 and OIDC")
|
||||||
class MultiSSOTestCase(unittest.HomeserverTestCase):
|
class MultiSSOTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
from twisted.test.proto_helpers import MemoryReactor
|
from twisted.test.proto_helpers import MemoryReactor
|
||||||
|
|
||||||
from synapse.rest import admin
|
from synapse.rest import admin
|
||||||
from synapse.rest.client import login, login_token_request
|
from synapse.rest.client import login, login_token_request, versions
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
from tests.unittest import override_config
|
from tests.unittest import override_config
|
||||||
|
|
||||||
endpoint = "/_matrix/client/unstable/org.matrix.msc3882/login/token"
|
GET_TOKEN_ENDPOINT = "/_matrix/client/v1/login/get_token"
|
||||||
|
|
||||||
|
|
||||||
class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
|
@ -30,6 +30,7 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
login.register_servlets,
|
login.register_servlets,
|
||||||
admin.register_servlets,
|
admin.register_servlets,
|
||||||
login_token_request.register_servlets,
|
login_token_request.register_servlets,
|
||||||
|
versions.register_servlets, # TODO: remove once unstable revision 0 support is removed
|
||||||
]
|
]
|
||||||
|
|
||||||
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
|
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
|
||||||
|
@ -46,26 +47,26 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
self.password = "password"
|
self.password = "password"
|
||||||
|
|
||||||
def test_disabled(self) -> None:
|
def test_disabled(self) -> None:
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=None)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=None)
|
||||||
self.assertEqual(channel.code, 404)
|
self.assertEqual(channel.code, 404)
|
||||||
|
|
||||||
self.register_user(self.user, self.password)
|
self.register_user(self.user, self.password)
|
||||||
token = self.login(self.user, self.password)
|
token = self.login(self.user, self.password)
|
||||||
|
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=token)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=token)
|
||||||
self.assertEqual(channel.code, 404)
|
self.assertEqual(channel.code, 404)
|
||||||
|
|
||||||
@override_config({"experimental_features": {"msc3882_enabled": True}})
|
@override_config({"login_via_existing_session": {"enabled": True}})
|
||||||
def test_require_auth(self) -> None:
|
def test_require_auth(self) -> None:
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=None)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=None)
|
||||||
self.assertEqual(channel.code, 401)
|
self.assertEqual(channel.code, 401)
|
||||||
|
|
||||||
@override_config({"experimental_features": {"msc3882_enabled": True}})
|
@override_config({"login_via_existing_session": {"enabled": True}})
|
||||||
def test_uia_on(self) -> None:
|
def test_uia_on(self) -> None:
|
||||||
user_id = self.register_user(self.user, self.password)
|
user_id = self.register_user(self.user, self.password)
|
||||||
token = self.login(self.user, self.password)
|
token = self.login(self.user, self.password)
|
||||||
|
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=token)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=token)
|
||||||
self.assertEqual(channel.code, 401)
|
self.assertEqual(channel.code, 401)
|
||||||
self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
|
self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
|
||||||
|
|
||||||
|
@ -80,9 +81,9 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
channel = self.make_request("POST", endpoint, uia, access_token=token)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, uia, access_token=token)
|
||||||
self.assertEqual(channel.code, 200)
|
self.assertEqual(channel.code, 200)
|
||||||
self.assertEqual(channel.json_body["expires_in"], 300)
|
self.assertEqual(channel.json_body["expires_in_ms"], 300000)
|
||||||
|
|
||||||
login_token = channel.json_body["login_token"]
|
login_token = channel.json_body["login_token"]
|
||||||
|
|
||||||
|
@ -95,15 +96,15 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(channel.json_body["user_id"], user_id)
|
self.assertEqual(channel.json_body["user_id"], user_id)
|
||||||
|
|
||||||
@override_config(
|
@override_config(
|
||||||
{"experimental_features": {"msc3882_enabled": True, "msc3882_ui_auth": False}}
|
{"login_via_existing_session": {"enabled": True, "require_ui_auth": False}}
|
||||||
)
|
)
|
||||||
def test_uia_off(self) -> None:
|
def test_uia_off(self) -> None:
|
||||||
user_id = self.register_user(self.user, self.password)
|
user_id = self.register_user(self.user, self.password)
|
||||||
token = self.login(self.user, self.password)
|
token = self.login(self.user, self.password)
|
||||||
|
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=token)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=token)
|
||||||
self.assertEqual(channel.code, 200)
|
self.assertEqual(channel.code, 200)
|
||||||
self.assertEqual(channel.json_body["expires_in"], 300)
|
self.assertEqual(channel.json_body["expires_in_ms"], 300000)
|
||||||
|
|
||||||
login_token = channel.json_body["login_token"]
|
login_token = channel.json_body["login_token"]
|
||||||
|
|
||||||
|
@ -117,10 +118,10 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
@override_config(
|
@override_config(
|
||||||
{
|
{
|
||||||
"experimental_features": {
|
"login_via_existing_session": {
|
||||||
"msc3882_enabled": True,
|
"enabled": True,
|
||||||
"msc3882_ui_auth": False,
|
"require_ui_auth": False,
|
||||||
"msc3882_token_timeout": "15s",
|
"token_timeout": "15s",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -128,6 +129,40 @@ class LoginTokenRequestServletTestCase(unittest.HomeserverTestCase):
|
||||||
self.register_user(self.user, self.password)
|
self.register_user(self.user, self.password)
|
||||||
token = self.login(self.user, self.password)
|
token = self.login(self.user, self.password)
|
||||||
|
|
||||||
channel = self.make_request("POST", endpoint, {}, access_token=token)
|
channel = self.make_request("POST", GET_TOKEN_ENDPOINT, {}, access_token=token)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self.assertEqual(channel.json_body["expires_in_ms"], 15000)
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"login_via_existing_session": {
|
||||||
|
"enabled": True,
|
||||||
|
"require_ui_auth": False,
|
||||||
|
"token_timeout": "15s",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_unstable_support(self) -> None:
|
||||||
|
# TODO: remove support for unstable MSC3882 is no longer needed
|
||||||
|
|
||||||
|
# check feature is advertised in versions response:
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET", "/_matrix/client/versions", {}, access_token=None
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["unstable_features"]["org.matrix.msc3882"], True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.register_user(self.user, self.password)
|
||||||
|
token = self.login(self.user, self.password)
|
||||||
|
|
||||||
|
# check feature is available via the unstable endpoint and returns an expires_in value in seconds
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
"/_matrix/client/unstable/org.matrix.msc3882/login/token",
|
||||||
|
{},
|
||||||
|
access_token=token,
|
||||||
|
)
|
||||||
self.assertEqual(channel.code, 200)
|
self.assertEqual(channel.code, 200)
|
||||||
self.assertEqual(channel.json_body["expires_in"], 15)
|
self.assertEqual(channel.json_body["expires_in"], 15)
|
||||||
|
|
Loading…
Reference in New Issue