2037: update python dependencies of admin container r=mergify[bot] a=ghostwheel42

## What type of PR?

updates python dependencies of admin container

## What does this PR do?

### Related issue(s)

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

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


Co-authored-by: Alexander Graf <ghostwheel42@users.noreply.github.com>
master
bors[bot] 3 years ago committed by GitHub
commit 1675399047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,7 +27,7 @@ ENV TZ Etc/UTC
# python3 shared with most images # python3 shared with most images
RUN set -eu \ RUN set -eu \
&& apk add --no-cache python3 py3-pip git bash tzdata \ && apk add --no-cache python3 py3-pip py3-wheel git bash tzdata \
&& pip3 install --upgrade pip && pip3 install --upgrade pip
RUN mkdir -p /app RUN mkdir -p /app
@ -37,13 +37,15 @@ COPY requirements-prod.txt requirements.txt
RUN set -eu \ RUN set -eu \
&& apk add --no-cache libressl curl postgresql-libs mariadb-connector-c \ && apk add --no-cache libressl curl postgresql-libs mariadb-connector-c \
&& apk add --no-cache --virtual build-dep libressl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \ && apk add --no-cache --virtual build-dep libressl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \
&& pip3 install -r requirements.txt \ && pip install --upgrade pip \
&& pip install -r requirements.txt \
&& apk del --no-cache build-dep && apk del --no-cache build-dep
COPY --from=assets static ./mailu/static COPY --from=assets static ./mailu/static
COPY mailu ./mailu COPY mailu ./mailu
COPY migrations ./migrations COPY migrations ./migrations
COPY start.py /start.py COPY start.py /start.py
COPY audit.py /audit.py
RUN pybabel compile -d mailu/translations RUN pybabel compile -d mailu/translations

@ -52,3 +52,8 @@ fieldset:disabled .form-control:disabled {
.select2-container--default .select2-selection--multiple .select2-selection__choice { .select2-container--default .select2-selection--multiple .select2-selection__choice {
color: black; color: black;
} }
/* range input spacing */
.input-group-text {
margin-right: 1em;
}

@ -18,7 +18,7 @@ $('document').ready(function() {
$.post({ $.post({
url: $(this).attr('href'), url: $(this).attr('href'),
success: function() { success: function() {
location.reload(); window.location = window.location.href;
}, },
}); });
}); });
@ -28,10 +28,10 @@ $('document').ready(function() {
var fieldset = $(this).parents('fieldset'); var fieldset = $(this).parents('fieldset');
if (this.checked) { if (this.checked) {
fieldset.removeAttr('disabled'); fieldset.removeAttr('disabled');
fieldset.find('input').not(this).removeAttr('disabled'); fieldset.find('input,textarea').not(this).removeAttr('disabled');
} else { } else {
fieldset.attr('disabled', ''); fieldset.attr('disabled', '');
fieldset.find('input').not(this).attr('disabled', ''); fieldset.find('input,textarea').not(this).attr('disabled', '');
} }
}); });
@ -43,7 +43,9 @@ $('document').ready(function() {
var infinity = $(this).data('infinity'); var infinity = $(this).data('infinity');
var step = $(this).attr('step'); var step = $(this).attr('step');
$(this).on('input', function() { $(this).on('input', function() {
value_element.text((infinity && this.value == 0) ? '∞' : (this.value/step).toFixed(2)); var num = (infinity && this.value == 0) ? '∞' : (this.value/step).toFixed(2);
if (num.endsWith('.00')) num = num.substr(0, num.length - 3);
value_element.text(num);
}).trigger('input'); }).trigger('input');
} }
}); });

@ -1,14 +1,19 @@
from mailu import app #!/usr/bin/python3
import sys import sys
import tabulate import tabulate
sys.path[0:0] = ['/app']
import mailu
app = mailu.create_app()
# Known endpoints without permissions # Known endpoints without permissions
known_missing_permissions = [ known_missing_permissions = [
"index", 'index',
"static", "bootstrap.static", 'static', 'bootstrap.static',
"admin.static", "admin.login" 'admin.static', 'admin.login'
] ]
@ -16,7 +21,7 @@ known_missing_permissions = [
missing_permissions = [] missing_permissions = []
permissions = {} permissions = {}
for endpoint, function in app.view_functions.items(): for endpoint, function in app.view_functions.items():
audit = function.__dict__.get("_audit_permissions") audit = function.__dict__.get('_audit_permissions')
if audit: if audit:
handler, args = audit handler, args = audit
if args: if args:
@ -28,16 +33,15 @@ for endpoint, function in app.view_functions.items():
elif endpoint not in known_missing_permissions: elif endpoint not in known_missing_permissions:
missing_permissions.append(endpoint) missing_permissions.append(endpoint)
# Fail if any endpoint is missing a permission check
if missing_permissions:
print("The following endpoints are missing permission checks:")
print(missing_permissions.join(","))
sys.exit(1)
# Display the permissions table # Display the permissions table
print(tabulate.tabulate([ print(tabulate.tabulate([
[route, *permissions[route.endpoint]] [route, *permissions[route.endpoint]]
for route in app.url_map.iter_rules() if route.endpoint in permissions for route in app.url_map.iter_rules() if route.endpoint in permissions
])) ]))
# Warn if any endpoint is missing a permission check
if missing_permissions:
print()
print('The following endpoints are missing permission checks:')
print(','.join(missing_permissions))

@ -33,7 +33,7 @@ def create_app_from_config(config):
app.srs_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('SRS_KEY', 'utf-8'), 'sha256').digest() app.srs_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('SRS_KEY', 'utf-8'), 'sha256').digest()
# Initialize list of translations # Initialize list of translations
config.translations = { app.config.translations = {
str(locale): locale str(locale): locale
for locale in sorted( for locale in sorted(
utils.babel.list_translations(), utils.babel.list_translations(),
@ -57,6 +57,15 @@ def create_app_from_config(config):
config = app.config, config = app.config,
) )
# Jinja filters
@app.template_filter()
def format_date(value):
return utils.flask_babel.format_date(value) if value else ''
@app.template_filter()
def format_datetime(value):
return utils.flask_babel.format_datetime(value) if value else ''
# Import views # Import views
from mailu import ui, internal, sso from mailu import ui, internal, sso
app.register_blueprint(ui.ui, url_prefix=app.config['WEB_ADMIN']) app.register_blueprint(ui.ui, url_prefix=app.config['WEB_ADMIN'])

@ -90,7 +90,7 @@ DEFAULT_CONFIG = {
'POD_ADDRESS_RANGE': None 'POD_ADDRESS_RANGE': None
} }
class ConfigManager(dict): class ConfigManager:
""" Naive configuration manager that uses environment only """ Naive configuration manager that uses environment only
""" """
@ -105,19 +105,16 @@ class ConfigManager(dict):
def get_host_address(self, name): def get_host_address(self, name):
# if MYSERVICE_ADDRESS is defined, use this # if MYSERVICE_ADDRESS is defined, use this
if '{}_ADDRESS'.format(name) in os.environ: if f'{name}_ADDRESS' in os.environ:
return os.environ.get('{}_ADDRESS'.format(name)) return os.environ.get(f'{name}_ADDRESS')
# otherwise use the host name and resolve it # otherwise use the host name and resolve it
return system.resolve_address(self.config['HOST_{}'.format(name)]) return system.resolve_address(self.config[f'HOST_{name}'])
def resolve_hosts(self): def resolve_hosts(self):
self.config["IMAP_ADDRESS"] = self.get_host_address("IMAP") for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']:
self.config["POP3_ADDRESS"] = self.get_host_address("POP3") self.config[f'{key}_ADDRESS'] = self.get_host_address(key)
self.config["AUTHSMTP_ADDRESS"] = self.get_host_address("AUTHSMTP") if self.config['WEBMAIL'] != 'none':
self.config["SMTP_ADDRESS"] = self.get_host_address("SMTP") self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL')
self.config["REDIS_ADDRESS"] = self.get_host_address("REDIS")
if self.config["WEBMAIL"] != "none":
self.config["WEBMAIL_ADDRESS"] = self.get_host_address("WEBMAIL")
def __get_env(self, key, value): def __get_env(self, key, value):
key_file = key + "_FILE" key_file = key + "_FILE"
@ -136,6 +133,7 @@ class ConfigManager(dict):
return value return value
def init_app(self, app): def init_app(self, app):
# get current app config
self.config.update(app.config) self.config.update(app.config)
# get environment variables # get environment variables
self.config.update({ self.config.update({
@ -149,9 +147,9 @@ class ConfigManager(dict):
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']] template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config) self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
self.config['RATELIMIT_STORAGE_URL'] = 'redis://{0}/2'.format(self.config['REDIS_ADDRESS']) self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
self.config['QUOTA_STORAGE_URL'] = 'redis://{0}/1'.format(self.config['REDIS_ADDRESS']) self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/1'
self.config['SESSION_STORAGE_URL'] = 'redis://{0}/3'.format(self.config['REDIS_ADDRESS']) self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict' self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
self.config['SESSION_COOKIE_HTTPONLY'] = True self.config['SESSION_COOKIE_HTTPONLY'] = True
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME'])) self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
@ -160,25 +158,7 @@ class ConfigManager(dict):
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s]) self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
self.config['HOSTNAMES'] = ','.join(hostnames) self.config['HOSTNAMES'] = ','.join(hostnames)
self.config['HOSTNAME'] = hostnames[0] self.config['HOSTNAME'] = hostnames[0]
# update the app config itself
app.config = self
def setdefault(self, key, value): # update the app config
if key not in self.config: app.config.update(self.config)
self.config[key] = value
return self.config[key]
def get(self, *args):
return self.config.get(*args)
def keys(self):
return self.config.keys()
def __getitem__(self, key):
return self.config.get(key)
def __setitem__(self, key, value):
self.config[key] = value
def __contains__(self, key):
return key in self.config

@ -1,6 +1,6 @@
import flask_debugtoolbar import flask_debugtoolbar
from werkzeug.contrib import profiler as werkzeug_profiler from werkzeug.middleware.profiler import ProfilerMiddleware
# Debugging toolbar # Debugging toolbar
@ -10,7 +10,7 @@ toolbar = flask_debugtoolbar.DebugToolbarExtension()
# Profiler # Profiler
class Profiler(object): class Profiler(object):
def init_app(self, app): def init_app(self, app):
app.wsgi_app = werkzeug_profiler.ProfilerMiddleware( app.wsgi_app = ProfilerMiddleware(
app.wsgi_app, restrictions=[30] app.wsgi_app, restrictions=[30]
) )

@ -19,7 +19,8 @@ import os
import hmac import hmac
import smtplib import smtplib
import idna import idna
import dns import dns.resolver
import dns.exception
from flask import current_app as app from flask import current_app as app
from sqlalchemy.ext import declarative from sqlalchemy.ext import declarative
@ -38,6 +39,8 @@ class IdnaDomain(db.TypeDecorator):
""" """
impl = db.String(80) impl = db.String(80)
cache_ok = True
python_type = str
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
""" encode unicode domain name to punycode """ """ encode unicode domain name to punycode """
@ -47,13 +50,13 @@ class IdnaDomain(db.TypeDecorator):
""" decode punycode domain name to unicode """ """ decode punycode domain name to unicode """
return idna.decode(value) return idna.decode(value)
python_type = str
class IdnaEmail(db.TypeDecorator): class IdnaEmail(db.TypeDecorator):
""" Stores a Unicode string in it's IDNA representation (ASCII only) """ Stores a Unicode string in it's IDNA representation (ASCII only)
""" """
impl = db.String(255) impl = db.String(255)
cache_ok = True
python_type = str
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
""" encode unicode domain part of email address to punycode """ """ encode unicode domain part of email address to punycode """
@ -69,13 +72,13 @@ class IdnaEmail(db.TypeDecorator):
localpart, domain_name = value.rsplit('@', 1) localpart, domain_name = value.rsplit('@', 1)
return f'{localpart}@{idna.decode(domain_name)}' return f'{localpart}@{idna.decode(domain_name)}'
python_type = str
class CommaSeparatedList(db.TypeDecorator): class CommaSeparatedList(db.TypeDecorator):
""" Stores a list as a comma-separated string, compatible with Postfix. """ Stores a list as a comma-separated string, compatible with Postfix.
""" """
impl = db.String impl = db.String
cache_ok = True
python_type = list
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
""" join list of items to comma separated string """ """ join list of items to comma separated string """
@ -90,13 +93,13 @@ class CommaSeparatedList(db.TypeDecorator):
""" split comma separated string to list """ """ split comma separated string to list """
return list(filter(bool, (item.strip() for item in value.split(',')))) if value else [] return list(filter(bool, (item.strip() for item in value.split(',')))) if value else []
python_type = list
class JSONEncoded(db.TypeDecorator): class JSONEncoded(db.TypeDecorator):
""" Represents an immutable structure as a json-encoded string. """ Represents an immutable structure as a json-encoded string.
""" """
impl = db.String impl = db.String
cache_ok = True
python_type = str
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
""" encode data as json """ """ encode data as json """
@ -106,8 +109,6 @@ class JSONEncoded(db.TypeDecorator):
""" decode json to data """ """ decode json to data """
return json.loads(value) if value else None return json.loads(value) if value else None
python_type = str
class Base(db.Model): class Base(db.Model):
""" Base class for all models """ Base class for all models
""" """

@ -145,6 +145,11 @@ class Logger:
if history.has_changes() and history.deleted: if history.has_changes() and history.deleted:
before = history.deleted[-1] before = history.deleted[-1]
after = getattr(target, attr.key) after = getattr(target, attr.key)
# we don't have ordered lists
if isinstance(before, list):
before = set(before)
if isinstance(after, list):
after = set(after)
# TODO: this can be removed when comment is not nullable in model # TODO: this can be removed when comment is not nullable in model
if attr.key == 'comment' and not before and not after: if attr.key == 'comment' and not before and not after:
pass pass

@ -5,7 +5,7 @@
<form class="form" method="post" role="form"> <form class="form" method="post" role="form">
{{ macros.form_field(form.email) }} {{ macros.form_field(form.email) }}
{{ macros.form_field(form.pw) }} {{ macros.form_field(form.pw) }}
{{ macros.form_fields(fields, label=False, class="btn btn-default", spacing=False) }} {{ macros.form_fields(fields, label=False, class="btn btn-default") }}
</form> </form>
{%- endcall %} {%- endcall %}
{%- endblock %} {%- endblock %}

@ -19,6 +19,7 @@ def login():
fields.append(form.submitAdmin) fields.append(form.submitAdmin)
if str(app.config["WEBMAIL"]).upper() != "NONE": if str(app.config["WEBMAIL"]).upper() != "NONE":
fields.append(form.submitWebmail) fields.append(form.submitWebmail)
fields = [fields]
if form.validate_on_submit(): if form.validate_on_submit():
if form.submitAdmin.data: if form.submitAdmin.data:

@ -34,8 +34,8 @@
<td>{{ alias }}</td> <td>{{ alias }}</td>
<td>{{ alias.destination|join(', ') or '-' }}</td> <td>{{ alias.destination|join(', ') or '-' }}</td>
<td>{{ alias.comment or '' }}</td> <td>{{ alias.comment or '' }}</td>
<td>{{ alias.created_at }}</td> <td>{{ alias.created_at | format_date }}</td>
<td>{{ alias.updated_at or '' }}</td> <td>{{ alias.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -19,6 +19,7 @@
<th>{% trans %}Actions{% endtrans %}</th> <th>{% trans %}Actions{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th> <th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th> <th>{% trans %}Created{% endtrans %}</th>
<th>{% trans %}Last edit{% endtrans %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -28,7 +29,8 @@
<a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a> <a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
</td> </td>
<td>{{ alternative }}</td> <td>{{ alternative }}</td>
<td>{{ alternative.created_at }}</td> <td>{{ alternative.created_at | format_date }}</td>
<td>{{ alternative.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -46,8 +46,8 @@
<td>{{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }}</td> <td>{{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }}</td>
<td>{{ domain.aliases | count }} / {{ '∞' if domain.max_aliases == -1 else domain.max_aliases }}</td> <td>{{ domain.aliases | count }} / {{ '∞' if domain.max_aliases == -1 else domain.max_aliases }}</td>
<td>{{ domain.comment or '' }}</td> <td>{{ domain.comment or '' }}</td>
<td>{{ domain.created_at }}</td> <td>{{ domain.created_at | format_date }}</td>
<td>{{ domain.updated_at or '' }}</td> <td>{{ domain.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -36,10 +36,10 @@
<td>{{ fetch.protocol }}{{ 's' if fetch.tls else '' }}://{{ fetch.host }}:{{ fetch.port }}</td> <td>{{ fetch.protocol }}{{ 's' if fetch.tls else '' }}://{{ fetch.host }}:{{ fetch.port }}</td>
<td>{{ fetch.username }}</td> <td>{{ fetch.username }}</td>
<td>{% if fetch.keep %}{% trans %}yes{% endtrans %}{% else %}{% trans %}no{% endtrans %}{% endif %}</td> <td>{% if fetch.keep %}{% trans %}yes{% endtrans %}{% else %}{% trans %}no{% endtrans %}{% endif %}</td>
<td>{{ fetch.last_check or '-' }}</td> <td>{{ fetch.last_check | format_datetime or '-' }}</td>
<td>{{ fetch.error or '-' }}</td> <td>{{ fetch.error or '-' }}</td>
<td>{{ fetch.created_at }}</td> <td>{{ fetch.created_at | format_date }}</td>
<td>{{ fetch.updated_at or '' }}</td> <td>{{ fetch.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -18,17 +18,19 @@
{%- endif %} {%- endif %}
{%- endmacro %} {%- endmacro %}
{%- macro form_fields(fields, prepend='', append='', label=True, spacing=True) %} {%- macro form_fields(fields, prepend='', append='', label=True) %}
{%- if spacing %}
{%- set width = (12 / fields|length)|int %} {%- set width = (12 / fields|length)|int %}
{%- else %}
{%- set width = 0 %}
{% endif %}
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
{%- for field in fields %} {%- for field in fields %}
<div class="col-lg-{{ width }} col-xs-12 {{ 'has-error' if field.errors else '' }}"> <div class="col-lg-{{ width }} col-xs-12 {{ 'has-error' if field.errors else '' }}">
{{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }} {%- if field.__class__.__name__ == 'list' %}
{%- for subfield in field %}
{{ form_individual_field(subfield, prepend=prepend, append=append, label=label, **kwargs) }}
{%- endfor %}
{%- else %}
{{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }}
{%- endif %}
</div> </div>
{%- endfor %} {%- endfor %}
</div> </div>

@ -32,8 +32,8 @@
<td>{{ relay.name }}</td> <td>{{ relay.name }}</td>
<td>{{ relay.smtp or '-' }}</td> <td>{{ relay.smtp or '-' }}</td>
<td>{{ relay.comment or '' }}</td> <td>{{ relay.comment or '' }}</td>
<td>{{ relay.created_at }}</td> <td>{{ relay.created_at | format_date }}</td>
<td>{{ relay.updated_at or '' }}</td> <td>{{ relay.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -20,6 +20,7 @@
<th>{% trans %}Comment{% endtrans %}</th> <th>{% trans %}Comment{% endtrans %}</th>
<th>{% trans %}Authorized IP{% endtrans %}</th> <th>{% trans %}Authorized IP{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th> <th>{% trans %}Created{% endtrans %}</th>
<th>{% trans %}Last edit{% endtrans %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -30,7 +31,8 @@
</td> </td>
<td>{{ token.comment }}</td> <td>{{ token.comment }}</td>
<td>{{ token.ip or "any" }}</td> <td>{{ token.ip or "any" }}</td>
<td>{{ token.created_at }}</td> <td>{{ token.created_at | format_date }}</td>
<td>{{ token.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -45,8 +45,8 @@
</td> </td>
<td>{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td> <td>{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td>
<td>{{ user.comment or '-' }}</td> <td>{{ user.comment or '-' }}</td>
<td>{{ user.created_at }}</td> <td>{{ user.created_at | format_date }}</td>
<td>{{ user.updated_at or '' }}</td> <td>{{ user.updated_at | format_date }}</td>
</tr> </tr>
{%- endfor %} {%- endfor %}
</tbody> </tbody>

@ -5,7 +5,6 @@ from flask import current_app as app
import flask import flask
import flask_login import flask_login
import wtforms_components import wtforms_components
import dns.resolver
@ui.route('/domain', methods=['GET']) @ui.route('/domain', methods=['GET'])

@ -6,18 +6,21 @@ try:
except ImportError: except ImportError:
import pickle import pickle
import dns
import dns.resolver import dns.resolver
import dns.exception
import dns.flags
import dns.rdtypes
import dns.rdatatype
import dns.rdataclass
import hmac import hmac
import secrets import secrets
import time import time
from multiprocessing import Value from multiprocessing import Value
from mailu import limiter from mailu import limiter
from flask import current_app as app from flask import current_app as app
import flask import flask
import flask_login import flask_login
import flask_migrate import flask_migrate
@ -28,7 +31,7 @@ import redis
from flask.sessions import SessionMixin, SessionInterface from flask.sessions import SessionMixin, SessionInterface
from itsdangerous.encoding import want_bytes from itsdangerous.encoding import want_bytes
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from werkzeug.contrib import fixers from werkzeug.middleware.proxy_fix import ProxyFix
# Login configuration # Login configuration
login = flask_login.LoginManager() login = flask_login.LoginManager()
@ -106,7 +109,7 @@ class PrefixMiddleware(object):
return self.app(environ, start_response) return self.app(environ, start_response)
def init_app(self, app): def init_app(self, app):
self.app = fixers.ProxyFix(app.wsgi_app, x_for=1, x_proto=1) self.app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
app.wsgi_app = self app.wsgi_app = self
proxy = PrefixMiddleware() proxy = PrefixMiddleware()
@ -265,7 +268,7 @@ class MailuSession(CallbackDict, SessionMixin):
# set uid from dict data # set uid from dict data
if self._uid is None: if self._uid is None:
self._uid = self.app.session_config.gen_uid(self.get('user_id', '')) self._uid = self.app.session_config.gen_uid(self.get('_user_id', ''))
# create new session id for new or regenerated sessions and force setting the cookie # create new session id for new or regenerated sessions and force setting the cookie
if self._sid is None: if self._sid is None:

@ -1,10 +1,12 @@
from __future__ import with_statement import logging
import tenacity
from alembic import context from alembic import context
from sqlalchemy import engine_from_config, pool from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig from logging.config import fileConfig
import logging
import tenacity from flask import current_app
from tenacity import retry from mailu import models
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@ -17,20 +19,12 @@ logger = logging.getLogger('alembic.env')
# add your model's MetaData object here # add your model's MetaData object here
# for 'autogenerate' support # for 'autogenerate' support
# from myapp import mymodel config.set_main_option(
# target_metadata = mymodel.Base.metadata 'sqlalchemy.url',
from flask import current_app current_app.config.get('SQLALCHEMY_DATABASE_URI')
config.set_main_option('sqlalchemy.url', )
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
#target_metadata = current_app.extensions['migrate'].db.metadata
from mailu import models
target_metadata = models.Base.metadata target_metadata = models.Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline(): def run_migrations_offline():
"""Run migrations in 'offline' mode. """Run migrations in 'offline' mode.
@ -44,7 +38,7 @@ def run_migrations_offline():
script output. script output.
""" """
url = config.get_main_option("sqlalchemy.url") url = config.get_main_option('sqlalchemy.url')
context.configure(url=url) context.configure(url=url)
with context.begin_transaction(): with context.begin_transaction():
@ -69,28 +63,35 @@ def run_migrations_online():
directives[:] = [] directives[:] = []
logger.info('No changes in schema detected.') logger.info('No changes in schema detected.')
engine = engine_from_config(config.get_section(config.config_ini_section), engine = engine_from_config(
prefix='sqlalchemy.', config.get_section(config.config_ini_section),
poolclass=pool.NullPool) prefix = 'sqlalchemy.',
poolclass = pool.NullPool
)
connection = tenacity.Retrying( @tenacity.retry(
stop=tenacity.stop_after_attempt(100), stop = tenacity.stop_after_attempt(100),
wait=tenacity.wait_random(min=2, max=5), wait = tenacity.wait_random(min=2, max=5),
before=tenacity.before_log(logging.getLogger("tenacity.retry"), logging.DEBUG), before = tenacity.before_log(logging.getLogger('tenacity.retry'), logging.DEBUG),
before_sleep=tenacity.before_sleep_log(logging.getLogger("tenacity.retry"), logging.INFO), before_sleep = tenacity.before_sleep_log(logging.getLogger('tenacity.retry'), logging.INFO),
after=tenacity.after_log(logging.getLogger("tenacity.retry"), logging.DEBUG) after = tenacity.after_log(logging.getLogger('tenacity.retry'), logging.DEBUG)
).call(engine.connect) )
def try_connect(db):
return db.connect()
context.configure(connection=connection, with try_connect(engine) as connection:
target_metadata=target_metadata,
process_revision_directives=process_revision_directives, context.configure(
**current_app.extensions['migrate'].configure_args) connection = connection,
target_metadata = target_metadata,
process_revision_directives = process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
try:
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()
finally:
connection.close() connection.close()
if context.is_offline_mode(): if context.is_offline_mode():
run_migrations_offline() run_migrations_offline()

@ -1,56 +1,75 @@
alembic==1.0.10 alembic==1.7.4
asn1crypto==0.24.0 appdirs==1.4.4
Babel==2.6.0 Babel==2.9.1
bcrypt==3.1.6 bcrypt==3.2.0
blinker==1.4 blinker==1.4
cffi==1.12.3 CacheControl==0.12.9
Click==7.0 certifi==2021.10.8
cryptography==3.4.7 cffi==1.15.0
decorator==4.4.0 chardet==4.0.0
dnspython==1.16.0 click==8.0.3
dominate==2.3.5 colorama==0.4.4
Flask==1.0.2 contextlib2==21.6.0
Flask-Babel==0.12.2 cryptography==35.0.0
decorator==5.1.0
# distlib==0.3.1
# distro==1.5.0
dnspython==2.1.0
dominate==2.6.0
email-validator==1.1.3
Flask==2.0.2
Flask-Babel==2.0.0
Flask-Bootstrap==3.3.7.1 Flask-Bootstrap==3.3.7.1
Flask-DebugToolbar==0.10.1 Flask-DebugToolbar==0.11.0
Flask-Limiter==1.0.1 Flask-Limiter==1.4
Flask-Login==0.4.1 Flask-Login==0.5.0
flask-marshmallow==0.14.0 flask-marshmallow==0.14.0
Flask-Migrate==2.4.0 Flask-Migrate==3.1.0
Flask-Script==2.0.6 Flask-Script==2.0.6
Flask-SQLAlchemy==2.4.0 Flask-SQLAlchemy==2.5.1
Flask-WTF==0.14.2 Flask-WTF==0.15.1
greenlet==1.1.2
gunicorn==20.1.0 gunicorn==20.1.0
idna==2.8 html5lib==1.1
infinity==1.4 idna==3.3
intervals==0.8.1 infinity==1.5
itsdangerous==1.1.0 intervals==0.9.2
Jinja2==2.11.3 itsdangerous==2.0.1
limits==1.3 Jinja2==3.0.2
Mako==1.0.9 limits==1.5.1
MarkupSafe==1.1.1 lockfile==0.12.2
mysqlclient==1.4.2.post1 Mako==1.1.5
marshmallow==3.10.0 MarkupSafe==2.0.1
marshmallow-sqlalchemy==0.24.1 marshmallow==3.14.0
marshmallow-sqlalchemy==0.26.1
msgpack==1.0.2
mysqlclient==2.0.3
ordered-set==4.0.2
# packaging==20.9
passlib==1.7.4 passlib==1.7.4
psycopg2==2.8.2 # pep517==0.10.0
pycparser==2.19 progress==1.6
Pygments==2.8.1 psycopg2==2.9.1
pyOpenSSL==20.0.1 pycparser==2.20
python-dateutil==2.8.0 Pygments==2.10.0
python-editor==1.0.4 pyOpenSSL==21.0.0
pytz==2019.1 pyparsing==3.0.4
PyYAML==5.4.1 pytz==2021.3
redis==3.2.1 PyYAML==6.0
#alpine3:12 provides six==1.15.0 redis==3.5.3
#six==1.12.0 requests==2.26.0
socrate==0.1.1 retrying==1.3.3
SQLAlchemy==1.3.3 # six==1.15.0
socrate==0.2.0
SQLAlchemy==1.4.26
srslib==0.1.4 srslib==0.1.4
tabulate==0.8.3 tabulate==0.8.9
tenacity==5.0.4 tenacity==8.0.1
validators==0.12.6 toml==0.10.2
urllib3==1.26.7
validators==0.18.2
visitor==0.1.3 visitor==0.1.3
Werkzeug==0.15.5 webencodings==0.5.1
WTForms==2.2.1 Werkzeug==2.0.2
WTForms-Components==0.10.4 WTForms==2.3.3
WTForms-Components==0.10.5

@ -18,10 +18,8 @@ PyYAML
PyOpenSSL PyOpenSSL
Pygments Pygments
dnspython dnspython
bcrypt
tenacity tenacity
mysqlclient mysqlclient
psycopg2
idna idna
srslib srslib
marshmallow marshmallow

Loading…
Cancel
Save