diff --git a/changelog.d/10283.feature b/changelog.d/10283.feature
new file mode 100644
index 0000000000..99d633dbfb
--- /dev/null
+++ b/changelog.d/10283.feature
@@ -0,0 +1 @@
+Initial support for MSC3244, Room version capabilities over the /capabilities API.
\ No newline at end of file
diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py
index a20abc5a65..8dd33dcb83 100644
--- a/synapse/api/room_versions.py
+++ b/synapse/api/room_versions.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from typing import Dict
+from typing import Callable, Dict, Optional
import attr
@@ -208,5 +208,39 @@ KNOWN_ROOM_VERSIONS: Dict[str, RoomVersion] = {
RoomVersions.MSC3083,
RoomVersions.V7,
)
- # Note that we do not include MSC2043 here unless it is enabled in the config.
+}
+
+
+@attr.s(slots=True, frozen=True, auto_attribs=True)
+class RoomVersionCapability:
+ """An object which describes the unique attributes of a room version."""
+
+ identifier: str # the identifier for this capability
+ preferred_version: Optional[RoomVersion]
+ support_check_lambda: Callable[[RoomVersion], bool]
+
+
+MSC3244_CAPABILITIES = {
+ cap.identifier: {
+ "preferred": cap.preferred_version.identifier
+ if cap.preferred_version is not None
+ else None,
+ "support": [
+ v.identifier
+ for v in KNOWN_ROOM_VERSIONS.values()
+ if cap.support_check_lambda(v)
+ ],
+ }
+ for cap in (
+ RoomVersionCapability(
+ "knock",
+ RoomVersions.V7,
+ lambda room_version: room_version.msc2403_knocking,
+ ),
+ RoomVersionCapability(
+ "restricted",
+ None,
+ lambda room_version: room_version.msc3083_join_rules,
+ ),
+ )
}
diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py
index e25ccba9ac..040c4504d8 100644
--- a/synapse/config/experimental.py
+++ b/synapse/config/experimental.py
@@ -32,3 +32,6 @@ class ExperimentalConfig(Config):
# MSC2716 (backfill existing history)
self.msc2716_enabled: bool = experimental.get("msc2716_enabled", False)
+
+ # MSC3244 (room version capabilities)
+ self.msc3244_enabled: bool = experimental.get("msc3244_enabled", False)
diff --git a/synapse/rest/client/v2_alpha/capabilities.py b/synapse/rest/client/v2_alpha/capabilities.py
index 6a24021484..88e3aac797 100644
--- a/synapse/rest/client/v2_alpha/capabilities.py
+++ b/synapse/rest/client/v2_alpha/capabilities.py
@@ -14,7 +14,7 @@
import logging
from typing import TYPE_CHECKING, Tuple
-from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
+from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, MSC3244_CAPABILITIES
from synapse.http.servlet import RestServlet
from synapse.http.site import SynapseRequest
from synapse.types import JsonDict
@@ -55,6 +55,12 @@ class CapabilitiesRestServlet(RestServlet):
"m.change_password": {"enabled": change_password},
}
}
+
+ if self.config.experimental.msc3244_enabled:
+ response["capabilities"]["m.room_versions"][
+ "org.matrix.msc3244.room_capabilities"
+ ] = MSC3244_CAPABILITIES
+
return 200, response
diff --git a/tests/rest/client/v2_alpha/test_capabilities.py b/tests/rest/client/v2_alpha/test_capabilities.py
index 874052c61c..f80f48a455 100644
--- a/tests/rest/client/v2_alpha/test_capabilities.py
+++ b/tests/rest/client/v2_alpha/test_capabilities.py
@@ -102,3 +102,49 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 200)
self.assertFalse(capabilities["m.change_password"]["enabled"])
+
+ def test_get_does_not_include_msc3244_fields_by_default(self):
+ localpart = "user"
+ password = "pass"
+ user = self.register_user(localpart, password)
+ access_token = self.get_success(
+ self.auth_handler.get_access_token_for_user_id(
+ 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, 200)
+ self.assertNotIn(
+ "org.matrix.msc3244.room_capabilities", capabilities["m.room_versions"]
+ )
+
+ @override_config({"experimental_features": {"msc3244_enabled": True}})
+ def test_get_does_include_msc3244_fields_when_enabled(self):
+ localpart = "user"
+ password = "pass"
+ user = self.register_user(localpart, password)
+ access_token = self.get_success(
+ self.auth_handler.get_access_token_for_user_id(
+ 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, 200)
+ for details in capabilities["m.room_versions"][
+ "org.matrix.msc3244.room_capabilities"
+ ].values():
+ if details["preferred"] is not None:
+ self.assertTrue(
+ details["preferred"] in KNOWN_ROOM_VERSIONS,
+ str(details["preferred"]),
+ )
+
+ self.assertGreater(len(details["support"]), 0)
+ for room_version in details["support"]:
+ self.assertTrue(room_version in KNOWN_ROOM_VERSIONS, str(room_version))
|