mirror of
https://github.com/optim-enterprises-bv/Mailu.git
synced 2025-10-30 01:32:23 +00:00
First batch of refactoring, using the app factory pattern
This commit is contained in:
@@ -1,132 +1,58 @@
|
|||||||
import flask
|
import flask
|
||||||
import flask_sqlalchemy
|
|
||||||
import flask_bootstrap
|
import flask_bootstrap
|
||||||
import flask_login
|
|
||||||
import flask_script
|
|
||||||
import flask_migrate
|
|
||||||
import flask_babel
|
|
||||||
import flask_limiter
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import docker
|
import docker
|
||||||
import socket
|
import socket
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from werkzeug.contrib import fixers
|
from mailu import utils, debug, db
|
||||||
|
|
||||||
# Create application
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
|
|
||||||
default_config = {
|
def create_app_from_config(config):
|
||||||
# Specific to the admin UI
|
""" Create a new application based on the given configuration
|
||||||
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
|
"""
|
||||||
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
app = flask.Flask(__name__)
|
||||||
'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
|
app.config = config
|
||||||
'BABEL_DEFAULT_LOCALE': 'en',
|
|
||||||
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
|
||||||
'BOOTSTRAP_SERVE_LOCAL': True,
|
|
||||||
'RATELIMIT_STORAGE_URL': 'redis://redis/2',
|
|
||||||
'QUOTA_STORAGE_URL': 'redis://redis/1',
|
|
||||||
'DEBUG': False,
|
|
||||||
'DOMAIN_REGISTRATION': False,
|
|
||||||
# Statistics management
|
|
||||||
'INSTANCE_ID_PATH': '/data/instance',
|
|
||||||
'STATS_ENDPOINT': '0.{}.stats.mailu.io',
|
|
||||||
# Common configuration variables
|
|
||||||
'SECRET_KEY': 'changeMe',
|
|
||||||
'DOMAIN': 'mailu.io',
|
|
||||||
'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
|
|
||||||
'POSTMASTER': 'postmaster',
|
|
||||||
'TLS_FLAVOR': 'cert',
|
|
||||||
'AUTH_RATELIMIT': '10/minute;1000/hour',
|
|
||||||
'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,
|
|
||||||
# Web settings
|
|
||||||
'SITENAME': 'Mailu',
|
|
||||||
'WEBSITE': 'https://mailu.io',
|
|
||||||
'WEB_ADMIN': '/admin',
|
|
||||||
'WEB_WEBMAIL': '/webmail',
|
|
||||||
'RECAPTCHA_PUBLIC_KEY': '',
|
|
||||||
'RECAPTCHA_PRIVATE_KEY': '',
|
|
||||||
# Advanced settings
|
|
||||||
'PASSWORD_SCHEME': 'BLF-CRYPT',
|
|
||||||
# Host settings
|
|
||||||
'HOST_IMAP': 'imap',
|
|
||||||
'HOST_POP3': 'imap',
|
|
||||||
'HOST_SMTP': 'smtp',
|
|
||||||
'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Load configuration from the environment if available
|
# Bootstrap is used for basic JS and CSS loading
|
||||||
for key, value in default_config.items():
|
# TODO: remove this and use statically generated assets instead
|
||||||
app.config[key] = os.environ.get(key, value)
|
app.bootstrap = flask_bootstrap.Bootstrap(app)
|
||||||
|
|
||||||
# Base application
|
# Initialize application extensions
|
||||||
flask_bootstrap.Bootstrap(app)
|
models.db.init_app(app)
|
||||||
db = flask_sqlalchemy.SQLAlchemy(app)
|
utils.limiter.init_app(app)
|
||||||
migrate = flask_migrate.Migrate(app, db)
|
utils.babel.init_app(app)
|
||||||
limiter = flask_limiter.Limiter(app, key_func=lambda: current_user.username)
|
utils.login.init_app(app)
|
||||||
|
utils.proxy.init_app(app)
|
||||||
|
manage.migrate.init_app(app)
|
||||||
|
manage.manager.init_app(app)
|
||||||
|
|
||||||
# Debugging toolbar
|
# Initialize debugging tools
|
||||||
if app.config.get("DEBUG"):
|
if app.config.get("app.debug"):
|
||||||
import flask_debugtoolbar
|
debug.toolbar.init_app(app)
|
||||||
toolbar = flask_debugtoolbar.DebugToolbarExtension(app)
|
debug.profiler.init_app(app)
|
||||||
|
|
||||||
# Manager commnad
|
# Inject the default variables in the Jinja parser
|
||||||
manager = flask_script.Manager(app)
|
@app.context_processor
|
||||||
manager.add_command('db', flask_migrate.MigrateCommand)
|
def inject_defaults():
|
||||||
|
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
|
||||||
|
return dict(
|
||||||
|
current_user=utils.login.current_user,
|
||||||
|
signup_domains=signup_domains,
|
||||||
|
config=app.config
|
||||||
|
)
|
||||||
|
|
||||||
# Babel configuration
|
# Import views
|
||||||
babel = flask_babel.Babel(app)
|
from mailu import ui, internal
|
||||||
translations = list(map(str, babel.list_translations()))
|
app.register_blueprint(ui.ui, url_prefix='/ui')
|
||||||
|
app.register_blueprint(internal.internal, url_prefix='/internal')
|
||||||
|
|
||||||
@babel.localeselector
|
return app
|
||||||
def get_locale():
|
|
||||||
return flask.request.accept_languages.best_match(translations)
|
|
||||||
|
|
||||||
# Login configuration
|
|
||||||
login_manager = flask_login.LoginManager()
|
|
||||||
login_manager.init_app(app)
|
|
||||||
login_manager.login_view = "ui.login"
|
|
||||||
|
|
||||||
@login_manager.unauthorized_handler
|
def create_app():
|
||||||
def handle_needs_login():
|
""" Create a new application based on the config module
|
||||||
return flask.redirect(
|
"""
|
||||||
flask.url_for('ui.login', next=flask.request.endpoint)
|
config = configuration.ConfigManager()
|
||||||
)
|
return create_app_from_config(config)
|
||||||
|
|
||||||
@app.context_processor
|
|
||||||
def inject_defaults():
|
|
||||||
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
|
|
||||||
return dict(
|
|
||||||
current_user=flask_login.current_user,
|
|
||||||
signup_domains=signup_domains,
|
|
||||||
config=app.config
|
|
||||||
)
|
|
||||||
|
|
||||||
# Import views
|
|
||||||
from mailu import ui, internal
|
|
||||||
app.register_blueprint(ui.ui, url_prefix='/ui')
|
|
||||||
app.register_blueprint(internal.internal, url_prefix='/internal')
|
|
||||||
|
|
||||||
# Create the prefix middleware
|
|
||||||
class PrefixMiddleware(object):
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '')
|
|
||||||
if prefix:
|
|
||||||
environ['SCRIPT_NAME'] = prefix
|
|
||||||
return self.app(environ, start_response)
|
|
||||||
|
|
||||||
app.wsgi_app = PrefixMiddleware(fixers.ProxyFix(app.wsgi_app))
|
|
||||||
|
|||||||
70
core/admin/mailu/configuration.py
Normal file
70
core/admin/mailu/configuration.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
# Specific to the admin UI
|
||||||
|
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
|
||||||
|
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
||||||
|
'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
|
||||||
|
'BABEL_DEFAULT_LOCALE': 'en',
|
||||||
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
||||||
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
||||||
|
'RATELIMIT_STORAGE_URL': 'redis://redis/2',
|
||||||
|
'QUOTA_STORAGE_URL': 'redis://redis/1',
|
||||||
|
'DEBUG': False,
|
||||||
|
'DOMAIN_REGISTRATION': False,
|
||||||
|
# Statistics management
|
||||||
|
'INSTANCE_ID_PATH': '/data/instance',
|
||||||
|
'STATS_ENDPOINT': '0.{}.stats.mailu.io',
|
||||||
|
# Common configuration variables
|
||||||
|
'SECRET_KEY': 'changeMe',
|
||||||
|
'DOMAIN': 'mailu.io',
|
||||||
|
'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
|
||||||
|
'POSTMASTER': 'postmaster',
|
||||||
|
'TLS_FLAVOR': 'cert',
|
||||||
|
'AUTH_RATELIMIT': '10/minute;1000/hour',
|
||||||
|
'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,
|
||||||
|
# Web settings
|
||||||
|
'SITENAME': 'Mailu',
|
||||||
|
'WEBSITE': 'https://mailu.io',
|
||||||
|
'WEB_ADMIN': '/admin',
|
||||||
|
'WEB_WEBMAIL': '/webmail',
|
||||||
|
'RECAPTCHA_PUBLIC_KEY': '',
|
||||||
|
'RECAPTCHA_PRIVATE_KEY': '',
|
||||||
|
# Advanced settings
|
||||||
|
'PASSWORD_SCHEME': 'BLF-CRYPT',
|
||||||
|
# Host settings
|
||||||
|
'HOST_IMAP': 'imap',
|
||||||
|
'HOST_POP3': 'imap',
|
||||||
|
'HOST_SMTP': 'smtp',
|
||||||
|
'HOST_WEBMAIL': 'webmail',
|
||||||
|
'HOST_FRONT': 'front',
|
||||||
|
'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'),
|
||||||
|
'POD_ADDRESS_RANGE': None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager(object):
|
||||||
|
""" Naive configuration manager that uses environment only
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = {
|
||||||
|
os.environ.get(key, value)
|
||||||
|
for key, value in DEFAULT_CONFIG.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
return self.config.get(*args)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.get(key)
|
||||||
17
core/admin/mailu/debug.py
Normal file
17
core/admin/mailu/debug.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import flask_debugtoolbar
|
||||||
|
|
||||||
|
from werkzeug.contrib import profiler as werkzeug_profiler
|
||||||
|
|
||||||
|
|
||||||
|
# Debugging toolbar
|
||||||
|
toolbar = flask_debugtoolbar.DebugToolbarExtension()
|
||||||
|
|
||||||
|
|
||||||
|
# Profiler
|
||||||
|
class Profiler(object):
|
||||||
|
def init_app(self):
|
||||||
|
app.wsgi_app = werkzeug_profiler.ProfilerMiddleware(
|
||||||
|
app.wsgi_app, restrictions=[30]
|
||||||
|
)
|
||||||
|
|
||||||
|
profiler = Profiler()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask_limiter import RateLimitExceeded
|
from flask_limiter import RateLimitExceeded
|
||||||
|
|
||||||
from mailu import limiter
|
from mailu import utils
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import flask
|
import flask
|
||||||
@@ -19,7 +19,7 @@ def rate_limit_handler(e):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@limiter.request_filter
|
@utils.limiter.request_filter
|
||||||
def whitelist_webmail():
|
def whitelist_webmail():
|
||||||
try:
|
try:
|
||||||
return flask.request.headers["Client-Ip"] ==\
|
return flask.request.headers["Client-Ip"] ==\
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from mailu import db, models, app
|
from mailu import models
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from mailu import db, models, app, limiter
|
from mailu import models, utils
|
||||||
from mailu.internal import internal, nginx
|
from mailu.internal import internal, nginx
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login
|
||||||
@@ -7,7 +8,7 @@ import base64
|
|||||||
|
|
||||||
|
|
||||||
@internal.route("/auth/email")
|
@internal.route("/auth/email")
|
||||||
@limiter.limit(
|
@utils.limiter.limit(
|
||||||
app.config["AUTH_RATELIMIT"],
|
app.config["AUTH_RATELIMIT"],
|
||||||
lambda: flask.request.headers["Client-Ip"]
|
lambda: flask.request.headers["Client-Ip"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.internal import internal
|
from mailu.internal import internal
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ def dovecot_quota(ns, user_email):
|
|||||||
user = models.User.query.get(user_email) or flask.abort(404)
|
user = models.User.query.get(user_email) or flask.abort(404)
|
||||||
if ns == "storage":
|
if ns == "storage":
|
||||||
user.quota_bytes_used = flask.request.get_json()
|
user.quota_bytes_used = flask.request.get_json()
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
return flask.jsonify(None)
|
return flask.jsonify(None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.internal import internal
|
from mailu.internal import internal
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -27,6 +27,6 @@ def fetch_done(fetch_id):
|
|||||||
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
|
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
|
||||||
fetch.last_check = datetime.datetime.now()
|
fetch.last_check = datetime.datetime.now()
|
||||||
fetch.error_message = str(flask.request.get_json())
|
fetch.error_message = str(flask.request.get_json())
|
||||||
db.session.add(fetch)
|
models.db.session.add(fetch)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.internal import internal
|
from mailu.internal import internal
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|||||||
305
core/admin/mailu/manage.py
Normal file
305
core/admin/mailu/manage.py
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
from mailu import models
|
||||||
|
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
manager = flask_script.Manager()
|
||||||
|
db = models.db
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def advertise():
|
||||||
|
""" Advertise this server against statistic services.
|
||||||
|
"""
|
||||||
|
if os.path.isfile(app.config["INSTANCE_ID_PATH"]):
|
||||||
|
with open(app.config["INSTANCE_ID_PATH"], "r") as handle:
|
||||||
|
instance_id = handle.read()
|
||||||
|
else:
|
||||||
|
instance_id = str(uuid.uuid4())
|
||||||
|
with open(app.config["INSTANCE_ID_PATH"], "w") as handle:
|
||||||
|
handle.write(instance_id)
|
||||||
|
if app.config["DISABLE_STATISTICS"].lower() != "true":
|
||||||
|
try:
|
||||||
|
socket.gethostbyname(app.config["STATS_ENDPOINT"].format(instance_id))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def admin(localpart, domain_name, password):
|
||||||
|
""" Create an admin user
|
||||||
|
"""
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
user = models.User(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
global_admin=True
|
||||||
|
)
|
||||||
|
user.set_password(password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def user(localpart, domain_name, password,
|
||||||
|
hash_scheme=app.config['PASSWORD_SCHEME']):
|
||||||
|
""" Create a user
|
||||||
|
"""
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
user = models.User(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
global_admin=False
|
||||||
|
)
|
||||||
|
user.set_password(password, hash_scheme=hash_scheme)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.option('-n', '--domain_name', dest='domain_name')
|
||||||
|
@manager.option('-u', '--max_users', dest='max_users')
|
||||||
|
@manager.option('-a', '--max_aliases', dest='max_aliases')
|
||||||
|
@manager.option('-q', '--max_quota_bytes', dest='max_quota_bytes')
|
||||||
|
def domain(domain_name, max_users=0, max_aliases=0, max_quota_bytes=0):
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def user_import(localpart, domain_name, password_hash,
|
||||||
|
hash_scheme=app.config['PASSWORD_SCHEME']):
|
||||||
|
""" Import a user along with password hash. Available hashes:
|
||||||
|
'SHA512-CRYPT'
|
||||||
|
'SHA256-CRYPT'
|
||||||
|
'MD5-CRYPT'
|
||||||
|
'CRYPT'
|
||||||
|
"""
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
user = models.User(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
global_admin=False
|
||||||
|
)
|
||||||
|
user.set_password(password_hash, hash_scheme=hash_scheme, raw=True)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def config_update(verbose=False, delete_objects=False):
|
||||||
|
"""sync configuration with data from YAML-formatted stdin"""
|
||||||
|
import yaml
|
||||||
|
import sys
|
||||||
|
new_config = yaml.load(sys.stdin)
|
||||||
|
# print new_config
|
||||||
|
domains = new_config.get('domains', [])
|
||||||
|
tracked_domains = set()
|
||||||
|
for domain_config in domains:
|
||||||
|
if verbose:
|
||||||
|
print(str(domain_config))
|
||||||
|
domain_name = domain_config['name']
|
||||||
|
max_users = domain_config.get('max_users', 0)
|
||||||
|
max_aliases = domain_config.get('max_aliases', 0)
|
||||||
|
max_quota_bytes = domain_config.get('max_quota_bytes', 0)
|
||||||
|
tracked_domains.add(domain_name)
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name,
|
||||||
|
max_users=max_users,
|
||||||
|
max_aliases=max_aliases,
|
||||||
|
max_quota_bytes=max_quota_bytes)
|
||||||
|
db.session.add(domain)
|
||||||
|
print("Added " + str(domain_config))
|
||||||
|
else:
|
||||||
|
domain.max_users = max_users
|
||||||
|
domain.max_aliases = max_aliases
|
||||||
|
domain.max_quota_bytes = max_quota_bytes
|
||||||
|
db.session.add(domain)
|
||||||
|
print("Updated " + str(domain_config))
|
||||||
|
|
||||||
|
users = new_config.get('users', [])
|
||||||
|
tracked_users = set()
|
||||||
|
user_optional_params = ('comment', 'quota_bytes', 'global_admin',
|
||||||
|
'enable_imap', 'enable_pop', 'forward_enabled',
|
||||||
|
'forward_destination', 'reply_enabled',
|
||||||
|
'reply_subject', 'reply_body', 'displayed_name',
|
||||||
|
'spam_enabled', 'email', 'spam_threshold')
|
||||||
|
for user_config in users:
|
||||||
|
if verbose:
|
||||||
|
print(str(user_config))
|
||||||
|
localpart = user_config['localpart']
|
||||||
|
domain_name = user_config['domain']
|
||||||
|
password_hash = user_config.get('password_hash', None)
|
||||||
|
hash_scheme = user_config.get('hash_scheme', None)
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
email = '{0}@{1}'.format(localpart, domain_name)
|
||||||
|
optional_params = {}
|
||||||
|
for k in user_optional_params:
|
||||||
|
if k in user_config:
|
||||||
|
optional_params[k] = user_config[k]
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
user = models.User.query.get(email)
|
||||||
|
tracked_users.add(email)
|
||||||
|
tracked_domains.add(domain_name)
|
||||||
|
if not user:
|
||||||
|
user = models.User(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
**optional_params
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for k in optional_params:
|
||||||
|
setattr(user, k, optional_params[k])
|
||||||
|
user.set_password(password_hash, hash_scheme=hash_scheme, raw=True)
|
||||||
|
db.session.add(user)
|
||||||
|
|
||||||
|
aliases = new_config.get('aliases', [])
|
||||||
|
tracked_aliases = set()
|
||||||
|
for alias_config in aliases:
|
||||||
|
if verbose:
|
||||||
|
print(str(alias_config))
|
||||||
|
localpart = alias_config['localpart']
|
||||||
|
domain_name = alias_config['domain']
|
||||||
|
if type(alias_config['destination']) is str:
|
||||||
|
destination = alias_config['destination'].split(',')
|
||||||
|
else:
|
||||||
|
destination = alias_config['destination']
|
||||||
|
wildcard = alias_config.get('wildcard', False)
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
email = '{0}@{1}'.format(localpart, domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
alias = models.Alias.query.get(email)
|
||||||
|
tracked_aliases.add(email)
|
||||||
|
tracked_domains.add(domain_name)
|
||||||
|
if not alias:
|
||||||
|
alias = models.Alias(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
wildcard=wildcard,
|
||||||
|
destination=destination,
|
||||||
|
email=email
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
alias.destination = destination
|
||||||
|
alias.wildcard = wildcard
|
||||||
|
db.session.add(alias)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
managers = new_config.get('managers', [])
|
||||||
|
# tracked_managers=set()
|
||||||
|
for manager_config in managers:
|
||||||
|
if verbose:
|
||||||
|
print(str(manager_config))
|
||||||
|
domain_name = manager_config['domain']
|
||||||
|
user_name = manager_config['user']
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
manageruser = models.User.query.get(user_name + '@' + domain_name)
|
||||||
|
if manageruser not in domain.managers:
|
||||||
|
domain.managers.append(manageruser)
|
||||||
|
db.session.add(domain)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if delete_objects:
|
||||||
|
for user in db.session.query(models.User).all():
|
||||||
|
if not (user.email in tracked_users):
|
||||||
|
if verbose:
|
||||||
|
print("Deleting user: " + str(user.email))
|
||||||
|
db.session.delete(user)
|
||||||
|
for alias in db.session.query(models.Alias).all():
|
||||||
|
if not (alias.email in tracked_aliases):
|
||||||
|
if verbose:
|
||||||
|
print("Deleting alias: " + str(alias.email))
|
||||||
|
db.session.delete(alias)
|
||||||
|
for domain in db.session.query(models.Domain).all():
|
||||||
|
if not (domain.name in tracked_domains):
|
||||||
|
if verbose:
|
||||||
|
print("Deleting domain: " + str(domain.name))
|
||||||
|
db.session.delete(domain)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def user_delete(email):
|
||||||
|
"""delete user"""
|
||||||
|
user = models.User.query.get(email)
|
||||||
|
if user:
|
||||||
|
db.session.delete(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def alias_delete(email):
|
||||||
|
"""delete alias"""
|
||||||
|
alias = models.Alias.query.get(email)
|
||||||
|
if alias:
|
||||||
|
db.session.delete(alias)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def alias(localpart, domain_name, destination):
|
||||||
|
""" Create an alias
|
||||||
|
"""
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
if not domain:
|
||||||
|
domain = models.Domain(name=domain_name)
|
||||||
|
db.session.add(domain)
|
||||||
|
alias = models.Alias(
|
||||||
|
localpart=localpart,
|
||||||
|
domain=domain,
|
||||||
|
destination=destination.split(','),
|
||||||
|
email="%s@%s" % (localpart, domain_name)
|
||||||
|
)
|
||||||
|
db.session.add(alias)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Set limits to a domain
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def setlimits(domain_name, max_users, max_aliases, max_quota_bytes):
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
domain.max_users = max_users
|
||||||
|
domain.max_aliases = max_aliases
|
||||||
|
domain.max_quota_bytes = max_quota_bytes
|
||||||
|
|
||||||
|
db.session.add(domain)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Make the user manager of a domain
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def setmanager(domain_name, user_name='manager'):
|
||||||
|
domain = models.Domain.query.get(domain_name)
|
||||||
|
manageruser = models.User.query.get(user_name + '@' + domain_name)
|
||||||
|
domain.managers.append(manageruser)
|
||||||
|
db.session.add(domain)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
manager.run()
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
from mailu import app, db, dkim, login_manager
|
from mailu import dkim
|
||||||
|
|
||||||
from sqlalchemy.ext import declarative
|
from sqlalchemy.ext import declarative
|
||||||
from passlib import context, hash
|
from passlib import context, hash
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from email.mime import text
|
from email.mime import text
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
import flask_sqlalchemy
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -15,6 +17,9 @@ import idna
|
|||||||
import dns
|
import dns
|
||||||
|
|
||||||
|
|
||||||
|
db = flask_sqlalchemy.SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
class IdnaDomain(db.TypeDecorator):
|
class IdnaDomain(db.TypeDecorator):
|
||||||
""" Stores a Unicode string in it's IDNA representation (ASCII only)
|
""" Stores a Unicode string in it's IDNA representation (ASCII only)
|
||||||
"""
|
"""
|
||||||
@@ -67,6 +72,27 @@ class CommaSeparatedList(db.TypeDecorator):
|
|||||||
return filter(bool, value.split(","))
|
return filter(bool, value.split(","))
|
||||||
|
|
||||||
|
|
||||||
|
class JSONEncoded(db.TypeDecorator):
|
||||||
|
"""Represents an immutable structure as a json-encoded string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
impl = db.String
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
return json.dumps(value) if value else None
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
return json.loads(value) if value else None
|
||||||
|
|
||||||
|
|
||||||
|
class Config(db.Model):
|
||||||
|
""" In-database configuration values
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = db.Column(db.String(255), primary_key=True, nullable=False)
|
||||||
|
value = db.Column(JSONEncoded)
|
||||||
|
|
||||||
|
|
||||||
# Many-to-many association table for domain managers
|
# Many-to-many association table for domain managers
|
||||||
managers = db.Table('manager',
|
managers = db.Table('manager',
|
||||||
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
|
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
|
||||||
@@ -324,8 +350,6 @@ class User(Base, Email):
|
|||||||
user = cls.query.get(email)
|
user = cls.query.get(email)
|
||||||
return user if (user and user.enabled and user.check_password(password)) else None
|
return user if (user and user.enabled and user.check_password(password)) else None
|
||||||
|
|
||||||
login_manager.user_loader(User.query.get)
|
|
||||||
|
|
||||||
|
|
||||||
class Alias(Base, Email):
|
class Alias(Base, Email):
|
||||||
""" An alias is an email address that redirects to some destination.
|
""" An alias is an email address that redirects to some destination.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import forms
|
from mailu.ui import forms
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -25,7 +25,7 @@ def admin_create():
|
|||||||
user = models.User.query.get(form.admin.data)
|
user = models.User.query.get(form.admin.data)
|
||||||
if user:
|
if user:
|
||||||
user.global_admin = True
|
user.global_admin = True
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s is now admin' % user)
|
flask.flash('User %s is now admin' % user)
|
||||||
return flask.redirect(flask.url_for('.admin_list'))
|
return flask.redirect(flask.url_for('.admin_list'))
|
||||||
else:
|
else:
|
||||||
@@ -40,7 +40,7 @@ def admin_delete(admin):
|
|||||||
user = models.User.query.get(admin)
|
user = models.User.query.get(admin)
|
||||||
if user:
|
if user:
|
||||||
user.global_admin = False
|
user.global_admin = False
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s is no longer admin' % user)
|
flask.flash('User %s is no longer admin' % user)
|
||||||
return flask.redirect(flask.url_for('.admin_list'))
|
return flask.redirect(flask.url_for('.admin_list'))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -27,8 +27,8 @@ def alias_create(domain_name):
|
|||||||
else:
|
else:
|
||||||
alias = models.Alias(domain=domain)
|
alias = models.Alias(domain=domain)
|
||||||
form.populate_obj(alias)
|
form.populate_obj(alias)
|
||||||
db.session.add(alias)
|
models.db.session.add(alias)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Alias %s created' % alias)
|
flask.flash('Alias %s created' % alias)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.alias_list', domain_name=domain.name))
|
flask.url_for('.alias_list', domain_name=domain.name))
|
||||||
@@ -45,7 +45,7 @@ def alias_edit(alias):
|
|||||||
form.localpart.validators = []
|
form.localpart.validators = []
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(alias)
|
form.populate_obj(alias)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Alias %s updated' % alias)
|
flask.flash('Alias %s updated' % alias)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.alias_list', domain_name=alias.domain.name))
|
flask.url_for('.alias_list', domain_name=alias.domain.name))
|
||||||
@@ -59,8 +59,8 @@ def alias_edit(alias):
|
|||||||
def alias_delete(alias):
|
def alias_delete(alias):
|
||||||
alias = models.Alias.query.get(alias) or flask.abort(404)
|
alias = models.Alias.query.get(alias) or flask.abort(404)
|
||||||
domain = alias.domain
|
domain = alias.domain
|
||||||
db.session.delete(alias)
|
models.db.session.delete(alias)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Alias %s deleted' % alias)
|
flask.flash('Alias %s deleted' % alias)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.alias_list', domain_name=domain.name))
|
flask.url_for('.alias_list', domain_name=domain.name))
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ def alternative_create(domain_name):
|
|||||||
else:
|
else:
|
||||||
alternative = models.Alternative(domain=domain)
|
alternative = models.Alternative(domain=domain)
|
||||||
form.populate_obj(alternative)
|
form.populate_obj(alternative)
|
||||||
db.session.add(alternative)
|
models.db.session.add(alternative)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Alternative domain %s created' % alternative)
|
flask.flash('Alternative domain %s created' % alternative)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.alternative_list', domain_name=domain.name))
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
||||||
@@ -41,8 +41,8 @@ def alternative_create(domain_name):
|
|||||||
def alternative_delete(alternative):
|
def alternative_delete(alternative):
|
||||||
alternative = models.Alternative.query.get(alternative) or flask.abort(404)
|
alternative = models.Alternative.query.get(alternative) or flask.abort(404)
|
||||||
domain = alternative.domain
|
domain = alternative.domain
|
||||||
db.session.delete(alternative)
|
models.db.session.delete(alternative)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Alternative %s deleted' % alternative)
|
flask.flash('Alternative %s deleted' % alternative)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.alternative_list', domain_name=domain.name))
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from mailu import dockercli, app, db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login
|
||||||
|
|
||||||
from urllib import parse
|
|
||||||
|
|
||||||
|
|
||||||
@ui.route('/', methods=["GET"])
|
@ui.route('/', methods=["GET"])
|
||||||
@access.authenticated
|
@access.authenticated
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from mailu import app, db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login
|
||||||
@@ -26,8 +27,8 @@ def domain_create():
|
|||||||
else:
|
else:
|
||||||
domain = models.Domain()
|
domain = models.Domain()
|
||||||
form.populate_obj(domain)
|
form.populate_obj(domain)
|
||||||
db.session.add(domain)
|
models.db.session.add(domain)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Domain %s created' % domain)
|
flask.flash('Domain %s created' % domain)
|
||||||
return flask.redirect(flask.url_for('.domain_list'))
|
return flask.redirect(flask.url_for('.domain_list'))
|
||||||
return flask.render_template('domain/create.html', form=form)
|
return flask.render_template('domain/create.html', form=form)
|
||||||
@@ -42,7 +43,7 @@ def domain_edit(domain_name):
|
|||||||
form.name.validators = []
|
form.name.validators = []
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(domain)
|
form.populate_obj(domain)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Domain %s saved' % domain)
|
flask.flash('Domain %s saved' % domain)
|
||||||
return flask.redirect(flask.url_for('.domain_list'))
|
return flask.redirect(flask.url_for('.domain_list'))
|
||||||
return flask.render_template('domain/edit.html', form=form,
|
return flask.render_template('domain/edit.html', form=form,
|
||||||
@@ -54,8 +55,8 @@ def domain_edit(domain_name):
|
|||||||
@access.confirmation_required("delete {domain_name}")
|
@access.confirmation_required("delete {domain_name}")
|
||||||
def domain_delete(domain_name):
|
def domain_delete(domain_name):
|
||||||
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
db.session.delete(domain)
|
models.db.session.delete(domain)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Domain %s deleted' % domain)
|
flask.flash('Domain %s deleted' % domain)
|
||||||
return flask.redirect(flask.url_for('.domain_list'))
|
return flask.redirect(flask.url_for('.domain_list'))
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ def domain_signup(domain_name=None):
|
|||||||
domain.max_users = 10
|
domain.max_users = 10
|
||||||
domain.max_aliases = 10
|
domain.max_aliases = 10
|
||||||
if domain.check_mx():
|
if domain.check_mx():
|
||||||
db.session.add(domain)
|
models.db.session.add(domain)
|
||||||
if flask_login.current_user.is_authenticated:
|
if flask_login.current_user.is_authenticated:
|
||||||
user = models.User.query.get(flask_login.current_user.email)
|
user = models.User.query.get(flask_login.current_user.email)
|
||||||
else:
|
else:
|
||||||
@@ -108,9 +109,9 @@ def domain_signup(domain_name=None):
|
|||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
user.set_password(form.pw.data)
|
user.set_password(form.pw.data)
|
||||||
user.quota_bytes = domain.max_quota_bytes
|
user.quota_bytes = domain.max_quota_bytes
|
||||||
db.session.add(user)
|
models.db.session.add(user)
|
||||||
domain.managers.append(user)
|
domain.managers.append(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Domain %s created' % domain)
|
flask.flash('Domain %s created' % domain)
|
||||||
return flask.redirect(flask.url_for('.domain_list'))
|
return flask.redirect(flask.url_for('.domain_list'))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -24,8 +24,8 @@ def fetch_create(user_email):
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
fetch = models.Fetch(user=user)
|
fetch = models.Fetch(user=user)
|
||||||
form.populate_obj(fetch)
|
form.populate_obj(fetch)
|
||||||
db.session.add(fetch)
|
models.db.session.add(fetch)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Fetch configuration created')
|
flask.flash('Fetch configuration created')
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.fetch_list', user_email=user.email))
|
flask.url_for('.fetch_list', user_email=user.email))
|
||||||
@@ -39,7 +39,7 @@ def fetch_edit(fetch_id):
|
|||||||
form = forms.FetchForm(obj=fetch)
|
form = forms.FetchForm(obj=fetch)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(fetch)
|
form.populate_obj(fetch)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Fetch configuration updated')
|
flask.flash('Fetch configuration updated')
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.fetch_list', user_email=fetch.user.email))
|
flask.url_for('.fetch_list', user_email=fetch.user.email))
|
||||||
@@ -53,8 +53,8 @@ def fetch_edit(fetch_id):
|
|||||||
def fetch_delete(fetch_id):
|
def fetch_delete(fetch_id):
|
||||||
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
|
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
|
||||||
user = fetch.user
|
user = fetch.user
|
||||||
db.session.delete(fetch)
|
models.db.session.delete(fetch)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Fetch configuration delete')
|
flask.flash('Fetch configuration delete')
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.fetch_list', user_email=user.email))
|
flask.url_for('.fetch_list', user_email=user.email))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -30,7 +30,7 @@ def manager_create(domain_name):
|
|||||||
flask.flash('User %s is already manager' % user, 'error')
|
flask.flash('User %s is already manager' % user, 'error')
|
||||||
else:
|
else:
|
||||||
domain.managers.append(user)
|
domain.managers.append(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s can now manage %s' % (user, domain.name))
|
flask.flash('User %s can now manage %s' % (user, domain.name))
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.manager_list', domain_name=domain.name))
|
flask.url_for('.manager_list', domain_name=domain.name))
|
||||||
@@ -46,7 +46,7 @@ def manager_delete(domain_name, user_email):
|
|||||||
user = models.User.query.get(user_email) or flask.abort(404)
|
user = models.User.query.get(user_email) or flask.abort(404)
|
||||||
if user in domain.managers:
|
if user in domain.managers:
|
||||||
domain.managers.remove(user)
|
domain.managers.remove(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s can no longer manager %s' % (user, domain))
|
flask.flash('User %s can no longer manager %s' % (user, domain))
|
||||||
else:
|
else:
|
||||||
flask.flash('User %s is not manager' % user, 'error')
|
flask.flash('User %s is not manager' % user, 'error')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@@ -25,8 +25,8 @@ def relay_create():
|
|||||||
else:
|
else:
|
||||||
relay = models.Relay()
|
relay = models.Relay()
|
||||||
form.populate_obj(relay)
|
form.populate_obj(relay)
|
||||||
db.session.add(relay)
|
models.db.session.add(relay)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Relayed domain %s created' % relay)
|
flask.flash('Relayed domain %s created' % relay)
|
||||||
return flask.redirect(flask.url_for('.relay_list'))
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
return flask.render_template('relay/create.html', form=form)
|
return flask.render_template('relay/create.html', form=form)
|
||||||
@@ -41,7 +41,7 @@ def relay_edit(relay_name):
|
|||||||
form.name.validators = []
|
form.name.validators = []
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(relay)
|
form.populate_obj(relay)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Relayed domain %s saved' % relay)
|
flask.flash('Relayed domain %s saved' % relay)
|
||||||
return flask.redirect(flask.url_for('.relay_list'))
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
return flask.render_template('relay/edit.html', form=form,
|
return flask.render_template('relay/edit.html', form=form,
|
||||||
@@ -53,8 +53,8 @@ def relay_edit(relay_name):
|
|||||||
@access.confirmation_required("delete {relay_name}")
|
@access.confirmation_required("delete {relay_name}")
|
||||||
def relay_delete(relay_name):
|
def relay_delete(relay_name):
|
||||||
relay = models.Relay.query.get(relay_name) or flask.abort(404)
|
relay = models.Relay.query.get(relay_name) or flask.abort(404)
|
||||||
db.session.delete(relay)
|
models.db.session.delete(relay)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Relayed domain %s deleted' % relay)
|
flask.flash('Relayed domain %s deleted' % relay)
|
||||||
return flask.redirect(flask.url_for('.relay_list'))
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from mailu import db, models
|
from mailu import models
|
||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
from passlib import pwd
|
from passlib import pwd
|
||||||
@@ -32,8 +32,8 @@ def token_create(user_email):
|
|||||||
token = models.Token(user=user)
|
token = models.Token(user=user)
|
||||||
token.set_password(form.raw_password.data)
|
token.set_password(form.raw_password.data)
|
||||||
form.populate_obj(token)
|
form.populate_obj(token)
|
||||||
db.session.add(token)
|
models.db.session.add(token)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Authentication token created')
|
flask.flash('Authentication token created')
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.token_list', user_email=user.email))
|
flask.url_for('.token_list', user_email=user.email))
|
||||||
@@ -46,8 +46,8 @@ def token_create(user_email):
|
|||||||
def token_delete(token_id):
|
def token_delete(token_id):
|
||||||
token = models.Token.query.get(token_id) or flask.abort(404)
|
token = models.Token.query.get(token_id) or flask.abort(404)
|
||||||
user = token.user
|
user = token.user
|
||||||
db.session.delete(token)
|
models.db.session.delete(token)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Authentication token deleted')
|
flask.flash('Authentication token deleted')
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.token_list', user_email=user.email))
|
flask.url_for('.token_list', user_email=user.email))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from mailu import db, models, app
|
from mailu import models
|
||||||
from mailu.ui import ui, access, forms
|
from mailu.ui import ui, access, forms
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login
|
||||||
@@ -33,8 +34,8 @@ def user_create(domain_name):
|
|||||||
user = models.User(domain=domain)
|
user = models.User(domain=domain)
|
||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
user.set_password(form.pw.data)
|
user.set_password(form.pw.data)
|
||||||
db.session.add(user)
|
models.db.session.add(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
user.send_welcome()
|
user.send_welcome()
|
||||||
flask.flash('User %s created' % user)
|
flask.flash('User %s created' % user)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
@@ -63,7 +64,7 @@ def user_edit(user_email):
|
|||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
if form.pw.data:
|
if form.pw.data:
|
||||||
user.set_password(form.pw.data)
|
user.set_password(form.pw.data)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s updated' % user)
|
flask.flash('User %s updated' % user)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||||
@@ -77,8 +78,8 @@ def user_edit(user_email):
|
|||||||
def user_delete(user_email):
|
def user_delete(user_email):
|
||||||
user = models.User.query.get(user_email) or flask.abort(404)
|
user = models.User.query.get(user_email) or flask.abort(404)
|
||||||
domain = user.domain
|
domain = user.domain
|
||||||
db.session.delete(user)
|
models.db.session.delete(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('User %s deleted' % user)
|
flask.flash('User %s deleted' % user)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.user_list', domain_name=domain.name))
|
flask.url_for('.user_list', domain_name=domain.name))
|
||||||
@@ -93,7 +94,7 @@ def user_settings(user_email):
|
|||||||
form = forms.UserSettingsForm(obj=user)
|
form = forms.UserSettingsForm(obj=user)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Settings updated for %s' % user)
|
flask.flash('Settings updated for %s' % user)
|
||||||
if user_email:
|
if user_email:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
@@ -113,7 +114,7 @@ def user_password(user_email):
|
|||||||
flask.flash('Passwords do not match', 'error')
|
flask.flash('Passwords do not match', 'error')
|
||||||
else:
|
else:
|
||||||
user.set_password(form.pw.data)
|
user.set_password(form.pw.data)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Password updated for %s' % user)
|
flask.flash('Password updated for %s' % user)
|
||||||
if user_email:
|
if user_email:
|
||||||
return flask.redirect(flask.url_for('.user_list',
|
return flask.redirect(flask.url_for('.user_list',
|
||||||
@@ -130,7 +131,7 @@ def user_forward(user_email):
|
|||||||
form = forms.UserForwardForm(obj=user)
|
form = forms.UserForwardForm(obj=user)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Forward destination updated for %s' % user)
|
flask.flash('Forward destination updated for %s' % user)
|
||||||
if user_email:
|
if user_email:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
@@ -147,7 +148,7 @@ def user_reply(user_email):
|
|||||||
form = forms.UserReplyForm(obj=user)
|
form = forms.UserReplyForm(obj=user)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
flask.flash('Auto-reply message updated for %s' % user)
|
flask.flash('Auto-reply message updated for %s' % user)
|
||||||
if user_email:
|
if user_email:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
@@ -179,8 +180,8 @@ def user_signup(domain_name=None):
|
|||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
user.set_password(form.pw.data)
|
user.set_password(form.pw.data)
|
||||||
user.quota_bytes = quota_bytes
|
user.quota_bytes = quota_bytes
|
||||||
db.session.add(user)
|
models.db.session.add(user)
|
||||||
db.session.commit()
|
models.db.session.commit()
|
||||||
user.send_welcome()
|
user.send_welcome()
|
||||||
flask.flash('Successfully signed up %s' % user)
|
flask.flash('Successfully signed up %s' % user)
|
||||||
return flask.redirect(flask.url_for('.index'))
|
return flask.redirect(flask.url_for('.index'))
|
||||||
|
|||||||
46
core/admin/mailu/utils.py
Normal file
46
core/admin/mailu/utils.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import flask
|
||||||
|
import flask_login
|
||||||
|
import flask_script
|
||||||
|
import flask_migrate
|
||||||
|
import flask_babel
|
||||||
|
import flask_limiter
|
||||||
|
|
||||||
|
|
||||||
|
# Login configuration
|
||||||
|
login = flask_login.LoginManager()
|
||||||
|
login.login_view = "ui.login"
|
||||||
|
login.user_loader(models.User.query.get)
|
||||||
|
|
||||||
|
@login_manager.unauthorized_handler
|
||||||
|
def handle_needs_login():
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('ui.login', next=flask.request.endpoint)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Request rate limitation
|
||||||
|
limiter = flask_limiter.Limiter(key_func=lambda: current_user.username)
|
||||||
|
|
||||||
|
|
||||||
|
# Application translation
|
||||||
|
babel = flask_babel.Babel()
|
||||||
|
|
||||||
|
@babel.localeselector
|
||||||
|
def get_locale():
|
||||||
|
translations = list(map(str, babel.list_translations()))
|
||||||
|
return flask.request.accept_languages.best_match(translations)
|
||||||
|
|
||||||
|
|
||||||
|
# Proxy fixer
|
||||||
|
class PrefixMiddleware(object):
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '')
|
||||||
|
if prefix:
|
||||||
|
environ['SCRIPT_NAME'] = prefix
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
self.app = fixers.ProxyFix(app.wsgi_app)
|
||||||
|
app.wsgi_app = self
|
||||||
|
|
||||||
|
proxy = PrefixMiddleware()
|
||||||
Reference in New Issue
Block a user