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