diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000..7195e58e --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,10 @@ +rules: + default: null + branches: + master: + protection: + required_status_checks: + contexts: + - continuous-integration/travis-ci + required_pull_request_reviews: + required_approving_review_count: 2 diff --git a/.travis.yml b/.travis.yml index d5114c0d..c3a19529 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,15 @@ env: - VERSION=$TRAVIS_BRANCH script: -- docker-compose -f tests/build.yml -p Mailu build + # Default to mailu for DOCKER_ORG + - if [ -z "$DOCKER_ORG" ]; then export DOCKER_ORG="mailu"; fi + - docker-compose -f tests/build.yml build + - tests/compose/test-script.sh + +deploy: + provider: script + script: bash tests/deploy.sh + on: + all_branches: true + condition: -n $DOCKER_UN + diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index 0adc626c..08de0e88 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -17,5 +17,6 @@ COPY start.sh /start.sh RUN pybabel compile -d mailu/translations EXPOSE 80/tcp +VOLUME ["/data"] CMD ["/start.sh"] diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index a3f0353c..d77420e6 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -11,7 +11,6 @@ import os import docker import socket import uuid -import redis from werkzeug.contrib import fixers @@ -89,9 +88,6 @@ manager.add_command('db', flask_migrate.MigrateCommand) babel = flask_babel.Babel(app) translations = list(map(str, babel.list_translations())) -# Quota manager -quota = redis.Redis.from_url(app.config.get("QUOTA_STORAGE_URL")) - @babel.localeselector def get_locale(): return flask.request.accept_languages.best_match(translations) diff --git a/core/admin/mailu/internal/__init__.py b/core/admin/mailu/internal/__init__.py index 6419ad10..80a3c754 100644 --- a/core/admin/mailu/internal/__init__.py +++ b/core/admin/mailu/internal/__init__.py @@ -6,7 +6,8 @@ import socket import flask -internal = flask.Blueprint('internal', __name__) +internal = flask.Blueprint('internal', __name__, template_folder='templates') + @internal.app_errorhandler(RateLimitExceeded) def rate_limit_handler(e): @@ -17,6 +18,7 @@ def rate_limit_handler(e): response.headers['Auth-Wait'] = '3' return response + @limiter.request_filter def whitelist_webmail(): try: @@ -26,4 +28,4 @@ def whitelist_webmail(): return False -from mailu.internal import views +from mailu.internal.views import * diff --git a/core/dovecot/sieve/before.sieve b/core/admin/mailu/internal/templates/default.sieve similarity index 51% rename from core/dovecot/sieve/before.sieve rename to core/admin/mailu/internal/templates/default.sieve index 81d20f30..214b8125 100644 --- a/core/dovecot/sieve/before.sieve +++ b/core/admin/mailu/internal/templates/default.sieve @@ -8,8 +8,6 @@ require "regex"; require "relational"; require "date"; require "comparator-i;ascii-numeric"; -require "vnd.dovecot.extdata"; -require "vnd.dovecot.execute"; require "spamtestplus"; require "editheader"; require "index"; @@ -20,22 +18,20 @@ if header :index 2 :matches "Received" "from * by * for <*>; *" addheader "Delivered-To" "<${3}>"; } -if allof (string :is "${extdata.spam_enabled}" "1", - spamtest :percent :value "gt" :comparator "i;ascii-numeric" "${extdata.spam_threshold}") +{% if user.spam_enabled %} +if spamtest :percent :value "gt" :comparator "i;ascii-numeric" "{{ user.spam_threshold }}" { setflag "\\seen"; fileinto :create "Junk"; stop; } +{% endif %} if exists "X-Virus" { discard; stop; } -if allof (string :is "${extdata.reply_enabled}" "1", - currentdate :value "ge" "date" "${extdata.reply_startdate}", - currentdate :value "le" "date" "${extdata.reply_enddate}") -{ - vacation :days 1 :subject "${extdata.reply_subject}" "${extdata.reply_body}"; -} +{% if user.reply_active and %} +vacation :days 1 :subject "{{ user.reply_subject }}" "{{ user.reply_body }}"; +{% endif %} diff --git a/core/admin/mailu/internal/views/__init__.py b/core/admin/mailu/internal/views/__init__.py new file mode 100644 index 00000000..a32106c0 --- /dev/null +++ b/core/admin/mailu/internal/views/__init__.py @@ -0,0 +1,3 @@ +__all__ = [ + 'auth', 'postfix', 'dovecot', 'fetch' +] diff --git a/core/admin/mailu/internal/views.py b/core/admin/mailu/internal/views/auth.py similarity index 99% rename from core/admin/mailu/internal/views.py rename to core/admin/mailu/internal/views/auth.py index b97d329e..823fbd40 100644 --- a/core/admin/mailu/internal/views.py +++ b/core/admin/mailu/internal/views/auth.py @@ -4,7 +4,6 @@ from mailu.internal import internal, nginx import flask import flask_login import base64 -import urllib @internal.route("/auth/email") diff --git a/core/admin/mailu/internal/views/dovecot.py b/core/admin/mailu/internal/views/dovecot.py new file mode 100644 index 00000000..c2f53794 --- /dev/null +++ b/core/admin/mailu/internal/views/dovecot.py @@ -0,0 +1,40 @@ +from mailu import db, models +from mailu.internal import internal + +import flask + + +@internal.route("/dovecot/passdb/") +def dovecot_passdb_dict(user_email): + user = models.User.query.get(user_email) or flask.abort(404) + return flask.jsonify({ + "password": user.password, + }) + + +@internal.route("/dovecot/userdb/") +def dovecot_userdb_dict(user_email): + user = models.User.query.get(user_email) or flask.abort(404) + return flask.jsonify({ + "quota_rule": "*:bytes={}".format(user.quota_bytes) + }) + + +@internal.route("/dovecot/quota//", methods=["POST"]) +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() + return flask.jsonify(None) + + +@internal.route("/dovecot/sieve/name/