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_sqlalchemy
import flask_bootstrap
import flask_login
import flask_script
import flask_migrate
import flask_babel
import flask_limiter
import os
import docker
import socket
import uuid
from werkzeug.contrib import fixers
from mailu import utils, debug, db
# Create application
app = flask.Flask(__name__)
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_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'),
}
def create_app_from_config(config):
""" Create a new application based on the given configuration
"""
app = flask.Flask(__name__)
app.config = config
# Load configuration from the environment if available
for key, value in default_config.items():
app.config[key] = os.environ.get(key, value)
# Bootstrap is used for basic JS and CSS loading
# TODO: remove this and use statically generated assets instead
app.bootstrap = flask_bootstrap.Bootstrap(app)
# Base application
flask_bootstrap.Bootstrap(app)
db = flask_sqlalchemy.SQLAlchemy(app)
migrate = flask_migrate.Migrate(app, db)
limiter = flask_limiter.Limiter(app, key_func=lambda: current_user.username)
# Initialize application extensions
models.db.init_app(app)
utils.limiter.init_app(app)
utils.babel.init_app(app)
utils.login.init_app(app)
utils.proxy.init_app(app)
manage.migrate.init_app(app)
manage.manager.init_app(app)
# Debugging toolbar
if app.config.get("DEBUG"):
import flask_debugtoolbar
toolbar = flask_debugtoolbar.DebugToolbarExtension(app)
# Initialize debugging tools
if app.config.get("app.debug"):
debug.toolbar.init_app(app)
debug.profiler.init_app(app)
# Manager commnad
manager = flask_script.Manager(app)
manager.add_command('db', flask_migrate.MigrateCommand)
# 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():
# Inject the default variables in the Jinja parser
@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,
current_user=utils.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')
# 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):
return app
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))
def create_app():
""" Create a new application based on the config module
"""
config = configuration.ConfigManager()
return create_app_from_config(config)

@ -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 mailu import limiter
from mailu import utils
import socket
import flask
@ -19,7 +19,7 @@ def rate_limit_handler(e):
return response
@limiter.request_filter
@utils.limiter.request_filter
def whitelist_webmail():
try:
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 socket

@ -1,5 +1,6 @@
from mailu import db, models, app, limiter
from mailu import models, utils
from mailu.internal import internal, nginx
from flask import current_app as app
import flask
import flask_login
@ -7,7 +8,7 @@ import base64
@internal.route("/auth/email")
@limiter.limit(
@utils.limiter.limit(
app.config["AUTH_RATELIMIT"],
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 flask import current_app as app
import flask
@ -25,7 +26,7 @@ def dovecot_quota(ns, user_email):
user = models.User.query.get(user_email) or flask.abort(404)
if ns == "storage":
user.quota_bytes_used = flask.request.get_json()
db.session.commit()
models.db.session.commit()
return flask.jsonify(None)

@ -1,4 +1,4 @@
from mailu import db, models
from mailu import models
from mailu.internal import internal
import flask
@ -27,6 +27,6 @@ def fetch_done(fetch_id):
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
fetch.last_check = datetime.datetime.now()
fetch.error_message = str(flask.request.get_json())
db.session.add(fetch)
db.session.commit()
models.db.session.add(fetch)
models.db.session.commit()
return ""

@ -1,4 +1,4 @@
from mailu import db, models
from mailu import models
from mailu.internal import internal
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 passlib import context, hash
from datetime import datetime, date
from email.mime import text
from flask import current_app as app
import flask_sqlalchemy
import sqlalchemy
import re
import time
@ -15,6 +17,9 @@ import idna
import dns
db = flask_sqlalchemy.SQLAlchemy()
class IdnaDomain(db.TypeDecorator):
""" Stores a Unicode string in it's IDNA representation (ASCII only)
"""
@ -67,6 +72,27 @@ class CommaSeparatedList(db.TypeDecorator):
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
managers = db.Table('manager',
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
@ -324,8 +350,6 @@ class User(Base, Email):
user = cls.query.get(email)
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):
""" 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
import flask

@ -1,4 +1,4 @@
from mailu import db, models
from mailu import models
from mailu.ui import ui, forms, access
import flask
@ -25,7 +25,7 @@ def admin_create():
user = models.User.query.get(form.admin.data)
if user:
user.global_admin = True
db.session.commit()
models.db.session.commit()
flask.flash('User %s is now admin' % user)
return flask.redirect(flask.url_for('.admin_list'))
else:
@ -40,7 +40,7 @@ def admin_delete(admin):
user = models.User.query.get(admin)
if user:
user.global_admin = False
db.session.commit()
models.db.session.commit()
flask.flash('User %s is no longer admin' % user)
return flask.redirect(flask.url_for('.admin_list'))
else:

@ -1,4 +1,4 @@
from mailu import db, models
from mailu import models
from mailu.ui import ui, forms, access
import flask
@ -27,8 +27,8 @@ def alias_create(domain_name):
else:
alias = models.Alias(domain=domain)
form.populate_obj(alias)
db.session.add(alias)
db.session.commit()
models.db.session.add(alias)
models.db.session.commit()
flask.flash('Alias %s created' % alias)
return flask.redirect(
flask.url_for('.alias_list', domain_name=domain.name))
@ -45,7 +45,7 @@ def alias_edit(alias):
form.localpart.validators = []
if form.validate_on_submit():
form.populate_obj(alias)
db.session.commit()
models.db.session.commit()
flask.flash('Alias %s updated' % alias)
return flask.redirect(
flask.url_for('.alias_list', domain_name=alias.domain.name))
@ -59,8 +59,8 @@ def alias_edit(alias):
def alias_delete(alias):
alias = models.Alias.query.get(alias) or flask.abort(404)
domain = alias.domain
db.session.delete(alias)
db.session.commit()
models.db.session.delete(alias)
models.db.session.commit()
flask.flash('Alias %s deleted' % alias)
return flask.redirect(
flask.url_for('.alias_list', domain_name=domain.name))

@ -26,8 +26,8 @@ def alternative_create(domain_name):
else:
alternative = models.Alternative(domain=domain)
form.populate_obj(alternative)
db.session.add(alternative)
db.session.commit()
models.db.session.add(alternative)
models.db.session.commit()
flask.flash('Alternative domain %s created' % alternative)
return flask.redirect(
flask.url_for('.alternative_list', domain_name=domain.name))
@ -41,8 +41,8 @@ def alternative_create(domain_name):
def alternative_delete(alternative):
alternative = models.Alternative.query.get(alternative) or flask.abort(404)
domain = alternative.domain
db.session.delete(alternative)
db.session.commit()
models.db.session.delete(alternative)
models.db.session.commit()
flask.flash('Alternative %s deleted' % alternative)
return flask.redirect(
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
import flask
import flask_login
from urllib import parse
@ui.route('/', methods=["GET"])
@access.authenticated

@ -1,5 +1,6 @@
from mailu import app, db, models
from mailu import models
from mailu.ui import ui, forms, access
from flask import current_app as app
import flask
import flask_login
@ -26,8 +27,8 @@ def domain_create():
else:
domain = models.Domain()
form.populate_obj(domain)
db.session.add(domain)
db.session.commit()
models.db.session.add(domain)
models.db.session.commit()
flask.flash('Domain %s created' % domain)
return flask.redirect(flask.url_for('.domain_list'))
return flask.render_template('domain/create.html', form=form)
@ -42,7 +43,7 @@ def domain_edit(domain_name):
form.name.validators = []
if form.validate_on_submit():
form.populate_obj(domain)
db.session.commit()
models.db.session.commit()
flask.flash('Domain %s saved' % domain)
return flask.redirect(flask.url_for('.domain_list'))
return flask.render_template('domain/edit.html', form=form,
@ -54,8 +55,8 @@ def domain_edit(domain_name):
@access.confirmation_required("delete {domain_name}")
def domain_delete(domain_name):
domain = models.Domain.query.get(domain_name) or flask.abort(404)
db.session.delete(domain)
db.session.commit()
models.db.session.delete(domain)
models.db.session.commit()
flask.flash('Domain %s deleted' % domain)
return flask.redirect(flask.url_for('.domain_list'))
@ -99,7 +100,7 @@ def domain_signup(domain_name=None):
domain.max_users = 10
domain.max_aliases = 10
if domain.check_mx():
db.session.add(domain)
models.db.session.add(domain)
if flask_login.current_user.is_authenticated:
user = models.User.query.get(flask_login.current_user.email)
else:
@ -108,9 +109,9 @@ def domain_signup(domain_name=None):
form.populate_obj(user)
user.set_password(form.pw.data)
user.quota_bytes = domain.max_quota_bytes
db.session.add(user)
models.db.session.add(user)
domain.managers.append(user)
db.session.commit()
models.db.session.commit()
flask.flash('Domain %s created' % domain)
return flask.redirect(flask.url_for('.domain_list'))
else:

@ -1,4 +1,4 @@
from mailu import db, models
from mailu import models
from mailu.ui import ui, forms, access
import flask
@ -24,8 +24,8 @@ def fetch_create(user_email):
if form.validate_on_submit():
fetch = models.Fetch(user=user)
form.populate_obj(fetch)
db.session.add(fetch)
db.session.commit()
models.db.session.add(fetch)
models.db.session.commit()
flask.flash('Fetch configuration created')
return flask.redirect(
flask.url_for('.fetch_list', user_email=user.email))
@ -39,7 +39,7 @@ def fetch_edit(fetch_id):
form = forms.FetchForm(obj=fetch)
if form.validate_on_submit():
form.populate_obj(fetch)
db.session.commit()
models.db.session.commit()
flask.flash('Fetch configuration updated')
return flask.redirect(
flask.url_for('.fetch_list', user_email=fetch.user.email))
@ -53,8 +53,8 @@ def fetch_edit(fetch_id):
def fetch_delete(fetch_id):
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
user = fetch.user
db.session.delete(fetch)
db.session.commit()
models.db.session.delete(fetch)
models.db.session.commit()
flask.flash('Fetch configuration delete')
return flask.redirect(
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
import flask
@ -30,7 +30,7 @@ def manager_create(domain_name):
flask.flash('User %s is already manager' % user, 'error')
else:
domain.managers.append(user)
db.session.commit()
models.db.session.commit()
flask.flash('User %s can now manage %s' % (user, domain.name))
return flask.redirect(
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)
if user in domain.managers:
domain.managers.remove(user)
db.session.commit()
models.db.session.commit()
flask.flash('User %s can no longer manager %s' % (user, domain))
else:
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
import flask
@ -25,8 +25,8 @@ def relay_create():
else:
relay = models.Relay()
form.populate_obj(relay)
db.session.add(relay)
db.session.commit()
models.db.session.add(relay)
models.db.session.commit()
flask.flash('Relayed domain %s created' % relay)
return flask.redirect(flask.url_for('.relay_list'))
return flask.render_template('relay/create.html', form=form)
@ -41,7 +41,7 @@ def relay_edit(relay_name):
form.name.validators = []
if form.validate_on_submit():
form.populate_obj(relay)
db.session.commit()
models.db.session.commit()
flask.flash('Relayed domain %s saved' % relay)
return flask.redirect(flask.url_for('.relay_list'))
return flask.render_template('relay/edit.html', form=form,
@ -53,8 +53,8 @@ def relay_edit(relay_name):
@access.confirmation_required("delete {relay_name}")
def relay_delete(relay_name):
relay = models.Relay.query.get(relay_name) or flask.abort(404)
db.session.delete(relay)
db.session.commit()
models.db.session.delete(relay)
models.db.session.commit()
flask.flash('Relayed domain %s deleted' % relay)
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 passlib import pwd
@ -32,8 +32,8 @@ def token_create(user_email):
token = models.Token(user=user)
token.set_password(form.raw_password.data)
form.populate_obj(token)
db.session.add(token)
db.session.commit()
models.db.session.add(token)
models.db.session.commit()
flask.flash('Authentication token created')
return flask.redirect(
flask.url_for('.token_list', user_email=user.email))
@ -46,8 +46,8 @@ def token_create(user_email):
def token_delete(token_id):
token = models.Token.query.get(token_id) or flask.abort(404)
user = token.user
db.session.delete(token)
db.session.commit()
models.db.session.delete(token)
models.db.session.commit()
flask.flash('Authentication token deleted')
return flask.redirect(
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 flask import current_app as app
import flask
import flask_login
@ -33,8 +34,8 @@ def user_create(domain_name):
user = models.User(domain=domain)
form.populate_obj(user)
user.set_password(form.pw.data)
db.session.add(user)
db.session.commit()
models.db.session.add(user)
models.db.session.commit()
user.send_welcome()
flask.flash('User %s created' % user)
return flask.redirect(
@ -63,7 +64,7 @@ def user_edit(user_email):
form.populate_obj(user)
if form.pw.data:
user.set_password(form.pw.data)
db.session.commit()
models.db.session.commit()
flask.flash('User %s updated' % user)
return flask.redirect(
flask.url_for('.user_list', domain_name=user.domain.name))
@ -77,8 +78,8 @@ def user_edit(user_email):
def user_delete(user_email):
user = models.User.query.get(user_email) or flask.abort(404)
domain = user.domain
db.session.delete(user)
db.session.commit()
models.db.session.delete(user)
models.db.session.commit()
flask.flash('User %s deleted' % user)
return flask.redirect(
flask.url_for('.user_list', domain_name=domain.name))
@ -93,7 +94,7 @@ def user_settings(user_email):
form = forms.UserSettingsForm(obj=user)
if form.validate_on_submit():
form.populate_obj(user)
db.session.commit()
models.db.session.commit()
flask.flash('Settings updated for %s' % user)
if user_email:
return flask.redirect(
@ -113,7 +114,7 @@ def user_password(user_email):
flask.flash('Passwords do not match', 'error')
else:
user.set_password(form.pw.data)
db.session.commit()
models.db.session.commit()
flask.flash('Password updated for %s' % user)
if user_email:
return flask.redirect(flask.url_for('.user_list',
@ -130,7 +131,7 @@ def user_forward(user_email):
form = forms.UserForwardForm(obj=user)
if form.validate_on_submit():
form.populate_obj(user)
db.session.commit()
models.db.session.commit()
flask.flash('Forward destination updated for %s' % user)
if user_email:
return flask.redirect(
@ -147,7 +148,7 @@ def user_reply(user_email):
form = forms.UserReplyForm(obj=user)
if form.validate_on_submit():
form.populate_obj(user)
db.session.commit()
models.db.session.commit()
flask.flash('Auto-reply message updated for %s' % user)
if user_email:
return flask.redirect(
@ -179,8 +180,8 @@ def user_signup(domain_name=None):
form.populate_obj(user)
user.set_password(form.pw.data)
user.quota_bytes = quota_bytes
db.session.add(user)
db.session.commit()
models.db.session.add(user)
models.db.session.commit()
user.send_welcome()
flask.flash('Successfully signed up %s' % user)
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