1
0
Fork 0
mirror of https://github.com/element-hq/synapse.git synced 2025-03-05 07:26:52 +00:00

Merge branch 'release-v1.121' into matrix-org-hotfixes

This commit is contained in:
Andrew Morgan 2024-12-04 14:52:27 +00:00
commit 0fb2633a27
32 changed files with 418 additions and 56 deletions

View file

@ -5,7 +5,7 @@ name: Build release artifacts
on: on:
# we build on PRs and develop to (hopefully) get early warning # we build on PRs and develop to (hopefully) get early warning
# of things breaking (but only build one set of debs). PRs skip # of things breaking (but only build one set of debs). PRs skip
# building wheels on ARM. # building wheels on macOS & ARM.
pull_request: pull_request:
push: push:
branches: ["develop", "release-*"] branches: ["develop", "release-*"]
@ -111,7 +111,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-22.04] os: [ubuntu-22.04, macos-13]
arch: [x86_64, aarch64] arch: [x86_64, aarch64]
# is_pr is a flag used to exclude certain jobs from the matrix on PRs. # is_pr is a flag used to exclude certain jobs from the matrix on PRs.
# It is not read by the rest of the workflow. # It is not read by the rest of the workflow.
@ -119,6 +119,12 @@ jobs:
- ${{ startsWith(github.ref, 'refs/pull/') }} - ${{ startsWith(github.ref, 'refs/pull/') }}
exclude: exclude:
# Don't build macos wheels on PR CI.
- is_pr: true
os: "macos-13"
# Don't build aarch64 wheels on mac.
- os: "macos-13"
arch: aarch64
# Don't build aarch64 wheels on PR CI. # Don't build aarch64 wheels on PR CI.
- is_pr: true - is_pr: true
arch: aarch64 arch: aarch64
@ -207,7 +213,7 @@ jobs:
tar -cvJf debs.tar.xz debs tar -cvJf debs.tar.xz debs
- name: Attach to release - name: Attach to release
# Pinned to work around https://github.com/softprops/action-gh-release/issues/445 # Pinned to work around https://github.com/softprops/action-gh-release/issues/445
uses: softprops/action-gh-release@v2.0.5 uses: softprops/action-gh-release@v0.1.15
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:

View file

@ -1,3 +1,50 @@
# Synapse 1.121.0rc1 (2024-12-04)
This release candidate contains the security fixes from [v1.120.2](https://github.com/element-hq/synapse/releases/tag/v1.120.2).
New changes listed below.
### Features
- Support for [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190): device management for Application Services. ([\#17705](https://github.com/element-hq/synapse/issues/17705))
- Update [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) Sliding Sync to include invite, ban, kick, targets when `$LAZY`-loading room members. ([\#17947](https://github.com/element-hq/synapse/issues/17947))
- Use stable `M_USER_LOCKED` error code for locked accounts, as per [Matrix 1.12](https://spec.matrix.org/v1.12/client-server-api/#account-locking). ([\#17965](https://github.com/element-hq/synapse/issues/17965))
- [MSC4076](https://github.com/matrix-org/matrix-spec-proposals/pull/4076): Add `disable_badge_count` to pusher configuration. ([\#17975](https://github.com/element-hq/synapse/issues/17975))
### Bugfixes
- Fix long-standing bug where read receipts could get overly delayed being sent over federation. ([\#17933](https://github.com/element-hq/synapse/issues/17933))
### Improved Documentation
- Add OIDC example configuration for Forgejo (fork of Gitea). ([\#17872](https://github.com/element-hq/synapse/issues/17872))
- Link to element-docker-demo from contrib/docker*. ([\#17953](https://github.com/element-hq/synapse/issues/17953))
### Internal Changes
- [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108): Add a `Content-Type` header on the `PUT` response to work around a faulty behavior in some caching reverse proxies. ([\#17253](https://github.com/element-hq/synapse/issues/17253))
- Fix incorrect comment in new schema delta. ([\#17936](https://github.com/element-hq/synapse/issues/17936))
- Raise setuptools_rust version cap to 1.10.2. ([\#17944](https://github.com/element-hq/synapse/issues/17944))
- Enable encrypted appservice related experimental features in the complement docker image. ([\#17945](https://github.com/element-hq/synapse/issues/17945))
- Return whether the user is suspended when querying the user account in the Admin API. ([\#17952](https://github.com/element-hq/synapse/issues/17952))
- Fix new scheduled tasks jumping the queue. ([\#17962](https://github.com/element-hq/synapse/issues/17962))
- Bump pyo3 and dependencies to v0.23.2. ([\#17966](https://github.com/element-hq/synapse/issues/17966))
- Update setuptools-rust and fix building abi3 wheels in latest version. ([\#17969](https://github.com/element-hq/synapse/issues/17969))
- Consolidate SSO redirects through `/_matrix/client/v3/login/sso/redirect(/{idpId})`. ([\#17972](https://github.com/element-hq/synapse/issues/17972))
- Fix Docker and Complement config to be able to use `public_baseurl`. ([\#17986](https://github.com/element-hq/synapse/issues/17986))
- Fix building wheels for MacOS which was temporarily disabled in Synapse 1.120.2. ([\#17993](https://github.com/element-hq/synapse/issues/17993))
- Fix release process to not create duplicate releases. ([\#17970](https://github.com/element-hq/synapse/issues/17970), [\#17995](https://github.com/element-hq/synapse/issues/17995))
### Updates to locked dependencies
* Bump bytes from 1.8.0 to 1.9.0. ([\#17982](https://github.com/element-hq/synapse/issues/17982))
* Bump pysaml2 from 7.3.1 to 7.5.0. ([\#17978](https://github.com/element-hq/synapse/issues/17978))
* Bump serde_json from 1.0.132 to 1.0.133. ([\#17939](https://github.com/element-hq/synapse/issues/17939))
* Bump tomli from 2.0.2 to 2.1.0. ([\#17959](https://github.com/element-hq/synapse/issues/17959))
* Bump tomli from 2.1.0 to 2.2.1. ([\#17979](https://github.com/element-hq/synapse/issues/17979))
* Bump tornado from 6.4.1 to 6.4.2. ([\#17955](https://github.com/element-hq/synapse/issues/17955))
# Synapse 1.120.2 (2024-12-03) # Synapse 1.120.2 (2024-12-03)
This version has building of wheels for macOS disabled. This version has building of wheels for macOS disabled.

View file

@ -1 +0,0 @@
[MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108): Add a `Content-Type` header on the `PUT` response to work around a faulty behavior in some caching reverse proxies.

View file

@ -1 +0,0 @@
Add OIDC example configuration for Forgejo (fork of Gitea).

View file

@ -1 +0,0 @@
Fix long-standing bug where read receipts could get overly delayed being sent over federation.

View file

@ -1 +0,0 @@
Fix incorrect comment in new schema delta.

View file

@ -1 +0,0 @@
Raise setuptools_rust version cap to 1.10.2.

View file

@ -1 +0,0 @@
Enable encrypted appservice related experimental features in the complement docker image.

View file

@ -1 +0,0 @@
Update [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) Sliding Sync to include invite, ban, kick, targets when `$LAZY`-loading room members.

View file

@ -1 +0,0 @@
Return whether the user is suspended when querying the user account in the Admin API.

View file

@ -1 +0,0 @@
Link to element-docker-demo from contrib/docker*.

View file

@ -1 +0,0 @@
Fix new scheduled tasks jumping the queue.

View file

@ -1 +0,0 @@
Use stable `M_USER_LOCKED` error code for locked accounts, as per [Matrix 1.12](https://spec.matrix.org/v1.12/client-server-api/#account-locking).

View file

@ -1 +0,0 @@
Bump pyo3 and dependencies to v0.23.2.

View file

@ -1 +0,0 @@
Update setuptools-rust and fix building abi3 wheels in latest version.

View file

@ -1 +0,0 @@
Fix release process to not create duplicate releases.

View file

@ -1 +0,0 @@
Consolidate SSO redirects through `/_matrix/client/v3/login/sso/redirect(/{idpId})`.

View file

@ -1 +0,0 @@
[MSC4076](https://github.com/matrix-org/matrix-spec-proposals/pull/4076): Add `disable_badge_count` to pusher configuration.

View file

@ -1 +0,0 @@
Fix Docker and Complement config to be able to use `public_baseurl`.

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
matrix-synapse-py3 (1.121.0~rc1) stable; urgency=medium
* New Synapse release 1.121.0rc1.
-- Synapse Packaging team <packages@matrix.org> Wed, 04 Dec 2024 14:47:23 +0000
matrix-synapse-py3 (1.120.2) stable; urgency=medium matrix-synapse-py3 (1.120.2) stable; urgency=medium
* New synapse release 1.120.2. * New synapse release 1.120.2.

View file

@ -97,7 +97,7 @@ module-name = "synapse.synapse_rust"
[tool.poetry] [tool.poetry]
name = "matrix-synapse" name = "matrix-synapse"
version = "1.120.2" version = "1.121.0rc1"
description = "Homeserver for the Matrix decentralised comms protocol" description = "Homeserver for the Matrix decentralised comms protocol"
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"] authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
@ -386,8 +386,11 @@ build-backend = "poetry.core.masonry.api"
# c.f. https://github.com/matrix-org/synapse/pull/14259 # c.f. https://github.com/matrix-org/synapse/pull/14259
skip = "cp36* cp37* cp38* pp37* pp38* *-musllinux_i686 pp*aarch64 *-musllinux_aarch64" skip = "cp36* cp37* cp38* pp37* pp38* *-musllinux_i686 pp*aarch64 *-musllinux_aarch64"
# We need a rust compiler # We need a rust compiler.
before-all = "curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y --profile minimal" #
# We temporarily pin Rust to 1.82.0 to work around
# https://github.com/element-hq/synapse/issues/17988
before-all = "curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.82.0 -y --profile minimal"
environment= { PATH = "$PATH:$HOME/.cargo/bin" } environment= { PATH = "$PATH:$HOME/.cargo/bin" }
# For some reason if we don't manually clean the build directory we # For some reason if we don't manually clean the build directory we

View file

@ -87,6 +87,7 @@ class ApplicationService:
ip_range_whitelist: Optional[IPSet] = None, ip_range_whitelist: Optional[IPSet] = None,
supports_ephemeral: bool = False, supports_ephemeral: bool = False,
msc3202_transaction_extensions: bool = False, msc3202_transaction_extensions: bool = False,
msc4190_device_management: bool = False,
): ):
self.token = token self.token = token
self.url = ( self.url = (
@ -100,6 +101,7 @@ class ApplicationService:
self.ip_range_whitelist = ip_range_whitelist self.ip_range_whitelist = ip_range_whitelist
self.supports_ephemeral = supports_ephemeral self.supports_ephemeral = supports_ephemeral
self.msc3202_transaction_extensions = msc3202_transaction_extensions self.msc3202_transaction_extensions = msc3202_transaction_extensions
self.msc4190_device_management = msc4190_device_management
if "|" in self.id: if "|" in self.id:
raise Exception("application service ID cannot contain '|' character") raise Exception("application service ID cannot contain '|' character")

View file

@ -183,6 +183,18 @@ def _load_appservice(
"The `org.matrix.msc3202` option should be true or false if specified." "The `org.matrix.msc3202` option should be true or false if specified."
) )
# Opt-in flag for the MSC4190 behaviours.
# When enabled, the following C-S API endpoints change for appservices:
# - POST /register does not return an access token
# - PUT /devices/{device_id} creates a new device if one does not exist
# - DELETE /devices/{device_id} no longer requires UIA
# - POST /delete_devices/{device_id} no longer requires UIA
msc4190_enabled = as_info.get("io.element.msc4190", False)
if not isinstance(msc4190_enabled, bool):
raise ValueError(
"The `io.element.msc4190` option should be true or false if specified."
)
return ApplicationService( return ApplicationService(
token=as_info["as_token"], token=as_info["as_token"],
url=as_info["url"], url=as_info["url"],
@ -195,4 +207,5 @@ def _load_appservice(
ip_range_whitelist=ip_range_whitelist, ip_range_whitelist=ip_range_whitelist,
supports_ephemeral=supports_ephemeral, supports_ephemeral=supports_ephemeral,
msc3202_transaction_extensions=msc3202_transaction_extensions, msc3202_transaction_extensions=msc3202_transaction_extensions,
msc4190_device_management=msc4190_enabled,
) )

View file

@ -729,6 +729,40 @@ class DeviceHandler(DeviceWorkerHandler):
await self.notify_device_update(user_id, device_ids) await self.notify_device_update(user_id, device_ids)
async def upsert_device(
self, user_id: str, device_id: str, display_name: Optional[str] = None
) -> bool:
"""Create or update a device
Args:
user_id: The user to update devices of.
device_id: The device to update.
display_name: The new display name for this device.
Returns:
True if the device was created, False if it was updated.
"""
# Reject a new displayname which is too long.
self._check_device_name_length(display_name)
created = await self.store.store_device(
user_id,
device_id,
initial_device_display_name=display_name,
)
if not created:
await self.store.update_device(
user_id,
device_id,
new_display_name=display_name,
)
await self.notify_device_update(user_id, [device_id])
return created
async def update_device(self, user_id: str, device_id: str, content: dict) -> None: async def update_device(self, user_id: str, device_id: str, content: dict) -> None:
"""Update the given device """Update the given device

View file

@ -630,7 +630,9 @@ class RegistrationHandler:
""" """
await self._auto_join_rooms(user_id) await self._auto_join_rooms(user_id)
async def appservice_register(self, user_localpart: str, as_token: str) -> str: async def appservice_register(
self, user_localpart: str, as_token: str
) -> Tuple[str, ApplicationService]:
user = UserID(user_localpart, self.hs.hostname) user = UserID(user_localpart, self.hs.hostname)
user_id = user.to_string() user_id = user.to_string()
service = self.store.get_app_service_by_token(as_token) service = self.store.get_app_service_by_token(as_token)
@ -653,7 +655,7 @@ class RegistrationHandler:
appservice_id=service_id, appservice_id=service_id,
create_profile_with_displayname=user.localpart, create_profile_with_displayname=user.localpart,
) )
return user_id return (user_id, service)
def check_user_id_not_appservice_exclusive( def check_user_id_not_appservice_exclusive(
self, user_id: str, allowed_appservice: Optional[ApplicationService] = None self, user_id: str, allowed_appservice: Optional[ApplicationService] = None

View file

@ -114,6 +114,10 @@ class DeleteDevicesRestServlet(RestServlet):
else: else:
raise e raise e
if requester.app_service and requester.app_service.msc4190_device_management:
# MSC4190 can skip UIA for this endpoint
pass
else:
await self.auth_handler.validate_user_via_ui_auth( await self.auth_handler.validate_user_via_ui_auth(
requester, requester,
request, request,
@ -175,9 +179,6 @@ class DeviceRestServlet(RestServlet):
async def on_DELETE( async def on_DELETE(
self, request: SynapseRequest, device_id: str self, request: SynapseRequest, device_id: str
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
if self._msc3861_oauth_delegation_enabled:
raise UnrecognizedRequestError(code=404)
requester = await self.auth.get_user_by_req(request) requester = await self.auth.get_user_by_req(request)
try: try:
@ -192,6 +193,15 @@ class DeviceRestServlet(RestServlet):
else: else:
raise raise
if requester.app_service and requester.app_service.msc4190_device_management:
# MSC4190 allows appservices to delete devices through this endpoint without UIA
# It's also allowed with MSC3861 enabled
pass
else:
if self._msc3861_oauth_delegation_enabled:
raise UnrecognizedRequestError(code=404)
await self.auth_handler.validate_user_via_ui_auth( await self.auth_handler.validate_user_via_ui_auth(
requester, requester,
request, request,
@ -216,6 +226,16 @@ class DeviceRestServlet(RestServlet):
requester = await self.auth.get_user_by_req(request, allow_guest=True) requester = await self.auth.get_user_by_req(request, allow_guest=True)
body = parse_and_validate_json_object_from_request(request, self.PutBody) body = parse_and_validate_json_object_from_request(request, self.PutBody)
# MSC4190 allows appservices to create devices through this endpoint
if requester.app_service and requester.app_service.msc4190_device_management:
created = await self.device_handler.upsert_device(
user_id=requester.user.to_string(),
device_id=device_id,
display_name=body.display_name,
)
return 201 if created else 200, {}
await self.device_handler.update_device( await self.device_handler.update_device(
requester.user.to_string(), device_id, body.dict() requester.user.to_string(), device_id, body.dict()
) )

View file

@ -771,9 +771,12 @@ class RegisterRestServlet(RestServlet):
body: JsonDict, body: JsonDict,
should_issue_refresh_token: bool = False, should_issue_refresh_token: bool = False,
) -> JsonDict: ) -> JsonDict:
user_id = await self.registration_handler.appservice_register( user_id, appservice = await self.registration_handler.appservice_register(
username, as_token username, as_token
) )
if appservice.msc4190_device_management:
body["inhibit_login"] = True
return await self._create_registration_details( return await self._create_registration_details(
user_id, user_id,
body, body,
@ -937,7 +940,7 @@ class RegisterAppServiceOnlyRestServlet(RestServlet):
as_token = self.auth.get_access_token_from_request(request) as_token = self.auth.get_access_token_from_request(request)
user_id = await self.registration_handler.appservice_register( user_id, _ = await self.registration_handler.appservice_register(
desired_username, as_token desired_username, as_token
) )
return 200, {"user_id": user_id} return 200, {"user_id": user_id}

View file

@ -1165,12 +1165,23 @@ class ApplicationServicesHandlerOtkCountsTestCase(unittest.HomeserverTestCase):
self.hs.get_datastores().main.services_cache = [self._service] self.hs.get_datastores().main.services_cache = [self._service]
# Register some appservice users # Register some appservice users
self._sender_user, self._sender_device = self.register_appservice_user( user_id, device_id = self.register_appservice_user(
"as.sender", self._service_token "as.sender", self._service_token
) )
self._namespaced_user, self._namespaced_device = self.register_appservice_user( # With MSC4190 enabled, there will not be a device created
# during AS registration. However MSC4190 is not enabled
# in this test. It may become the default behaviour in the
# future, in which case this test will need to be updated.
assert device_id is not None
self._sender_user = user_id
self._sender_device = device_id
user_id, device_id = self.register_appservice_user(
"_as_user1", self._service_token "_as_user1", self._service_token
) )
assert device_id is not None
self._namespaced_user = user_id
self._namespaced_device = device_id
# Register a real user as well. # Register a real user as well.
self._real_user = self.register_user("real.user", "meow") self._real_user = self.register_user("real.user", "meow")

View file

@ -560,9 +560,15 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
self.assertEqual(channel.code, 401, channel.json_body) self.assertEqual(channel.code, 401, channel.json_body)
def expect_unrecognized( def expect_unrecognized(
self, method: str, path: str, content: Union[bytes, str, JsonDict] = "" self,
method: str,
path: str,
content: Union[bytes, str, JsonDict] = "",
auth: bool = False,
) -> None: ) -> None:
channel = self.make_request(method, path, content) channel = self.make_request(
method, path, content, access_token="token" if auth else None
)
self.assertEqual(channel.code, 404, channel.json_body) self.assertEqual(channel.code, 404, channel.json_body)
self.assertEqual( self.assertEqual(
@ -648,8 +654,25 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
def test_device_management_endpoints_removed(self) -> None: def test_device_management_endpoints_removed(self) -> None:
"""Test that device management endpoints that were removed in MSC2964 are no longer available.""" """Test that device management endpoints that were removed in MSC2964 are no longer available."""
self.expect_unrecognized("POST", "/_matrix/client/v3/delete_devices")
self.expect_unrecognized("DELETE", "/_matrix/client/v3/devices/{DEVICE}") # Because we still support those endpoints with ASes, it checks the
# access token before returning 404
self.http_client.request = AsyncMock(
return_value=FakeResponse.json(
code=200,
payload={
"active": True,
"sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]),
"username": USERNAME,
},
)
)
self.expect_unrecognized("POST", "/_matrix/client/v3/delete_devices", auth=True)
self.expect_unrecognized(
"DELETE", "/_matrix/client/v3/devices/{DEVICE}", auth=True
)
def test_openid_endpoints_removed(self) -> None: def test_openid_endpoints_removed(self) -> None:
"""Test that OpenID id_token endpoints that were removed in MSC2964 are no longer available.""" """Test that OpenID id_token endpoints that were removed in MSC2964 are no longer available."""

View file

@ -24,6 +24,7 @@ from twisted.internet.defer import ensureDeferred
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
from synapse.api.errors import NotFoundError from synapse.api.errors import NotFoundError
from synapse.appservice import ApplicationService
from synapse.rest import admin, devices, sync from synapse.rest import admin, devices, sync
from synapse.rest.client import keys, login, register from synapse.rest.client import keys, login, register
from synapse.server import HomeServer from synapse.server import HomeServer
@ -455,3 +456,183 @@ class DehydratedDeviceTestCase(unittest.HomeserverTestCase):
token, token,
) )
self.assertEqual(channel.json_body["device_keys"], {"@mikey:test": {}}) self.assertEqual(channel.json_body["device_keys"], {"@mikey:test": {}})
class MSC4190AppserviceDevicesTestCase(unittest.HomeserverTestCase):
servlets = [
register.register_servlets,
devices.register_servlets,
]
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
self.hs = self.setup_test_homeserver()
# This application service uses the new MSC4190 behaviours
self.msc4190_service = ApplicationService(
id="msc4190",
token="some_token",
hs_token="some_token",
sender="@as:example.com",
namespaces={
ApplicationService.NS_USERS: [{"regex": "@.*", "exclusive": False}]
},
msc4190_device_management=True,
)
# This application service doesn't use the new MSC4190 behaviours
self.pre_msc_service = ApplicationService(
id="regular",
token="other_token",
hs_token="other_token",
sender="@as2:example.com",
namespaces={
ApplicationService.NS_USERS: [{"regex": "@.*", "exclusive": False}]
},
msc4190_device_management=False,
)
self.hs.get_datastores().main.services_cache.append(self.msc4190_service)
self.hs.get_datastores().main.services_cache.append(self.pre_msc_service)
return self.hs
def test_PUT_device(self) -> None:
self.register_appservice_user("alice", self.msc4190_service.token)
self.register_appservice_user("bob", self.pre_msc_service.token)
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(channel.json_body, {"devices": []})
channel = self.make_request(
"PUT",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@alice:test",
content={"display_name": "Alice's device"},
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 201, channel.json_body)
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(len(channel.json_body["devices"]), 1)
self.assertEqual(channel.json_body["devices"][0]["device_id"], "AABBCCDD")
# Doing a second time should return a 200 instead of a 201
channel = self.make_request(
"PUT",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@alice:test",
content={"display_name": "Alice's device"},
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
# On the regular service, that API should not allow for the
# creation of new devices.
channel = self.make_request(
"PUT",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@bob:test",
content={"display_name": "Bob's device"},
access_token=self.pre_msc_service.token,
)
self.assertEqual(channel.code, 404, channel.json_body)
def test_DELETE_device(self) -> None:
self.register_appservice_user("alice", self.msc4190_service.token)
# There should be no device
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(channel.json_body, {"devices": []})
# Create a device
channel = self.make_request(
"PUT",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@alice:test",
content={},
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 201, channel.json_body)
# There should be one device
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(len(channel.json_body["devices"]), 1)
# Delete the device. UIA should not be required.
channel = self.make_request(
"DELETE",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
# There should be no device again
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(channel.json_body, {"devices": []})
def test_POST_delete_devices(self) -> None:
self.register_appservice_user("alice", self.msc4190_service.token)
# There should be no device
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(channel.json_body, {"devices": []})
# Create a device
channel = self.make_request(
"PUT",
"/_matrix/client/v3/devices/AABBCCDD?user_id=@alice:test",
content={},
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 201, channel.json_body)
# There should be one device
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(len(channel.json_body["devices"]), 1)
# Delete the device with delete_devices
# UIA should not be required.
channel = self.make_request(
"POST",
"/_matrix/client/v3/delete_devices?user_id=@alice:test",
content={"devices": ["AABBCCDD"]},
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
# There should be no device again
channel = self.make_request(
"GET",
"/_matrix/client/v3/devices?user_id=@alice:test",
access_token=self.msc4190_service.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
self.assertEqual(channel.json_body, {"devices": []})

View file

@ -120,6 +120,34 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 401, msg=channel.result) self.assertEqual(channel.code, 401, msg=channel.result)
def test_POST_appservice_msc4190_enabled(self) -> None:
# With MSC4190 enabled, the registration should *not* return an access token
user_id = "@as_user_kermit:test"
as_token = "i_am_an_app_service"
appservice = ApplicationService(
as_token,
id="1234",
namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
sender="@as:test",
msc4190_device_management=True,
)
self.hs.get_datastores().main.services_cache.append(appservice)
request_data = {
"username": "as_user_kermit",
"type": APP_SERVICE_REGISTRATION_TYPE,
}
channel = self.make_request(
b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
)
self.assertEqual(channel.code, 200, msg=channel.result)
det_data = {"user_id": user_id, "home_server": self.hs.hostname}
self.assertLessEqual(det_data.items(), channel.json_body.items())
self.assertNotIn("access_token", channel.json_body)
def test_POST_bad_password(self) -> None: def test_POST_bad_password(self) -> None:
request_data = {"username": "kermit", "password": 666} request_data = {"username": "kermit", "password": 666}
channel = self.make_request(b"POST", self.url, request_data) channel = self.make_request(b"POST", self.url, request_data)

View file

@ -781,7 +781,7 @@ class HomeserverTestCase(TestCase):
self, self,
username: str, username: str,
appservice_token: str, appservice_token: str,
) -> Tuple[str, str]: ) -> Tuple[str, Optional[str]]:
"""Register an appservice user as an application service. """Register an appservice user as an application service.
Requires the client-facing registration API be registered. Requires the client-facing registration API be registered.
@ -805,7 +805,7 @@ class HomeserverTestCase(TestCase):
access_token=appservice_token, access_token=appservice_token,
) )
self.assertEqual(channel.code, 200, channel.json_body) self.assertEqual(channel.code, 200, channel.json_body)
return channel.json_body["user_id"], channel.json_body["device_id"] return channel.json_body["user_id"], channel.json_body.get("device_id")
def login( def login(
self, self,