def register_txn_path(servlet, regex_string, http_server, with_get=False): """Registers a transaction-based path. This registers two paths: PUT regex_string/$txnid POST regex_string Args: regex_string (str): The regex string to register. Must NOT have a trailing $ as this string will be appended to. http_server : The http_server to register paths with. with_get: True to also register respective GET paths for the PUTs. """ http_server.register_paths( "POST", client_patterns(regex_string + "$", v1=True), servlet.on_POST, servlet.__class__.__name__, ) http_server.register_paths( "PUT", client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True), servlet.on_PUT, servlet.__class__.__name__, ) if with_get: http_server.register_paths( "GET", client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True), servlet.on_GET, servlet.__class__.__name__, )
def register(self, http_server: HttpServer) -> None: # /room/$roomid/state/$eventtype no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$" # /room/$roomid/state/$eventtype/$statekey state_key = ("/rooms/(?P<room_id>[^/]*)/state/" "(?P<event_type>[^/]*)/(?P<state_key>[^/]*)$") http_server.register_paths( "GET", client_patterns(state_key, v1=True), self.on_GET, self.__class__.__name__, ) http_server.register_paths( "PUT", client_patterns(state_key, v1=True), self.on_PUT, self.__class__.__name__, ) http_server.register_paths( "GET", client_patterns(no_state_key, v1=True), self.on_GET_no_state_key, self.__class__.__name__, ) http_server.register_paths( "PUT", client_patterns(no_state_key, v1=True), self.on_PUT_no_state_key, self.__class__.__name__, )
class WhoisRestServlet(RestServlet): path_regex = "/whois/(?P<user_id>[^/]*)$" PATTERNS = [ *admin_patterns(path_regex), # URL for spec reason # https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid *client_patterns("/admin" + path_regex, v1=True), ] def __init__(self, hs: "HomeServer"): self.auth = hs.get_auth() self.admin_handler = hs.get_admin_handler() self.is_mine = hs.is_mine async def on_GET(self, request: SynapseRequest, user_id: str) -> Tuple[int, JsonDict]: target_user = UserID.from_string(user_id) requester = await self.auth.get_user_by_req(request) auth_user = requester.user if target_user != auth_user: await assert_user_is_admin(self.auth, auth_user) if not self.is_mine(target_user): raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only whois a local user") ret = await self.admin_handler.get_whois(target_user) return HTTPStatus.OK, ret
class ClientDirectoryListServer(RestServlet): PATTERNS = client_patterns("/directory/list/room/(?P<room_id>[^/]*)$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.store = hs.get_datastores().main self.directory_handler = hs.get_directory_handler() self.auth = hs.get_auth() async def on_GET(self, request: Request, room_id: str) -> Tuple[int, JsonDict]: room = await self.store.get_room(room_id) if room is None: raise NotFoundError("Unknown room") return 200, { "visibility": "public" if room["is_public"] else "private" } async def on_PUT(self, request: SynapseRequest, room_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) content = parse_json_object_from_request(request) visibility = content.get("visibility", "public") await self.directory_handler.edit_published_room_list( requester, room_id, visibility) return 200, {}
class RoomEventServlet(RestServlet): PATTERNS = client_patterns( "/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.clock = hs.get_clock() self.event_handler = hs.get_event_handler() self._event_serializer = hs.get_event_client_serializer() self.auth = hs.get_auth() async def on_GET(self, request: SynapseRequest, room_id: str, event_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) try: event = await self.event_handler.get_event(requester.user, room_id, event_id) except AuthError: # This endpoint is supposed to return a 404 when the requester does # not have permission to access the event # https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-rooms-roomid-event-eventid raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() if event: event_dict = await self._event_serializer.serialize_event( event, time_now) return 200, event_dict raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
class RefreshTokenServlet(RestServlet): PATTERNS = client_patterns("/org.matrix.msc2918.refresh_token/refresh$", releases=(), unstable=True) 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 async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: refresh_submission = parse_json_object_from_request(request) assert_params_in_dict(refresh_submission, ["refresh_token"]) token = refresh_submission["refresh_token"] 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, }, )
class ProfileRestServlet(RestServlet): PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.profile_handler = hs.get_profile_handler() self.auth = hs.get_auth() async def on_GET( self, request: SynapseRequest, user_id: str ) -> Tuple[int, JsonDict]: requester_user = None if self.hs.config.server.require_auth_for_profile_requests: requester = await self.auth.get_user_by_req(request) requester_user = requester.user user = UserID.from_string(user_id) await self.profile_handler.check_profile_query_allowed(user, requester_user) displayname = await self.profile_handler.get_displayname(user) avatar_url = await self.profile_handler.get_avatar_url(user) ret = {} if displayname is not None: ret["displayname"] = displayname if avatar_url is not None: ret["avatar_url"] = avatar_url return 200, ret
class ClientAppserviceDirectoryListServer(RestServlet): PATTERNS = client_patterns( "/directory/list/appservice/(?P<network_id>[^/]*)/(?P<room_id>[^/]*)$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.store = hs.get_datastores().main self.directory_handler = hs.get_directory_handler() self.auth = hs.get_auth() async def on_PUT(self, request: SynapseRequest, network_id: str, room_id: str) -> Tuple[int, JsonDict]: content = parse_json_object_from_request(request) visibility = content.get("visibility", "public") return await self._edit(request, network_id, room_id, visibility) async def on_DELETE(self, request: SynapseRequest, network_id: str, room_id: str) -> Tuple[int, JsonDict]: return await self._edit(request, network_id, room_id, "private") async def _edit(self, request: SynapseRequest, network_id: str, room_id: str, visibility: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) if not requester.app_service: raise AuthError( 403, "Only appservices can edit the appservice published room list") await self.directory_handler.edit_published_appservice_room_list( requester.app_service.id, network_id, room_id, visibility) return 200, {}
def register_txn_path( servlet: RestServlet, regex_string: str, http_server: HttpServer, with_get: bool = False, ) -> None: """Registers a transaction-based path. This registers two paths: PUT regex_string/$txnid POST regex_string Args: regex_string: The regex string to register. Must NOT have a trailing $ as this string will be appended to. http_server: The http_server to register paths with. with_get: True to also register respective GET paths for the PUTs. """ on_POST = getattr(servlet, "on_POST", None) on_PUT = getattr(servlet, "on_PUT", None) if on_POST is None or on_PUT is None: raise RuntimeError( "on_POST and on_PUT must exist when using register_txn_path") http_server.register_paths( "POST", client_patterns(regex_string + "$", v1=True), on_POST, servlet.__class__.__name__, ) http_server.register_paths( "PUT", client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True), on_PUT, servlet.__class__.__name__, ) on_GET = getattr(servlet, "on_GET", None) if with_get: if on_GET is None: raise RuntimeError( "register_txn_path called with with_get = True, but no on_GET method exists" ) http_server.register_paths( "GET", client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True), on_GET, servlet.__class__.__name__, )
class RelationPaginationServlet(RestServlet): """API to paginate relations on an event by topological ordering, optionally filtered by relation type and event type. """ PATTERNS = client_patterns( "/rooms/(?P<room_id>[^/]*)/relations/(?P<parent_id>[^/]*)" "(/(?P<relation_type>[^/]*)(/(?P<event_type>[^/]*))?)?$", releases=("v1", ), ) def __init__(self, hs: "HomeServer"): super().__init__() self.auth = hs.get_auth() self.store = hs.get_datastores().main self._relations_handler = hs.get_relations_handler() async def on_GET( self, request: SynapseRequest, room_id: str, parent_id: str, relation_type: Optional[str] = None, event_type: Optional[str] = None, ) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) limit = parse_integer(request, "limit", default=5) direction = parse_string(request, "org.matrix.msc3715.dir", default="b", allowed_values=["f", "b"]) from_token_str = parse_string(request, "from") to_token_str = parse_string(request, "to") # Return the relations from_token = None if from_token_str: from_token = await StreamToken.from_string(self.store, from_token_str) to_token = None if to_token_str: to_token = await StreamToken.from_string(self.store, to_token_str) result = await self._relations_handler.get_relations( requester=requester, event_id=parent_id, room_id=room_id, relation_type=relation_type, event_type=event_type, limit=limit, direction=direction, from_token=from_token, to_token=to_token, ) return 200, result
class DeviceRestServlet(RestServlet): PATTERNS = client_patterns("/devices/(?P<device_id>[^/]*)$") def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() self.device_handler = hs.get_device_handler() self.auth_handler = hs.get_auth_handler() async def on_GET(self, request: SynapseRequest, device_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) device = await self.device_handler.get_device( requester.user.to_string(), device_id) if device is None: raise NotFoundError("No device found") return 200, device @interactive_auth_handler async def on_DELETE(self, request: SynapseRequest, device_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) try: body = parse_json_object_from_request(request) except errors.SynapseError as e: if e.errcode == errors.Codes.NOT_JSON: # deal with older clients which didn't pass a JSON dict # the same as those that pass an empty dict body = {} else: raise await self.auth_handler.validate_user_via_ui_auth( requester, request, body, "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_device(requester.user.to_string(), device_id) return 200, {} async def on_PUT(self, request: SynapseRequest, device_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) body = parse_json_object_from_request(request) await self.device_handler.update_device(requester.user.to_string(), device_id, body) return 200, {}
class ProfileDisplaynameRestServlet(RestServlet): PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)/displayname", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.profile_handler = hs.get_profile_handler() self.auth = hs.get_auth() async def on_GET(self, request: SynapseRequest, user_id: str) -> Tuple[int, JsonDict]: requester_user = None if self.hs.config.server.require_auth_for_profile_requests: requester = await self.auth.get_user_by_req(request) requester_user = requester.user if not UserID.is_valid(user_id): raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM) user = UserID.from_string(user_id) await self.profile_handler.check_profile_query_allowed( user, requester_user) displayname = await self.profile_handler.get_displayname(user) ret = {} if displayname is not None: ret["displayname"] = displayname return 200, ret async def on_PUT(self, request: SynapseRequest, user_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) user = UserID.from_string(user_id) is_admin = await self.auth.is_server_admin(requester.user) content = parse_json_object_from_request(request) try: new_name = content["displayname"] except Exception: raise SynapseError( code=400, msg="Unable to parse name", errcode=Codes.BAD_JSON, ) await self.profile_handler.set_displayname(user, requester, new_name, is_admin) return 200, {}
class ClaimDehydratedDeviceServlet(RestServlet): """Claim a dehydrated device. POST /org.matrix.msc2697.v2/dehydrated_device/claim Content-Type: application/json { "device_id": "dehydrated_device_id" } HTTP/1.1 200 OK Content-Type: application/json { "success": true, } """ PATTERNS = client_patterns( "/org.matrix.msc2697.v2/dehydrated_device/claim", releases=() ) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() self.device_handler = hs.get_device_handler() async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) submission = parse_json_object_from_request(request) if "device_id" not in submission: raise errors.SynapseError( 400, "device_id missing", errcode=errors.Codes.MISSING_PARAM, ) elif not isinstance(submission["device_id"], str): raise errors.SynapseError( 400, "device_id must be a string", errcode=errors.Codes.INVALID_PARAM, ) result = await self.device_handler.rehydrate_device( requester.user.to_string(), self.auth.get_access_token_from_request(request), submission["device_id"], ) return 200, result
class SsoRedirectServlet(RestServlet): PATTERNS = list(client_patterns("/login/(cas|sso)/redirect$", v1=True)) + [ re.compile("^" + CLIENT_API_PREFIX + "/r0/login/sso/redirect/(?P<idp_id>[A-Za-z0-9_.~-]+)$") ] def __init__(self, hs: "HomeServer"): # make sure that the relevant handlers are instantiated, so that they # register themselves with the main SSOHandler. _load_sso_handlers(hs) self._sso_handler = hs.get_sso_handler() self._public_baseurl = hs.config.server.public_baseurl async def on_GET(self, request: SynapseRequest, idp_id: Optional[str] = None) -> None: if not self._public_baseurl: raise SynapseError(400, "SSO requires a valid public_baseurl") # if this isn't the expected hostname, redirect to the right one, so that we # get our cookies back. requested_uri = get_request_uri(request) baseurl_bytes = self._public_baseurl.encode("utf-8") if not requested_uri.startswith(baseurl_bytes): # swap out the incorrect base URL for the right one. # # The idea here is to redirect from # https://foo.bar/whatever/_matrix/... # to # https://public.baseurl/_matrix/... # i = requested_uri.index(b"/_matrix") new_uri = baseurl_bytes[:-1] + requested_uri[i:] logger.info( "Requested URI %s is not canonical: redirecting to %s", requested_uri.decode("utf-8", errors="replace"), new_uri.decode("utf-8", errors="replace"), ) request.redirect(new_uri) finish_request(request) return args: Dict[bytes, List[bytes]] = request.args # type: ignore client_redirect_url = parse_bytes_from_args(args, "redirectUrl", required=True) sso_url = await self._sso_handler.handle_redirect_request( request, client_redirect_url, idp_id, ) logger.info("Redirecting to %s", sso_url) request.redirect(sso_url) finish_request(request)
class ProfileAvatarURLRestServlet(RestServlet): PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)/avatar_url", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.profile_handler = hs.get_profile_handler() self.auth = hs.get_auth() async def on_GET( self, request: SynapseRequest, user_id: str ) -> Tuple[int, JsonDict]: requester_user = None if self.hs.config.server.require_auth_for_profile_requests: requester = await self.auth.get_user_by_req(request) requester_user = requester.user user = UserID.from_string(user_id) await self.profile_handler.check_profile_query_allowed(user, requester_user) avatar_url = await self.profile_handler.get_avatar_url(user) ret = {} if avatar_url is not None: ret["avatar_url"] = avatar_url return 200, ret async def on_PUT( self, request: SynapseRequest, user_id: str ) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) user = UserID.from_string(user_id) is_admin = await self.auth.is_server_admin(requester.user) content = parse_json_object_from_request(request) try: new_avatar_url = content["avatar_url"] except KeyError: raise SynapseError( 400, "Missing key 'avatar_url'", errcode=Codes.MISSING_PARAM ) await self.profile_handler.set_avatar_url( user, requester, new_avatar_url, is_admin ) return 200, {} def on_OPTIONS(self, request: SynapseRequest, user_id: str) -> Tuple[int, JsonDict]: return 200, {}
class RoomTypingRestServlet(RestServlet): PATTERNS = client_patterns( "/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.presence_handler = hs.get_presence_handler() self.auth = hs.get_auth() # If we're not on the typing writer instance we should scream if we get # requests. self._is_typing_writer = (hs.get_instance_name() in hs.config.worker.writers.typing) async def on_PUT(self, request: SynapseRequest, room_id: str, user_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) if not self._is_typing_writer: raise Exception( "Got /typing request on instance that is not typing writer") room_id = urlparse.unquote(room_id) target_user = UserID.from_string(urlparse.unquote(user_id)) content = parse_json_object_from_request(request) await self.presence_handler.bump_presence_active_time(requester.user) # Limit timeout to stop people from setting silly typing timeouts. timeout = min(content.get("timeout", 30000), 120000) # Defer getting the typing handler since it will raise on workers. typing_handler = self.hs.get_typing_writer_handler() try: if content["typing"]: await typing_handler.started_typing( target_user=target_user, requester=requester, room_id=room_id, timeout=timeout, ) else: await typing_handler.stopped_typing(target_user=target_user, requester=requester, room_id=room_id) except ShadowBanError: # Pretend this worked without error. pass return 200, {}
class JoinedRoomsRestServlet(RestServlet): PATTERNS = client_patterns("/joined_rooms$", v1=True) def __init__(self, hs): super().__init__() self.store = hs.get_datastore() self.auth = hs.get_auth() async def on_GET(self, request): requester = await self.auth.get_user_by_req(request, allow_guest=True) room_ids = await self.store.get_rooms_for_user(requester.user.to_string()) return 200, {"joined_rooms": list(room_ids)}
class RoomEventContextServlet(RestServlet): PATTERNS = client_patterns( "/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.clock = hs.get_clock() self.room_context_handler = hs.get_room_context_handler() self._event_serializer = hs.get_event_client_serializer() self.auth = hs.get_auth() async def on_GET(self, request: SynapseRequest, room_id: str, event_id: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) limit = parse_integer(request, "limit", default=10) # picking the API shape for symmetry with /messages filter_str = parse_string(request, "filter", encoding="utf-8") if filter_str: filter_json = urlparse.unquote(filter_str) event_filter: Optional[Filter] = Filter( json_decoder.decode(filter_json)) else: event_filter = None results = await self.room_context_handler.get_event_context( requester, room_id, event_id, limit, event_filter) if not results: raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() results[ "events_before"] = await self._event_serializer.serialize_events( results["events_before"], time_now) results["event"] = await self._event_serializer.serialize_event( results["event"], time_now) results[ "events_after"] = await self._event_serializer.serialize_events( results["events_after"], time_now) results["state"] = await self._event_serializer.serialize_events( results["state"], time_now, # No need to bundle aggregations for state events bundle_aggregations=False, ) return 200, results
class RoomMemberListRestServlet(RestServlet): PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/members$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.message_handler = hs.get_message_handler() self.auth = hs.get_auth() self.store = hs.get_datastores().main @cancellable async def on_GET(self, request: SynapseRequest, room_id: str) -> Tuple[int, JsonDict]: # TODO support Pagination stream API (limit/tokens) requester = await self.auth.get_user_by_req(request, allow_guest=True) handler = self.message_handler # request the state as of a given event, as identified by a stream token, # for consistency with /messages etc. # useful for getting the membership in retrospect as of a given /sync # response. at_token_string = parse_string(request, "at") if at_token_string is None: at_token = None else: at_token = await StreamToken.from_string(self.store, at_token_string) # let you filter down on particular memberships. # XXX: this may not be the best shape for this API - we could pass in a filter # instead, except filters aren't currently aware of memberships. # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details. membership = parse_string(request, "membership") not_membership = parse_string(request, "not_membership") events = await handler.get_state_events( room_id=room_id, user_id=requester.user.to_string(), at_token=at_token, state_filter=StateFilter.from_types([(EventTypes.Member, None)]), ) chunk = [] for event in events: if (membership and event["content"].get("membership") != membership ) or (not_membership and event["content"].get("membership") == not_membership): continue chunk.append(event) return 200, {"chunk": chunk}
class DevicesRestServlet(RestServlet): PATTERNS = client_patterns("/devices$") def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() self.device_handler = hs.get_device_handler() async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) devices = await self.device_handler.get_devices_by_user( requester.user.to_string()) return 200, {"devices": devices}
def register(self, http_server: HttpServer) -> None: super().register(http_server) if self._msc2858_enabled: # expose additional endpoint for MSC2858 support: backwards-compat support # for clients which don't yet support the stable endpoints. http_server.register_paths( "GET", client_patterns( "/org.matrix.msc2858/login/sso/redirect/(?P<idp_id>[A-Za-z0-9_.~-]+)$", releases=(), unstable=True, ), self.on_GET, self.__class__.__name__, )
class LegacyPushersRemoveRestServlet(UnsubscribeResource, RestServlet): """ A servlet to handle legacy "email unsubscribe" links, forwarding requests to the ``UnsubscribeResource`` This should be kept for some time, so unsubscribe links in past emails stay valid. """ PATTERNS = client_patterns("/pushers/remove$", releases=[], v1=False, unstable=True) async def on_GET(self, request: SynapseRequest) -> None: # Forward the request to the UnsubscribeResource await self._async_render(request)
class RoomInitialSyncRestServlet(RestServlet): PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$", v1=True) def __init__(self, hs): super().__init__() self.initial_sync_handler = hs.get_initial_sync_handler() self.auth = hs.get_auth() self.store = hs.get_datastore() async def on_GET(self, request, room_id): requester = await self.auth.get_user_by_req(request, allow_guest=True) pagination_config = await PaginationConfig.from_request(self.store, request) content = await self.initial_sync_handler.room_initial_sync( room_id=room_id, requester=requester, pagin_config=pagination_config ) return 200, content
class JoinedRoomMemberListRestServlet(RestServlet): PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$", v1=True) def __init__(self, hs): super().__init__() self.message_handler = hs.get_message_handler() self.auth = hs.get_auth() async def on_GET(self, request, room_id): requester = await self.auth.get_user_by_req(request) users_with_profile = await self.message_handler.get_joined_members( requester, room_id ) return 200, {"joined": users_with_profile}
class PushersRestServlet(RestServlet): PATTERNS = client_patterns("/pushers$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) user = requester.user pushers = await self.hs.get_datastore().get_pushers_by_user_id(user.to_string()) filtered_pushers = [p.as_dict() for p in pushers] return 200, {"pushers": filtered_pushers}
class DeleteDevicesRestServlet(RestServlet): """ API for bulk deletion of devices. Accepts a JSON object with a devices key which lists the device_ids to delete. Requires user interactive auth. """ PATTERNS = client_patterns("/delete_devices") def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() self.device_handler = hs.get_device_handler() self.auth_handler = hs.get_auth_handler() @interactive_auth_handler async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) try: body = parse_json_object_from_request(request) except errors.SynapseError as e: if e.errcode == errors.Codes.NOT_JSON: # DELETE # deal with older clients which didn't pass a JSON dict # the same as those that pass an empty dict body = {} else: raise e assert_params_in_dict(body, ["devices"]) await self.auth_handler.validate_user_via_ui_auth( requester, request, body, "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"] ) return 200, {}
class CancellableRestServlet(RestServlet): """A `RestServlet` with a mix of cancellable and uncancellable handlers.""" PATTERNS = client_patterns("/sleep$") def __init__(self, hs: HomeServer): super().__init__() self.clock = hs.get_clock() @cancellable async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: await self.clock.sleep(1.0) return HTTPStatus.OK, {"result": True} async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: await self.clock.sleep(1.0) return HTTPStatus.OK, {"result": True}
class VoipRestServlet(RestServlet): PATTERNS = client_patterns("/voip/turnServer$", v1=True) def __init__(self, hs: "HomeServer"): super().__init__() self.hs = hs self.auth = hs.get_auth() async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req( request, self.hs.config.turn_allow_guests) turnUris = self.hs.config.turn_uris turnSecret = self.hs.config.turn_shared_secret turnUsername = self.hs.config.turn_username turnPassword = self.hs.config.turn_password userLifetime = self.hs.config.turn_user_lifetime if turnUris and turnSecret and userLifetime: expiry = (self.hs.get_clock().time_msec() + userLifetime) / 1000 username = "******" % (expiry, requester.user.to_string()) mac = hmac.new(turnSecret.encode(), msg=username.encode(), digestmod=hashlib.sha1) # We need to use standard padded base64 encoding here # encode_base64 because we need to add the standard padding to get the # same result as the TURN server. password = base64.b64encode(mac.digest()).decode("ascii") elif turnUris and turnUsername and turnPassword and userLifetime: username = turnUsername password = turnPassword else: return 200, {} return ( 200, { "username": username, "password": password, "ttl": userLifetime / 1000, "uris": turnUris, }, )
class SearchRestServlet(RestServlet): PATTERNS = client_patterns("/search$", v1=True) def __init__(self, hs): super().__init__() self.search_handler = hs.get_search_handler() self.auth = hs.get_auth() async def on_POST(self, request): requester = await self.auth.get_user_by_req(request) content = parse_json_object_from_request(request) batch = parse_string(request, "next_batch") results = await self.search_handler.search(requester.user, content, batch) return 200, results
class RoomStateRestServlet(RestServlet): PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/state$", v1=True) def __init__(self, hs): super().__init__() self.message_handler = hs.get_message_handler() self.auth = hs.get_auth() async def on_GET(self, request, room_id): requester = await self.auth.get_user_by_req(request, allow_guest=True) # Get all the current state for this room events = await self.message_handler.get_state_events( room_id=room_id, user_id=requester.user.to_string(), is_guest=requester.is_guest, ) return 200, events