Files
Mailu/core/admin/mailu/configuration.py
bors[bot] 78dd13a217 Merge #2042
2042: Add MESSAGE_RATELIMIT_EXEMPTION r=mergify[bot] a=nextgens

## What type of PR?

Enhancement

## What does this PR do?

Add a new knob called ```MESSAGE_RATELIMIT_EXEMPTION```.

### Related issue(s)
- #1774

## Prerequisites
Before we can consider review and merge, please make sure the following list is done and checked.
If an entry in not applicable, you can check it or remove it from the list.

- [x] In case of feature or enhancement: documentation updated accordingly
- [ ] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file.


Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
2021-11-08 15:29:50 +00:00

185 lines
6.5 KiB
Python

import os
from datetime import timedelta
from socrate import system
import ipaddress
DEFAULT_CONFIG = {
# Specific to the admin UI
'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
'BABEL_DEFAULT_LOCALE': 'en',
'BABEL_DEFAULT_TIMEZONE': 'UTC',
'BOOTSTRAP_SERVE_LOCAL': True,
'RATELIMIT_STORAGE_URL': '',
'QUOTA_STORAGE_URL': '',
'DEBUG': False,
'DOMAIN_REGISTRATION': False,
'TEMPLATES_AUTO_RELOAD': True,
'MEMORY_SESSIONS': False,
# Database settings
'DB_FLAVOR': None,
'DB_USER': 'mailu',
'DB_PW': None,
'DB_HOST': 'database',
'DB_NAME': 'mailu',
'SQLITE_DATABASE_FILE':'data/main.db',
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
# Statistics management
'INSTANCE_ID_PATH': '/data/instance',
'STATS_ENDPOINT': '18.{}.stats.mailu.io',
# Common configuration variables
'SECRET_KEY': 'changeMe',
'DOMAIN': 'mailu.io',
'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
'POSTMASTER': 'postmaster',
'WILDCARD_SENDERS': '',
'TLS_FLAVOR': 'cert',
'INBOUND_TLS_ENFORCE': False,
'DEFER_ON_TLS_ERROR': True,
'AUTH_RATELIMIT_IP': '60/hour',
'AUTH_RATELIMIT_IP_V4_MASK': 24,
'AUTH_RATELIMIT_IP_V6_MASK': 56,
'AUTH_RATELIMIT_USER': '100/day',
'AUTH_RATELIMIT_EXEMPTION': '',
'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
'DISABLE_STATISTICS': False,
# Mail settings
'DMARC_RUA': None,
'DMARC_RUF': None,
'WELCOME': False,
'WELCOME_SUBJECT': 'Dummy welcome topic',
'WELCOME_BODY': 'Dummy welcome body',
'DKIM_SELECTOR': 'dkim',
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
'DEFAULT_QUOTA': 1000000000,
'MESSAGE_RATELIMIT': '200/day',
'MESSAGE_RATELIMIT_EXEMPTION': '',
'RECIPIENT_DELIMITER': '',
# Web settings
'SITENAME': 'Mailu',
'WEBSITE': 'https://mailu.io',
'ADMIN' : 'none',
'WEB_ADMIN': '/admin',
'WEB_WEBMAIL': '/webmail',
'WEBMAIL': 'none',
'RECAPTCHA_PUBLIC_KEY': '',
'RECAPTCHA_PRIVATE_KEY': '',
'LOGO_URL': None,
'LOGO_BACKGROUND': None,
# Advanced settings
'LOG_LEVEL': 'WARNING',
'SESSION_KEY_BITS': 128,
'SESSION_LIFETIME': 24,
'SESSION_COOKIE_SECURE': True,
'CREDENTIAL_ROUNDS': 12,
'TZ': 'Etc/UTC',
# Host settings
'HOST_IMAP': 'imap',
'HOST_LMTP': 'imap:2525',
'HOST_POP3': 'imap',
'HOST_SMTP': 'smtp',
'HOST_AUTHSMTP': 'smtp',
'HOST_ADMIN': 'admin',
'HOST_WEBMAIL': 'webmail',
'HOST_WEBDAV': 'webdav:5232',
'HOST_REDIS': 'redis',
'HOST_FRONT': 'front',
'SUBNET': '192.168.203.0/24',
'SUBNET6': None,
'POD_ADDRESS_RANGE': None
}
class ConfigManager(dict):
""" Naive configuration manager that uses environment only
"""
DB_TEMPLATES = {
'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'
}
def __init__(self):
self.config = dict()
def get_host_address(self, name):
# if MYSERVICE_ADDRESS is defined, use this
if '{}_ADDRESS'.format(name) in os.environ:
return os.environ.get('{}_ADDRESS'.format(name))
# otherwise use the host name and resolve it
return system.resolve_address(self.config['HOST_{}'.format(name)])
def resolve_hosts(self):
self.config["IMAP_ADDRESS"] = self.get_host_address("IMAP")
self.config["POP3_ADDRESS"] = self.get_host_address("POP3")
self.config["AUTHSMTP_ADDRESS"] = self.get_host_address("AUTHSMTP")
self.config["SMTP_ADDRESS"] = self.get_host_address("SMTP")
self.config["REDIS_ADDRESS"] = self.get_host_address("REDIS")
if self.config["WEBMAIL"] != "none":
self.config["WEBMAIL_ADDRESS"] = self.get_host_address("WEBMAIL")
def __get_env(self, key, value):
key_file = key + "_FILE"
if key_file in os.environ:
with open(os.environ.get(key_file)) as file:
value_from_file = file.read()
return value_from_file.strip()
else:
return os.environ.get(key, value)
def __coerce_value(self, value):
if isinstance(value, str) and value.lower() in ('true','yes'):
return True
elif isinstance(value, str) and value.lower() in ('false', 'no'):
return False
return value
def init_app(self, app):
self.config.update(app.config)
# get environment variables
self.config.update({
key: self.__coerce_value(self.__get_env(key, value))
for key, value in DEFAULT_CONFIG.items()
})
self.resolve_hosts()
# automatically set the sqlalchemy string
if self.config['DB_FLAVOR']:
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
self.config['RATELIMIT_STORAGE_URL'] = 'redis://{0}/2'.format(self.config['REDIS_ADDRESS'])
self.config['QUOTA_STORAGE_URL'] = 'redis://{0}/1'.format(self.config['REDIS_ADDRESS'])
self.config['SESSION_STORAGE_URL'] = 'redis://{0}/3'.format(self.config['REDIS_ADDRESS'])
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
self.config['SESSION_COOKIE_HTTPONLY'] = True
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
self.config['HOSTNAMES'] = ','.join(hostnames)
self.config['HOSTNAME'] = hostnames[0]
# update the app config itself
app.config = self
def setdefault(self, key, value):
if key not in self.config:
self.config[key] = value
return self.config[key]
def get(self, *args):
return self.config.get(*args)
def keys(self):
return self.config.keys()
def __getitem__(self, key):
return self.config.get(key)
def __setitem__(self, key, value):
self.config[key] = value
def __contains__(self, key):
return key in self.config