123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 |
- # -*- coding: utf-8 -*-
- # Copyright 2019 The Matrix.org Foundation C.I.C.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import hashlib
- import hmac
- import logging
- import re
- from http import HTTPStatus
- from synapse.api.constants import UserTypes
- from synapse.api.errors import Codes, NotFoundError, SynapseError
- from synapse.http.servlet import (
- RestServlet,
- assert_params_in_dict,
- parse_boolean,
- parse_integer,
- parse_json_object_from_request,
- parse_string,
- )
- from synapse.rest.admin._base import (
- assert_requester_is_admin,
- assert_user_is_admin,
- historical_admin_path_patterns,
- )
- from synapse.types import UserID
- logger = logging.getLogger(__name__)
- class UsersRestServlet(RestServlet):
- PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$")
- def __init__(self, hs):
- self.hs = hs
- self.store = hs.get_datastore()
- self.auth = hs.get_auth()
- self.admin_handler = hs.get_handlers().admin_handler
- async def on_GET(self, request, user_id):
- target_user = UserID.from_string(user_id)
- await assert_requester_is_admin(self.auth, request)
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "Can only users a local user")
- ret = await self.store.get_users()
- return 200, ret
- class UsersRestServletV2(RestServlet):
- PATTERNS = (re.compile("^/_synapse/admin/v2/users$"),)
- """Get request to list all local users.
- This needs user to have administrator access in Synapse.
- GET /_synapse/admin/v2/users?from=0&limit=10&guests=false
- returns:
- 200 OK with list of users if success otherwise an error.
- The parameters `from` and `limit` are required only for pagination.
- By default, a `limit` of 100 is used.
- The parameter `user_id` can be used to filter by user id.
- The parameter `guests` can be used to exclude guest users.
- The parameter `deactivated` can be used to include deactivated users.
- """
- def __init__(self, hs):
- self.hs = hs
- self.store = hs.get_datastore()
- self.auth = hs.get_auth()
- self.admin_handler = hs.get_handlers().admin_handler
- async def on_GET(self, request):
- await assert_requester_is_admin(self.auth, request)
- start = parse_integer(request, "from", default=0)
- limit = parse_integer(request, "limit", default=100)
- user_id = parse_string(request, "user_id", default=None)
- guests = parse_boolean(request, "guests", default=True)
- deactivated = parse_boolean(request, "deactivated", default=False)
- users, total = await self.store.get_users_paginate(
- start, limit, user_id, guests, deactivated
- )
- ret = {"users": users, "total": total}
- if len(users) >= limit:
- ret["next_token"] = str(start + len(users))
- return 200, ret
- class UserRestServletV2(RestServlet):
- PATTERNS = (re.compile("^/_synapse/admin/v2/users/(?P<user_id>[^/]+)$"),)
- """Get request to list user details.
- This needs user to have administrator access in Synapse.
- GET /_synapse/admin/v2/users/<user_id>
- returns:
- 200 OK with user details if success otherwise an error.
- Put request to allow an administrator to add or modify a user.
- This needs user to have administrator access in Synapse.
- We use PUT instead of POST since we already know the id of the user
- object to create. POST could be used to create guests.
- PUT /_synapse/admin/v2/users/<user_id>
- {
- "password": "secret",
- "displayname": "User"
- }
- returns:
- 201 OK with new user object if user was created or
- 200 OK with modified user object if user was modified
- otherwise an error.
- """
- def __init__(self, hs):
- self.hs = hs
- self.auth = hs.get_auth()
- self.admin_handler = hs.get_handlers().admin_handler
- self.store = hs.get_datastore()
- self.auth_handler = hs.get_auth_handler()
- self.profile_handler = hs.get_profile_handler()
- self.set_password_handler = hs.get_set_password_handler()
- self.deactivate_account_handler = hs.get_deactivate_account_handler()
- self.registration_handler = hs.get_registration_handler()
- self.pusher_pool = hs.get_pusherpool()
- async def on_GET(self, request, user_id):
- await assert_requester_is_admin(self.auth, request)
- target_user = UserID.from_string(user_id)
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "Can only lookup local users")
- ret = await self.admin_handler.get_user(target_user)
- if not ret:
- raise NotFoundError("User not found")
- return 200, ret
- async def on_PUT(self, request, user_id):
- requester = await self.auth.get_user_by_req(request)
- await assert_user_is_admin(self.auth, requester.user)
- target_user = UserID.from_string(user_id)
- body = parse_json_object_from_request(request)
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "This endpoint can only be used with local users")
- user = await self.admin_handler.get_user(target_user)
- user_id = target_user.to_string()
- if user: # modify user
- if "displayname" in body:
- await self.profile_handler.set_displayname(
- target_user, requester, body["displayname"], True
- )
- if "threepids" in body:
- # check for required parameters for each threepid
- for threepid in body["threepids"]:
- assert_params_in_dict(threepid, ["medium", "address"])
- # remove old threepids from user
- threepids = await self.store.user_get_threepids(user_id)
- for threepid in threepids:
- try:
- await self.auth_handler.delete_threepid(
- user_id, threepid["medium"], threepid["address"], None
- )
- except Exception:
- logger.exception("Failed to remove threepids")
- raise SynapseError(500, "Failed to remove threepids")
- # add new threepids to user
- current_time = self.hs.get_clock().time_msec()
- for threepid in body["threepids"]:
- await self.auth_handler.add_threepid(
- user_id, threepid["medium"], threepid["address"], current_time
- )
- if "avatar_url" in body and type(body["avatar_url"]) == str:
- await self.profile_handler.set_avatar_url(
- target_user, requester, body["avatar_url"], True
- )
- if "admin" in body:
- set_admin_to = bool(body["admin"])
- if set_admin_to != user["admin"]:
- auth_user = requester.user
- if target_user == auth_user and not set_admin_to:
- raise SynapseError(400, "You may not demote yourself.")
- await self.store.set_server_admin(target_user, set_admin_to)
- if "password" in body:
- if not isinstance(body["password"], str) or len(body["password"]) > 512:
- raise SynapseError(400, "Invalid password")
- else:
- new_password = body["password"]
- logout_devices = True
- new_password_hash = await self.auth_handler.hash(new_password)
- await self.set_password_handler.set_password(
- target_user.to_string(),
- new_password_hash,
- logout_devices,
- requester,
- )
- if "deactivated" in body:
- deactivate = body["deactivated"]
- if not isinstance(deactivate, bool):
- raise SynapseError(
- 400, "'deactivated' parameter is not of type boolean"
- )
- if deactivate and not user["deactivated"]:
- await self.deactivate_account_handler.deactivate_account(
- target_user.to_string(), False
- )
- user = await self.admin_handler.get_user(target_user)
- return 200, user
- else: # create user
- password = body.get("password")
- password_hash = None
- if password is not None:
- if not isinstance(password, str) or len(password) > 512:
- raise SynapseError(400, "Invalid password")
- password_hash = await self.auth_handler.hash(password)
- admin = body.get("admin", None)
- user_type = body.get("user_type", None)
- displayname = body.get("displayname", None)
- threepids = body.get("threepids", None)
- if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
- raise SynapseError(400, "Invalid user type")
- user_id = await self.registration_handler.register_user(
- localpart=target_user.localpart,
- password_hash=password_hash,
- admin=bool(admin),
- default_display_name=displayname,
- user_type=user_type,
- by_admin=True,
- )
- if "threepids" in body:
- # check for required parameters for each threepid
- for threepid in body["threepids"]:
- assert_params_in_dict(threepid, ["medium", "address"])
- current_time = self.hs.get_clock().time_msec()
- for threepid in body["threepids"]:
- await self.auth_handler.add_threepid(
- user_id, threepid["medium"], threepid["address"], current_time
- )
- if (
- self.hs.config.email_enable_notifs
- and self.hs.config.email_notif_for_new_users
- ):
- await self.pusher_pool.add_pusher(
- user_id=user_id,
- access_token=None,
- kind="email",
- app_id="m.email",
- app_display_name="Email Notifications",
- device_display_name=threepid["address"],
- pushkey=threepid["address"],
- lang=None, # We don't know a user's language here
- data={},
- )
- if "avatar_url" in body and type(body["avatar_url"]) == str:
- await self.profile_handler.set_avatar_url(
- user_id, requester, body["avatar_url"], True
- )
- ret = await self.admin_handler.get_user(target_user)
- return 201, ret
- class UserRegisterServlet(RestServlet):
- """
- Attributes:
- NONCE_TIMEOUT (int): Seconds until a generated nonce won't be accepted
- nonces (dict[str, int]): The nonces that we will accept. A dict of
- nonce to the time it was generated, in int seconds.
- """
- PATTERNS = historical_admin_path_patterns("/register")
- NONCE_TIMEOUT = 60
- def __init__(self, hs):
- self.auth_handler = hs.get_auth_handler()
- self.reactor = hs.get_reactor()
- self.nonces = {}
- self.hs = hs
- def _clear_old_nonces(self):
- """
- Clear out old nonces that are older than NONCE_TIMEOUT.
- """
- now = int(self.reactor.seconds())
- for k, v in list(self.nonces.items()):
- if now - v > self.NONCE_TIMEOUT:
- del self.nonces[k]
- def on_GET(self, request):
- """
- Generate a new nonce.
- """
- self._clear_old_nonces()
- nonce = self.hs.get_secrets().token_hex(64)
- self.nonces[nonce] = int(self.reactor.seconds())
- return 200, {"nonce": nonce}
- async def on_POST(self, request):
- self._clear_old_nonces()
- if not self.hs.config.registration_shared_secret:
- raise SynapseError(400, "Shared secret registration is not enabled")
- body = parse_json_object_from_request(request)
- if "nonce" not in body:
- raise SynapseError(400, "nonce must be specified", errcode=Codes.BAD_JSON)
- nonce = body["nonce"]
- if nonce not in self.nonces:
- raise SynapseError(400, "unrecognised nonce")
- # Delete the nonce, so it can't be reused, even if it's invalid
- del self.nonces[nonce]
- if "username" not in body:
- raise SynapseError(
- 400, "username must be specified", errcode=Codes.BAD_JSON
- )
- else:
- if not isinstance(body["username"], str) or len(body["username"]) > 512:
- raise SynapseError(400, "Invalid username")
- username = body["username"].encode("utf-8")
- if b"\x00" in username:
- raise SynapseError(400, "Invalid username")
- if "password" not in body:
- raise SynapseError(
- 400, "password must be specified", errcode=Codes.BAD_JSON
- )
- else:
- password = body["password"]
- if not isinstance(password, str) or len(password) > 512:
- raise SynapseError(400, "Invalid password")
- password_bytes = password.encode("utf-8")
- if b"\x00" in password_bytes:
- raise SynapseError(400, "Invalid password")
- password_hash = await self.auth_handler.hash(password)
- admin = body.get("admin", None)
- user_type = body.get("user_type", None)
- if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
- raise SynapseError(400, "Invalid user type")
- got_mac = body["mac"]
- want_mac_builder = hmac.new(
- key=self.hs.config.registration_shared_secret.encode(),
- digestmod=hashlib.sha1,
- )
- want_mac_builder.update(nonce.encode("utf8"))
- want_mac_builder.update(b"\x00")
- want_mac_builder.update(username)
- want_mac_builder.update(b"\x00")
- want_mac_builder.update(password_bytes)
- want_mac_builder.update(b"\x00")
- want_mac_builder.update(b"admin" if admin else b"notadmin")
- if user_type:
- want_mac_builder.update(b"\x00")
- want_mac_builder.update(user_type.encode("utf8"))
- want_mac = want_mac_builder.hexdigest()
- if not hmac.compare_digest(want_mac.encode("ascii"), got_mac.encode("ascii")):
- raise SynapseError(403, "HMAC incorrect")
- # Reuse the parts of RegisterRestServlet to reduce code duplication
- from synapse.rest.client.v2_alpha.register import RegisterRestServlet
- register = RegisterRestServlet(self.hs)
- user_id = await register.registration_handler.register_user(
- localpart=body["username"].lower(),
- password_hash=password_hash,
- admin=bool(admin),
- user_type=user_type,
- by_admin=True,
- )
- result = await register._create_registration_details(user_id, body)
- return 200, result
- class WhoisRestServlet(RestServlet):
- PATTERNS = historical_admin_path_patterns("/whois/(?P<user_id>[^/]*)")
- def __init__(self, hs):
- self.hs = hs
- self.auth = hs.get_auth()
- self.handlers = hs.get_handlers()
- async def on_GET(self, request, user_id):
- 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.hs.is_mine(target_user):
- raise SynapseError(400, "Can only whois a local user")
- ret = await self.handlers.admin_handler.get_whois(target_user)
- return 200, ret
- class DeactivateAccountRestServlet(RestServlet):
- PATTERNS = historical_admin_path_patterns("/deactivate/(?P<target_user_id>[^/]*)")
- def __init__(self, hs):
- self._deactivate_account_handler = hs.get_deactivate_account_handler()
- self.auth = hs.get_auth()
- async def on_POST(self, request, target_user_id):
- await assert_requester_is_admin(self.auth, request)
- body = parse_json_object_from_request(request, allow_empty_body=True)
- erase = body.get("erase", False)
- if not isinstance(erase, bool):
- raise SynapseError(
- HTTPStatus.BAD_REQUEST,
- "Param 'erase' must be a boolean, if given",
- Codes.BAD_JSON,
- )
- UserID.from_string(target_user_id)
- result = await self._deactivate_account_handler.deactivate_account(
- target_user_id, erase
- )
- if result:
- id_server_unbind_result = "success"
- else:
- id_server_unbind_result = "no-support"
- return 200, {"id_server_unbind_result": id_server_unbind_result}
- class AccountValidityRenewServlet(RestServlet):
- PATTERNS = historical_admin_path_patterns("/account_validity/validity$")
- def __init__(self, hs):
- """
- Args:
- hs (synapse.server.HomeServer): server
- """
- self.hs = hs
- self.account_activity_handler = hs.get_account_validity_handler()
- self.auth = hs.get_auth()
- async def on_POST(self, request):
- await assert_requester_is_admin(self.auth, request)
- body = parse_json_object_from_request(request)
- if "user_id" not in body:
- raise SynapseError(400, "Missing property 'user_id' in the request body")
- expiration_ts = await self.account_activity_handler.renew_account_for_user(
- body["user_id"],
- body.get("expiration_ts"),
- not body.get("enable_renewal_emails", True),
- )
- res = {"expiration_ts": expiration_ts}
- return 200, res
- class ResetPasswordRestServlet(RestServlet):
- """Post request to allow an administrator reset password for a user.
- This needs user to have administrator access in Synapse.
- Example:
- http://localhost:8008/_synapse/admin/v1/reset_password/
- @user:to_reset_password?access_token=admin_access_token
- JsonBodyToSend:
- {
- "new_password": "secret"
- }
- Returns:
- 200 OK with empty object if success otherwise an error.
- """
- PATTERNS = historical_admin_path_patterns(
- "/reset_password/(?P<target_user_id>[^/]*)"
- )
- def __init__(self, hs):
- self.store = hs.get_datastore()
- self.hs = hs
- self.auth = hs.get_auth()
- self.auth_handler = hs.get_auth_handler()
- self._set_password_handler = hs.get_set_password_handler()
- async def on_POST(self, request, target_user_id):
- """Post request to allow an administrator reset password for a user.
- This needs user to have administrator access in Synapse.
- """
- requester = await self.auth.get_user_by_req(request)
- await assert_user_is_admin(self.auth, requester.user)
- UserID.from_string(target_user_id)
- params = parse_json_object_from_request(request)
- assert_params_in_dict(params, ["new_password"])
- new_password = params["new_password"]
- logout_devices = params.get("logout_devices", True)
- new_password_hash = await self.auth_handler.hash(new_password)
- await self._set_password_handler.set_password(
- target_user_id, new_password_hash, logout_devices, requester
- )
- return 200, {}
- class SearchUsersRestServlet(RestServlet):
- """Get request to search user table for specific users according to
- search term.
- This needs user to have administrator access in Synapse.
- Example:
- http://localhost:8008/_synapse/admin/v1/search_users/
- @admin:user?access_token=admin_access_token&term=alice
- Returns:
- 200 OK with json object {list[dict[str, Any]], count} or empty object.
- """
- PATTERNS = historical_admin_path_patterns("/search_users/(?P<target_user_id>[^/]*)")
- def __init__(self, hs):
- self.hs = hs
- self.store = hs.get_datastore()
- self.auth = hs.get_auth()
- self.handlers = hs.get_handlers()
- async def on_GET(self, request, target_user_id):
- """Get request to search user table for specific users according to
- search term.
- This needs user to have a administrator access in Synapse.
- """
- await assert_requester_is_admin(self.auth, request)
- target_user = UserID.from_string(target_user_id)
- # To allow all users to get the users list
- # if not is_admin and target_user != auth_user:
- # raise AuthError(403, "You are not a server admin")
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "Can only users a local user")
- term = parse_string(request, "term", required=True)
- logger.info("term: %s ", term)
- ret = await self.handlers.store.search_users(term)
- return 200, ret
- class UserAdminServlet(RestServlet):
- """
- Get or set whether or not a user is a server administrator.
- Note that only local users can be server administrators, and that an
- administrator may not demote themselves.
- Only server administrators can use this API.
- Examples:
- * Get
- GET /_synapse/admin/v1/users/@nonadmin:example.com/admin
- response on success:
- {
- "admin": false
- }
- * Set
- PUT /_synapse/admin/v1/users/@reivilibre:librepush.net/admin
- request body:
- {
- "admin": true
- }
- response on success:
- {}
- """
- PATTERNS = (re.compile("^/_synapse/admin/v1/users/(?P<user_id>[^/]*)/admin$"),)
- def __init__(self, hs):
- self.hs = hs
- self.store = hs.get_datastore()
- self.auth = hs.get_auth()
- async def on_GET(self, request, user_id):
- await assert_requester_is_admin(self.auth, request)
- target_user = UserID.from_string(user_id)
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "Only local users can be admins of this homeserver")
- is_admin = await self.store.is_server_admin(target_user)
- return 200, {"admin": is_admin}
- async def on_PUT(self, request, user_id):
- requester = await self.auth.get_user_by_req(request)
- await assert_user_is_admin(self.auth, requester.user)
- auth_user = requester.user
- target_user = UserID.from_string(user_id)
- body = parse_json_object_from_request(request)
- assert_params_in_dict(body, ["admin"])
- if not self.hs.is_mine(target_user):
- raise SynapseError(400, "Only local users can be admins of this homeserver")
- set_admin_to = bool(body["admin"])
- if target_user == auth_user and not set_admin_to:
- raise SynapseError(400, "You may not demote yourself.")
- await self.store.set_server_admin(target_user, set_admin_to)
- return 200, {}
|