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