summary refs log tree commit diff
path: root/synapse/rest/admin/users.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--synapse/rest/admin/users.py231
1 files changed, 140 insertions, 91 deletions
diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py

index ad515bd5a3..7671e020e0 100644 --- a/synapse/rest/admin/users.py +++ b/synapse/rest/admin/users.py
@@ -27,8 +27,8 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union import attr -from synapse._pydantic_compat import HAS_PYDANTIC_V2 -from synapse.api.constants import Direction, UserTypes +from synapse._pydantic_compat import StrictBool, StrictInt, StrictStr +from synapse.api.constants import Direction from synapse.api.errors import Codes, NotFoundError, SynapseError from synapse.http.servlet import ( RestServlet, @@ -50,17 +50,12 @@ from synapse.rest.admin._base import ( from synapse.rest.client._base import client_patterns from synapse.storage.databases.main.registration import ExternalIDReuseException from synapse.storage.databases.main.stats import UserSortOrder -from synapse.types import JsonDict, JsonMapping, UserID +from synapse.types import JsonDict, JsonMapping, TaskStatus, UserID from synapse.types.rest import RequestBodyModel if TYPE_CHECKING: from synapse.server import HomeServer -if TYPE_CHECKING or HAS_PYDANTIC_V2: - from pydantic.v1 import StrictBool -else: - from pydantic import StrictBool - logger = logging.getLogger(__name__) @@ -235,6 +230,7 @@ class UserRestServletV2(RestServlet): self.registration_handler = hs.get_registration_handler() self.pusher_pool = hs.get_pusherpool() self._msc3866_enabled = hs.config.experimental.msc3866.enabled + self._all_user_types = hs.config.user_types.all_user_types async def on_GET( self, request: SynapseRequest, user_id: str @@ -269,12 +265,6 @@ class UserRestServletV2(RestServlet): user = await self.admin_handler.get_user(target_user) user_id = target_user.to_string() - # check for required parameters for each threepid - threepids = body.get("threepids") - if threepids is not None: - for threepid in threepids: - assert_params_in_dict(threepid, ["medium", "address"]) - # check for required parameters for each external_id external_ids = body.get("external_ids") if external_ids is not None: @@ -282,7 +272,7 @@ class UserRestServletV2(RestServlet): assert_params_in_dict(external_id, ["auth_provider", "external_id"]) user_type = body.get("user_type", None) - if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: + if user_type is not None and user_type not in self._all_user_types: raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") set_admin_to = body.get("admin", False) @@ -338,51 +328,12 @@ class UserRestServletV2(RestServlet): for external_id in external_ids ] - # convert List[Dict[str, str]] into Set[Tuple[str, str]] - if threepids is not None: - new_threepids = { - (threepid["medium"], threepid["address"]) for threepid in threepids - } - if user: # modify user if "displayname" in body: await self.profile_handler.set_displayname( target_user, requester, body["displayname"], True ) - if threepids is not None: - # get changed threepids (added and removed) - cur_threepids = { - (threepid.medium, threepid.address) - for threepid in await self.store.user_get_threepids(user_id) - } - add_threepids = new_threepids - cur_threepids - del_threepids = cur_threepids - new_threepids - - # remove old threepids - for medium, address in del_threepids: - try: - # Attempt to remove any known bindings of this third-party ID - # and user ID from identity servers. - await self.hs.get_identity_handler().try_unbind_threepid( - user_id, medium, address, id_server=None - ) - except Exception: - logger.exception("Failed to remove threepids") - raise SynapseError(500, "Failed to remove threepids") - - # Delete the local association of this user ID and third-party ID. - await self.auth_handler.delete_local_threepid( - user_id, medium, address - ) - - # add new threepids - current_time = self.hs.get_clock().time_msec() - for medium, address in add_threepids: - await self.auth_handler.add_threepid( - user_id, medium, address, current_time - ) - if external_ids is not None: try: await self.store.replace_user_external_id( @@ -467,28 +418,6 @@ class UserRestServletV2(RestServlet): approved=new_user_approved, ) - if threepids is not None: - current_time = self.hs.get_clock().time_msec() - for medium, address in new_threepids: - await self.auth_handler.add_threepid( - user_id, medium, address, current_time - ) - if ( - self.hs.config.email.email_enable_notifs - and self.hs.config.email.email_notif_for_new_users - and medium == "email" - ): - await self.pusher_pool.add_or_update_pusher( - user_id=user_id, - kind="email", - app_id="m.email", - app_display_name="Email Notifications", - device_display_name=address, - pushkey=address, - lang=None, - data={}, - ) - if external_ids is not None: try: for auth_provider, external_id in new_external_ids: @@ -529,6 +458,7 @@ class UserRegisterServlet(RestServlet): self.reactor = hs.get_reactor() self.nonces: Dict[str, int] = {} self.hs = hs + self._all_user_types = hs.config.user_types.all_user_types def _clear_old_nonces(self) -> None: """ @@ -610,7 +540,7 @@ class UserRegisterServlet(RestServlet): user_type = body.get("user_type", None) displayname = body.get("displayname", None) - if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: + if user_type is not None and user_type not in self._all_user_types: raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") if "mac" not in body: @@ -988,7 +918,7 @@ class UserAdminServlet(RestServlet): class UserMembershipRestServlet(RestServlet): """ - Get room list of an user. + Get list of joined room ID's for a user. """ PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/joined_rooms$") @@ -1004,8 +934,9 @@ class UserMembershipRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) room_ids = await self.store.get_rooms_for_user(user_id) - ret = {"joined_rooms": list(room_ids), "total": len(room_ids)} - return HTTPStatus.OK, ret + rooms_response = {"joined_rooms": list(room_ids), "total": len(room_ids)} + + return HTTPStatus.OK, rooms_response class PushersRestServlet(RestServlet): @@ -1387,26 +1318,144 @@ class UserByExternalId(RestServlet): return HTTPStatus.OK, {"user_id": user_id} -class UserByThreePid(RestServlet): - """Find a user based on 3PID of a particular medium""" +class RedactUser(RestServlet): + """ + Redact all the events of a given user in the given rooms or if empty dict is provided + then all events in all rooms user is member of. Kicks off a background process and + returns an id that can be used to check on the progress of the redaction progress + """ - PATTERNS = admin_patterns("/threepid/(?P<medium>[^/]*)/users/(?P<address>[^/]*)") + PATTERNS = admin_patterns("/user/(?P<user_id>[^/]*)/redact") def __init__(self, hs: "HomeServer"): self._auth = hs.get_auth() self._store = hs.get_datastores().main + self.admin_handler = hs.get_admin_handler() + + class PostBody(RequestBodyModel): + rooms: List[StrictStr] + reason: Optional[StrictStr] + limit: Optional[StrictInt] + + async def on_POST( + self, request: SynapseRequest, user_id: str + ) -> Tuple[int, JsonDict]: + requester = await self._auth.get_user_by_req(request) + await assert_user_is_admin(self._auth, requester) + + # parse provided user id to check that it is valid + UserID.from_string(user_id) + + body = parse_and_validate_json_object_from_request(request, self.PostBody) + + limit = body.limit + if limit and limit <= 0: + raise SynapseError( + HTTPStatus.BAD_REQUEST, + "If limit is provided it must be a non-negative integer greater than 0.", + ) + + rooms = body.rooms + if not rooms: + current_rooms = list(await self._store.get_rooms_for_user(user_id)) + banned_rooms = list( + await self._store.get_rooms_user_currently_banned_from(user_id) + ) + rooms = current_rooms + banned_rooms + + redact_id = await self.admin_handler.start_redact_events( + user_id, rooms, requester.serialize(), body.reason, limit + ) + + return HTTPStatus.OK, {"redact_id": redact_id} + + +class RedactUserStatus(RestServlet): + """ + Check on the progress of the redaction request represented by the provided ID, returning + the status of the process and a dict of events that were unable to be redacted, if any + """ + + PATTERNS = admin_patterns("/user/redact_status/(?P<redact_id>[^/]*)$") + + def __init__(self, hs: "HomeServer"): + self._auth = hs.get_auth() + self.admin_handler = hs.get_admin_handler() async def on_GET( - self, - request: SynapseRequest, - medium: str, - address: str, + self, request: SynapseRequest, redact_id: str ) -> Tuple[int, JsonDict]: await assert_requester_is_admin(self._auth, request) - user_id = await self._store.get_user_id_by_threepid(medium, address) + task = await self.admin_handler.get_redact_task(redact_id) + + if task: + if task.status == TaskStatus.ACTIVE: + return HTTPStatus.OK, {"status": TaskStatus.ACTIVE} + elif task.status == TaskStatus.COMPLETE: + assert task.result is not None + failed_redactions = task.result.get("failed_redactions") + return HTTPStatus.OK, { + "status": TaskStatus.COMPLETE, + "failed_redactions": failed_redactions if failed_redactions else {}, + } + elif task.status == TaskStatus.SCHEDULED: + return HTTPStatus.OK, {"status": TaskStatus.SCHEDULED} + else: + return HTTPStatus.OK, { + "status": TaskStatus.FAILED, + "error": ( + task.error + if task.error + else "Unknown error, please check the logs for more information." + ), + } + else: + raise NotFoundError("redact id '%s' not found" % redact_id) - if user_id is None: - raise NotFoundError("User not found") - return HTTPStatus.OK, {"user_id": user_id} +class UserInvitesCount(RestServlet): + """ + Return the count of invites that the user has sent after the given timestamp + """ + + PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/sent_invite_count") + + def __init__(self, hs: "HomeServer"): + self._auth = hs.get_auth() + self.store = hs.get_datastores().main + + async def on_GET( + self, request: SynapseRequest, user_id: str + ) -> Tuple[int, JsonDict]: + await assert_requester_is_admin(self._auth, request) + from_ts = parse_integer(request, "from_ts", required=True) + + sent_invite_count = await self.store.get_sent_invite_count_by_user( + user_id, from_ts + ) + + return HTTPStatus.OK, {"invite_count": sent_invite_count} + + +class UserJoinedRoomCount(RestServlet): + """ + Return the count of rooms that the user has joined at or after the given timestamp, even + if they have subsequently left/been banned from those rooms. + """ + + PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/cumulative_joined_room_count") + + def __init__(self, hs: "HomeServer"): + self._auth = hs.get_auth() + self.store = hs.get_datastores().main + + async def on_GET( + self, request: SynapseRequest, user_id: str + ) -> Tuple[int, JsonDict]: + await assert_requester_is_admin(self._auth, request) + from_ts = parse_integer(request, "from_ts", required=True) + + joined_rooms = await self.store.get_rooms_for_user_by_date(user_id, from_ts) + + return HTTPStatus.OK, {"cumulative_joined_room_count": len(joined_rooms)}