mirror of
https://github.com/element-hq/synapse.git
synced 2026-11-04 06:57:20 +00:00
Allow session TTL to be configured
This commit is contained in:
1 parent
90e3a7b79b
commit
6b7998d6b7
4 files changed
+80
-5
No files matched your search
@@ -248,6 +248,19 @@ class ExperimentalConfig(Config):
|
||||
("experimental", "msc4388_enabled"),
|
||||
)
|
||||
|
||||
# How long a rendezvous session lasts before it expires. MSC4388
|
||||
# recommends at least 2 minutes for usability, to allow the user time to
|
||||
# complete the sign in including any extra steps such as MFA, and at
|
||||
# most 5 minutes for security.
|
||||
self.msc4388_session_ttl_ms = self.parse_duration(
|
||||
experimental.get("msc4388_session_ttl", "2m")
|
||||
)
|
||||
if not 2 * 60 * 1000 <= self.msc4388_session_ttl_ms <= 5 * 60 * 1000:
|
||||
raise ConfigError(
|
||||
"msc4388_session_ttl must be between 2 and 5 minutes",
|
||||
("experimental", "msc4388_session_ttl"),
|
||||
)
|
||||
|
||||
# MSC4388 requests are ratelimited by client IP address, as most are
|
||||
# unauthenticated: only discovery and creating a session may carry an
|
||||
# access token, and even then it is not used for ratelimiting.
|
||||
@@ -255,8 +268,8 @@ class ExperimentalConfig(Config):
|
||||
# Creating a session is limited more strictly than other requests, as
|
||||
# there is a cap on the number of concurrent sessions, and creating new
|
||||
# sessions beyond it evicts the oldest ones. A client using these
|
||||
# defaults can hold at most around 20 live sessions at once, as each
|
||||
# session expires after 2 minutes.
|
||||
# defaults can hold at most around 20 live sessions at once with the
|
||||
# default session TTL of 2 minutes, or 40 with the maximum of 5 minutes.
|
||||
self.msc4388_rc_create = RatelimitSettings.parse(
|
||||
experimental,
|
||||
"msc4388_rc_create",
|
||||
|
||||
+3
-1
@@ -1209,7 +1209,9 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
|
||||
@cache_in_self
|
||||
def get_msc4388_rendezvous_handler(self) -> MSC4388RendezvousHandler:
|
||||
return MSC4388RendezvousHandler(self)
|
||||
return MSC4388RendezvousHandler(
|
||||
self, ttl=self.config.experimental.msc4388_session_ttl_ms
|
||||
)
|
||||
|
||||
@cache_in_self
|
||||
def get_outbound_redis_connection(self) -> "ConnectionHandler":
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import yaml
|
||||
from parameterized import parameterized
|
||||
|
||||
from synapse.config import ConfigError
|
||||
from synapse.config._base import RootConfig
|
||||
from synapse.config.experimental import ExperimentalConfig
|
||||
from synapse.config.homeserver import HomeServerConfig
|
||||
@@ -56,9 +57,39 @@ class ExperimentalConfigTestCase(unittest.TestCase):
|
||||
|
||||
_read_config(config_values)
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
["default", None, 2 * 60 * 1000],
|
||||
["minimum", "2m", 2 * 60 * 1000],
|
||||
["maximum", "5m", 5 * 60 * 1000],
|
||||
["milliseconds", 200000, 200000],
|
||||
]
|
||||
)
|
||||
def test_msc4388_session_ttl(
|
||||
self, test_description: str, ttl: str | int | None, expected_ms: int
|
||||
) -> None:
|
||||
"""
|
||||
Test that the MSC4388 session TTL is parsed as a duration.
|
||||
"""
|
||||
experimental_features = {} if ttl is None else {"msc4388_session_ttl": ttl}
|
||||
config = _read_config({"experimental_features": experimental_features})
|
||||
self.assertEqual(config.msc4388_session_ttl_ms, expected_ms)
|
||||
|
||||
def _read_config(config_values: JsonDict) -> None:
|
||||
ExperimentalConfig(RootConfig()).read_config(
|
||||
@parameterized.expand([["too_short", "119s"], ["too_long", "301s"]])
|
||||
def test_msc4388_session_ttl_out_of_range(
|
||||
self, test_description: str, ttl: str
|
||||
) -> None:
|
||||
"""
|
||||
Test that an MSC4388 session TTL outside of the range recommended by the
|
||||
MSC is rejected.
|
||||
"""
|
||||
with self.assertRaises(ConfigError):
|
||||
_read_config({"experimental_features": {"msc4388_session_ttl": ttl}})
|
||||
|
||||
|
||||
def _read_config(config_values: JsonDict) -> ExperimentalConfig:
|
||||
config = ExperimentalConfig(RootConfig())
|
||||
config.read_config(
|
||||
yaml.safe_load(
|
||||
HomeServerConfig().generate_config(
|
||||
config_dir_path="CONFDIR",
|
||||
@@ -69,3 +100,4 @@ def _read_config(config_values: JsonDict) -> None:
|
||||
| config_values,
|
||||
allow_secrets_in_config=False,
|
||||
)
|
||||
return config
|
||||
@@ -622,6 +622,34 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
|
||||
)
|
||||
self.assertEqual(channel.code, 404)
|
||||
|
||||
@override_config({"experimental_features": {"msc4388_session_ttl": "5m"}})
|
||||
def test_configured_ttl(self) -> None:
|
||||
"""
|
||||
Test that sessions expire after the configured TTL.
|
||||
"""
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
rz_endpoint,
|
||||
{"data": "foo=bar"},
|
||||
access_token=None,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["expires_in_ms"], 5 * 60 * 1000)
|
||||
session_endpoint = rz_endpoint + "/" + channel.json_body["id"]
|
||||
|
||||
# The session is still there after the default TTL of 2 minutes...
|
||||
self.reactor.advance(120)
|
||||
channel = self.make_request("GET", session_endpoint, access_token=None)
|
||||
self.assertEqual(channel.code, 200)
|
||||
# (allowing for the time taken by the requests)
|
||||
self.assertLessEqual(channel.json_body["expires_in_ms"], 3 * 60 * 1000)
|
||||
self.assertGreater(channel.json_body["expires_in_ms"], 3 * 60 * 1000 - 1000)
|
||||
|
||||
# ...but is gone after the configured TTL of 5 minutes
|
||||
self.reactor.advance(180)
|
||||
channel = self.make_request("GET", session_endpoint, access_token=None)
|
||||
self.assertEqual(channel.code, 404)
|
||||
|
||||
def test_delete_expired(self) -> None:
|
||||
"""
|
||||
Test that deleting a session which has expired, but has not yet been
|
||||
|
||||
Reference in new issue
Block a user