diff --git a/synapse/rest/client/_base.py b/synapse/rest/client/_base.py
index a0971ce994..b4cb90cb76 100644
--- a/synapse/rest/client/_base.py
+++ b/synapse/rest/client/_base.py
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
def client_patterns(
path_regex: str,
- releases: Iterable[int] = (0,),
+ releases: Iterable[str] = ("r0", "v3"),
unstable: bool = True,
v1: bool = False,
) -> Iterable[Pattern]:
@@ -52,7 +52,7 @@ def client_patterns(
v1_prefix = CLIENT_API_PREFIX + "/api/v1"
patterns.append(re.compile("^" + v1_prefix + path_regex))
for release in releases:
- new_prefix = CLIENT_API_PREFIX + "/r%d" % (release,)
+ new_prefix = CLIENT_API_PREFIX + f"/{release}"
patterns.append(re.compile("^" + new_prefix + path_regex))
return patterns
diff --git a/synapse/rest/client/keys.py b/synapse/rest/client/keys.py
index 7281b2ee29..730c18f08f 100644
--- a/synapse/rest/client/keys.py
+++ b/synapse/rest/client/keys.py
@@ -262,7 +262,7 @@ class SigningKeyUploadServlet(RestServlet):
}
"""
- PATTERNS = client_patterns("/keys/device_signing/upload$", releases=())
+ PATTERNS = client_patterns("/keys/device_signing/upload$", releases=("v3",))
def __init__(self, hs: "HomeServer"):
super().__init__()
diff --git a/synapse/rest/client/login.py b/synapse/rest/client/login.py
index 467444a041..09f378f919 100644
--- a/synapse/rest/client/login.py
+++ b/synapse/rest/client/login.py
@@ -14,7 +14,17 @@
import logging
import re
-from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Tuple
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Awaitable,
+ Callable,
+ Dict,
+ List,
+ Optional,
+ Tuple,
+ Union,
+)
from typing_extensions import TypedDict
@@ -28,7 +38,6 @@ from synapse.http.server import HttpServer, finish_request
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
- parse_boolean,
parse_bytes_from_args,
parse_json_object_from_request,
parse_string,
@@ -72,6 +81,7 @@ class LoginRestServlet(RestServlet):
# JWT configuration variables.
self.jwt_enabled = hs.config.jwt.jwt_enabled
self.jwt_secret = hs.config.jwt.jwt_secret
+ self.jwt_subject_claim = hs.config.jwt.jwt_subject_claim
self.jwt_algorithm = hs.config.jwt.jwt_algorithm
self.jwt_issuer = hs.config.jwt.jwt_issuer
self.jwt_audiences = hs.config.jwt.jwt_audiences
@@ -80,7 +90,9 @@ class LoginRestServlet(RestServlet):
self.saml2_enabled = hs.config.saml2.saml2_enabled
self.cas_enabled = hs.config.cas.cas_enabled
self.oidc_enabled = hs.config.oidc.oidc_enabled
- self._msc2918_enabled = hs.config.registration.access_token_lifetime is not None
+ self._msc2918_enabled = (
+ hs.config.registration.refreshable_access_token_lifetime is not None
+ )
self.auth = hs.get_auth()
@@ -152,11 +164,14 @@ class LoginRestServlet(RestServlet):
login_submission = parse_json_object_from_request(request)
if self._msc2918_enabled:
- # Check if this login should also issue a refresh token, as per
- # MSC2918
- should_issue_refresh_token = parse_boolean(
- request, name=LoginRestServlet.REFRESH_TOKEN_PARAM, default=False
+ # Check if this login should also issue a refresh token, as per MSC2918
+ should_issue_refresh_token = login_submission.get(
+ "org.matrix.msc2918.refresh_token", False
)
+ if not isinstance(should_issue_refresh_token, bool):
+ raise SynapseError(
+ 400, "`org.matrix.msc2918.refresh_token` should be true or false."
+ )
else:
should_issue_refresh_token = False
@@ -413,7 +428,7 @@ class LoginRestServlet(RestServlet):
errcode=Codes.FORBIDDEN,
)
- user = payload.get("sub", None)
+ user = payload.get(self.jwt_subject_claim, None)
if user is None:
raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN)
@@ -452,7 +467,10 @@ class RefreshTokenServlet(RestServlet):
def __init__(self, hs: "HomeServer"):
self._auth_handler = hs.get_auth_handler()
self._clock = hs.get_clock()
- self.access_token_lifetime = hs.config.registration.access_token_lifetime
+ self.refreshable_access_token_lifetime = (
+ hs.config.registration.refreshable_access_token_lifetime
+ )
+ self.refresh_token_lifetime = hs.config.registration.refresh_token_lifetime
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
refresh_submission = parse_json_object_from_request(request)
@@ -462,20 +480,33 @@ class RefreshTokenServlet(RestServlet):
if not isinstance(token, str):
raise SynapseError(400, "Invalid param: refresh_token", Codes.INVALID_PARAM)
- valid_until_ms = self._clock.time_msec() + self.access_token_lifetime
- access_token, refresh_token = await self._auth_handler.refresh_token(
- token, valid_until_ms
- )
- expires_in_ms = valid_until_ms - self._clock.time_msec()
- return (
- 200,
- {
- "access_token": access_token,
- "refresh_token": refresh_token,
- "expires_in_ms": expires_in_ms,
- },
+ now = self._clock.time_msec()
+ access_valid_until_ms = None
+ if self.refreshable_access_token_lifetime is not None:
+ access_valid_until_ms = now + self.refreshable_access_token_lifetime
+ refresh_valid_until_ms = None
+ if self.refresh_token_lifetime is not None:
+ refresh_valid_until_ms = now + self.refresh_token_lifetime
+
+ (
+ access_token,
+ refresh_token,
+ actual_access_token_expiry,
+ ) = await self._auth_handler.refresh_token(
+ token, access_valid_until_ms, refresh_valid_until_ms
)
+ response: Dict[str, Union[str, int]] = {
+ "access_token": access_token,
+ "refresh_token": refresh_token,
+ }
+
+ # expires_in_ms is only present if the token expires
+ if actual_access_token_expiry is not None:
+ response["expires_in_ms"] = actual_access_token_expiry - now
+
+ return 200, response
+
class SsoRedirectServlet(RestServlet):
PATTERNS = list(client_patterns("/login/(cas|sso)/redirect$", v1=True)) + [
@@ -561,7 +592,7 @@ class CasTicketServlet(RestServlet):
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
LoginRestServlet(hs).register(http_server)
- if hs.config.registration.access_token_lifetime is not None:
+ if hs.config.registration.refreshable_access_token_lifetime is not None:
RefreshTokenServlet(hs).register(http_server)
SsoRedirectServlet(hs).register(http_server)
if hs.config.cas.cas_enabled:
diff --git a/synapse/rest/client/register.py b/synapse/rest/client/register.py
index bf3cb34146..11fd6cd24d 100644
--- a/synapse/rest/client/register.py
+++ b/synapse/rest/client/register.py
@@ -41,7 +41,6 @@ from synapse.http.server import HttpServer, finish_request, respond_with_html
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
- parse_boolean,
parse_json_object_from_request,
parse_string,
)
@@ -420,7 +419,9 @@ class RegisterRestServlet(RestServlet):
self.password_policy_handler = hs.get_password_policy_handler()
self.clock = hs.get_clock()
self._registration_enabled = self.hs.config.registration.enable_registration
- self._msc2918_enabled = hs.config.registration.access_token_lifetime is not None
+ self._msc2918_enabled = (
+ hs.config.registration.refreshable_access_token_lifetime is not None
+ )
self._registration_flows = _calculate_registration_flows(
hs.config, self.auth_handler
@@ -447,9 +448,13 @@ class RegisterRestServlet(RestServlet):
if self._msc2918_enabled:
# Check if this registration should also issue a refresh token, as
# per MSC2918
- should_issue_refresh_token = parse_boolean(
- request, name="org.matrix.msc2918.refresh_token", default=False
+ should_issue_refresh_token = body.get(
+ "org.matrix.msc2918.refresh_token", False
)
+ if not isinstance(should_issue_refresh_token, bool):
+ raise SynapseError(
+ 400, "`org.matrix.msc2918.refresh_token` should be true or false."
+ )
else:
should_issue_refresh_token = False
diff --git a/synapse/rest/client/relations.py b/synapse/rest/client/relations.py
index 184cfbe196..45e9f1dd90 100644
--- a/synapse/rest/client/relations.py
+++ b/synapse/rest/client/relations.py
@@ -224,17 +224,17 @@ class RelationPaginationServlet(RestServlet):
)
now = self.clock.time_msec()
- # We set bundle_aggregations to False when retrieving the original
+ # We set bundle_relations to False when retrieving the original
# event because we want the content before relations were applied to
# it.
original_event = await self._event_serializer.serialize_event(
- event, now, bundle_aggregations=False
+ event, now, bundle_relations=False
)
# Similarly, we don't allow relations to be applied to relations, so we
# return the original relations without any aggregations on top of them
# here.
serialized_events = await self._event_serializer.serialize_events(
- events, now, bundle_aggregations=False
+ events, now, bundle_relations=False
)
return_value = pagination_chunk.to_dict()
diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py
index 03a353d53c..73d0f7c950 100644
--- a/synapse/rest/client/room.py
+++ b/synapse/rest/client/room.py
@@ -719,7 +719,7 @@ class RoomEventContextServlet(RestServlet):
results["state"],
time_now,
# No need to bundle aggregations for state events
- bundle_aggregations=False,
+ bundle_relations=False,
)
return 200, results
@@ -1138,12 +1138,12 @@ class RoomSpaceSummaryRestServlet(RestServlet):
class RoomHierarchyRestServlet(RestServlet):
- PATTERNS = (
+ PATTERNS = [
re.compile(
- "^/_matrix/client/unstable/org.matrix.msc2946"
+ "^/_matrix/client/(v1|unstable/org.matrix.msc2946)"
"/rooms/(?P<room_id>[^/]*)/hierarchy$"
),
- )
+ ]
def __init__(self, hs: "HomeServer"):
super().__init__()
@@ -1168,7 +1168,7 @@ class RoomHierarchyRestServlet(RestServlet):
)
return 200, await self._room_summary_handler.get_room_hierarchy(
- requester.user.to_string(),
+ requester,
room_id,
suggested_only=parse_boolean(request, "suggested_only", default=False),
max_depth=max_depth,
diff --git a/synapse/rest/client/sync.py b/synapse/rest/client/sync.py
index 8c0fdb1940..b6a2485732 100644
--- a/synapse/rest/client/sync.py
+++ b/synapse/rest/client/sync.py
@@ -522,7 +522,7 @@ class SyncRestServlet(RestServlet):
time_now=time_now,
# We don't bundle "live" events, as otherwise clients
# will end up double counting annotations.
- bundle_aggregations=False,
+ bundle_relations=False,
token_id=token_id,
event_format=event_formatter,
only_event_fields=only_fields,
|