@@ -0,0 +1 @@ | |||
Refactor configuration loading to allow better typechecking. |
@@ -4,10 +4,6 @@ plugins=mypy_zope:plugin | |||
follow_imports=skip | |||
mypy_path=stubs | |||
[mypy-synapse.config.homeserver] | |||
# this is a mess because of the metaclass shenanigans | |||
ignore_errors = True | |||
[mypy-zope] | |||
ignore_missing_imports = True | |||
@@ -52,3 +48,15 @@ ignore_missing_imports = True | |||
[mypy-signedjson.*] | |||
ignore_missing_imports = True | |||
[mypy-prometheus_client.*] | |||
ignore_missing_imports = True | |||
[mypy-service_identity.*] | |||
ignore_missing_imports = True | |||
[mypy-daemonize] | |||
ignore_missing_imports = True | |||
[mypy-sentry_sdk] | |||
ignore_missing_imports = True |
@@ -18,7 +18,9 @@ | |||
import argparse | |||
import errno | |||
import os | |||
from collections import OrderedDict | |||
from textwrap import dedent | |||
from typing import Any, MutableMapping, Optional | |||
from six import integer_types | |||
@@ -51,7 +53,56 @@ Missing mandatory `server_name` config option. | |||
""" | |||
def path_exists(file_path): | |||
"""Check if a file exists | |||
Unlike os.path.exists, this throws an exception if there is an error | |||
checking if the file exists (for example, if there is a perms error on | |||
the parent dir). | |||
Returns: | |||
bool: True if the file exists; False if not. | |||
""" | |||
try: | |||
os.stat(file_path) | |||
return True | |||
except OSError as e: | |||
if e.errno != errno.ENOENT: | |||
raise e | |||
return False | |||
class Config(object): | |||
""" | |||
A configuration section, containing configuration keys and values. | |||
Attributes: | |||
section (str): The section title of this config object, such as | |||
"tls" or "logger". This is used to refer to it on the root | |||
logger (for example, `config.tls.some_option`). Must be | |||
defined in subclasses. | |||
""" | |||
section = None | |||
def __init__(self, root_config=None): | |||
self.root = root_config | |||
def __getattr__(self, item: str) -> Any: | |||
""" | |||
Try and fetch a configuration option that does not exist on this class. | |||
This is so that existing configs that rely on `self.value`, where value | |||
is actually from a different config section, continue to work. | |||
""" | |||
if item in ["generate_config_section", "read_config"]: | |||
raise AttributeError(item) | |||
if self.root is None: | |||
raise AttributeError(item) | |||
else: | |||
return self.root._get_unclassed_config(self.section, item) | |||
@staticmethod | |||
def parse_size(value): | |||
if isinstance(value, integer_types): | |||
@@ -88,22 +139,7 @@ class Config(object): | |||
@classmethod | |||
def path_exists(cls, file_path): | |||
"""Check if a file exists | |||
Unlike os.path.exists, this throws an exception if there is an error | |||
checking if the file exists (for example, if there is a perms error on | |||
the parent dir). | |||
Returns: | |||
bool: True if the file exists; False if not. | |||
""" | |||
try: | |||
os.stat(file_path) | |||
return True | |||
except OSError as e: | |||
if e.errno != errno.ENOENT: | |||
raise e | |||
return False | |||
return path_exists(file_path) | |||
@classmethod | |||
def check_file(cls, file_path, config_name): | |||
@@ -136,42 +172,106 @@ class Config(object): | |||
with open(file_path) as file_stream: | |||
return file_stream.read() | |||
def invoke_all(self, name, *args, **kargs): | |||
"""Invoke all instance methods with the given name and arguments in the | |||
class's MRO. | |||
class RootConfig(object): | |||
""" | |||
Holder of an application's configuration. | |||
What configuration this object holds is defined by `config_classes`, a list | |||
of Config classes that will be instantiated and given the contents of a | |||
configuration file to read. They can then be accessed on this class by their | |||
section name, defined in the Config or dynamically set to be the name of the | |||
class, lower-cased and with "Config" removed. | |||
""" | |||
config_classes = [] | |||
def __init__(self): | |||
self._configs = OrderedDict() | |||
for config_class in self.config_classes: | |||
if config_class.section is None: | |||
raise ValueError("%r requires a section name" % (config_class,)) | |||
try: | |||
conf = config_class(self) | |||
except Exception as e: | |||
raise Exception("Failed making %s: %r" % (config_class.section, e)) | |||
self._configs[config_class.section] = conf | |||
def __getattr__(self, item: str) -> Any: | |||
""" | |||
Redirect lookups on this object either to config objects, or values on | |||
config objects, so that `config.tls.blah` works, as well as legacy uses | |||
of things like `config.server_name`. It will first look up the config | |||
section name, and then values on those config classes. | |||
""" | |||
if item in self._configs.keys(): | |||
return self._configs[item] | |||
return self._get_unclassed_config(None, item) | |||
def _get_unclassed_config(self, asking_section: Optional[str], item: str): | |||
""" | |||
Fetch a config value from one of the instantiated config classes that | |||
has not been fetched directly. | |||
Args: | |||
asking_section: If this check is coming from a Config child, which | |||
one? This section will not be asked if it has the value. | |||
item: The configuration value key. | |||
Raises: | |||
AttributeError if no config classes have the config key. The body | |||
will contain what sections were checked. | |||
""" | |||
for key, val in self._configs.items(): | |||
if key == asking_section: | |||
continue | |||
if item in dir(val): | |||
return getattr(val, item) | |||
raise AttributeError(item, "not found in %s" % (list(self._configs.keys()),)) | |||
def invoke_all(self, func_name: str, *args, **kwargs) -> MutableMapping[str, Any]: | |||
""" | |||
Invoke a function on all instantiated config objects this RootConfig is | |||
configured to use. | |||
Args: | |||
name (str): Name of function to invoke | |||
func_name: Name of function to invoke | |||
*args | |||
**kwargs | |||
Returns: | |||
list: The list of the return values from each method called | |||
ordered dictionary of config section name and the result of the | |||
function from it. | |||
""" | |||
results = [] | |||
for cls in type(self).mro(): | |||
if name in cls.__dict__: | |||
results.append(getattr(cls, name)(self, *args, **kargs)) | |||
return results | |||
res = OrderedDict() | |||
for name, config in self._configs.items(): | |||
if hasattr(config, func_name): | |||
res[name] = getattr(config, func_name)(*args, **kwargs) | |||
return res | |||
@classmethod | |||
def invoke_all_static(cls, name, *args, **kargs): | |||
"""Invoke all static methods with the given name and arguments in the | |||
class's MRO. | |||
def invoke_all_static(cls, func_name: str, *args, **kwargs): | |||
""" | |||
Invoke a static function on config objects this RootConfig is | |||
configured to use. | |||
Args: | |||
name (str): Name of function to invoke | |||
func_name: Name of function to invoke | |||
*args | |||
**kwargs | |||
Returns: | |||
list: The list of the return values from each method called | |||
ordered dictionary of config section name and the result of the | |||
function from it. | |||
""" | |||
results = [] | |||
for c in cls.mro(): | |||
if name in c.__dict__: | |||
results.append(getattr(c, name)(*args, **kargs)) | |||
return results | |||
for config in cls.config_classes: | |||
if hasattr(config, func_name): | |||
getattr(config, func_name)(*args, **kwargs) | |||
def generate_config( | |||
self, | |||
@@ -187,7 +287,8 @@ class Config(object): | |||
tls_private_key_path=None, | |||
acme_domain=None, | |||
): | |||
"""Build a default configuration file | |||
""" | |||
Build a default configuration file | |||
This is used when the user explicitly asks us to generate a config file | |||
(eg with --generate_config). | |||
@@ -242,6 +343,7 @@ class Config(object): | |||
Returns: | |||
str: the yaml config file | |||
""" | |||
return "\n\n".join( | |||
dedent(conf) | |||
for conf in self.invoke_all( | |||
@@ -257,7 +359,7 @@ class Config(object): | |||
tls_certificate_path=tls_certificate_path, | |||
tls_private_key_path=tls_private_key_path, | |||
acme_domain=acme_domain, | |||
) | |||
).values() | |||
) | |||
@classmethod | |||
@@ -444,7 +546,7 @@ class Config(object): | |||
) | |||
(config_path,) = config_files | |||
if not cls.path_exists(config_path): | |||
if not path_exists(config_path): | |||
print("Generating config file %s" % (config_path,)) | |||
if config_args.data_directory: | |||
@@ -469,7 +571,7 @@ class Config(object): | |||
open_private_ports=config_args.open_private_ports, | |||
) | |||
if not cls.path_exists(config_dir_path): | |||
if not path_exists(config_dir_path): | |||
os.makedirs(config_dir_path) | |||
with open(config_path, "w") as config_file: | |||
config_file.write("# vim:ft=yaml\n\n") | |||
@@ -518,7 +620,7 @@ class Config(object): | |||
return obj | |||
def parse_config_dict(self, config_dict, config_dir_path, data_dir_path): | |||
def parse_config_dict(self, config_dict, config_dir_path=None, data_dir_path=None): | |||
"""Read the information from the config dict into this Config object. | |||
Args: | |||
@@ -607,3 +709,6 @@ def find_config_files(search_paths): | |||
else: | |||
config_files.append(config_path) | |||
return config_files | |||
__all__ = ["Config", "RootConfig"] |
@@ -0,0 +1,135 @@ | |||
from typing import Any, List, Optional | |||
from synapse.config import ( | |||
api, | |||
appservice, | |||
captcha, | |||
cas, | |||
consent_config, | |||
database, | |||
emailconfig, | |||
groups, | |||
jwt_config, | |||
key, | |||
logger, | |||
metrics, | |||
password, | |||
password_auth_providers, | |||
push, | |||
ratelimiting, | |||
registration, | |||
repository, | |||
room_directory, | |||
saml2_config, | |||
server, | |||
server_notices_config, | |||
spam_checker, | |||
stats, | |||
third_party_event_rules, | |||
tls, | |||
tracer, | |||
user_directory, | |||
voip, | |||
workers, | |||
) | |||
class ConfigError(Exception): ... | |||
MISSING_REPORT_STATS_CONFIG_INSTRUCTIONS: str | |||
MISSING_REPORT_STATS_SPIEL: str | |||
MISSING_SERVER_NAME: str | |||
def path_exists(file_path: str): ... | |||
class RootConfig: | |||
server: server.ServerConfig | |||
tls: tls.TlsConfig | |||
database: database.DatabaseConfig | |||
logging: logger.LoggingConfig | |||
ratelimit: ratelimiting.RatelimitConfig | |||
media: repository.ContentRepositoryConfig | |||
captcha: captcha.CaptchaConfig | |||
voip: voip.VoipConfig | |||
registration: registration.RegistrationConfig | |||
metrics: metrics.MetricsConfig | |||
api: api.ApiConfig | |||
appservice: appservice.AppServiceConfig | |||
key: key.KeyConfig | |||
saml2: saml2_config.SAML2Config | |||
cas: cas.CasConfig | |||
jwt: jwt_config.JWTConfig | |||
password: password.PasswordConfig | |||
email: emailconfig.EmailConfig | |||
worker: workers.WorkerConfig | |||
authproviders: password_auth_providers.PasswordAuthProviderConfig | |||
push: push.PushConfig | |||
spamchecker: spam_checker.SpamCheckerConfig | |||
groups: groups.GroupsConfig | |||
userdirectory: user_directory.UserDirectoryConfig | |||
consent: consent_config.ConsentConfig | |||
stats: stats.StatsConfig | |||
servernotices: server_notices_config.ServerNoticesConfig | |||
roomdirectory: room_directory.RoomDirectoryConfig | |||
thirdpartyrules: third_party_event_rules.ThirdPartyRulesConfig | |||
tracer: tracer.TracerConfig | |||
config_classes: List = ... | |||
def __init__(self) -> None: ... | |||
def invoke_all(self, func_name: str, *args: Any, **kwargs: Any): ... | |||
@classmethod | |||
def invoke_all_static(cls, func_name: str, *args: Any, **kwargs: Any) -> None: ... | |||
def __getattr__(self, item: str): ... | |||
def parse_config_dict( | |||
self, | |||
config_dict: Any, | |||
config_dir_path: Optional[Any] = ..., | |||
data_dir_path: Optional[Any] = ..., | |||
) -> None: ... | |||
read_config: Any = ... | |||
def generate_config( | |||
self, | |||
config_dir_path: str, | |||
data_dir_path: str, | |||
server_name: str, | |||
generate_secrets: bool = ..., | |||
report_stats: Optional[str] = ..., | |||
open_private_ports: bool = ..., | |||
listeners: Optional[Any] = ..., | |||
database_conf: Optional[Any] = ..., | |||
tls_certificate_path: Optional[str] = ..., | |||
tls_private_key_path: Optional[str] = ..., | |||
acme_domain: Optional[str] = ..., | |||
): ... | |||
@classmethod | |||
def load_or_generate_config(cls, description: Any, argv: Any): ... | |||
@classmethod | |||
def load_config(cls, description: Any, argv: Any): ... | |||
@classmethod | |||
def add_arguments_to_parser(cls, config_parser: Any) -> None: ... | |||
@classmethod | |||
def load_config_with_parser(cls, parser: Any, argv: Any): ... | |||
def generate_missing_files( | |||
self, config_dict: dict, config_dir_path: str | |||
) -> None: ... | |||
class Config: | |||
root: RootConfig | |||
def __init__(self, root_config: Optional[RootConfig] = ...) -> None: ... | |||
def __getattr__(self, item: str, from_root: bool = ...): ... | |||
@staticmethod | |||
def parse_size(value: Any): ... | |||
@staticmethod | |||
def parse_duration(value: Any): ... | |||
@staticmethod | |||
def abspath(file_path: Optional[str]): ... | |||
@classmethod | |||
def path_exists(cls, file_path: str): ... | |||
@classmethod | |||
def check_file(cls, file_path: str, config_name: str): ... | |||
@classmethod | |||
def ensure_directory(cls, dir_path: str): ... | |||
@classmethod | |||
def read_file(cls, file_path: str, config_name: str): ... | |||
def read_config_files(config_files: List[str]): ... | |||
def find_config_files(search_paths: List[str]): ... |
@@ -18,6 +18,8 @@ from ._base import Config | |||
class ApiConfig(Config): | |||
section = "api" | |||
def read_config(self, config, **kwargs): | |||
self.room_invite_state_types = config.get( | |||
"room_invite_state_types", | |||
@@ -30,6 +30,8 @@ logger = logging.getLogger(__name__) | |||
class AppServiceConfig(Config): | |||
section = "appservice" | |||
def read_config(self, config, **kwargs): | |||
self.app_service_config_files = config.get("app_service_config_files", []) | |||
self.notify_appservices = config.get("notify_appservices", True) | |||
@@ -16,6 +16,8 @@ from ._base import Config | |||
class CaptchaConfig(Config): | |||
section = "captcha" | |||
def read_config(self, config, **kwargs): | |||
self.recaptcha_private_key = config.get("recaptcha_private_key") | |||
self.recaptcha_public_key = config.get("recaptcha_public_key") | |||
@@ -22,6 +22,8 @@ class CasConfig(Config): | |||
cas_server_url: URL of CAS server | |||
""" | |||
section = "cas" | |||
def read_config(self, config, **kwargs): | |||
cas_config = config.get("cas_config", None) | |||
if cas_config: | |||
@@ -73,6 +73,9 @@ DEFAULT_CONFIG = """\ | |||
class ConsentConfig(Config): | |||
section = "consent" | |||
def __init__(self, *args): | |||
super(ConsentConfig, self).__init__(*args) | |||
@@ -21,6 +21,8 @@ from ._base import Config | |||
class DatabaseConfig(Config): | |||
section = "database" | |||
def read_config(self, config, **kwargs): | |||
self.event_cache_size = self.parse_size(config.get("event_cache_size", "10K")) | |||
@@ -28,6 +28,8 @@ from ._base import Config, ConfigError | |||
class EmailConfig(Config): | |||
section = "email" | |||
def read_config(self, config, **kwargs): | |||
# TODO: We should separate better the email configuration from the notification | |||
# and account validity config. | |||
@@ -17,6 +17,8 @@ from ._base import Config | |||
class GroupsConfig(Config): | |||
section = "groups" | |||
def read_config(self, config, **kwargs): | |||
self.enable_group_creation = config.get("enable_group_creation", False) | |||
self.group_creation_prefix = config.get("group_creation_prefix", "") | |||
@@ -14,6 +14,7 @@ | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
from ._base import RootConfig | |||
from .api import ApiConfig | |||
from .appservice import AppServiceConfig | |||
from .captcha import CaptchaConfig | |||
@@ -46,36 +47,37 @@ from .voip import VoipConfig | |||
from .workers import WorkerConfig | |||
class HomeServerConfig( | |||
ServerConfig, | |||
TlsConfig, | |||
DatabaseConfig, | |||
LoggingConfig, | |||
RatelimitConfig, | |||
ContentRepositoryConfig, | |||
CaptchaConfig, | |||
VoipConfig, | |||
RegistrationConfig, | |||
MetricsConfig, | |||
ApiConfig, | |||
AppServiceConfig, | |||
KeyConfig, | |||
SAML2Config, | |||
CasConfig, | |||
JWTConfig, | |||
PasswordConfig, | |||
EmailConfig, | |||
WorkerConfig, | |||
PasswordAuthProviderConfig, | |||
PushConfig, | |||
SpamCheckerConfig, | |||
GroupsConfig, | |||
UserDirectoryConfig, | |||
ConsentConfig, | |||
StatsConfig, | |||
ServerNoticesConfig, | |||
RoomDirectoryConfig, | |||
ThirdPartyRulesConfig, | |||
TracerConfig, | |||
): | |||
pass | |||
class HomeServerConfig(RootConfig): | |||
config_classes = [ | |||
ServerConfig, | |||
TlsConfig, | |||
DatabaseConfig, | |||
LoggingConfig, | |||
RatelimitConfig, | |||
ContentRepositoryConfig, | |||
CaptchaConfig, | |||
VoipConfig, | |||
RegistrationConfig, | |||
MetricsConfig, | |||
ApiConfig, | |||
AppServiceConfig, | |||
KeyConfig, | |||
SAML2Config, | |||
CasConfig, | |||
JWTConfig, | |||
PasswordConfig, | |||
EmailConfig, | |||
WorkerConfig, | |||
PasswordAuthProviderConfig, | |||
PushConfig, | |||
SpamCheckerConfig, | |||
GroupsConfig, | |||
UserDirectoryConfig, | |||
ConsentConfig, | |||
StatsConfig, | |||
ServerNoticesConfig, | |||
RoomDirectoryConfig, | |||
ThirdPartyRulesConfig, | |||
TracerConfig, | |||
] |
@@ -23,6 +23,8 @@ MISSING_JWT = """Missing jwt library. This is required for jwt login. | |||
class JWTConfig(Config): | |||
section = "jwt" | |||
def read_config(self, config, **kwargs): | |||
jwt_config = config.get("jwt_config", None) | |||
if jwt_config: | |||
@@ -92,6 +92,8 @@ class TrustedKeyServer(object): | |||
class KeyConfig(Config): | |||
section = "key" | |||
def read_config(self, config, config_dir_path, **kwargs): | |||
# the signing key can be specified inline or in a separate file | |||
if "signing_key" in config: | |||
@@ -84,6 +84,8 @@ root: | |||
class LoggingConfig(Config): | |||
section = "logging" | |||
def read_config(self, config, **kwargs): | |||
self.log_config = self.abspath(config.get("log_config")) | |||
self.no_redirect_stdio = config.get("no_redirect_stdio", False) | |||
@@ -34,6 +34,8 @@ class MetricsFlags(object): | |||
class MetricsConfig(Config): | |||
section = "metrics" | |||
def read_config(self, config, **kwargs): | |||
self.enable_metrics = config.get("enable_metrics", False) | |||
self.report_stats = config.get("report_stats", None) | |||
@@ -20,6 +20,8 @@ class PasswordConfig(Config): | |||
"""Password login configuration | |||
""" | |||
section = "password" | |||
def read_config(self, config, **kwargs): | |||
password_config = config.get("password_config", {}) | |||
if password_config is None: | |||
@@ -23,6 +23,8 @@ LDAP_PROVIDER = "ldap_auth_provider.LdapAuthProvider" | |||
class PasswordAuthProviderConfig(Config): | |||
section = "authproviders" | |||
def read_config(self, config, **kwargs): | |||
self.password_providers = [] # type: List[Any] | |||
providers = [] | |||
@@ -18,6 +18,8 @@ from ._base import Config | |||
class PushConfig(Config): | |||
section = "push" | |||
def read_config(self, config, **kwargs): | |||
push_config = config.get("push", {}) | |||
self.push_include_content = push_config.get("include_content", True) | |||
@@ -36,6 +36,8 @@ class FederationRateLimitConfig(object): | |||
class RatelimitConfig(Config): | |||
section = "ratelimiting" | |||
def read_config(self, config, **kwargs): | |||
# Load the new-style messages config if it exists. Otherwise fall back | |||
@@ -24,6 +24,8 @@ from synapse.util.stringutils import random_string_with_symbols | |||
class AccountValidityConfig(Config): | |||
section = "accountvalidity" | |||
def __init__(self, config, synapse_config): | |||
self.enabled = config.get("enabled", False) | |||
self.renew_by_email_enabled = "renew_at" in config | |||
@@ -77,6 +79,8 @@ class AccountValidityConfig(Config): | |||
class RegistrationConfig(Config): | |||
section = "registration" | |||
def read_config(self, config, **kwargs): | |||
self.enable_registration = bool( | |||
strtobool(str(config.get("enable_registration", False))) | |||
@@ -78,6 +78,8 @@ def parse_thumbnail_requirements(thumbnail_sizes): | |||
class ContentRepositoryConfig(Config): | |||
section = "media" | |||
def read_config(self, config, **kwargs): | |||
# Only enable the media repo if either the media repo is enabled or the | |||
@@ -19,6 +19,8 @@ from ._base import Config, ConfigError | |||
class RoomDirectoryConfig(Config): | |||
section = "roomdirectory" | |||
def read_config(self, config, **kwargs): | |||
self.enable_room_list_search = config.get("enable_room_list_search", True) | |||
@@ -55,6 +55,8 @@ def _dict_merge(merge_dict, into_dict): | |||
class SAML2Config(Config): | |||
section = "saml2" | |||
def read_config(self, config, **kwargs): | |||
self.saml2_enabled = False | |||
@@ -58,6 +58,8 @@ on how to configure the new listener. | |||
class ServerConfig(Config): | |||
section = "server" | |||
def read_config(self, config, **kwargs): | |||
self.server_name = config["server_name"] | |||
self.server_context = config.get("server_context", None) | |||
@@ -59,6 +59,8 @@ class ServerNoticesConfig(Config): | |||
None if server notices are not enabled. | |||
""" | |||
section = "servernotices" | |||
def __init__(self, *args): | |||
super(ServerNoticesConfig, self).__init__(*args) | |||
self.server_notices_mxid = None | |||
@@ -19,6 +19,8 @@ from ._base import Config | |||
class SpamCheckerConfig(Config): | |||
section = "spamchecker" | |||
def read_config(self, config, **kwargs): | |||
self.spam_checker = None | |||
@@ -25,6 +25,8 @@ class StatsConfig(Config): | |||
Configuration for the behaviour of synapse's stats engine | |||
""" | |||
section = "stats" | |||
def read_config(self, config, **kwargs): | |||
self.stats_enabled = True | |||
self.stats_bucket_size = 86400 * 1000 | |||
@@ -19,6 +19,8 @@ from ._base import Config | |||
class ThirdPartyRulesConfig(Config): | |||
section = "thirdpartyrules" | |||
def read_config(self, config, **kwargs): | |||
self.third_party_event_rules = None | |||
@@ -18,6 +18,7 @@ import os | |||
import warnings | |||
from datetime import datetime | |||
from hashlib import sha256 | |||
from typing import List | |||
import six | |||
@@ -33,7 +34,9 @@ logger = logging.getLogger(__name__) | |||
class TlsConfig(Config): | |||
def read_config(self, config, config_dir_path, **kwargs): | |||
section = "tls" | |||
def read_config(self, config: dict, config_dir_path: str, **kwargs): | |||
acme_config = config.get("acme", None) | |||
if acme_config is None: | |||
@@ -57,7 +60,7 @@ class TlsConfig(Config): | |||
self.tls_certificate_file = self.abspath(config.get("tls_certificate_path")) | |||
self.tls_private_key_file = self.abspath(config.get("tls_private_key_path")) | |||
if self.has_tls_listener(): | |||
if self.root.server.has_tls_listener(): | |||
if not self.tls_certificate_file: | |||
raise ConfigError( | |||
"tls_certificate_path must be specified if TLS-enabled listeners are " | |||
@@ -108,7 +111,7 @@ class TlsConfig(Config): | |||
) | |||
# Support globs (*) in whitelist values | |||
self.federation_certificate_verification_whitelist = [] | |||
self.federation_certificate_verification_whitelist = [] # type: List[str] | |||
for entry in fed_whitelist_entries: | |||
try: | |||
entry_regex = glob_to_regex(entry.encode("ascii").decode("ascii")) | |||
@@ -19,6 +19,8 @@ from ._base import Config, ConfigError | |||
class TracerConfig(Config): | |||
section = "tracing" | |||
def read_config(self, config, **kwargs): | |||
opentracing_config = config.get("opentracing") | |||
if opentracing_config is None: | |||
@@ -21,6 +21,8 @@ class UserDirectoryConfig(Config): | |||
Configuration for the behaviour of the /user_directory API | |||
""" | |||
section = "userdirectory" | |||
def read_config(self, config, **kwargs): | |||
self.user_directory_search_enabled = True | |||
self.user_directory_search_all_users = False | |||
@@ -16,6 +16,8 @@ from ._base import Config | |||
class VoipConfig(Config): | |||
section = "voip" | |||
def read_config(self, config, **kwargs): | |||
self.turn_uris = config.get("turn_uris", []) | |||
self.turn_shared_secret = config.get("turn_shared_secret") | |||
@@ -21,6 +21,8 @@ class WorkerConfig(Config): | |||
They have their own pid_file and listener configuration. They use the | |||
replication_url to talk to the main synapse process.""" | |||
section = "worker" | |||
def read_config(self, config, **kwargs): | |||
self.worker_app = config.get("worker_app") | |||
@@ -21,17 +21,24 @@ import yaml | |||
from OpenSSL import SSL | |||
from synapse.config._base import Config, RootConfig | |||
from synapse.config.tls import ConfigError, TlsConfig | |||
from synapse.crypto.context_factory import ClientTLSOptionsFactory | |||
from tests.unittest import TestCase | |||
class TestConfig(TlsConfig): | |||
class FakeServer(Config): | |||
section = "server" | |||
def has_tls_listener(self): | |||
return False | |||
class TestConfig(RootConfig): | |||
config_classes = [FakeServer, TlsConfig] | |||
class TLSConfigTests(TestCase): | |||
def test_warn_self_signed(self): | |||
""" | |||
@@ -202,13 +209,13 @@ s4niecZKPBizL6aucT59CsunNmmb5Glq8rlAcU+1ZTZZzGYqVYhF6axB9Qg= | |||
conf = TestConfig() | |||
conf.read_config( | |||
yaml.safe_load( | |||
TestConfig().generate_config_section( | |||
TestConfig().generate_config( | |||
"/config_dir_path", | |||
"my_super_secure_server", | |||
"/data_dir_path", | |||
"/tls_cert_path", | |||
"tls_private_key", | |||
None, # This is the acme_domain | |||
tls_certificate_path="/tls_cert_path", | |||
tls_private_key_path="tls_private_key", | |||
acme_domain=None, # This is the acme_domain | |||
) | |||
), | |||
"/config_dir_path", | |||
@@ -223,13 +230,13 @@ s4niecZKPBizL6aucT59CsunNmmb5Glq8rlAcU+1ZTZZzGYqVYhF6axB9Qg= | |||
conf = TestConfig() | |||
conf.read_config( | |||
yaml.safe_load( | |||
TestConfig().generate_config_section( | |||
TestConfig().generate_config( | |||
"/config_dir_path", | |||
"my_super_secure_server", | |||
"/data_dir_path", | |||
"/tls_cert_path", | |||
"tls_private_key", | |||
"my_supe_secure_server", # This is the acme_domain | |||
tls_certificate_path="/tls_cert_path", | |||
tls_private_key_path="tls_private_key", | |||
acme_domain="my_supe_secure_server", # This is the acme_domain | |||
) | |||
), | |||
"/config_dir_path", | |||
@@ -163,10 +163,9 @@ deps = | |||
{[base]deps} | |||
mypy | |||
mypy-zope | |||
typeshed | |||
env = | |||
MYPYPATH = stubs/ | |||
extras = all | |||
commands = mypy --show-traceback \ | |||
commands = mypy --show-traceback --check-untyped-defs --show-error-codes --follow-imports=normal \ | |||
synapse/logging/ \ | |||
synapse/config/ |