mirror of
https://github.com/Mailu/Mailu.git
synced 2024-12-12 10:45:38 +02:00
Process review comments (PR2464)
This commit is contained in:
parent
afb224e796
commit
61d092922c
@ -75,7 +75,7 @@ def create_app_from_config(config):
|
||||
app.register_blueprint(internal.internal, url_prefix='/internal')
|
||||
app.register_blueprint(sso.sso, url_prefix='/sso')
|
||||
if app.config.get('API_TOKEN'):
|
||||
api.register(app, web_api=app.config.get('WEB_API'))
|
||||
api.register(app, web_api_root=app.config.get('WEB_API'))
|
||||
return app
|
||||
|
||||
|
||||
|
@ -1,24 +1,20 @@
|
||||
from flask import redirect, url_for
|
||||
from flask_restx import apidoc
|
||||
from . import v1
|
||||
from . import v1 as APIv1
|
||||
|
||||
|
||||
def register(app, web_api):
|
||||
def register(app, web_api_root):
|
||||
|
||||
ACTIVE=v1
|
||||
ROOT=web_api
|
||||
v1.app = app
|
||||
APIv1.app = app
|
||||
# register api bluprint(s)
|
||||
apidoc.apidoc.url_prefix = f'{ROOT}/v{int(v1.VERSION)}'
|
||||
v1.api_token = app.config['API_TOKEN']
|
||||
app.register_blueprint(v1.blueprint, url_prefix=f'{ROOT}/v{int(v1.VERSION)}')
|
||||
|
||||
|
||||
apidoc.apidoc.url_prefix = f'{web_api_root}/v{int(APIv1.VERSION)}'
|
||||
APIv1.api_token = app.config['API_TOKEN']
|
||||
app.register_blueprint(APIv1.blueprint, url_prefix=f'{web_api_root}/v{int(APIv1.VERSION)}')
|
||||
|
||||
# add redirect to current api version
|
||||
@app.route(f'{ROOT}/')
|
||||
@app.route(f'{web_api_root}/')
|
||||
def redir():
|
||||
return redirect(url_for(f'{ACTIVE.blueprint.name}.root'))
|
||||
return redirect(url_for(f'{APIv1.blueprint.name}.root'))
|
||||
|
||||
# swagger ui config
|
||||
app.config.SWAGGER_UI_DOC_EXPANSION = 'list'
|
||||
|
@ -5,13 +5,15 @@ import flask
|
||||
import hmac
|
||||
from functools import wraps
|
||||
from flask_restx import abort
|
||||
from sqlalchemy.sql.expression import label
|
||||
|
||||
def fqdn_in_use(*names):
|
||||
for name in names:
|
||||
for model in models.Domain, models.Alternative, models.Relay:
|
||||
if model.query.get(name):
|
||||
return model
|
||||
return None
|
||||
def fqdn_in_use(name):
|
||||
d = models.db.session.query(label('name', models.Domain.name))
|
||||
a = models.db.session.query(label('name', models.Alternative.name))
|
||||
r = models.db.session.query(label('name', models.Relay.name))
|
||||
if d.union_all(a).union_all(r).filter_by(name=name).count() > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
""" Decorator for validating api token for authentication """
|
||||
def api_token_authorization(func):
|
||||
@ -20,14 +22,12 @@ def api_token_authorization(func):
|
||||
client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
|
||||
if utils.limiter.should_rate_limit_ip(client_ip):
|
||||
abort(429, 'Too many attempts from your IP (rate-limit)' )
|
||||
if (request.args.get('api_token') == '' or
|
||||
request.args.get('api_token') == None):
|
||||
abort(401, 'A valid API token is expected as query string parameter')
|
||||
if not hmac.compare_digest(request.args.get('api_token'), v1.api_token):
|
||||
if not request.headers.get('Authorization'):
|
||||
abort(401, 'A valid API token is expected which is provided as request header')
|
||||
if not hmac.compare_digest(request.headers.get('Authorization'), v1.api_token):
|
||||
utils.limiter.rate_limit_ip(client_ip)
|
||||
flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.')
|
||||
abort(403, 'A valid API token is expected as query string parameter')
|
||||
else:
|
||||
flask.current_app.logger.info(f'Valid API token provided by {client_ip}.')
|
||||
abort(403, 'A valid API token is expected which is provided as request header')
|
||||
flask.current_app.logger.info(f'Valid API token provided by {client_ip}.')
|
||||
return func(*args, **kwds)
|
||||
return decorated_function
|
||||
|
@ -8,20 +8,19 @@ api_token = None
|
||||
blueprint = Blueprint(f'api_v{int(VERSION)}', __name__)
|
||||
|
||||
authorization = {
|
||||
'apikey': {
|
||||
'Bearer': {
|
||||
'type': 'apiKey',
|
||||
'in': 'query',
|
||||
'name': 'api_token'
|
||||
'in': 'header',
|
||||
'name': 'Authorization'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
api = Api(
|
||||
blueprint, version=f'{VERSION:.1f}',
|
||||
title='Mailu API', default_label='Mailu',
|
||||
validate=True,
|
||||
authorizations=authorization,
|
||||
security='apikey',
|
||||
security='Bearer',
|
||||
doc='/swaggerui/'
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,7 @@ alias_fields = alias.inherit('Alias',alias_fields_update, {
|
||||
class Aliases(Resource):
|
||||
@alias.doc('list_alias')
|
||||
@alias.marshal_with(alias_fields, as_list=True, skip_none=True, mask=None)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List aliases """
|
||||
@ -35,7 +35,7 @@ class Aliases(Resource):
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.response(409, 'Duplicate alias', response_fields)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create a new alias """
|
||||
@ -60,7 +60,7 @@ class Alias(Resource):
|
||||
@alias.doc('find_alias')
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, alias):
|
||||
""" Find alias """
|
||||
@ -75,7 +75,7 @@ class Alias(Resource):
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def put(self, alias):
|
||||
""" Update alias """
|
||||
@ -97,7 +97,7 @@ class Alias(Resource):
|
||||
@alias.doc('delete_alias')
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, alias):
|
||||
""" Delete alias """
|
||||
@ -113,7 +113,7 @@ class AliasWithDest(Resource):
|
||||
@alias.doc('find_alias_filter_domain')
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(404, 'Alias or domain not found', response_fields)
|
||||
@alias.doc(security='apikey')
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Find aliases of domain """
|
||||
|
@ -78,7 +78,7 @@ alternative_fields = api.model('AlternativeDomain', {
|
||||
class Domains(Resource):
|
||||
@dom.doc('list_domain')
|
||||
@dom.marshal_with(domain_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List domains """
|
||||
@ -89,7 +89,7 @@ class Domains(Resource):
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create a new domain """
|
||||
@ -133,7 +133,7 @@ class Domain(Resource):
|
||||
@dom.doc('find_domain')
|
||||
@dom.response(200, 'Success', domain_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Find domain by name """
|
||||
@ -150,7 +150,7 @@ class Domain(Resource):
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def put(self, domain):
|
||||
""" Update an existing domain """
|
||||
@ -194,7 +194,7 @@ class Domain(Resource):
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, domain):
|
||||
""" Delete domain """
|
||||
@ -213,7 +213,7 @@ class Domain(Resource):
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, domain):
|
||||
""" Generate new DKIM/DMARC keys for domain """
|
||||
@ -232,7 +232,7 @@ class Manager(Resource):
|
||||
@dom.marshal_with(manager_fields, as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'domain not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" List managers of domain """
|
||||
@ -249,7 +249,7 @@ class Manager(Resource):
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'User or domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain manager', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, domain):
|
||||
""" Create a new domain manager """
|
||||
@ -275,7 +275,7 @@ class Domain(Resource):
|
||||
@dom.doc('find_manager')
|
||||
@dom.response(200, 'Success', manager_fields)
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain, email):
|
||||
""" Find manager by email address """
|
||||
@ -301,7 +301,7 @@ class Domain(Resource):
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, domain, email):
|
||||
if not validators.email(email):
|
||||
@ -327,7 +327,7 @@ class User(Resource):
|
||||
@dom.marshal_with(user.user_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='apikey')
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" List users from domain """
|
||||
@ -343,7 +343,7 @@ class Alternatives(Resource):
|
||||
|
||||
@alt.doc('list_alternative')
|
||||
@alt.marshal_with(alternative_fields, as_list=True, skip_none=True, mask=None)
|
||||
@alt.doc(security='apikey')
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List alternatives """
|
||||
@ -356,7 +356,7 @@ class Alternatives(Resource):
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.response(404, 'Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate alternative domain name', response_fields)
|
||||
@alt.doc(security='apikey')
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create new alternative (for domain) """
|
||||
@ -379,7 +379,7 @@ class Alternatives(Resource):
|
||||
@alt.route('/<string:alt>')
|
||||
class Alternative(Resource):
|
||||
@alt.doc('find_alternative')
|
||||
@alt.doc(security='apikey')
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, alt):
|
||||
""" Find alternative (of domain) """
|
||||
@ -395,7 +395,7 @@ class Alternative(Resource):
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.response(404, 'Alternative/Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate domain name', response_fields)
|
||||
@alt.doc(security='apikey')
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, alt):
|
||||
""" Delete alternative (for domain) """
|
||||
|
@ -24,7 +24,7 @@ relay_fields_update = api.model('RelayUpdate', {
|
||||
class Relays(Resource):
|
||||
@relay.doc('list_relays')
|
||||
@relay.marshal_with(relay_fields, as_list=True, skip_none=True, mask=None)
|
||||
@relay.doc(security='apikey')
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
"List relays"
|
||||
@ -35,7 +35,7 @@ class Relays(Resource):
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception')
|
||||
@relay.response(409, 'Duplicate relay', response_fields)
|
||||
@relay.doc(security='apikey')
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create relay """
|
||||
@ -61,7 +61,7 @@ class Relay(Resource):
|
||||
@relay.doc('find_relay')
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='apikey')
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, name):
|
||||
""" Find relay """
|
||||
@ -79,7 +79,7 @@ class Relay(Resource):
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.response(409, 'Duplicate relay', response_fields)
|
||||
@relay.doc(security='apikey')
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def put(self, name):
|
||||
""" Update relay """
|
||||
@ -105,7 +105,7 @@ class Relay(Resource):
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='apikey')
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, name):
|
||||
""" Delete relay """
|
||||
|
@ -64,7 +64,7 @@ user_fields_put = api.model('UserUpdate', {
|
||||
class Users(Resource):
|
||||
@user.doc('list_users')
|
||||
@user.marshal_with(user_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@user.doc(security='apikey')
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
"List users"
|
||||
@ -75,7 +75,7 @@ class Users(Resource):
|
||||
@user.response(200, 'Success', response_fields)
|
||||
@user.response(400, 'Input validation exception')
|
||||
@user.response(409, 'Duplicate user', response_fields)
|
||||
@user.doc(security='apikey')
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create user """
|
||||
@ -141,7 +141,7 @@ class User(Resource):
|
||||
@user.doc('find_user')
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.doc(security='apikey')
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, email):
|
||||
""" Find user """
|
||||
@ -159,7 +159,7 @@ class User(Resource):
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.response(409, 'Duplicate user', response_fields)
|
||||
@user.doc(security='apikey')
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def put(self, email):
|
||||
""" Update user """
|
||||
@ -222,7 +222,7 @@ class User(Resource):
|
||||
@user.response(200, 'Success', response_fields)
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.doc(security='apikey')
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, email):
|
||||
""" Delete user """
|
||||
|
@ -71,7 +71,7 @@ DEFAULT_CONFIG = {
|
||||
'LOGO_BACKGROUND': None,
|
||||
'API': False,
|
||||
# Advanced settings
|
||||
'API' : 'false',
|
||||
'API' : False,
|
||||
'WEB_API' : '/api',
|
||||
'API_TOKEN': None,
|
||||
'LOG_LEVEL': 'WARNING',
|
||||
|
@ -203,14 +203,13 @@ Depending on your particular deployment you most probably will want to change th
|
||||
Advanced settings
|
||||
-----------------
|
||||
|
||||
The ``API`` (default: False) setting controls if the API endpoint is publicly
|
||||
reachable.
|
||||
The ``API`` (default: False) setting controls if the API endpoint is reachable.
|
||||
|
||||
The ``WEB_API`` (default: /api) setting configures the endpoint that the API
|
||||
listens on publicly&interally. The path must always start with a leading slash.
|
||||
|
||||
The ``API_TOKEN`` (default: None) enables the API endpoint. This token must be
|
||||
passed as query parameter with requests to the API as authentication token.
|
||||
passed as request header to the API as authentication token.
|
||||
|
||||
The ``CREDENTIAL_ROUNDS`` (default: 12) setting is the number of rounds used by the
|
||||
password hashing scheme. The number of rounds can be reduced in case faster
|
||||
|
Loading…
Reference in New Issue
Block a user