mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-14 11:57:44 +00:00
An federation whitelist query endpoint extension (#16848)
This is to allow clients to query the configured federation whitelist. Disabled by default. --------- Co-authored-by: Devon Hudson <devonhudson@librem.one> Co-authored-by: devonh <devon.dmytro@gmail.com> Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
This commit is contained in:
parent
59ac541310
commit
038b9ec59a
8 changed files with 243 additions and 0 deletions
1
changelog.d/16848.feature
Normal file
1
changelog.d/16848.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add a feature that allows clients to query the configured federation whitelist. Disabled by default.
|
|
@ -1232,6 +1232,31 @@ federation_domain_whitelist:
|
|||
- syd.example.com
|
||||
```
|
||||
---
|
||||
### `federation_whitelist_endpoint_enabled`
|
||||
|
||||
Enables an endpoint for fetching the federation whitelist config.
|
||||
|
||||
The request method and path is `GET /_synapse/client/config/federation_whitelist`, and the
|
||||
response format is:
|
||||
|
||||
```json
|
||||
{
|
||||
"whitelist_enabled": true, // Whether the federation whitelist is being enforced
|
||||
"whitelist": [ // Which server names are allowed by the whitelist
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If `whitelist_enabled` is `false` then the server is permitted to federate with all others.
|
||||
|
||||
The endpoint requires authentication.
|
||||
|
||||
Example configuration:
|
||||
```yaml
|
||||
federation_whitelist_endpoint_enabled: true
|
||||
```
|
||||
---
|
||||
### `federation_metrics_domains`
|
||||
|
||||
Report prometheus metrics on the age of PDUs being sent to and received from
|
||||
|
|
|
@ -42,6 +42,10 @@ class FederationConfig(Config):
|
|||
for domain in federation_domain_whitelist:
|
||||
self.federation_domain_whitelist[domain] = True
|
||||
|
||||
self.federation_whitelist_endpoint_enabled = config.get(
|
||||
"federation_whitelist_endpoint_enabled", False
|
||||
)
|
||||
|
||||
federation_metrics_domains = config.get("federation_metrics_domains") or []
|
||||
validate_config(
|
||||
_METRICS_FOR_DOMAINS_SCHEMA,
|
||||
|
|
|
@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Mapping
|
|||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
from synapse.rest.synapse.client.federation_whitelist import FederationWhitelistResource
|
||||
from synapse.rest.synapse.client.new_user_consent import NewUserConsentResource
|
||||
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
||||
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
||||
|
@ -77,6 +78,9 @@ def build_synapse_client_resource_tree(hs: "HomeServer") -> Mapping[str, Resourc
|
|||
# To be removed in Synapse v1.32.0.
|
||||
resources["/_matrix/saml2"] = res
|
||||
|
||||
if hs.config.federation.federation_whitelist_endpoint_enabled:
|
||||
resources[FederationWhitelistResource.PATH] = FederationWhitelistResource(hs)
|
||||
|
||||
if hs.config.experimental.msc4108_enabled:
|
||||
resources["/_synapse/client/rendezvous"] = MSC4108RendezvousSessionResource(hs)
|
||||
|
||||
|
|
66
synapse/rest/synapse/client/federation_whitelist.py
Normal file
66
synapse/rest/synapse/client/federation_whitelist.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2024 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
from synapse.http.server import DirectServeJsonResource
|
||||
from synapse.http.site import SynapseRequest
|
||||
from synapse.types import JsonDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FederationWhitelistResource(DirectServeJsonResource):
|
||||
"""Custom endpoint (disabled by default) to fetch the federation whitelist
|
||||
config.
|
||||
|
||||
Only enabled if `federation_whitelist_endpoint_enabled` feature is enabled.
|
||||
|
||||
Response format:
|
||||
|
||||
{
|
||||
"whitelist_enabled": true, // Whether the federation whitelist is being enforced
|
||||
"whitelist": [ // Which server names are allowed by the whitelist
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
PATH = "/_synapse/client/v1/config/federation_whitelist"
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__()
|
||||
|
||||
self._federation_whitelist = hs.config.federation.federation_domain_whitelist
|
||||
|
||||
self._auth = hs.get_auth()
|
||||
|
||||
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||
await self._auth.get_user_by_req(request)
|
||||
|
||||
whitelist = []
|
||||
if self._federation_whitelist:
|
||||
# federation_whitelist is actually a dict, not a list
|
||||
whitelist = list(self._federation_whitelist)
|
||||
|
||||
return_dict: JsonDict = {
|
||||
"whitelist_enabled": self._federation_whitelist is not None,
|
||||
"whitelist": whitelist,
|
||||
}
|
||||
|
||||
return 200, return_dict
|
12
tests/rest/synapse/__init__.py
Normal file
12
tests/rest/synapse/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2024 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
12
tests/rest/synapse/client/__init__.py
Normal file
12
tests/rest/synapse/client/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2024 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
119
tests/rest/synapse/client/test_federation_whitelist.py
Normal file
119
tests/rest/synapse/client/test_federation_whitelist.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2024 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
from synapse.rest import admin
|
||||
from synapse.rest.client import login
|
||||
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class FederationWhitelistTests(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
admin.register_servlets_for_client_rest_resource,
|
||||
login.register_servlets,
|
||||
]
|
||||
|
||||
def create_resource_dict(self) -> Dict[str, Resource]:
|
||||
base = super().create_resource_dict()
|
||||
base.update(build_synapse_client_resource_tree(self.hs))
|
||||
return base
|
||||
|
||||
def test_default(self) -> None:
|
||||
"If the config option is not enabled, the endpoint should 404"
|
||||
channel = self.make_request(
|
||||
"GET", "/_synapse/client/v1/config/federation_whitelist", shorthand=False
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 404)
|
||||
|
||||
@unittest.override_config({"federation_whitelist_endpoint_enabled": True})
|
||||
def test_no_auth(self) -> None:
|
||||
"Endpoint requires auth when enabled"
|
||||
|
||||
channel = self.make_request(
|
||||
"GET", "/_synapse/client/v1/config/federation_whitelist", shorthand=False
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 401)
|
||||
|
||||
@unittest.override_config({"federation_whitelist_endpoint_enabled": True})
|
||||
def test_no_whitelist(self) -> None:
|
||||
"Test when there is no whitelist configured"
|
||||
|
||||
self.register_user("user", "password")
|
||||
tok = self.login("user", "password")
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/client/v1/config/federation_whitelist",
|
||||
shorthand=False,
|
||||
access_token=tok,
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body, {"whitelist_enabled": False, "whitelist": []}
|
||||
)
|
||||
|
||||
@unittest.override_config(
|
||||
{
|
||||
"federation_whitelist_endpoint_enabled": True,
|
||||
"federation_domain_whitelist": ["example.com"],
|
||||
}
|
||||
)
|
||||
def test_whitelist(self) -> None:
|
||||
"Test when there is a whitelist configured"
|
||||
|
||||
self.register_user("user", "password")
|
||||
tok = self.login("user", "password")
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/client/v1/config/federation_whitelist",
|
||||
shorthand=False,
|
||||
access_token=tok,
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body, {"whitelist_enabled": True, "whitelist": ["example.com"]}
|
||||
)
|
||||
|
||||
@unittest.override_config(
|
||||
{
|
||||
"federation_whitelist_endpoint_enabled": True,
|
||||
"federation_domain_whitelist": ["example.com", "example.com"],
|
||||
}
|
||||
)
|
||||
def test_whitelist_no_duplicates(self) -> None:
|
||||
"Test when there is a whitelist configured with duplicates, no duplicates are returned"
|
||||
|
||||
self.register_user("user", "password")
|
||||
tok = self.login("user", "password")
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/client/v1/config/federation_whitelist",
|
||||
shorthand=False,
|
||||
access_token=tok,
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body, {"whitelist_enabled": True, "whitelist": ["example.com"]}
|
||||
)
|
Loading…
Reference in a new issue