mirror of
https://github.com/element-hq/synapse.git
synced 2026-09-29 17:58:31 +00:00
Adds a `redis.username` config option. Details: A `username` without a `password` (or `password_path`) is refused at startup. Redis has no wire form for a username without a password, and txredisapi only sends `AUTH` when a password is set, so the username would otherwise be silently ignored. An explicitly empty password is accepted, since that is how a `nopass` ACL user is configured. This relies on txredisapi 1.4.12, the first release to accept a `username` kwarg. That upstream support was contributed by @karolyi specifically to unblock this. Fixes #19238. ### Pull Request Checklist <!-- Please read https://element-hq.github.io/synapse/latest/development/contributing_guide.html before submitting your pull request --> * [X] Pull request is based on the develop branch * [X] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). The entry should: - Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - Use markdown where necessary, mostly for `code blocks`. - End with either a period (.) or an exclamation mark (!). - Start with a capital letter. - Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry. * [X] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
#
|
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
|
#
|
|
# Copyright (C) 2026 Element Creations 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 tempfile
|
|
|
|
from synapse.config._base import ConfigError
|
|
from synapse.config.homeserver import HomeServerConfig
|
|
|
|
from tests.unittest import TestCase
|
|
from tests.utils import default_config
|
|
|
|
try:
|
|
import hiredis
|
|
except ImportError:
|
|
hiredis = None # type: ignore
|
|
|
|
|
|
class RedisConfigTestCase(TestCase):
|
|
# hiredis is part of the `redis` extra, which `RedisConfig` requires to
|
|
# parse an enabled redis config.
|
|
if not hiredis:
|
|
skip = "Requires hiredis"
|
|
|
|
def _make_config(self, redis_config: dict) -> HomeServerConfig:
|
|
config_dict = default_config(server_name="test")
|
|
config_dict["redis"] = redis_config
|
|
config = HomeServerConfig()
|
|
config.parse_config_dict(config_dict, "", "")
|
|
return config
|
|
|
|
def test_username_defaults_to_none(self) -> None:
|
|
"""`redis.username` is `None` when not configured."""
|
|
config = self._make_config({"enabled": True, "password": "hunter2"})
|
|
self.assertIsNone(config.redis.redis_username)
|
|
|
|
def test_username_is_parsed(self) -> None:
|
|
"""`redis.username` is parsed through to `redis_username` when set
|
|
alongside `password`."""
|
|
config = self._make_config(
|
|
{"enabled": True, "username": "alice", "password": "hunter2"}
|
|
)
|
|
self.assertEqual(config.redis.redis_username, "alice")
|
|
|
|
def test_username_is_parsed_with_password_path(self) -> None:
|
|
"""`redis.username` is also accepted alongside `password_path`, the
|
|
documented alternative to an inline `password`."""
|
|
with tempfile.NamedTemporaryFile(buffering=0) as password_file:
|
|
password_file.write(b"hunter2")
|
|
|
|
config = self._make_config(
|
|
{
|
|
"enabled": True,
|
|
"username": "alice",
|
|
"password_path": password_file.name,
|
|
}
|
|
)
|
|
self.assertEqual(config.redis.redis_username, "alice")
|
|
self.assertEqual(config.redis.redis_password, "hunter2")
|
|
|
|
def test_username_with_empty_password_is_accepted(self) -> None:
|
|
"""`redis.username` with an explicitly empty `password` is allowed: that
|
|
is how a Redis ACL user declared `nopass` is configured."""
|
|
config = self._make_config(
|
|
{"enabled": True, "username": "alice", "password": ""}
|
|
)
|
|
self.assertEqual(config.redis.redis_username, "alice")
|
|
self.assertEqual(config.redis.redis_password, "")
|
|
|
|
def test_username_without_password_is_rejected(self) -> None:
|
|
"""`redis.username` without any of `password`/`password_path` is
|
|
rejected: Redis ACL authentication requires both."""
|
|
with self.assertRaises(ConfigError):
|
|
self._make_config({"enabled": True, "username": "alice"})
|