From 37381629e77d3c596f2d07c33be61a12d3c66ef0 Mon Sep 17 00:00:00 2001 From: "Kai A. Hiller" Date: Thu, 9 Jan 2025 15:13:06 +0100 Subject: [PATCH] appservice: Add {as,hs}_token_path config options --- changelog.d/18071.feature | 1 + synapse/config/appservice.py | 48 ++++++++++++++++++++++++++++++------ tests/config/test_load.py | 37 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 changelog.d/18071.feature diff --git a/changelog.d/18071.feature b/changelog.d/18071.feature new file mode 100644 index 00000000000..5c802eee364 --- /dev/null +++ b/changelog.d/18071.feature @@ -0,0 +1 @@ +Add appservice config options `as_token_path` and `hs_token_path`. \ No newline at end of file diff --git a/synapse/config/appservice.py b/synapse/config/appservice.py index dda6bcd1b79..adc9751e9e0 100644 --- a/synapse/config/appservice.py +++ b/synapse/config/appservice.py @@ -30,10 +30,20 @@ from synapse.appservice import ApplicationService from synapse.types import JsonDict, UserID -from ._base import Config, ConfigError +from ._base import Config, ConfigError, read_file logger = logging.getLogger(__name__) +CONFLICTING_AS_TOKEN_OPTS_ERROR = """\ +You have configured both `as_token` and `as_token_path`. +These are mutually incompatible. +""" + +CONFLICTING_HS_TOKEN_OPTS_ERROR = """\ +You have configured both `hs_token` and `hs_token_path`. +These are mutually incompatible. +""" + class AppServiceConfig(Config): section = "appservice" @@ -105,13 +115,37 @@ def load_appservices( def _load_appservice( hostname: str, as_info: JsonDict, config_filename: str ) -> ApplicationService: - required_string_fields = ["id", "as_token", "hs_token", "sender_localpart"] - for field in required_string_fields: - if not isinstance(as_info.get(field), str): + required_fields = ["id", "sender_localpart"] + for field in required_fields: + if field not in as_info: + raise KeyError("Required field: '%s' (%s)" % (field, config_filename)) + string_fields = [ + "id", + "sender_localpart", + "as_token", + "as_token_path", + "hs_token", + "hs_token_path", + ] + for field in string_fields: + if not isinstance(as_info.get(field, ""), (str, type(None))): raise KeyError( - "Required string field: '%s' (%s)" % (field, config_filename) + "Field must be a string: '%s' (%s)" % (field, config_filename) ) + if token_path := as_info.get("as_token_path"): + if as_info.get("as_token"): + raise ConfigError(CONFLICTING_AS_TOKEN_OPTS_ERROR) + token = read_file(token_path, ("as_token_path",)).strip() + else: + token = as_info["as_token"] + + hs_token = as_info.get("hs_token") + if hs_token_path := as_info.get("hs_token_path"): + if "hs_token" in as_info: + raise ConfigError(CONFLICTING_HS_TOKEN_OPTS_ERROR) + hs_token = read_file(hs_token_path, ("hs_token_path",)).strip() + # 'url' must either be a string or explicitly null, not missing # to avoid accidentally turning off push for ASes. if not isinstance(as_info.get("url"), str) and as_info.get("url", "") is not None: @@ -196,10 +230,10 @@ def _load_appservice( ) return ApplicationService( - token=as_info["as_token"], + token=token, url=as_info["url"], namespaces=as_info["namespaces"], - hs_token=as_info["hs_token"], + hs_token=hs_token, sender=user_id, id=as_info["id"], protocols=protocols, diff --git a/tests/config/test_load.py b/tests/config/test_load.py index f8f7b72e401..c91472c230d 100644 --- a/tests/config/test_load.py +++ b/tests/config/test_load.py @@ -27,6 +27,7 @@ from synapse.config import ConfigError from synapse.config._base import RootConfig +from synapse.config.appservice import _load_appservice from synapse.config.homeserver import HomeServerConfig from tests.config.utils import ConfigFileTestCase @@ -179,3 +180,39 @@ def test_secret_files_existing( config = HomeServerConfig.load_config("", ["-c", self.config_file]) self.assertEqual(get_secret(config), b"53C237") + + def test_secret_files_appservice(self) -> None: + with tempfile.NamedTemporaryFile(buffering=0) as token_file: + token_file.write(b"53C237") + + as_info = { + "id": "fake-as-id", + "url": "example.org", + "as_token_path": token_file.name, + "hs_token_path": token_file.name, + "sender_localpart": "not-real-part", + "namespaces": {}, + } + + appservice = _load_appservice( + hostname="example.org", + as_info=as_info, + config_filename="mock-config-file.name", + ) + + self.assertEqual(appservice.token, "53C237") + self.assertEqual(appservice.hs_token, "53C237") + + with self.assertRaises(ConfigError): + _load_appservice( + hostname="example.org", + as_info={**as_info, "as_token_path": "/does/not/exist"}, + config_filename="mock-config-file.name", + ) + + with self.assertRaises(ConfigError): + _load_appservice( + hostname="example.org", + as_info={**as_info, "hs_token_path": "/does/not/exist"}, + config_filename="mock-config-file.name", + )