1# Copyright 2019 The Matrix.org Foundation C.I.C. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14import hashlib 15import hmac 16import logging 17import secrets 18from http import HTTPStatus 19from typing import TYPE_CHECKING, Dict, List, Optional, Tuple 20 21from synapse.api.constants import UserTypes 22from synapse.api.errors import Codes, NotFoundError, SynapseError 23from synapse.http.servlet import ( 24 RestServlet, 25 assert_params_in_dict, 26 parse_boolean, 27 parse_integer, 28 parse_json_object_from_request, 29 parse_string, 30) 31from synapse.http.site import SynapseRequest 32from synapse.rest.admin._base import ( 33 admin_patterns, 34 assert_requester_is_admin, 35 assert_user_is_admin, 36) 37from synapse.rest.client._base import client_patterns 38from synapse.storage.databases.main.registration import ExternalIDReuseException 39from synapse.storage.databases.main.stats import UserSortOrder 40from synapse.types import JsonDict, UserID 41 42if TYPE_CHECKING: 43 from synapse.server import HomeServer 44 45logger = logging.getLogger(__name__) 46 47 48class UsersRestServletV2(RestServlet): 49 PATTERNS = admin_patterns("/users$", "v2") 50 51 """Get request to list all local users. 52 This needs user to have administrator access in Synapse. 53 54 GET /_synapse/admin/v2/users?from=0&limit=10&guests=false 55 56 returns: 57 200 OK with list of users if success otherwise an error. 58 59 The parameters `from` and `limit` are required only for pagination. 60 By default, a `limit` of 100 is used. 61 The parameter `user_id` can be used to filter by user id. 62 The parameter `name` can be used to filter by user id or display name. 63 The parameter `guests` can be used to exclude guest users. 64 The parameter `deactivated` can be used to include deactivated users. 65 The parameter `order_by` can be used to order the result. 66 """ 67 68 def __init__(self, hs: "HomeServer"): 69 self.store = hs.get_datastore() 70 self.auth = hs.get_auth() 71 self.admin_handler = hs.get_admin_handler() 72 73 async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: 74 await assert_requester_is_admin(self.auth, request) 75 76 start = parse_integer(request, "from", default=0) 77 limit = parse_integer(request, "limit", default=100) 78 79 if start < 0: 80 raise SynapseError( 81 HTTPStatus.BAD_REQUEST, 82 "Query parameter from must be a string representing a positive integer.", 83 errcode=Codes.INVALID_PARAM, 84 ) 85 86 if limit < 0: 87 raise SynapseError( 88 HTTPStatus.BAD_REQUEST, 89 "Query parameter limit must be a string representing a positive integer.", 90 errcode=Codes.INVALID_PARAM, 91 ) 92 93 user_id = parse_string(request, "user_id") 94 name = parse_string(request, "name") 95 guests = parse_boolean(request, "guests", default=True) 96 deactivated = parse_boolean(request, "deactivated", default=False) 97 98 order_by = parse_string( 99 request, 100 "order_by", 101 default=UserSortOrder.NAME.value, 102 allowed_values=( 103 UserSortOrder.NAME.value, 104 UserSortOrder.DISPLAYNAME.value, 105 UserSortOrder.GUEST.value, 106 UserSortOrder.ADMIN.value, 107 UserSortOrder.DEACTIVATED.value, 108 UserSortOrder.USER_TYPE.value, 109 UserSortOrder.AVATAR_URL.value, 110 UserSortOrder.SHADOW_BANNED.value, 111 UserSortOrder.CREATION_TS.value, 112 ), 113 ) 114 115 direction = parse_string(request, "dir", default="f", allowed_values=("f", "b")) 116 117 users, total = await self.store.get_users_paginate( 118 start, limit, user_id, name, guests, deactivated, order_by, direction 119 ) 120 ret = {"users": users, "total": total} 121 if (start + limit) < total: 122 ret["next_token"] = str(start + len(users)) 123 124 return HTTPStatus.OK, ret 125 126 127class UserRestServletV2(RestServlet): 128 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)$", "v2") 129 130 """Get request to list user details. 131 This needs user to have administrator access in Synapse. 132 133 GET /_synapse/admin/v2/users/<user_id> 134 135 returns: 136 200 OK with user details if success otherwise an error. 137 138 Put request to allow an administrator to add or modify a user. 139 This needs user to have administrator access in Synapse. 140 We use PUT instead of POST since we already know the id of the user 141 object to create. POST could be used to create guests. 142 143 PUT /_synapse/admin/v2/users/<user_id> 144 { 145 "password": "secret", 146 "displayname": "User" 147 } 148 149 returns: 150 201 OK with new user object if user was created or 151 200 OK with modified user object if user was modified 152 otherwise an error. 153 """ 154 155 def __init__(self, hs: "HomeServer"): 156 self.hs = hs 157 self.auth = hs.get_auth() 158 self.admin_handler = hs.get_admin_handler() 159 self.store = hs.get_datastore() 160 self.auth_handler = hs.get_auth_handler() 161 self.profile_handler = hs.get_profile_handler() 162 self.set_password_handler = hs.get_set_password_handler() 163 self.deactivate_account_handler = hs.get_deactivate_account_handler() 164 self.registration_handler = hs.get_registration_handler() 165 self.pusher_pool = hs.get_pusherpool() 166 167 async def on_GET( 168 self, request: SynapseRequest, user_id: str 169 ) -> Tuple[int, JsonDict]: 170 await assert_requester_is_admin(self.auth, request) 171 172 target_user = UserID.from_string(user_id) 173 if not self.hs.is_mine(target_user): 174 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") 175 176 ret = await self.admin_handler.get_user(target_user) 177 178 if not ret: 179 raise NotFoundError("User not found") 180 181 return HTTPStatus.OK, ret 182 183 async def on_PUT( 184 self, request: SynapseRequest, user_id: str 185 ) -> Tuple[int, JsonDict]: 186 requester = await self.auth.get_user_by_req(request) 187 await assert_user_is_admin(self.auth, requester.user) 188 189 target_user = UserID.from_string(user_id) 190 body = parse_json_object_from_request(request) 191 192 if not self.hs.is_mine(target_user): 193 raise SynapseError( 194 HTTPStatus.BAD_REQUEST, 195 "This endpoint can only be used with local users", 196 ) 197 198 user = await self.admin_handler.get_user(target_user) 199 user_id = target_user.to_string() 200 201 # check for required parameters for each threepid 202 threepids = body.get("threepids") 203 if threepids is not None: 204 for threepid in threepids: 205 assert_params_in_dict(threepid, ["medium", "address"]) 206 207 # check for required parameters for each external_id 208 external_ids = body.get("external_ids") 209 if external_ids is not None: 210 for external_id in external_ids: 211 assert_params_in_dict(external_id, ["auth_provider", "external_id"]) 212 213 user_type = body.get("user_type", None) 214 if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: 215 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") 216 217 set_admin_to = body.get("admin", False) 218 if not isinstance(set_admin_to, bool): 219 raise SynapseError( 220 HTTPStatus.BAD_REQUEST, 221 "Param 'admin' must be a boolean, if given", 222 Codes.BAD_JSON, 223 ) 224 225 password = body.get("password", None) 226 if password is not None: 227 if not isinstance(password, str) or len(password) > 512: 228 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") 229 230 deactivate = body.get("deactivated", False) 231 if not isinstance(deactivate, bool): 232 raise SynapseError( 233 HTTPStatus.BAD_REQUEST, "'deactivated' parameter is not of type boolean" 234 ) 235 236 # convert List[Dict[str, str]] into List[Tuple[str, str]] 237 if external_ids is not None: 238 new_external_ids = [ 239 (external_id["auth_provider"], external_id["external_id"]) 240 for external_id in external_ids 241 ] 242 243 # convert List[Dict[str, str]] into Set[Tuple[str, str]] 244 if threepids is not None: 245 new_threepids = { 246 (threepid["medium"], threepid["address"]) for threepid in threepids 247 } 248 249 if user: # modify user 250 if "displayname" in body: 251 await self.profile_handler.set_displayname( 252 target_user, requester, body["displayname"], True 253 ) 254 255 if threepids is not None: 256 # get changed threepids (added and removed) 257 # convert List[Dict[str, Any]] into Set[Tuple[str, str]] 258 cur_threepids = { 259 (threepid["medium"], threepid["address"]) 260 for threepid in await self.store.user_get_threepids(user_id) 261 } 262 add_threepids = new_threepids - cur_threepids 263 del_threepids = cur_threepids - new_threepids 264 265 # remove old threepids 266 for medium, address in del_threepids: 267 try: 268 await self.auth_handler.delete_threepid( 269 user_id, medium, address, None 270 ) 271 except Exception: 272 logger.exception("Failed to remove threepids") 273 raise SynapseError(500, "Failed to remove threepids") 274 275 # add new threepids 276 current_time = self.hs.get_clock().time_msec() 277 for medium, address in add_threepids: 278 await self.auth_handler.add_threepid( 279 user_id, medium, address, current_time 280 ) 281 282 if external_ids is not None: 283 try: 284 await self.store.replace_user_external_id( 285 new_external_ids, 286 user_id, 287 ) 288 except ExternalIDReuseException: 289 raise SynapseError( 290 HTTPStatus.CONFLICT, "External id is already in use." 291 ) 292 293 if "avatar_url" in body and isinstance(body["avatar_url"], str): 294 await self.profile_handler.set_avatar_url( 295 target_user, requester, body["avatar_url"], True 296 ) 297 298 if "admin" in body: 299 if set_admin_to != user["admin"]: 300 auth_user = requester.user 301 if target_user == auth_user and not set_admin_to: 302 raise SynapseError( 303 HTTPStatus.BAD_REQUEST, "You may not demote yourself." 304 ) 305 306 await self.store.set_server_admin(target_user, set_admin_to) 307 308 if password is not None: 309 logout_devices = True 310 new_password_hash = await self.auth_handler.hash(password) 311 312 await self.set_password_handler.set_password( 313 target_user.to_string(), 314 new_password_hash, 315 logout_devices, 316 requester, 317 ) 318 319 if "deactivated" in body: 320 if deactivate and not user["deactivated"]: 321 await self.deactivate_account_handler.deactivate_account( 322 target_user.to_string(), False, requester, by_admin=True 323 ) 324 elif not deactivate and user["deactivated"]: 325 if ( 326 "password" not in body 327 and self.auth_handler.can_change_password() 328 ): 329 raise SynapseError( 330 HTTPStatus.BAD_REQUEST, 331 "Must provide a password to re-activate an account.", 332 ) 333 334 await self.deactivate_account_handler.activate_account( 335 target_user.to_string() 336 ) 337 338 if "user_type" in body: 339 await self.store.set_user_type(target_user, user_type) 340 341 user = await self.admin_handler.get_user(target_user) 342 assert user is not None 343 344 return HTTPStatus.OK, user 345 346 else: # create user 347 displayname = body.get("displayname", None) 348 349 password_hash = None 350 if password is not None: 351 password_hash = await self.auth_handler.hash(password) 352 353 user_id = await self.registration_handler.register_user( 354 localpart=target_user.localpart, 355 password_hash=password_hash, 356 admin=set_admin_to, 357 default_display_name=displayname, 358 user_type=user_type, 359 by_admin=True, 360 ) 361 362 if threepids is not None: 363 current_time = self.hs.get_clock().time_msec() 364 for medium, address in new_threepids: 365 await self.auth_handler.add_threepid( 366 user_id, medium, address, current_time 367 ) 368 if ( 369 self.hs.config.email.email_enable_notifs 370 and self.hs.config.email.email_notif_for_new_users 371 ): 372 await self.pusher_pool.add_pusher( 373 user_id=user_id, 374 access_token=None, 375 kind="email", 376 app_id="m.email", 377 app_display_name="Email Notifications", 378 device_display_name=address, 379 pushkey=address, 380 lang=None, # We don't know a user's language here 381 data={}, 382 ) 383 384 if external_ids is not None: 385 try: 386 for auth_provider, external_id in new_external_ids: 387 await self.store.record_user_external_id( 388 auth_provider, 389 external_id, 390 user_id, 391 ) 392 except ExternalIDReuseException: 393 raise SynapseError( 394 HTTPStatus.CONFLICT, "External id is already in use." 395 ) 396 397 if "avatar_url" in body and isinstance(body["avatar_url"], str): 398 await self.profile_handler.set_avatar_url( 399 target_user, requester, body["avatar_url"], True 400 ) 401 402 user = await self.admin_handler.get_user(target_user) 403 assert user is not None 404 405 return 201, user 406 407 408class UserRegisterServlet(RestServlet): 409 """ 410 Attributes: 411 NONCE_TIMEOUT (int): Seconds until a generated nonce won't be accepted 412 nonces (dict[str, int]): The nonces that we will accept. A dict of 413 nonce to the time it was generated, in int seconds. 414 """ 415 416 PATTERNS = admin_patterns("/register$") 417 NONCE_TIMEOUT = 60 418 419 def __init__(self, hs: "HomeServer"): 420 self.auth_handler = hs.get_auth_handler() 421 self.reactor = hs.get_reactor() 422 self.nonces: Dict[str, int] = {} 423 self.hs = hs 424 425 def _clear_old_nonces(self) -> None: 426 """ 427 Clear out old nonces that are older than NONCE_TIMEOUT. 428 """ 429 now = int(self.reactor.seconds()) 430 431 for k, v in list(self.nonces.items()): 432 if now - v > self.NONCE_TIMEOUT: 433 del self.nonces[k] 434 435 def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: 436 """ 437 Generate a new nonce. 438 """ 439 self._clear_old_nonces() 440 441 nonce = secrets.token_hex(64) 442 self.nonces[nonce] = int(self.reactor.seconds()) 443 return HTTPStatus.OK, {"nonce": nonce} 444 445 async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: 446 self._clear_old_nonces() 447 448 if not self.hs.config.registration.registration_shared_secret: 449 raise SynapseError( 450 HTTPStatus.BAD_REQUEST, "Shared secret registration is not enabled" 451 ) 452 453 body = parse_json_object_from_request(request) 454 455 if "nonce" not in body: 456 raise SynapseError( 457 HTTPStatus.BAD_REQUEST, 458 "nonce must be specified", 459 errcode=Codes.BAD_JSON, 460 ) 461 462 nonce = body["nonce"] 463 464 if nonce not in self.nonces: 465 raise SynapseError(HTTPStatus.BAD_REQUEST, "unrecognised nonce") 466 467 # Delete the nonce, so it can't be reused, even if it's invalid 468 del self.nonces[nonce] 469 470 if "username" not in body: 471 raise SynapseError( 472 HTTPStatus.BAD_REQUEST, 473 "username must be specified", 474 errcode=Codes.BAD_JSON, 475 ) 476 else: 477 if not isinstance(body["username"], str) or len(body["username"]) > 512: 478 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid username") 479 480 username = body["username"].encode("utf-8") 481 if b"\x00" in username: 482 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid username") 483 484 if "password" not in body: 485 raise SynapseError( 486 HTTPStatus.BAD_REQUEST, 487 "password must be specified", 488 errcode=Codes.BAD_JSON, 489 ) 490 else: 491 password = body["password"] 492 if not isinstance(password, str) or len(password) > 512: 493 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") 494 495 password_bytes = password.encode("utf-8") 496 if b"\x00" in password_bytes: 497 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") 498 499 password_hash = await self.auth_handler.hash(password) 500 501 admin = body.get("admin", None) 502 user_type = body.get("user_type", None) 503 displayname = body.get("displayname", None) 504 505 if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: 506 raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") 507 508 if "mac" not in body: 509 raise SynapseError( 510 HTTPStatus.BAD_REQUEST, "mac must be specified", errcode=Codes.BAD_JSON 511 ) 512 513 got_mac = body["mac"] 514 515 want_mac_builder = hmac.new( 516 key=self.hs.config.registration.registration_shared_secret.encode(), 517 digestmod=hashlib.sha1, 518 ) 519 want_mac_builder.update(nonce.encode("utf8")) 520 want_mac_builder.update(b"\x00") 521 want_mac_builder.update(username) 522 want_mac_builder.update(b"\x00") 523 want_mac_builder.update(password_bytes) 524 want_mac_builder.update(b"\x00") 525 want_mac_builder.update(b"admin" if admin else b"notadmin") 526 if user_type: 527 want_mac_builder.update(b"\x00") 528 want_mac_builder.update(user_type.encode("utf8")) 529 530 want_mac = want_mac_builder.hexdigest() 531 532 if not hmac.compare_digest(want_mac.encode("ascii"), got_mac.encode("ascii")): 533 raise SynapseError(HTTPStatus.FORBIDDEN, "HMAC incorrect") 534 535 # Reuse the parts of RegisterRestServlet to reduce code duplication 536 from synapse.rest.client.register import RegisterRestServlet 537 538 register = RegisterRestServlet(self.hs) 539 540 user_id = await register.registration_handler.register_user( 541 localpart=body["username"].lower(), 542 password_hash=password_hash, 543 admin=bool(admin), 544 user_type=user_type, 545 default_display_name=displayname, 546 by_admin=True, 547 ) 548 549 result = await register._create_registration_details(user_id, body) 550 return HTTPStatus.OK, result 551 552 553class WhoisRestServlet(RestServlet): 554 path_regex = "/whois/(?P<user_id>[^/]*)$" 555 PATTERNS = [ 556 *admin_patterns(path_regex), 557 # URL for spec reason 558 # https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid 559 *client_patterns("/admin" + path_regex, v1=True), 560 ] 561 562 def __init__(self, hs: "HomeServer"): 563 self.auth = hs.get_auth() 564 self.admin_handler = hs.get_admin_handler() 565 self.is_mine = hs.is_mine 566 567 async def on_GET( 568 self, request: SynapseRequest, user_id: str 569 ) -> Tuple[int, JsonDict]: 570 target_user = UserID.from_string(user_id) 571 requester = await self.auth.get_user_by_req(request) 572 auth_user = requester.user 573 574 if target_user != auth_user: 575 await assert_user_is_admin(self.auth, auth_user) 576 577 if not self.is_mine(target_user): 578 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only whois a local user") 579 580 ret = await self.admin_handler.get_whois(target_user) 581 582 return HTTPStatus.OK, ret 583 584 585class DeactivateAccountRestServlet(RestServlet): 586 PATTERNS = admin_patterns("/deactivate/(?P<target_user_id>[^/]*)$") 587 588 def __init__(self, hs: "HomeServer"): 589 self._deactivate_account_handler = hs.get_deactivate_account_handler() 590 self.auth = hs.get_auth() 591 self.is_mine = hs.is_mine 592 self.store = hs.get_datastore() 593 594 async def on_POST( 595 self, request: SynapseRequest, target_user_id: str 596 ) -> Tuple[int, JsonDict]: 597 requester = await self.auth.get_user_by_req(request) 598 await assert_user_is_admin(self.auth, requester.user) 599 600 if not self.is_mine(UserID.from_string(target_user_id)): 601 raise SynapseError( 602 HTTPStatus.BAD_REQUEST, "Can only deactivate local users" 603 ) 604 605 if not await self.store.get_user_by_id(target_user_id): 606 raise NotFoundError("User not found") 607 608 body = parse_json_object_from_request(request, allow_empty_body=True) 609 erase = body.get("erase", False) 610 if not isinstance(erase, bool): 611 raise SynapseError( 612 HTTPStatus.BAD_REQUEST, 613 "Param 'erase' must be a boolean, if given", 614 Codes.BAD_JSON, 615 ) 616 617 result = await self._deactivate_account_handler.deactivate_account( 618 target_user_id, erase, requester, by_admin=True 619 ) 620 if result: 621 id_server_unbind_result = "success" 622 else: 623 id_server_unbind_result = "no-support" 624 625 return HTTPStatus.OK, {"id_server_unbind_result": id_server_unbind_result} 626 627 628class AccountValidityRenewServlet(RestServlet): 629 PATTERNS = admin_patterns("/account_validity/validity$") 630 631 def __init__(self, hs: "HomeServer"): 632 self.account_activity_handler = hs.get_account_validity_handler() 633 self.auth = hs.get_auth() 634 635 async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: 636 await assert_requester_is_admin(self.auth, request) 637 638 if self.account_activity_handler.on_legacy_admin_request_callback: 639 expiration_ts = await ( 640 self.account_activity_handler.on_legacy_admin_request_callback(request) 641 ) 642 else: 643 body = parse_json_object_from_request(request) 644 645 if "user_id" not in body: 646 raise SynapseError( 647 HTTPStatus.BAD_REQUEST, 648 "Missing property 'user_id' in the request body", 649 ) 650 651 expiration_ts = await self.account_activity_handler.renew_account_for_user( 652 body["user_id"], 653 body.get("expiration_ts"), 654 not body.get("enable_renewal_emails", True), 655 ) 656 657 res = {"expiration_ts": expiration_ts} 658 return HTTPStatus.OK, res 659 660 661class ResetPasswordRestServlet(RestServlet): 662 """Post request to allow an administrator reset password for a user. 663 This needs user to have administrator access in Synapse. 664 Example: 665 http://localhost:8008/_synapse/admin/v1/reset_password/ 666 @user:to_reset_password?access_token=admin_access_token 667 JsonBodyToSend: 668 { 669 "new_password": "secret" 670 } 671 Returns: 672 200 OK with empty object if success otherwise an error. 673 """ 674 675 PATTERNS = admin_patterns("/reset_password/(?P<target_user_id>[^/]*)$") 676 677 def __init__(self, hs: "HomeServer"): 678 self.store = hs.get_datastore() 679 self.auth = hs.get_auth() 680 self.auth_handler = hs.get_auth_handler() 681 self._set_password_handler = hs.get_set_password_handler() 682 683 async def on_POST( 684 self, request: SynapseRequest, target_user_id: str 685 ) -> Tuple[int, JsonDict]: 686 """Post request to allow an administrator reset password for a user. 687 This needs user to have administrator access in Synapse. 688 """ 689 requester = await self.auth.get_user_by_req(request) 690 await assert_user_is_admin(self.auth, requester.user) 691 692 UserID.from_string(target_user_id) 693 694 params = parse_json_object_from_request(request) 695 assert_params_in_dict(params, ["new_password"]) 696 new_password = params["new_password"] 697 logout_devices = params.get("logout_devices", True) 698 699 new_password_hash = await self.auth_handler.hash(new_password) 700 701 await self._set_password_handler.set_password( 702 target_user_id, new_password_hash, logout_devices, requester 703 ) 704 return HTTPStatus.OK, {} 705 706 707class SearchUsersRestServlet(RestServlet): 708 """Get request to search user table for specific users according to 709 search term. 710 This needs user to have administrator access in Synapse. 711 Example: 712 http://localhost:8008/_synapse/admin/v1/search_users/ 713 @admin:user?access_token=admin_access_token&term=alice 714 Returns: 715 200 OK with json object {list[dict[str, Any]], count} or empty object. 716 """ 717 718 PATTERNS = admin_patterns("/search_users/(?P<target_user_id>[^/]*)$") 719 720 def __init__(self, hs: "HomeServer"): 721 self.store = hs.get_datastore() 722 self.auth = hs.get_auth() 723 self.is_mine = hs.is_mine 724 725 async def on_GET( 726 self, request: SynapseRequest, target_user_id: str 727 ) -> Tuple[int, Optional[List[JsonDict]]]: 728 """Get request to search user table for specific users according to 729 search term. 730 This needs user to have a administrator access in Synapse. 731 """ 732 await assert_requester_is_admin(self.auth, request) 733 734 target_user = UserID.from_string(target_user_id) 735 736 # To allow all users to get the users list 737 # if not is_admin and target_user != auth_user: 738 # raise AuthError(HTTPStatus.FORBIDDEN, "You are not a server admin") 739 740 if not self.is_mine(target_user): 741 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only users a local user") 742 743 term = parse_string(request, "term", required=True) 744 logger.info("term: %s ", term) 745 746 ret = await self.store.search_users(term) 747 return HTTPStatus.OK, ret 748 749 750class UserAdminServlet(RestServlet): 751 """ 752 Get or set whether or not a user is a server administrator. 753 754 Note that only local users can be server administrators, and that an 755 administrator may not demote themselves. 756 757 Only server administrators can use this API. 758 759 Examples: 760 * Get 761 GET /_synapse/admin/v1/users/@nonadmin:example.com/admin 762 response on success: 763 { 764 "admin": false 765 } 766 * Set 767 PUT /_synapse/admin/v1/users/@reivilibre:librepush.net/admin 768 request body: 769 { 770 "admin": true 771 } 772 response on success: 773 {} 774 """ 775 776 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/admin$") 777 778 def __init__(self, hs: "HomeServer"): 779 self.store = hs.get_datastore() 780 self.auth = hs.get_auth() 781 self.is_mine = hs.is_mine 782 783 async def on_GET( 784 self, request: SynapseRequest, user_id: str 785 ) -> Tuple[int, JsonDict]: 786 await assert_requester_is_admin(self.auth, request) 787 788 target_user = UserID.from_string(user_id) 789 790 if not self.is_mine(target_user): 791 raise SynapseError( 792 HTTPStatus.BAD_REQUEST, 793 "Only local users can be admins of this homeserver", 794 ) 795 796 is_admin = await self.store.is_server_admin(target_user) 797 798 return HTTPStatus.OK, {"admin": is_admin} 799 800 async def on_PUT( 801 self, request: SynapseRequest, user_id: str 802 ) -> Tuple[int, JsonDict]: 803 requester = await self.auth.get_user_by_req(request) 804 await assert_user_is_admin(self.auth, requester.user) 805 auth_user = requester.user 806 807 target_user = UserID.from_string(user_id) 808 809 body = parse_json_object_from_request(request) 810 811 assert_params_in_dict(body, ["admin"]) 812 813 if not self.is_mine(target_user): 814 raise SynapseError( 815 HTTPStatus.BAD_REQUEST, 816 "Only local users can be admins of this homeserver", 817 ) 818 819 set_admin_to = bool(body["admin"]) 820 821 if target_user == auth_user and not set_admin_to: 822 raise SynapseError(HTTPStatus.BAD_REQUEST, "You may not demote yourself.") 823 824 await self.store.set_server_admin(target_user, set_admin_to) 825 826 return HTTPStatus.OK, {} 827 828 829class UserMembershipRestServlet(RestServlet): 830 """ 831 Get room list of an user. 832 """ 833 834 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/joined_rooms$") 835 836 def __init__(self, hs: "HomeServer"): 837 self.is_mine = hs.is_mine 838 self.auth = hs.get_auth() 839 self.store = hs.get_datastore() 840 841 async def on_GET( 842 self, request: SynapseRequest, user_id: str 843 ) -> Tuple[int, JsonDict]: 844 await assert_requester_is_admin(self.auth, request) 845 846 room_ids = await self.store.get_rooms_for_user(user_id) 847 ret = {"joined_rooms": list(room_ids), "total": len(room_ids)} 848 return HTTPStatus.OK, ret 849 850 851class PushersRestServlet(RestServlet): 852 """ 853 Gets information about all pushers for a specific `user_id`. 854 855 Example: 856 http://localhost:8008/_synapse/admin/v1/users/ 857 @user:server/pushers 858 859 Returns: 860 pushers: Dictionary containing pushers information. 861 total: Number of pushers in dictionary `pushers`. 862 """ 863 864 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/pushers$") 865 866 def __init__(self, hs: "HomeServer"): 867 self.is_mine = hs.is_mine 868 self.store = hs.get_datastore() 869 self.auth = hs.get_auth() 870 871 async def on_GET( 872 self, request: SynapseRequest, user_id: str 873 ) -> Tuple[int, JsonDict]: 874 await assert_requester_is_admin(self.auth, request) 875 876 if not self.is_mine(UserID.from_string(user_id)): 877 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") 878 879 if not await self.store.get_user_by_id(user_id): 880 raise NotFoundError("User not found") 881 882 pushers = await self.store.get_pushers_by_user_id(user_id) 883 884 filtered_pushers = [p.as_dict() for p in pushers] 885 886 return HTTPStatus.OK, { 887 "pushers": filtered_pushers, 888 "total": len(filtered_pushers), 889 } 890 891 892class UserTokenRestServlet(RestServlet): 893 """An admin API for logging in as a user. 894 895 Example: 896 897 POST /_synapse/admin/v1/users/@test:example.com/login 898 {} 899 900 200 OK 901 { 902 "access_token": "<some_token>" 903 } 904 """ 905 906 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/login$") 907 908 def __init__(self, hs: "HomeServer"): 909 self.store = hs.get_datastore() 910 self.auth = hs.get_auth() 911 self.auth_handler = hs.get_auth_handler() 912 self.is_mine_id = hs.is_mine_id 913 914 async def on_POST( 915 self, request: SynapseRequest, user_id: str 916 ) -> Tuple[int, JsonDict]: 917 requester = await self.auth.get_user_by_req(request) 918 await assert_user_is_admin(self.auth, requester.user) 919 auth_user = requester.user 920 921 if not self.is_mine_id(user_id): 922 raise SynapseError( 923 HTTPStatus.BAD_REQUEST, "Only local users can be logged in as" 924 ) 925 926 body = parse_json_object_from_request(request, allow_empty_body=True) 927 928 valid_until_ms = body.get("valid_until_ms") 929 if valid_until_ms and not isinstance(valid_until_ms, int): 930 raise SynapseError( 931 HTTPStatus.BAD_REQUEST, "'valid_until_ms' parameter must be an int" 932 ) 933 934 if auth_user.to_string() == user_id: 935 raise SynapseError( 936 HTTPStatus.BAD_REQUEST, "Cannot use admin API to login as self" 937 ) 938 939 token = await self.auth_handler.create_access_token_for_user_id( 940 user_id=auth_user.to_string(), 941 device_id=None, 942 valid_until_ms=valid_until_ms, 943 puppets_user_id=user_id, 944 ) 945 946 return HTTPStatus.OK, {"access_token": token} 947 948 949class ShadowBanRestServlet(RestServlet): 950 """An admin API for controlling whether a user is shadow-banned. 951 952 A shadow-banned users receives successful responses to their client-server 953 API requests, but the events are not propagated into rooms. 954 955 Shadow-banning a user should be used as a tool of last resort and may lead 956 to confusing or broken behaviour for the client. 957 958 Example of shadow-banning a user: 959 960 POST /_synapse/admin/v1/users/@test:example.com/shadow_ban 961 {} 962 963 200 OK 964 {} 965 966 Example of removing a user from being shadow-banned: 967 968 DELETE /_synapse/admin/v1/users/@test:example.com/shadow_ban 969 {} 970 971 200 OK 972 {} 973 """ 974 975 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/shadow_ban$") 976 977 def __init__(self, hs: "HomeServer"): 978 self.store = hs.get_datastore() 979 self.auth = hs.get_auth() 980 self.is_mine_id = hs.is_mine_id 981 982 async def on_POST( 983 self, request: SynapseRequest, user_id: str 984 ) -> Tuple[int, JsonDict]: 985 await assert_requester_is_admin(self.auth, request) 986 987 if not self.is_mine_id(user_id): 988 raise SynapseError( 989 HTTPStatus.BAD_REQUEST, "Only local users can be shadow-banned" 990 ) 991 992 await self.store.set_shadow_banned(UserID.from_string(user_id), True) 993 994 return HTTPStatus.OK, {} 995 996 async def on_DELETE( 997 self, request: SynapseRequest, user_id: str 998 ) -> Tuple[int, JsonDict]: 999 await assert_requester_is_admin(self.auth, request) 1000 1001 if not self.is_mine_id(user_id): 1002 raise SynapseError( 1003 HTTPStatus.BAD_REQUEST, "Only local users can be shadow-banned" 1004 ) 1005 1006 await self.store.set_shadow_banned(UserID.from_string(user_id), False) 1007 1008 return HTTPStatus.OK, {} 1009 1010 1011class RateLimitRestServlet(RestServlet): 1012 """An admin API to override ratelimiting for an user. 1013 1014 Example: 1015 POST /_synapse/admin/v1/users/@test:example.com/override_ratelimit 1016 { 1017 "messages_per_second": 0, 1018 "burst_count": 0 1019 } 1020 200 OK 1021 { 1022 "messages_per_second": 0, 1023 "burst_count": 0 1024 } 1025 """ 1026 1027 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/override_ratelimit$") 1028 1029 def __init__(self, hs: "HomeServer"): 1030 self.store = hs.get_datastore() 1031 self.auth = hs.get_auth() 1032 self.is_mine_id = hs.is_mine_id 1033 1034 async def on_GET( 1035 self, request: SynapseRequest, user_id: str 1036 ) -> Tuple[int, JsonDict]: 1037 await assert_requester_is_admin(self.auth, request) 1038 1039 if not self.is_mine_id(user_id): 1040 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") 1041 1042 if not await self.store.get_user_by_id(user_id): 1043 raise NotFoundError("User not found") 1044 1045 ratelimit = await self.store.get_ratelimit_for_user(user_id) 1046 1047 if ratelimit: 1048 # convert `null` to `0` for consistency 1049 # both values do the same in retelimit handler 1050 ret = { 1051 "messages_per_second": 0 1052 if ratelimit.messages_per_second is None 1053 else ratelimit.messages_per_second, 1054 "burst_count": 0 1055 if ratelimit.burst_count is None 1056 else ratelimit.burst_count, 1057 } 1058 else: 1059 ret = {} 1060 1061 return HTTPStatus.OK, ret 1062 1063 async def on_POST( 1064 self, request: SynapseRequest, user_id: str 1065 ) -> Tuple[int, JsonDict]: 1066 await assert_requester_is_admin(self.auth, request) 1067 1068 if not self.is_mine_id(user_id): 1069 raise SynapseError( 1070 HTTPStatus.BAD_REQUEST, "Only local users can be ratelimited" 1071 ) 1072 1073 if not await self.store.get_user_by_id(user_id): 1074 raise NotFoundError("User not found") 1075 1076 body = parse_json_object_from_request(request, allow_empty_body=True) 1077 1078 messages_per_second = body.get("messages_per_second", 0) 1079 burst_count = body.get("burst_count", 0) 1080 1081 if not isinstance(messages_per_second, int) or messages_per_second < 0: 1082 raise SynapseError( 1083 HTTPStatus.BAD_REQUEST, 1084 "%r parameter must be a positive int" % (messages_per_second,), 1085 errcode=Codes.INVALID_PARAM, 1086 ) 1087 1088 if not isinstance(burst_count, int) or burst_count < 0: 1089 raise SynapseError( 1090 HTTPStatus.BAD_REQUEST, 1091 "%r parameter must be a positive int" % (burst_count,), 1092 errcode=Codes.INVALID_PARAM, 1093 ) 1094 1095 await self.store.set_ratelimit_for_user( 1096 user_id, messages_per_second, burst_count 1097 ) 1098 ratelimit = await self.store.get_ratelimit_for_user(user_id) 1099 assert ratelimit is not None 1100 1101 ret = { 1102 "messages_per_second": ratelimit.messages_per_second, 1103 "burst_count": ratelimit.burst_count, 1104 } 1105 1106 return HTTPStatus.OK, ret 1107 1108 async def on_DELETE( 1109 self, request: SynapseRequest, user_id: str 1110 ) -> Tuple[int, JsonDict]: 1111 await assert_requester_is_admin(self.auth, request) 1112 1113 if not self.is_mine_id(user_id): 1114 raise SynapseError( 1115 HTTPStatus.BAD_REQUEST, "Only local users can be ratelimited" 1116 ) 1117 1118 if not await self.store.get_user_by_id(user_id): 1119 raise NotFoundError("User not found") 1120 1121 await self.store.delete_ratelimit_for_user(user_id) 1122 1123 return HTTPStatus.OK, {} 1124 1125 1126class AccountDataRestServlet(RestServlet): 1127 """Retrieve the given user's account data""" 1128 1129 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/accountdata") 1130 1131 def __init__(self, hs: "HomeServer"): 1132 self._auth = hs.get_auth() 1133 self._store = hs.get_datastore() 1134 self._is_mine_id = hs.is_mine_id 1135 1136 async def on_GET( 1137 self, request: SynapseRequest, user_id: str 1138 ) -> Tuple[int, JsonDict]: 1139 await assert_requester_is_admin(self._auth, request) 1140 1141 if not self._is_mine_id(user_id): 1142 raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") 1143 1144 if not await self._store.get_user_by_id(user_id): 1145 raise NotFoundError("User not found") 1146 1147 global_data, by_room_data = await self._store.get_account_data_for_user(user_id) 1148 return HTTPStatus.OK, { 1149 "account_data": { 1150 "global": global_data, 1151 "rooms": by_room_data, 1152 }, 1153 } 1154