diff --git a/synapse/config/_util.py b/synapse/config/_util.py
new file mode 100644
index 0000000000..cd31b1c3c9
--- /dev/null
+++ b/synapse/config/_util.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Matrix.org Foundation C.I.C.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Any, List
+
+import jsonschema
+
+from synapse.config._base import ConfigError
+from synapse.types import JsonDict
+
+
+def validate_config(json_schema: JsonDict, config: Any, config_path: List[str]) -> None:
+ """Validates a config setting against a JsonSchema definition
+
+ This can be used to validate a section of the config file against a schema
+ definition. If the validation fails, a ConfigError is raised with a textual
+ description of the problem.
+
+ Args:
+ json_schema: the schema to validate against
+ config: the configuration value to be validated
+ config_path: the path within the config file. This will be used as a basis
+ for the error message.
+ """
+ try:
+ jsonschema.validate(config, json_schema)
+ except jsonschema.ValidationError as e:
+ # copy `config_path` before modifying it.
+ path = list(config_path)
+ for p in list(e.path):
+ if isinstance(p, int):
+ path.append("<item %i>" % p)
+ else:
+ path.append(str(p))
+
+ raise ConfigError(
+ "Unable to parse configuration: %s at %s" % (e.message, ".".join(path))
+ )
diff --git a/synapse/config/database.py b/synapse/config/database.py
index 62bccd9ef5..8a18a9ca2a 100644
--- a/synapse/config/database.py
+++ b/synapse/config/database.py
@@ -100,7 +100,10 @@ class DatabaseConnectionConfig:
self.name = name
self.config = db_config
- self.data_stores = data_stores
+
+ # The `data_stores` config is actually talking about `databases` (we
+ # changed the name).
+ self.databases = data_stores
class DatabaseConfig(Config):
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index dd775a97e8..c96e6ef62a 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -55,24 +55,33 @@ formatters:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - \
%(request)s - %(message)s'
-filters:
- context:
- (): synapse.logging.context.LoggingContextFilter
- request: ""
-
handlers:
file:
- class: logging.handlers.RotatingFileHandler
+ class: logging.handlers.TimedRotatingFileHandler
formatter: precise
filename: ${log_file}
- maxBytes: 104857600
- backupCount: 10
- filters: [context]
+ when: midnight
+ backupCount: 3 # Does not include the current log file.
encoding: utf8
+
+ # Default to buffering writes to log file for efficiency. This means that
+ # will be a delay for INFO/DEBUG logs to get written, but WARNING/ERROR
+ # logs will still be flushed immediately.
+ buffer:
+ class: logging.handlers.MemoryHandler
+ target: file
+ # The capacity is the number of log lines that are buffered before
+ # being written to disk. Increasing this will lead to better
+ # performance, at the expensive of it taking longer for log lines to
+ # be written to disk.
+ capacity: 10
+ flushLevel: 30 # Flush for WARNING logs as well
+
+ # A handler that writes logs to stderr. Unused by default, but can be used
+ # instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
- filters: [context]
loggers:
synapse.storage.SQL:
@@ -80,9 +89,24 @@ loggers:
# information such as access tokens.
level: INFO
+ twisted:
+ # We send the twisted logging directly to the file handler,
+ # to work around https://github.com/matrix-org/synapse/issues/3471
+ # when using "buffer" logger. Use "console" to log to stderr instead.
+ handlers: [file]
+ propagate: false
+
root:
level: INFO
- handlers: [file, console]
+
+ # Write logs to the `buffer` handler, which will buffer them together in memory,
+ # then write them to a file.
+ #
+ # Replace "buffer" with "console" to log to stderr instead. (Note that you'll
+ # also need to update the configuation for the `twisted` logger above, in
+ # this case.)
+ #
+ handlers: [buffer]
disable_existing_loggers: false
"""
@@ -168,11 +192,26 @@ def _setup_stdlib_logging(config, log_config, logBeginner: LogBeginner):
handler = logging.StreamHandler()
handler.setFormatter(formatter)
- handler.addFilter(LoggingContextFilter(request=""))
logger.addHandler(handler)
else:
logging.config.dictConfig(log_config)
+ # We add a log record factory that runs all messages through the
+ # LoggingContextFilter so that we get the context *at the time we log*
+ # rather than when we write to a handler. This can be done in config using
+ # filter options, but care must when using e.g. MemoryHandler to buffer
+ # writes.
+
+ log_filter = LoggingContextFilter(request="")
+ old_factory = logging.getLogRecordFactory()
+
+ def factory(*args, **kwargs):
+ record = old_factory(*args, **kwargs)
+ log_filter.filter(record)
+ return record
+
+ logging.setLogRecordFactory(factory)
+
# Route Twisted's native logging through to the standard library logging
# system.
observer = STDLibLogObserver()
diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py
index 2dd94bae2b..b2c78ac40c 100644
--- a/synapse/config/ratelimiting.py
+++ b/synapse/config/ratelimiting.py
@@ -93,6 +93,15 @@ class RatelimitConfig(Config):
if rc_admin_redaction:
self.rc_admin_redaction = RateLimitConfig(rc_admin_redaction)
+ self.rc_joins_local = RateLimitConfig(
+ config.get("rc_joins", {}).get("local", {}),
+ defaults={"per_second": 0.1, "burst_count": 3},
+ )
+ self.rc_joins_remote = RateLimitConfig(
+ config.get("rc_joins", {}).get("remote", {}),
+ defaults={"per_second": 0.01, "burst_count": 3},
+ )
+
def generate_config_section(self, **kwargs):
return """\
## Ratelimiting ##
@@ -118,6 +127,10 @@ class RatelimitConfig(Config):
# - one for ratelimiting redactions by room admins. If this is not explicitly
# set then it uses the same ratelimiting as per rc_message. This is useful
# to allow room admins to deal with abuse quickly.
+ # - two for ratelimiting number of rooms a user can join, "local" for when
+ # users are joining rooms the server is already in (this is cheap) vs
+ # "remote" for when users are trying to join rooms not on the server (which
+ # can be more expensive)
#
# The defaults are as shown below.
#
@@ -143,6 +156,14 @@ class RatelimitConfig(Config):
#rc_admin_redaction:
# per_second: 1
# burst_count: 50
+ #
+ #rc_joins:
+ # local:
+ # per_second: 0.1
+ # burst_count: 3
+ # remote:
+ # per_second: 0.01
+ # burst_count: 3
# Ratelimiting settings for incoming federation
diff --git a/synapse/config/saml2_config.py b/synapse/config/saml2_config.py
index 293643b2de..9277b5f342 100644
--- a/synapse/config/saml2_config.py
+++ b/synapse/config/saml2_config.py
@@ -15,7 +15,9 @@
# limitations under the License.
import logging
+from typing import Any, List
+import attr
import jinja2
import pkg_resources
@@ -23,6 +25,7 @@ from synapse.python_dependencies import DependencyException, check_requirements
from synapse.util.module_loader import load_module, load_python_module
from ._base import Config, ConfigError
+from ._util import validate_config
logger = logging.getLogger(__name__)
@@ -80,6 +83,11 @@ class SAML2Config(Config):
self.saml2_enabled = True
+ attribute_requirements = saml2_config.get("attribute_requirements") or []
+ self.attribute_requirements = _parse_attribute_requirements_def(
+ attribute_requirements
+ )
+
self.saml2_grandfathered_mxid_source_attribute = saml2_config.get(
"grandfathered_mxid_source_attribute", "uid"
)
@@ -341,6 +349,17 @@ class SAML2Config(Config):
#
#grandfathered_mxid_source_attribute: upn
+ # It is possible to configure Synapse to only allow logins if SAML attributes
+ # match particular values. The requirements can be listed under
+ # `attribute_requirements` as shown below. All of the listed attributes must
+ # match for the login to be permitted.
+ #
+ #attribute_requirements:
+ # - attribute: userGroup
+ # value: "staff"
+ # - attribute: department
+ # value: "sales"
+
# Directory in which Synapse will try to find the template files below.
# If not set, default templates from within the Synapse package will be used.
#
@@ -368,3 +387,34 @@ class SAML2Config(Config):
""" % {
"config_dir_path": config_dir_path
}
+
+
+@attr.s(frozen=True)
+class SamlAttributeRequirement:
+ """Object describing a single requirement for SAML attributes."""
+
+ attribute = attr.ib(type=str)
+ value = attr.ib(type=str)
+
+ JSON_SCHEMA = {
+ "type": "object",
+ "properties": {"attribute": {"type": "string"}, "value": {"type": "string"}},
+ "required": ["attribute", "value"],
+ }
+
+
+ATTRIBUTE_REQUIREMENTS_SCHEMA = {
+ "type": "array",
+ "items": SamlAttributeRequirement.JSON_SCHEMA,
+}
+
+
+def _parse_attribute_requirements_def(
+ attribute_requirements: Any,
+) -> List[SamlAttributeRequirement]:
+ validate_config(
+ ATTRIBUTE_REQUIREMENTS_SCHEMA,
+ attribute_requirements,
+ config_path=["saml2_config", "attribute_requirements"],
+ )
+ return [SamlAttributeRequirement(**x) for x in attribute_requirements]
diff --git a/synapse/config/server.py b/synapse/config/server.py
index 848587d232..9f15ed109e 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -530,6 +530,21 @@ class ServerConfig(Config):
"request_token_inhibit_3pid_errors", False,
)
+ # List of users trialing the new experimental default push rules. This setting is
+ # not included in the sample configuration file on purpose as it's a temporary
+ # hack, so that some users can trial the new defaults without impacting every
+ # user on the homeserver.
+ users_new_default_push_rules = (
+ config.get("users_new_default_push_rules") or []
+ ) # type: list
+ if not isinstance(users_new_default_push_rules, list):
+ raise ConfigError("'users_new_default_push_rules' must be a list")
+
+ # Turn the list into a set to improve lookup speed.
+ self.users_new_default_push_rules = set(
+ users_new_default_push_rules
+ ) # type: set
+
def has_tls_listener(self) -> bool:
return any(listener.tls for listener in self.listeners)
|