1# Copyright 2014-2021 The Matrix.org Foundation C.I.C.
2# Copyright 2020 Sorunome
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15import logging
16from typing import Dict, Iterable, List, Optional, Tuple, Type
17
18from typing_extensions import Literal
19
20from synapse.api.errors import FederationDeniedError, SynapseError
21from synapse.federation.transport.server._base import (
22    Authenticator,
23    BaseFederationServlet,
24)
25from synapse.federation.transport.server.federation import (
26    FEDERATION_SERVLET_CLASSES,
27    FederationTimestampLookupServlet,
28)
29from synapse.federation.transport.server.groups_local import GROUP_LOCAL_SERVLET_CLASSES
30from synapse.federation.transport.server.groups_server import (
31    GROUP_SERVER_SERVLET_CLASSES,
32)
33from synapse.http.server import HttpServer, JsonResource
34from synapse.http.servlet import (
35    parse_boolean_from_args,
36    parse_integer_from_args,
37    parse_string_from_args,
38)
39from synapse.server import HomeServer
40from synapse.types import JsonDict, ThirdPartyInstanceID
41from synapse.util.ratelimitutils import FederationRateLimiter
42
43logger = logging.getLogger(__name__)
44
45
46class TransportLayerServer(JsonResource):
47    """Handles incoming federation HTTP requests"""
48
49    def __init__(self, hs: HomeServer, servlet_groups: Optional[List[str]] = None):
50        """Initialize the TransportLayerServer
51
52        Will by default register all servlets. For custom behaviour, pass in
53        a list of servlet_groups to register.
54
55        Args:
56            hs: homeserver
57            servlet_groups: List of servlet groups to register.
58                Defaults to ``DEFAULT_SERVLET_GROUPS``.
59        """
60        self.hs = hs
61        self.clock = hs.get_clock()
62        self.servlet_groups = servlet_groups
63
64        super().__init__(hs, canonical_json=False)
65
66        self.authenticator = Authenticator(hs)
67        self.ratelimiter = hs.get_federation_ratelimiter()
68
69        self.register_servlets()
70
71    def register_servlets(self) -> None:
72        register_servlets(
73            self.hs,
74            resource=self,
75            ratelimiter=self.ratelimiter,
76            authenticator=self.authenticator,
77            servlet_groups=self.servlet_groups,
78        )
79
80
81class PublicRoomList(BaseFederationServlet):
82    """
83    Fetch the public room list for this server.
84
85    This API returns information in the same format as /publicRooms on the
86    client API, but will only ever include local public rooms and hence is
87    intended for consumption by other homeservers.
88
89    GET /publicRooms HTTP/1.1
90
91    HTTP/1.1 200 OK
92    Content-Type: application/json
93
94    {
95        "chunk": [
96            {
97                "aliases": [
98                    "#test:localhost"
99                ],
100                "guest_can_join": false,
101                "name": "test room",
102                "num_joined_members": 3,
103                "room_id": "!whkydVegtvatLfXmPN:localhost",
104                "world_readable": false
105            }
106        ],
107        "end": "END",
108        "start": "START"
109    }
110    """
111
112    PATH = "/publicRooms"
113
114    def __init__(
115        self,
116        hs: HomeServer,
117        authenticator: Authenticator,
118        ratelimiter: FederationRateLimiter,
119        server_name: str,
120    ):
121        super().__init__(hs, authenticator, ratelimiter, server_name)
122        self.handler = hs.get_room_list_handler()
123        self.allow_access = hs.config.server.allow_public_rooms_over_federation
124
125    async def on_GET(
126        self, origin: str, content: Literal[None], query: Dict[bytes, List[bytes]]
127    ) -> Tuple[int, JsonDict]:
128        if not self.allow_access:
129            raise FederationDeniedError(origin)
130
131        limit = parse_integer_from_args(query, "limit", 0)
132        since_token = parse_string_from_args(query, "since", None)
133        include_all_networks = parse_boolean_from_args(
134            query, "include_all_networks", default=False
135        )
136        third_party_instance_id = parse_string_from_args(
137            query, "third_party_instance_id", None
138        )
139
140        if include_all_networks:
141            network_tuple = None
142        elif third_party_instance_id:
143            network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
144        else:
145            network_tuple = ThirdPartyInstanceID(None, None)
146
147        if limit == 0:
148            # zero is a special value which corresponds to no limit.
149            limit = None
150
151        data = await self.handler.get_local_public_room_list(
152            limit, since_token, network_tuple=network_tuple, from_federation=True
153        )
154        return 200, data
155
156    async def on_POST(
157        self, origin: str, content: JsonDict, query: Dict[bytes, List[bytes]]
158    ) -> Tuple[int, JsonDict]:
159        # This implements MSC2197 (Search Filtering over Federation)
160        if not self.allow_access:
161            raise FederationDeniedError(origin)
162
163        limit: Optional[int] = int(content.get("limit", 100))
164        since_token = content.get("since", None)
165        search_filter = content.get("filter", None)
166
167        include_all_networks = content.get("include_all_networks", False)
168        third_party_instance_id = content.get("third_party_instance_id", None)
169
170        if include_all_networks:
171            network_tuple = None
172            if third_party_instance_id is not None:
173                raise SynapseError(
174                    400, "Can't use include_all_networks with an explicit network"
175                )
176        elif third_party_instance_id is None:
177            network_tuple = ThirdPartyInstanceID(None, None)
178        else:
179            network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
180
181        if search_filter is None:
182            logger.warning("Nonefilter")
183
184        if limit == 0:
185            # zero is a special value which corresponds to no limit.
186            limit = None
187
188        data = await self.handler.get_local_public_room_list(
189            limit=limit,
190            since_token=since_token,
191            search_filter=search_filter,
192            network_tuple=network_tuple,
193            from_federation=True,
194        )
195
196        return 200, data
197
198
199class FederationGroupsRenewAttestaionServlet(BaseFederationServlet):
200    """A group or user's server renews their attestation"""
201
202    PATH = "/groups/(?P<group_id>[^/]*)/renew_attestation/(?P<user_id>[^/]*)"
203
204    def __init__(
205        self,
206        hs: HomeServer,
207        authenticator: Authenticator,
208        ratelimiter: FederationRateLimiter,
209        server_name: str,
210    ):
211        super().__init__(hs, authenticator, ratelimiter, server_name)
212        self.handler = hs.get_groups_attestation_renewer()
213
214    async def on_POST(
215        self,
216        origin: str,
217        content: JsonDict,
218        query: Dict[bytes, List[bytes]],
219        group_id: str,
220        user_id: str,
221    ) -> Tuple[int, JsonDict]:
222        # We don't need to check auth here as we check the attestation signatures
223
224        new_content = await self.handler.on_renew_attestation(
225            group_id, user_id, content
226        )
227
228        return 200, new_content
229
230
231class OpenIdUserInfo(BaseFederationServlet):
232    """
233    Exchange a bearer token for information about a user.
234
235    The response format should be compatible with:
236        http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
237
238    GET /openid/userinfo?access_token=ABDEFGH HTTP/1.1
239
240    HTTP/1.1 200 OK
241    Content-Type: application/json
242
243    {
244        "sub": "@userpart:example.org",
245    }
246    """
247
248    PATH = "/openid/userinfo"
249
250    REQUIRE_AUTH = False
251
252    def __init__(
253        self,
254        hs: HomeServer,
255        authenticator: Authenticator,
256        ratelimiter: FederationRateLimiter,
257        server_name: str,
258    ):
259        super().__init__(hs, authenticator, ratelimiter, server_name)
260        self.handler = hs.get_federation_server()
261
262    async def on_GET(
263        self,
264        origin: Optional[str],
265        content: Literal[None],
266        query: Dict[bytes, List[bytes]],
267    ) -> Tuple[int, JsonDict]:
268        token = parse_string_from_args(query, "access_token")
269        if token is None:
270            return (
271                401,
272                {"errcode": "M_MISSING_TOKEN", "error": "Access Token required"},
273            )
274
275        user_id = await self.handler.on_openid_userinfo(token)
276
277        if user_id is None:
278            return (
279                401,
280                {
281                    "errcode": "M_UNKNOWN_TOKEN",
282                    "error": "Access Token unknown or expired",
283                },
284            )
285
286        return 200, {"sub": user_id}
287
288
289DEFAULT_SERVLET_GROUPS: Dict[str, Iterable[Type[BaseFederationServlet]]] = {
290    "federation": FEDERATION_SERVLET_CLASSES,
291    "room_list": (PublicRoomList,),
292    "group_server": GROUP_SERVER_SERVLET_CLASSES,
293    "group_local": GROUP_LOCAL_SERVLET_CLASSES,
294    "group_attestation": (FederationGroupsRenewAttestaionServlet,),
295    "openid": (OpenIdUserInfo,),
296}
297
298
299def register_servlets(
300    hs: HomeServer,
301    resource: HttpServer,
302    authenticator: Authenticator,
303    ratelimiter: FederationRateLimiter,
304    servlet_groups: Optional[Iterable[str]] = None,
305) -> None:
306    """Initialize and register servlet classes.
307
308    Will by default register all servlets. For custom behaviour, pass in
309    a list of servlet_groups to register.
310
311    Args:
312        hs: homeserver
313        resource: resource class to register to
314        authenticator: authenticator to use
315        ratelimiter: ratelimiter to use
316        servlet_groups: List of servlet groups to register.
317            Defaults to ``DEFAULT_SERVLET_GROUPS``.
318    """
319    if not servlet_groups:
320        servlet_groups = DEFAULT_SERVLET_GROUPS.keys()
321
322    for servlet_group in servlet_groups:
323        # Skip unknown servlet groups.
324        if servlet_group not in DEFAULT_SERVLET_GROUPS:
325            raise RuntimeError(
326                f"Attempting to register unknown federation servlet: '{servlet_group}'"
327            )
328
329        for servletclass in DEFAULT_SERVLET_GROUPS[servlet_group]:
330            # Only allow the `/timestamp_to_event` servlet if msc3030 is enabled
331            if (
332                servletclass == FederationTimestampLookupServlet
333                and not hs.config.experimental.msc3030_enabled
334            ):
335                continue
336
337            servletclass(
338                hs=hs,
339                authenticator=authenticator,
340                ratelimiter=ratelimiter,
341                server_name=hs.hostname,
342            ).register(resource)
343