diff --git a/synapse/rest/client/devices.py b/synapse/rest/client/devices.py
index 8313d687b7..0b075cc2f2 100644
--- a/synapse/rest/client/devices.py
+++ b/synapse/rest/client/devices.py
@@ -24,13 +24,7 @@ import logging
from http import HTTPStatus
from typing import TYPE_CHECKING, List, Optional, Tuple
-from synapse._pydantic_compat import HAS_PYDANTIC_V2
-
-if TYPE_CHECKING or HAS_PYDANTIC_V2:
- from pydantic.v1 import Extra, StrictStr
-else:
- from pydantic import Extra, StrictStr
-
+from synapse._pydantic_compat import Extra, StrictStr
from synapse.api import errors
from synapse.api.errors import NotFoundError, SynapseError, UnrecognizedRequestError
from synapse.handlers.device import DeviceHandler
@@ -120,15 +114,19 @@ class DeleteDevicesRestServlet(RestServlet):
else:
raise e
- await self.auth_handler.validate_user_via_ui_auth(
- requester,
- request,
- body.dict(exclude_unset=True),
- "remove device(s) from your account",
- # Users might call this multiple times in a row while cleaning up
- # devices, allow a single UI auth session to be re-used.
- can_skip_ui_auth=True,
- )
+ 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(
+ requester,
+ request,
+ body.dict(exclude_unset=True),
+ "remove device(s) from your account",
+ # Users might call this multiple times in a row while cleaning up
+ # devices, allow a single UI auth session to be re-used.
+ can_skip_ui_auth=True,
+ )
await self.device_handler.delete_devices(
requester.user.to_string(), body.devices
@@ -145,11 +143,11 @@ class DeviceRestServlet(RestServlet):
self.hs = hs
self.auth = hs.get_auth()
handler = hs.get_device_handler()
- assert isinstance(handler, DeviceHandler)
self.device_handler = handler
self.auth_handler = hs.get_auth_handler()
self._msc3852_enabled = hs.config.experimental.msc3852_enabled
self._msc3861_oauth_delegation_enabled = hs.config.experimental.msc3861.enabled
+ self._is_main_process = hs.config.worker.worker_app is None
async def on_GET(
self, request: SynapseRequest, device_id: str
@@ -181,8 +179,13 @@ class DeviceRestServlet(RestServlet):
async def on_DELETE(
self, request: SynapseRequest, device_id: str
) -> Tuple[int, JsonDict]:
- if self._msc3861_oauth_delegation_enabled:
- raise UnrecognizedRequestError(code=404)
+ # Can only be run on main process, as changes to device lists must
+ # happen on main.
+ if not self._is_main_process:
+ error_message = "DELETE on /devices/ must be routed to main process"
+ logger.error(error_message)
+ raise SynapseError(500, error_message)
+ assert isinstance(self.device_handler, DeviceHandler)
requester = await self.auth.get_user_by_req(request)
@@ -198,15 +201,24 @@ class DeviceRestServlet(RestServlet):
else:
raise
- await self.auth_handler.validate_user_via_ui_auth(
- requester,
- request,
- body.dict(exclude_unset=True),
- "remove a device from your account",
- # Users might call this multiple times in a row while cleaning up
- # devices, allow a single UI auth session to be re-used.
- can_skip_ui_auth=True,
- )
+ 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(
+ requester,
+ request,
+ body.dict(exclude_unset=True),
+ "remove a device from your account",
+ # Users might call this multiple times in a row while cleaning up
+ # devices, allow a single UI auth session to be re-used.
+ can_skip_ui_auth=True,
+ )
await self.device_handler.delete_devices(
requester.user.to_string(), [device_id]
@@ -219,9 +231,27 @@ class DeviceRestServlet(RestServlet):
async def on_PUT(
self, request: SynapseRequest, device_id: str
) -> Tuple[int, JsonDict]:
+ # Can only be run on main process, as changes to device lists must
+ # happen on main.
+ if not self._is_main_process:
+ error_message = "PUT on /devices/ must be routed to main process"
+ logger.error(error_message)
+ raise SynapseError(500, error_message)
+ assert isinstance(self.device_handler, DeviceHandler)
+
requester = await self.auth.get_user_by_req(request, allow_guest=True)
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(
requester.user.to_string(), device_id, body.dict()
)
@@ -571,9 +601,9 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
):
DeleteDevicesRestServlet(hs).register(http_server)
DevicesRestServlet(hs).register(http_server)
+ DeviceRestServlet(hs).register(http_server)
if hs.config.worker.worker_app is None:
- DeviceRestServlet(hs).register(http_server)
if hs.config.experimental.msc2697_enabled:
DehydratedDeviceServlet(hs).register(http_server)
ClaimDehydratedDeviceServlet(hs).register(http_server)
|