First batch of refactoring, using the app factory pattern

master
kaiyou 6 years ago
parent 7c82be904f
commit fc24426291

@ -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():
# Babel configuration
babel = flask_babel.Babel(app)
translations = list(map(str, babel.list_translations()))
@babel.localeselector
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 handle_needs_login():
return flask.redirect(
flask.url_for('ui.login', next=flask.request.endpoint)
)
@app.context_processor
def inject_defaults():
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all() signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
return dict( return dict(
current_user=flask_login.current_user, current_user=utils.login.current_user,
signup_domains=signup_domains, signup_domains=signup_domains,
config=app.config config=app.config
) )
# Import views # Import views
from mailu import ui, internal from mailu import ui, internal
app.register_blueprint(ui.ui, url_prefix='/ui') app.register_blueprint(ui.ui, url_prefix='/ui')
app.register_blueprint(internal.internal, url_prefix='/internal') app.register_blueprint(internal.internal, url_prefix='/internal')
# Create the prefix middleware return app
class PrefixMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response): def create_app():
prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '') """ Create a new application based on the config module
if prefix: """
environ['SCRIPT_NAME'] = prefix config = configuration.ConfigManager()
return self.app(environ, start_response) return create_app_from_config(config)
app.wsgi_app = PrefixMiddleware(fixers.ProxyFix(app.wsgi_app))

@ -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)

@ -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

@ -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'))

@ -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()
Loading…
Cancel
Save