228 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
#  Copyright 2021 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.
 | 
						|
 | 
						|
import logging
 | 
						|
from typing import Any, List, Optional
 | 
						|
 | 
						|
import attr
 | 
						|
 | 
						|
from synapse.config._base import Config, ConfigError
 | 
						|
from synapse.types import JsonDict
 | 
						|
 | 
						|
logger = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
@attr.s(slots=True, frozen=True, auto_attribs=True)
 | 
						|
class RetentionPurgeJob:
 | 
						|
    """Object describing the configuration of the manhole"""
 | 
						|
 | 
						|
    interval: int
 | 
						|
    shortest_max_lifetime: Optional[int]
 | 
						|
    longest_max_lifetime: Optional[int]
 | 
						|
 | 
						|
 | 
						|
class RetentionConfig(Config):
 | 
						|
    section = "retention"
 | 
						|
 | 
						|
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
 | 
						|
        retention_config = config.get("retention")
 | 
						|
        if retention_config is None:
 | 
						|
            retention_config = {}
 | 
						|
 | 
						|
        self.retention_enabled = retention_config.get("enabled", False)
 | 
						|
 | 
						|
        retention_default_policy = retention_config.get("default_policy")
 | 
						|
 | 
						|
        if retention_default_policy is not None:
 | 
						|
            self.retention_default_min_lifetime = retention_default_policy.get(
 | 
						|
                "min_lifetime"
 | 
						|
            )
 | 
						|
            if self.retention_default_min_lifetime is not None:
 | 
						|
                self.retention_default_min_lifetime = self.parse_duration(
 | 
						|
                    self.retention_default_min_lifetime
 | 
						|
                )
 | 
						|
 | 
						|
            self.retention_default_max_lifetime = retention_default_policy.get(
 | 
						|
                "max_lifetime"
 | 
						|
            )
 | 
						|
            if self.retention_default_max_lifetime is not None:
 | 
						|
                self.retention_default_max_lifetime = self.parse_duration(
 | 
						|
                    self.retention_default_max_lifetime
 | 
						|
                )
 | 
						|
 | 
						|
            if (
 | 
						|
                self.retention_default_min_lifetime is not None
 | 
						|
                and self.retention_default_max_lifetime is not None
 | 
						|
                and (
 | 
						|
                    self.retention_default_min_lifetime
 | 
						|
                    > self.retention_default_max_lifetime
 | 
						|
                )
 | 
						|
            ):
 | 
						|
                raise ConfigError(
 | 
						|
                    "The default retention policy's 'min_lifetime' can not be greater"
 | 
						|
                    " than its 'max_lifetime'"
 | 
						|
                )
 | 
						|
        else:
 | 
						|
            self.retention_default_min_lifetime = None
 | 
						|
            self.retention_default_max_lifetime = None
 | 
						|
 | 
						|
        if self.retention_enabled:
 | 
						|
            logger.info(
 | 
						|
                "Message retention policies support enabled with the following default"
 | 
						|
                " policy: min_lifetime = %s ; max_lifetime = %s",
 | 
						|
                self.retention_default_min_lifetime,
 | 
						|
                self.retention_default_max_lifetime,
 | 
						|
            )
 | 
						|
 | 
						|
        self.retention_allowed_lifetime_min = retention_config.get(
 | 
						|
            "allowed_lifetime_min"
 | 
						|
        )
 | 
						|
        if self.retention_allowed_lifetime_min is not None:
 | 
						|
            self.retention_allowed_lifetime_min = self.parse_duration(
 | 
						|
                self.retention_allowed_lifetime_min
 | 
						|
            )
 | 
						|
 | 
						|
        self.retention_allowed_lifetime_max = retention_config.get(
 | 
						|
            "allowed_lifetime_max"
 | 
						|
        )
 | 
						|
        if self.retention_allowed_lifetime_max is not None:
 | 
						|
            self.retention_allowed_lifetime_max = self.parse_duration(
 | 
						|
                self.retention_allowed_lifetime_max
 | 
						|
            )
 | 
						|
 | 
						|
        if (
 | 
						|
            self.retention_allowed_lifetime_min is not None
 | 
						|
            and self.retention_allowed_lifetime_max is not None
 | 
						|
            and self.retention_allowed_lifetime_min
 | 
						|
            > self.retention_allowed_lifetime_max
 | 
						|
        ):
 | 
						|
            raise ConfigError(
 | 
						|
                "Invalid retention policy limits: 'allowed_lifetime_min' can not be"
 | 
						|
                " greater than 'allowed_lifetime_max'"
 | 
						|
            )
 | 
						|
 | 
						|
        self.retention_purge_jobs: List[RetentionPurgeJob] = []
 | 
						|
        for purge_job_config in retention_config.get("purge_jobs", []):
 | 
						|
            interval_config = purge_job_config.get("interval")
 | 
						|
 | 
						|
            if interval_config is None:
 | 
						|
                raise ConfigError(
 | 
						|
                    "A retention policy's purge jobs configuration must have the"
 | 
						|
                    " 'interval' key set."
 | 
						|
                )
 | 
						|
 | 
						|
            interval = self.parse_duration(interval_config)
 | 
						|
 | 
						|
            shortest_max_lifetime = purge_job_config.get("shortest_max_lifetime")
 | 
						|
 | 
						|
            if shortest_max_lifetime is not None:
 | 
						|
                shortest_max_lifetime = self.parse_duration(shortest_max_lifetime)
 | 
						|
 | 
						|
            longest_max_lifetime = purge_job_config.get("longest_max_lifetime")
 | 
						|
 | 
						|
            if longest_max_lifetime is not None:
 | 
						|
                longest_max_lifetime = self.parse_duration(longest_max_lifetime)
 | 
						|
 | 
						|
            if (
 | 
						|
                shortest_max_lifetime is not None
 | 
						|
                and longest_max_lifetime is not None
 | 
						|
                and shortest_max_lifetime > longest_max_lifetime
 | 
						|
            ):
 | 
						|
                raise ConfigError(
 | 
						|
                    "A retention policy's purge jobs configuration's"
 | 
						|
                    " 'shortest_max_lifetime' value can not be greater than its"
 | 
						|
                    " 'longest_max_lifetime' value."
 | 
						|
                )
 | 
						|
 | 
						|
            self.retention_purge_jobs.append(
 | 
						|
                RetentionPurgeJob(interval, shortest_max_lifetime, longest_max_lifetime)
 | 
						|
            )
 | 
						|
 | 
						|
        if not self.retention_purge_jobs:
 | 
						|
            self.retention_purge_jobs = [
 | 
						|
                RetentionPurgeJob(self.parse_duration("1d"), None, None)
 | 
						|
            ]
 | 
						|
 | 
						|
    def generate_config_section(self, **kwargs: Any) -> str:
 | 
						|
        return """\
 | 
						|
        # Message retention policy at the server level.
 | 
						|
        #
 | 
						|
        # Room admins and mods can define a retention period for their rooms using the
 | 
						|
        # 'm.room.retention' state event, and server admins can cap this period by setting
 | 
						|
        # the 'allowed_lifetime_min' and 'allowed_lifetime_max' config options.
 | 
						|
        #
 | 
						|
        # If this feature is enabled, Synapse will regularly look for and purge events
 | 
						|
        # which are older than the room's maximum retention period. Synapse will also
 | 
						|
        # filter events received over federation so that events that should have been
 | 
						|
        # purged are ignored and not stored again.
 | 
						|
        #
 | 
						|
        retention:
 | 
						|
          # The message retention policies feature is disabled by default. Uncomment the
 | 
						|
          # following line to enable it.
 | 
						|
          #
 | 
						|
          #enabled: true
 | 
						|
 | 
						|
          # Default retention policy. If set, Synapse will apply it to rooms that lack the
 | 
						|
          # 'm.room.retention' state event. Currently, the value of 'min_lifetime' doesn't
 | 
						|
          # matter much because Synapse doesn't take it into account yet.
 | 
						|
          #
 | 
						|
          #default_policy:
 | 
						|
          #  min_lifetime: 1d
 | 
						|
          #  max_lifetime: 1y
 | 
						|
 | 
						|
          # Retention policy limits. If set, and the state of a room contains a
 | 
						|
          # 'm.room.retention' event in its state which contains a 'min_lifetime' or a
 | 
						|
          # 'max_lifetime' that's out of these bounds, Synapse will cap the room's policy
 | 
						|
          # to these limits when running purge jobs.
 | 
						|
          #
 | 
						|
          #allowed_lifetime_min: 1d
 | 
						|
          #allowed_lifetime_max: 1y
 | 
						|
 | 
						|
          # Server admins can define the settings of the background jobs purging the
 | 
						|
          # events which lifetime has expired under the 'purge_jobs' section.
 | 
						|
          #
 | 
						|
          # If no configuration is provided, a single job will be set up to delete expired
 | 
						|
          # events in every room daily.
 | 
						|
          #
 | 
						|
          # Each job's configuration defines which range of message lifetimes the job
 | 
						|
          # takes care of. For example, if 'shortest_max_lifetime' is '2d' and
 | 
						|
          # 'longest_max_lifetime' is '3d', the job will handle purging expired events in
 | 
						|
          # rooms whose state defines a 'max_lifetime' that's both higher than 2 days, and
 | 
						|
          # lower than or equal to 3 days. Both the minimum and the maximum value of a
 | 
						|
          # range are optional, e.g. a job with no 'shortest_max_lifetime' and a
 | 
						|
          # 'longest_max_lifetime' of '3d' will handle every room with a retention policy
 | 
						|
          # which 'max_lifetime' is lower than or equal to three days.
 | 
						|
          #
 | 
						|
          # The rationale for this per-job configuration is that some rooms might have a
 | 
						|
          # retention policy with a low 'max_lifetime', where history needs to be purged
 | 
						|
          # of outdated messages on a more frequent basis than for the rest of the rooms
 | 
						|
          # (e.g. every 12h), but not want that purge to be performed by a job that's
 | 
						|
          # iterating over every room it knows, which could be heavy on the server.
 | 
						|
          #
 | 
						|
          # If any purge job is configured, it is strongly recommended to have at least
 | 
						|
          # a single job with neither 'shortest_max_lifetime' nor 'longest_max_lifetime'
 | 
						|
          # set, or one job without 'shortest_max_lifetime' and one job without
 | 
						|
          # 'longest_max_lifetime' set. Otherwise some rooms might be ignored, even if
 | 
						|
          # 'allowed_lifetime_min' and 'allowed_lifetime_max' are set, because capping a
 | 
						|
          # room's policy to these values is done after the policies are retrieved from
 | 
						|
          # Synapse's database (which is done using the range specified in a purge job's
 | 
						|
          # configuration).
 | 
						|
          #
 | 
						|
          #purge_jobs:
 | 
						|
          #  - longest_max_lifetime: 3d
 | 
						|
          #    interval: 12h
 | 
						|
          #  - shortest_max_lifetime: 3d
 | 
						|
          #    interval: 1d
 | 
						|
        """
 |