mirror of
https://github.com/optim-enterprises-bv/Mailu-OIDC.git
synced 2025-10-29 17:22:20 +00:00
Merge #3203
3203: Add automatic tests for RESTful API r=mergify[bot] a=Diman0 and fix all remaining issues that I could find with the API. ## What type of PR? internal feature / bug-fix ## What does this PR do? I first wanted to finish #3113 before continuing on the tests to keep the scope smaller of the PR. This PR adds automatic tests that tests **all** the interfaces of the RESTful API. Practically it only tests the normal Ok (http 200) situations. Maybe in the future we could add more tests to check if the validation checks work correctly for each interface. I also fixed any issues I could find with the RESTful API. I can at least confirm that all interfaces work now. I think the validation checks are also complete now. ### 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. - [n/a] In case of feature or enhancement: documentation updated accordingly - [n/a] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Dimitri Huisman <diman@huisman.xyz>
This commit is contained in:
2
.github/workflows/build_test_deploy.yml
vendored
2
.github/workflows/build_test_deploy.yml
vendored
@@ -418,7 +418,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["core", "fetchmail", "filters", "webmail", "webdav"]
|
||||
target: ["api", "core", "fetchmail", "filters", "webmail", "webdav"]
|
||||
time: ["2"]
|
||||
include:
|
||||
- target: "filters"
|
||||
|
||||
@@ -2,6 +2,7 @@ from flask_restx import Resource, fields, marshal
|
||||
from . import api, response_fields
|
||||
from .. import common
|
||||
from ... import models
|
||||
import validators
|
||||
|
||||
db = models.db
|
||||
|
||||
@@ -15,7 +16,7 @@ alias_fields_update = alias.model('AliasUpdate', {
|
||||
|
||||
alias_fields = alias.inherit('Alias',alias_fields_update, {
|
||||
'email': fields.String(description='the alias email address', example='user@example.com', required=True),
|
||||
'destination': fields.List(fields.String(description='alias email address', example='user@example.com', required=True)),
|
||||
'destination': fields.List(fields.String(description='destination email address', example='user@example.com', required=True)),
|
||||
|
||||
})
|
||||
|
||||
@@ -24,6 +25,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(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -34,6 +36,8 @@ class Aliases(Resource):
|
||||
@alias.expect(alias_fields)
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.response(404, 'Not found', response_fields)
|
||||
@alias.response(409, 'Duplicate alias', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -41,6 +45,20 @@ class Aliases(Resource):
|
||||
""" Create a new alias """
|
||||
data = api.payload
|
||||
|
||||
if not validators.email(data['email']):
|
||||
return { 'code': 400, 'message': f'Provided alias {data["email"]} is not a valid email address'}, 400
|
||||
localpart, domain_name = data['email'].lower().rsplit('@', 1)
|
||||
domain_found = models.Domain.query.get(domain_name)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain_name} does not exist ({data["email"]})'}, 404
|
||||
if not domain_found.max_aliases == -1 and len(domain_found.aliases) >= domain_found.max_aliases:
|
||||
return { 'code': 409, 'message': f'Too many aliases for domain {domain_name}'}, 409
|
||||
for dest in data['destination']:
|
||||
if not validators.email(dest):
|
||||
return { 'code': 400, 'message': f'Provided destination email address {dest} is not a valid email address'}, 400
|
||||
elif models.User.query.filter_by(email=dest).first() is None:
|
||||
return { 'code': 404, 'message': f'Provided destination email address {dest} does not exist'}, 404
|
||||
|
||||
alias_found = models.Alias.query.filter_by(email = data['email']).first()
|
||||
if alias_found:
|
||||
return { 'code': 409, 'message': f'Duplicate alias {data["email"]}'}, 409
|
||||
@@ -53,17 +71,21 @@ class Aliases(Resource):
|
||||
db.session.add(alias_model)
|
||||
db.session.commit()
|
||||
|
||||
return {'code': 200, 'message': f'Alias {data["email"]} to destination {data["destination"]} has been created'}, 200
|
||||
return {'code': 200, 'message': f'Alias {data["email"]} to destination(s) {data["destination"]} has been created'}, 200
|
||||
|
||||
@alias.route('/<string:alias>')
|
||||
class Alias(Resource):
|
||||
@alias.doc('find_alias')
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, alias):
|
||||
""" Look up the specified alias """
|
||||
if not validators.email(alias):
|
||||
return { 'code': 400, 'message': f'Provided alias (email address) {alias} is not a valid email address'}, 400
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
@@ -73,6 +95,7 @@ class Alias(Resource):
|
||||
@alias.doc('update_alias')
|
||||
@alias.expect(alias_fields_update)
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@@ -80,6 +103,9 @@ class Alias(Resource):
|
||||
def patch(self, alias):
|
||||
""" Update the specfied alias """
|
||||
data = api.payload
|
||||
|
||||
if not validators.email(alias):
|
||||
return { 'code': 400, 'message': f'Provided alias (email address) {alias} is not a valid email address'}, 400
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
@@ -87,6 +113,11 @@ class Alias(Resource):
|
||||
alias_found.comment = data['comment']
|
||||
if 'destination' in data:
|
||||
alias_found.destination = data['destination']
|
||||
for dest in data['destination']:
|
||||
if not validators.email(dest):
|
||||
return { 'code': 400, 'message': f'Provided destination email address {dest} is not a valid email address'}, 400
|
||||
elif models.User.query.filter_by(email=dest).first() is None:
|
||||
return { 'code': 404, 'message': f'Provided destination email address {dest} does not exist'}, 404
|
||||
if 'wildcard' in data:
|
||||
alias_found.wildcard = data['wildcard']
|
||||
db.session.add(alias_found)
|
||||
@@ -95,11 +126,15 @@ class Alias(Resource):
|
||||
|
||||
@alias.doc('delete_alias')
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, alias):
|
||||
""" Delete the specified alias """
|
||||
if not validators.email(alias):
|
||||
return { 'code': 400, 'message': f'Provided alias (email address) {alias} is not a valid email address'}, 400
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
@@ -110,12 +145,16 @@ class Alias(Resource):
|
||||
@alias.route('/destination/<string:domain>')
|
||||
class AliasWithDest(Resource):
|
||||
@alias.doc('find_alias_filter_domain')
|
||||
@alias.marshal_with(alias_fields, code=200, description='Success' ,as_list=True, skip_none=True, mask=None)
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alias.response(404, 'Alias or domain not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Look up the aliases of the specified domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.filter_by(name=domain).first()
|
||||
if domain_found is None:
|
||||
return { 'code': 404, 'message': f'Domain {domain} cannot be found'}, 404
|
||||
@@ -123,4 +162,4 @@ class AliasWithDest(Resource):
|
||||
if aliases_found.count == 0:
|
||||
return { 'code': 404, 'message': f'No alias can be found for domain {domain}'}, 404
|
||||
else:
|
||||
return marshal(aliases_found, alias_fields, as_list=True), 200
|
||||
return marshal(aliases_found, alias_fields), 200
|
||||
|
||||
@@ -16,7 +16,7 @@ domain_fields = api.model('Domain', {
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN'), example='["example.com"]'),
|
||||
})
|
||||
|
||||
domain_fields_update = api.model('DomainUpdate', {
|
||||
@@ -25,17 +25,18 @@ domain_fields_update = api.model('DomainUpdate', {
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN'), example='["example.com"]'),
|
||||
})
|
||||
|
||||
domain_fields_get = api.model('DomainGet', {
|
||||
'name': fields.String(description='FQDN (e.g. example.com)', example='example.com', required=True),
|
||||
'comment': fields.String(description='a comment'),
|
||||
'managers': fields.List(fields.String(attribute='email', description='manager of domain')),
|
||||
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN'), example='["example.com"]'),
|
||||
'dns_autoconfig': fields.List(fields.String(description='DNS client auto-configuration entry')),
|
||||
'dns_mx': fields.String(Description='MX record for domain'),
|
||||
'dns_spf': fields.String(Description='SPF record for domain'),
|
||||
@@ -56,8 +57,7 @@ domain_fields_dns = api.model('DomainDNS', {
|
||||
})
|
||||
|
||||
manager_fields = api.model('Manager', {
|
||||
'domain_name': fields.String(description='domain managed by manager'),
|
||||
'user_email': fields.String(description='email address of manager'),
|
||||
'managers': fields.List(fields.String(attribute='email', description='manager of domain')),
|
||||
})
|
||||
|
||||
manager_fields_create = api.model('ManagerCreate', {
|
||||
@@ -78,6 +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(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -88,6 +89,7 @@ class Domains(Resource):
|
||||
@dom.expect(domain_fields)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -131,15 +133,16 @@ class Domains(Resource):
|
||||
class Domain(Resource):
|
||||
|
||||
@dom.doc('find_domain')
|
||||
@dom.marshal_with(domain_fields, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@dom.response(200, 'Success', domain_fields_get)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Look up the specified domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 200
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
@@ -149,6 +152,7 @@ class Domain(Resource):
|
||||
@dom.expect(domain_fields_update)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@@ -158,7 +162,7 @@ class Domain(Resource):
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {data["name"]} does not exist'}, 404
|
||||
data = api.payload
|
||||
|
||||
@@ -172,7 +176,7 @@ class Domain(Resource):
|
||||
if not validators.domain(item):
|
||||
return { 'code': 400, 'message': f'Alternative domain {item} is not a valid domain'}, 400
|
||||
for item in data['alternatives']:
|
||||
alternative = models.Alternative(name=item, domain_name=data['name'])
|
||||
alternative = models.Alternative(name=item, domain_name=domain)
|
||||
models.db.session.add(alternative)
|
||||
|
||||
if 'comment' in data:
|
||||
@@ -194,6 +198,7 @@ class Domain(Resource):
|
||||
@dom.doc('delete_domain')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -202,7 +207,7 @@ class Domain(Resource):
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
db.session.delete(domain_found)
|
||||
db.session.commit()
|
||||
@@ -213,6 +218,7 @@ class Domain(Resource):
|
||||
@dom.doc('generate_dkim')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -230,8 +236,9 @@ class Domain(Resource):
|
||||
@dom.route('/<domain>/manager')
|
||||
class Manager(Resource):
|
||||
@dom.doc('list_managers')
|
||||
@dom.marshal_with(manager_fields, code=200, description='Success', as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(200, 'Success', manager_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -239,15 +246,16 @@ class Manager(Resource):
|
||||
""" List all managers of the specified domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
if not domain:
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
domain = models.Domain.query.filter_by(name=domain)
|
||||
return domain.managers
|
||||
return marshal(domain_found, manager_fields), 200
|
||||
|
||||
@dom.doc('create_manager')
|
||||
@dom.expect(manager_fields_create)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'User or domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain manager', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@@ -274,13 +282,14 @@ class Manager(Resource):
|
||||
@dom.route('/<domain>/manager/<email>')
|
||||
class Domain(Resource):
|
||||
@dom.doc('find_manager')
|
||||
@dom.marshal_with(manager_fields, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain, email):
|
||||
""" Look up the specified manager of the specified domain """
|
||||
""" Check if the specified user is a manager of the specified domain """
|
||||
if not validators.email(email):
|
||||
return {'code': 400, 'message': f'Invalid email address {email}'}, 400
|
||||
if not validators.domain(domain):
|
||||
@@ -294,7 +303,7 @@ class Domain(Resource):
|
||||
if user in domain.managers:
|
||||
for manager in domain.managers:
|
||||
if manager.email == email:
|
||||
return marshal(manager, manager_fields),200
|
||||
return { 'code': 200, 'message': f'User {email} is a manager of the domain {domain}'}, 200
|
||||
else:
|
||||
return { 'code': 404, 'message': f'User {email} is not a manager of the domain {domain}'}, 404
|
||||
|
||||
@@ -302,6 +311,7 @@ class Domain(Resource):
|
||||
@dom.doc('delete_manager')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -327,8 +337,9 @@ class Domain(Resource):
|
||||
@dom.route('/<domain>/users')
|
||||
class User(Resource):
|
||||
@dom.doc('list_user_domain')
|
||||
@dom.marshal_with(user.user_fields_get, code=200, description='Success', as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(200, 'Success', user.user_fields_get)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -339,13 +350,14 @@ class User(Resource):
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
return models.User.query.filter_by(domain=domain_found).all()
|
||||
return marshal(models.User.query.filter_by(domain=domain_found).all(), user.user_fields_get),200
|
||||
|
||||
@alt.route('')
|
||||
class Alternatives(Resource):
|
||||
|
||||
@alt.doc('list_alternative')
|
||||
@alt.marshal_with(alternative_fields, as_list=True, skip_none=True, mask=None)
|
||||
@alt.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -357,6 +369,7 @@ class Alternatives(Resource):
|
||||
@alt.expect(alternative_fields)
|
||||
@alt.response(200, 'Success', response_fields)
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alt.response(404, 'Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate alternative domain name', response_fields)
|
||||
@alt.doc(security='Bearer')
|
||||
@@ -383,8 +396,9 @@ class Alternatives(Resource):
|
||||
class Alternative(Resource):
|
||||
@alt.doc('find_alternative')
|
||||
@alt.doc(security='Bearer')
|
||||
@alt.marshal_with(alternative_fields, code=200, description='Success' ,as_list=True, skip_none=True, mask=None)
|
||||
@alt.response(200, 'Success', alternative_fields)
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alt.response(404, 'Alternative not found or missing', response_fields)
|
||||
@common.api_token_authorization
|
||||
def get(self, alt):
|
||||
@@ -399,6 +413,7 @@ class Alternative(Resource):
|
||||
@alt.doc('delete_alternative')
|
||||
@alt.response(200, 'Success', response_fields)
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@alt.response(404, 'Alternative/Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate domain name', response_fields)
|
||||
@alt.doc(security='Bearer')
|
||||
|
||||
@@ -24,6 +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(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -34,6 +35,7 @@ class Relays(Resource):
|
||||
@relay.expect(relay_fields)
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@relay.response(409, 'Duplicate relay', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -58,8 +60,9 @@ class Relays(Resource):
|
||||
@relay.route('/<string:name>')
|
||||
class Relay(Resource):
|
||||
@relay.doc('find_relay')
|
||||
@relay.marshal_with(relay_fields, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@relay.response(200, 'Success', relay_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -77,6 +80,7 @@ class Relay(Resource):
|
||||
@relay.expect(relay_fields_update)
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -103,6 +107,7 @@ class Relay(Resource):
|
||||
@relay.doc('delete_relay')
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
|
||||
@@ -15,20 +15,20 @@ token_user_fields = api.model('TokenGetResponse', {
|
||||
'id': fields.String(description='The record id of the token (unique identifier)', example='1'),
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'AuthorizedIP': fields.List(fields.String(description='White listed IP addresses or networks that may use this token.', example="203.0.113.0/24"), attribute='ip'),
|
||||
'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at'),
|
||||
'Last edit': fields.String(description='The date when the token was last modifified', example='John.Doe@example.com', attribute='updated_at')
|
||||
})
|
||||
|
||||
token_user_fields_post = api.model('TokenPost', {
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email', required=True),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'AuthorizedIP': fields.List(fields.String(description='White listed IP addresses or networks that may use this token.', example="203.0.113.0/24")),
|
||||
})
|
||||
|
||||
token_user_fields_post2 = api.model('TokenPost2', {
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'AuthorizedIP': fields.List(fields.String(description='White listed IP addresses or networks that may use this token.', example="203.0.113.0/24")),
|
||||
})
|
||||
|
||||
token_user_post_response = api.model('TokenPostResponse', {
|
||||
@@ -36,14 +36,17 @@ token_user_post_response = api.model('TokenPostResponse', {
|
||||
'token': fields.String(description='The created authentication token for the user.', example='2caf6607de5129e4748a2c061aee56f2', attribute='password'),
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'AuthorizedIP': fields.List(fields.String(description='White listed IP addresses or networks that may use this token.', example="203.0.113.0/24")),
|
||||
'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at')
|
||||
})
|
||||
|
||||
|
||||
|
||||
@token.route('')
|
||||
class Tokens(Resource):
|
||||
@token.doc('list_tokens')
|
||||
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -52,8 +55,10 @@ class Tokens(Resource):
|
||||
|
||||
@token.doc('create_token')
|
||||
@token.expect(token_user_fields_post)
|
||||
@token.marshal_with(token_user_post_response, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@token.response(200, 'Success', token_user_post_response)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'User not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
@@ -71,7 +76,11 @@ class Tokens(Resource):
|
||||
if 'comment' in data:
|
||||
token_new.comment = data['comment']
|
||||
if 'AuthorizedIP' in data:
|
||||
token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
|
||||
token_new.ip = data['AuthorizedIP']
|
||||
for ip in token_new.ip:
|
||||
if (not validators.ip_address.ipv4(ip,cidr=True, strict=False, host_bit=False) and
|
||||
not validators.ip_address.ipv6(ip,cidr=True, strict=False, host_bit=False)):
|
||||
return { 'code': 400, 'message': f'Provided AuthorizedIP {ip} in {token_new.ip} is invalid'}, 400
|
||||
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
|
||||
token_new.set_password(raw_password)
|
||||
models.db.session.add(token_new)
|
||||
@@ -91,8 +100,9 @@ class Tokens(Resource):
|
||||
@token.route('user/<string:email>')
|
||||
class Token(Resource):
|
||||
@token.doc('find_tokens_of_user')
|
||||
@token.marshal_with(token_user_fields, code=200, description='Success', as_list=True, skip_none=True, mask=None)
|
||||
@token.response(200, 'Success', token_user_fields)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'Token not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -104,12 +114,25 @@ class Token(Resource):
|
||||
if not user_found:
|
||||
return {'code': 404, 'message': f'User {email} cannot be found'}, 404
|
||||
tokens = user_found.tokens
|
||||
return tokens
|
||||
response_list = []
|
||||
for token in tokens:
|
||||
response_dict = {
|
||||
'id' : token.id,
|
||||
'email' : token.user_email,
|
||||
'comment' : token.comment,
|
||||
'AuthorizedIP' : token.ip,
|
||||
'Created': str(token.created_at),
|
||||
'Last edit': str(token.updated_at)
|
||||
}
|
||||
response_list.append(response_dict)
|
||||
return response_list
|
||||
|
||||
@token.doc('create_token')
|
||||
@token.expect(token_user_fields_post2)
|
||||
@token.response(200, 'Success', token_user_post_response)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'User not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, email):
|
||||
@@ -125,7 +148,11 @@ class Token(Resource):
|
||||
if 'comment' in data:
|
||||
token_new.comment = data['comment']
|
||||
if 'AuthorizedIP' in data:
|
||||
token_new.ip = token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
|
||||
token_new.ip = token_new.ip = data['AuthorizedIP']
|
||||
for ip in token_new.ip:
|
||||
if (not validators.ip_address.ipv4(ip,cidr=True, strict=False, host_bit=False) and
|
||||
not validators.ip_address.ipv6(ip,cidr=True, strict=False, host_bit=False)):
|
||||
return { 'code': 400, 'message': f'Provided AuthorizedIP {ip} in {token_new.ip} is invalid'}, 400
|
||||
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
|
||||
token_new.set_password(raw_password)
|
||||
models.db.session.add(token_new)
|
||||
@@ -144,7 +171,8 @@ class Token(Resource):
|
||||
@token.route('/<string:token_id>')
|
||||
class Token(Resource):
|
||||
@token.doc('find_token')
|
||||
@token.marshal_with(token_user_fields, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@token.response(200, 'Success', token_user_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'Token not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -153,11 +181,48 @@ class Token(Resource):
|
||||
token = models.Token.query.get(token_id)
|
||||
if not token:
|
||||
return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404
|
||||
return token
|
||||
response_dict = {
|
||||
'id' : token.id,
|
||||
'email' : token.user_email,
|
||||
'comment' : token.comment,
|
||||
'AuthorizedIP' : token.ip,
|
||||
'Created': str(token.created_at),
|
||||
'Last edit': str(token.updated_at)
|
||||
}
|
||||
return response_dict
|
||||
|
||||
@token.doc('update_token')
|
||||
@token.expect(token_user_fields_post2)
|
||||
@token.response(200, 'Success', response_fields)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'User not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
def patch(self, token_id):
|
||||
""" Update the specified token """
|
||||
data = api.payload
|
||||
|
||||
token = models.Token.query.get(token_id)
|
||||
if not token:
|
||||
return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404
|
||||
|
||||
if 'comment' in data:
|
||||
token.comment = data['comment']
|
||||
if 'AuthorizedIP' in data:
|
||||
token.ip = token.ip = data['AuthorizedIP']
|
||||
for ip in token.ip:
|
||||
if (not validators.ip_address.ipv4(ip,cidr=True, strict=False, host_bit=False) and
|
||||
not validators.ip_address.ipv6(ip,cidr=True, strict=False, host_bit=False)):
|
||||
return { 'code': 400, 'message': f'Provided AuthorizedIP {ip} in {token.ip} is invalid'}, 400
|
||||
models.db.session.add(token)
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Token with id {token_id} has been updated'}, 200
|
||||
|
||||
|
||||
@token.doc('delete_token')
|
||||
@token.response(200, 'Success', response_fields)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@token.response(404, 'Token not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
|
||||
@@ -22,7 +22,7 @@ user_fields_get = api.model('UserGet', {
|
||||
'enable_pop': fields.Boolean(description='Allow email retrieval via POP3'),
|
||||
'allow_spoofing': fields.Boolean(description='Allow the user to spoof the sender (send email as anyone)'),
|
||||
'forward_enabled': fields.Boolean(description='Enable auto forwarding'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='Other@example.com'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='["Other@example.com"]'),
|
||||
'forward_keep': fields.Boolean(description='Keep a copy of the forwarded email in the inbox'),
|
||||
'reply_enabled': fields.Boolean(description='Enable automatic replies. This is also known as out of office (ooo) or out of facility (oof) replies'),
|
||||
'reply_subject': fields.String(description='Optional subject for the automatic reply', example='Out of office'),
|
||||
@@ -47,7 +47,7 @@ user_fields_post = api.model('UserCreate', {
|
||||
'enable_pop': fields.Boolean(description='Allow email retrieval via POP3'),
|
||||
'allow_spoofing': fields.Boolean(description='Allow the user to spoof the sender (send email as anyone)'),
|
||||
'forward_enabled': fields.Boolean(description='Enable auto forwarding'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='Other@example.com'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='["Other@example.com"]'),
|
||||
'forward_keep': fields.Boolean(description='Keep a copy of the forwarded email in the inbox'),
|
||||
'reply_enabled': fields.Boolean(description='Enable automatic replies. This is also known as out of office (ooo) or out of facility (oof) replies'),
|
||||
'reply_subject': fields.String(description='Optional subject for the automatic reply', example='Out of office'),
|
||||
@@ -71,7 +71,7 @@ user_fields_put = api.model('UserUpdate', {
|
||||
'enable_pop': fields.Boolean(description='Allow email retrieval via POP3'),
|
||||
'allow_spoofing': fields.Boolean(description='Allow the user to spoof the sender (send email as anyone)'),
|
||||
'forward_enabled': fields.Boolean(description='Enable auto forwarding'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='Other@example.com'),
|
||||
'forward_destination': fields.List(fields.String(description='Email address to forward emails to'), example='["Other@example.com"]'),
|
||||
'forward_keep': fields.Boolean(description='Keep a copy of the forwarded email in the inbox'),
|
||||
'reply_enabled': fields.Boolean(description='Enable automatic replies. This is also known as out of office (ooo) or out of facility (oof) replies'),
|
||||
'reply_subject': fields.String(description='Optional subject for the automatic reply', example='Out of office'),
|
||||
@@ -89,6 +89,7 @@ user_fields_put = api.model('UserUpdate', {
|
||||
class Users(Resource):
|
||||
@user.doc('list_user')
|
||||
@user.marshal_with(user_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@user.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
@@ -99,6 +100,7 @@ class Users(Resource):
|
||||
@user.expect(user_fields_post)
|
||||
@user.response(200, 'Success', response_fields)
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@user.response(409, 'Duplicate user', response_fields)
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -107,14 +109,22 @@ class Users(Resource):
|
||||
data = api.payload
|
||||
if not validators.email(data['email']):
|
||||
return { 'code': 400, 'message': f'Provided email address {data["email"]} is not a valid email address'}, 400
|
||||
if 'forward_destination' in data and len(data['forward_destination']) > 0:
|
||||
for dest in data['forward_destination']:
|
||||
if not validators.email(dest):
|
||||
return { 'code': 400, 'message': f'Provided forward destination email address {dest} is not a valid email address'}, 400
|
||||
localpart, domain_name = data['email'].lower().rsplit('@', 1)
|
||||
domain_found = models.Domain.query.get(domain_name)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain_name} does not exist'}, 404
|
||||
if not domain_found.max_users == -1 and len(domain_found.users) >= domain_found.max_users:
|
||||
return { 'code': 409, 'message': f'Too many users for domain {domain_name}'}, 409
|
||||
email_found = models.User.query.filter_by(email=data['email']).first()
|
||||
if email_found:
|
||||
return { 'code': 409, 'message': f'User {data["email"]} already exists'}, 409
|
||||
|
||||
if 'forward_enabled' in data and data['forward_enabled'] is True:
|
||||
if ('forward_destination' in data and len(data['forward_destination']) == 0) or 'forward_destination' not in data:
|
||||
return { 'code': 400, 'message': f'forward_destination is mandatory when forward_enabled is true'}, 400
|
||||
|
||||
user_new = models.User(email=data['email'])
|
||||
if 'raw_password' in data:
|
||||
@@ -137,7 +147,7 @@ class Users(Resource):
|
||||
user_new.allow_spoofing = data['allow_spoofing']
|
||||
if 'forward_enabled' in data:
|
||||
user_new.forward_enabled = data['forward_enabled']
|
||||
if 'forward_destination' in data:
|
||||
if 'forward_destination' in data and len(data['forward_destination']) > 0:
|
||||
user_new.forward_destination = data['forward_destination']
|
||||
if 'forward_keep' in data:
|
||||
user_new.forward_keep = data['forward_keep']
|
||||
@@ -168,12 +178,12 @@ class Users(Resource):
|
||||
|
||||
return {'code': 200,'message': f'User {data["email"]} has been created'}, 200
|
||||
|
||||
|
||||
@user.route('/<string:email>')
|
||||
class User(Resource):
|
||||
@user.doc('find_user')
|
||||
@user.marshal_with(user_fields_get, code=200, description='Success', as_list=False, skip_none=True, mask=None)
|
||||
@user.response(200, 'Success', user_fields_get)
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -191,6 +201,7 @@ class User(Resource):
|
||||
@user.expect(user_fields_put)
|
||||
@user.response(200, 'Success', response_fields)
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
@@ -198,10 +209,17 @@ class User(Resource):
|
||||
""" Update the specified user """
|
||||
data = api.payload
|
||||
if not validators.email(email):
|
||||
return { 'code': 400, 'message': f'Provided email address {data["email"]} is not a valid email address'}, 400
|
||||
return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400
|
||||
if 'forward_destination' in data and len(data['forward_destination']) > 0:
|
||||
for dest in data['forward_destination']:
|
||||
if not validators.email(dest):
|
||||
return { 'code': 400, 'message': f'Provided forward destination email address {dest} is not a valid email address'}, 400
|
||||
user_found = models.User.query.get(email)
|
||||
if not user_found:
|
||||
return {'code': 404, 'message': f'User {email} cannot be found'}, 404
|
||||
if ('forward_enabled' in data and data['forward_enabled'] is True) or ('forward_enabled' not in data and user_found.forward_enabled):
|
||||
if ('forward_destination' in data and len(data['forward_destination']) == 0):
|
||||
return { 'code': 400, 'message': f'forward_destination is mandatory when forward_enabled is true'}, 400
|
||||
|
||||
if 'raw_password' in data:
|
||||
user_found.set_password(data['raw_password'])
|
||||
@@ -223,8 +241,9 @@ class User(Resource):
|
||||
user_found.allow_spoofing = data['allow_spoofing']
|
||||
if 'forward_enabled' in data:
|
||||
user_found.forward_enabled = data['forward_enabled']
|
||||
if 'forward_destination' in data:
|
||||
user_found.forward_destination = data['forward_destination']
|
||||
if 'forward_destination' in data and len(data['forward_destination']) > 0:
|
||||
if len(data['forward_destination']) == 0:
|
||||
user_found.forward_destination = data['forward_destination']
|
||||
if 'forward_keep' in data:
|
||||
user_found.forward_keep = data['forward_keep']
|
||||
if 'reply_enabled' in data:
|
||||
@@ -258,6 +277,7 @@ class User(Resource):
|
||||
@user.doc('delete_user')
|
||||
@user.response(200, 'Success', response_fields)
|
||||
@user.response(400, 'Input validation exception', response_fields)
|
||||
@user.doc(responses={401: 'Authorization header missing', 403: 'Invalid authorization header'})
|
||||
@user.response(404, 'User not found', response_fields)
|
||||
@user.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
|
||||
@@ -93,7 +93,12 @@ def user_settings(user_email):
|
||||
form = forms.UserSettingsForm(obj=user)
|
||||
utils.formatCSVField(form.forward_destination)
|
||||
if form.validate_on_submit():
|
||||
form.forward_destination.data = form.forward_destination.data.replace(" ","").split(",")
|
||||
if form.forward_enabled.data and (form.forward_destination.data in ['', None] or type(form.forward_destination.data) is list):
|
||||
flask.flash('Destination email address is missing', 'error')
|
||||
user.forward_enabled = True
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
if form.forward_enabled.data:
|
||||
form.forward_destination.data = form.forward_destination.data.replace(" ","").split(",")
|
||||
form.populate_obj(user)
|
||||
models.db.session.commit()
|
||||
form.forward_destination.data = ", ".join(form.forward_destination.data)
|
||||
@@ -101,6 +106,9 @@ def user_settings(user_email):
|
||||
if user_email:
|
||||
return flask.redirect(
|
||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||
elif form.is_submitted() and not form.validate():
|
||||
user.forward_enabled = form.forward_enabled.data
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
|
||||
def _process_password_change(form, user_email):
|
||||
|
||||
106
tests/compose/api/00_create_users.sh
Executable file
106
tests/compose/api/00_create_users.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
# create user admin@maiu.io
|
||||
echo "Create users"
|
||||
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/domain' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "mailu.io",
|
||||
"comment": "internal domain for testing",
|
||||
"max_users": -1,
|
||||
"max_aliases": -1,
|
||||
"max_quota_bytes": 0,
|
||||
"signup_enabled": false
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Domain mail.io has been created successfully"
|
||||
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/user' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"email": "admin@mailu.io",
|
||||
"raw_password": "password",
|
||||
"comment": "created for testing RESTful API",
|
||||
"global_admin": true,
|
||||
"enabled": true,
|
||||
"change_pw_next_login": false,
|
||||
"enable_imap": true,
|
||||
"enable_pop": true,
|
||||
"allow_spoofing": false,
|
||||
"forward_enabled": false,
|
||||
"reply_enabled": false,
|
||||
"displayed_name": "admin",
|
||||
"spam_enabled": true,
|
||||
"spam_mark_as_read": true
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Created admin user (admin@mailu.io) successfully"
|
||||
|
||||
# Test if creating duplicate returns 409 HTTP response.
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/user' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"email": "admin@mailu.io",
|
||||
"raw_password": "password",
|
||||
"comment": "created for testing RESTful API",
|
||||
"global_admin": true,
|
||||
"enabled": true,
|
||||
"change_pw_next_login": false,
|
||||
"enable_imap": true,
|
||||
"enable_pop": true,
|
||||
"allow_spoofing": false,
|
||||
"forward_enabled": false,
|
||||
"reply_enabled": false,
|
||||
"displayed_name": "admin",
|
||||
"spam_enabled": true,
|
||||
"spam_mark_as_read": true
|
||||
}' | grep 409
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "OK. Failed creating duplicate user."
|
||||
|
||||
# create user user@mailu.io
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/user' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"email": "user@mailu.io",
|
||||
"raw_password": "password",
|
||||
"comment": "created for testing RESTful API",
|
||||
"global_admin": false,
|
||||
"enabled": true,
|
||||
"change_pw_next_login": false,
|
||||
"enable_imap": true,
|
||||
"enable_pop": true,
|
||||
"allow_spoofing": false,
|
||||
"forward_enabled": false,
|
||||
"reply_enabled": false,
|
||||
"displayed_name": "admin",
|
||||
"spam_enabled": true,
|
||||
"spam_mark_as_read": true
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Created user (user@mailu.io) successfully"
|
||||
|
||||
echo "Finished 00_create_users.sh"
|
||||
80
tests/compose/api/01_test_user_interfaces.sh
Executable file
80
tests/compose/api/01_test_user_interfaces.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
echo "Test user interfaces"
|
||||
# create user user@mailu.io for testing deletion
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/user' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"email": "user2@mailu.io",
|
||||
"raw_password": "password",
|
||||
"comment": "created for testing RESTful API",
|
||||
"global_admin": false,
|
||||
"enabled": true,
|
||||
"change_pw_next_login": false,
|
||||
"enable_imap": true,
|
||||
"enable_pop": true,
|
||||
"allow_spoofing": false,
|
||||
"forward_enabled": false,
|
||||
"reply_enabled": false,
|
||||
"displayed_name": "admin",
|
||||
"spam_enabled": true,
|
||||
"spam_mark_as_read": true
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "created user (user2@mailu.io) successfully"
|
||||
|
||||
#delete user2@mailu.io
|
||||
curl --silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/user/user2%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Deleted user2 (user2@mailu.io) successfully"
|
||||
|
||||
#Check if updating user works
|
||||
curl --silent --insecure -X 'PATCH' \
|
||||
'https://localhost/api/v1/user/user%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "updated_comment"
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Updated user(user@mailu.io) successfully"
|
||||
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/user/user%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep updated_comment
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed that comment attribute of user was correctly updated"
|
||||
|
||||
# try get all users. At this moment we should have 2 users total
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/user' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep -o "email" | grep -c "email" | grep 2
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all users successfully"
|
||||
|
||||
echo "Finished 01_test_user_interfaces.sh"
|
||||
145
tests/compose/api/02_test_domain_interfaces.sh
Executable file
145
tests/compose/api/02_test_domain_interfaces.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
echo "Test Domain interfaces"
|
||||
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/domain' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "mailu2.io",
|
||||
"comment": "internal domain for testing",
|
||||
"max_users": -1,
|
||||
"max_aliases": -1,
|
||||
"max_quota_bytes": 0,
|
||||
"signup_enabled": false
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Domain mail2.io has been created successfully"
|
||||
|
||||
curl --silent --insecure -X 'PATCH' \
|
||||
'https://localhost/api/v1/domain/mailu2.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "updated_domain"
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Domain mail2.io has been updated"
|
||||
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/domain/mailu2.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep updated_domain
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed that comment attribute of domain mailu2.io was correctly updated"
|
||||
|
||||
# try get all domains. At this moment we should have 2 domains total
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/domain' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep -o "name" | grep -c "name" | grep 2
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all domains successfully"
|
||||
|
||||
# try create dkim keys
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/domain/mailu2.io/dkim' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-d '' \
|
||||
| grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "dkim keys were created successfully for domain mailu2.io"
|
||||
|
||||
# try deleting a domain
|
||||
curl --silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/domain/mailu2.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Domain mailu2.io was deleted successfully"
|
||||
|
||||
# try looking up all users of a domain. There should be 2 users.
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/domain/mailu.io/users' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o "email" | grep -c "email" | grep 2
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all users of domain mailu.io successfully"
|
||||
|
||||
|
||||
#### Alternatives
|
||||
|
||||
#try to create an alternative
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/alternative' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "mailu2.io",
|
||||
"domain": "mailu.io"
|
||||
}' | grep 200
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Alternative mailu2.io for domain mailu.io was created successfully"
|
||||
|
||||
# try get all alternatives. At this moment we should have 1 alternative total
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/alternative' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer apitest' \
|
||||
| grep -o "name" | grep -c "name" | grep 1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all alternatives successfully"
|
||||
|
||||
# try to check if an alternative exists
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/alternative/mailu2.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep '{"name": "mailu2.io", "domain": "mailu.io"}'
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Lookup for alternative mailu2.io was successful"
|
||||
|
||||
# try to delete an alternative
|
||||
curl --silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/alternative/mailu2.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest'
|
||||
|
||||
echo "Finshed 02_test_domain_interfaces.sh"
|
||||
107
tests/compose/api/03_test_token_interfaces.sh
Executable file
107
tests/compose/api/03_test_token_interfaces.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
echo "start token tests"
|
||||
|
||||
# Try creating a token /token
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/token' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"email": "user@mailu.io",
|
||||
"comment": "my token related comment",
|
||||
"AuthorizedIP": [
|
||||
"203.0.113.0/24",
|
||||
"203.2.114.2/32"
|
||||
]
|
||||
}' | grep '"token": "'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "created a token for user@mailu.io successfully"
|
||||
|
||||
# Try create a token for a specific user /tokenuser/{email}
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/tokenuser/user%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "token test"
|
||||
}' | grep '"token": "'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "created a second token for user@mailu.io successfully"
|
||||
|
||||
# Try retrieving all tokens /token. We expect to retrieve 2 in total.
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/token' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o "id" | grep -c "id" | grep 2
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all tokens (2 in total) successfully"
|
||||
|
||||
# Try finding a specific token /token/{token_id}
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/token/2' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep '"id": 2'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved token with id 2 successfully"
|
||||
|
||||
# Try deleting a token /token/{token_id}
|
||||
curl --silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/token/1' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Deleted token with id 1 successfully"
|
||||
|
||||
# Try updating a token /token/{token_id}
|
||||
curl --silent --insecure -X 'PATCH' \
|
||||
'https://localhost/api/v1/token/2' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "updated_comment",
|
||||
"AuthorizedIP": [
|
||||
"203.0.112.0/24"
|
||||
]
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Updated token with id 2 successfully"
|
||||
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/token/2' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep 'comment": "updated_comment"'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed that comment field of token with id 2 was correctly updated"
|
||||
|
||||
# Try looking up all tokens of a specific user /tokenuser/{email}
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/tokenuser/user%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o "id" | grep -c "id" | grep 1
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all tokens (1 in total) for user@mailu.io successfully"
|
||||
|
||||
echo "Finished 03_test_token_interfaces.sh"
|
||||
98
tests/compose/api/04_test_relay_interfaces.sh
Executable file
98
tests/compose/api/04_test_relay_interfaces.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
echo "Start 04_test_relay_interfaces.sh"
|
||||
|
||||
# Try creating a new relay /relay
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/relay' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "relay1.mailu.io",
|
||||
"smtp": "relay1.mailu.io:8755",
|
||||
"comment": "backup relay1"
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "created a relay for domain relay1.mailu.io successfully"
|
||||
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/relay' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "relay2.mailu.io",
|
||||
"comment": "backup relay2"
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "created a relay for domain relay2.mailu.io successfully"
|
||||
|
||||
# Try retrieving all relays /relay. We expect to retrieve 2 in total
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/relay' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o '"name":' | grep -c '"name":' | grep 2
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved all relays (2 in total) successfully"
|
||||
|
||||
# Try looking up a specific relay /relay/{name}
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/relay/relay1.mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep '"name": "relay1.mailu.io"'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrieved the specified relay (relay1.mailu.io) successfully"
|
||||
|
||||
# Try deleting a specific relay /relay/{name}
|
||||
curl -silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/relay/relay2.mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Deleted relay2.mailu.io successfully"
|
||||
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/relay' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o '"name":' | grep -c '"name":' | grep 1
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "confirmed we only have 1 relay now"
|
||||
|
||||
# Try updating a specific relay /relay/{name}
|
||||
curl --silent --insecure -X 'PATCH' \
|
||||
'https://localhost/api/v1/relay/relay1.mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"smtp": "anotherName",
|
||||
"comment": "updated_comment"
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "update of relay was succcessful"
|
||||
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/relay/relay1.mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep anotherName | grep updated_comment
|
||||
echo "confirmed that smtp attribute and comment attribute were correctly updated"
|
||||
|
||||
echo "Finished 04_test_relay_interfaces.sh"
|
||||
111
tests/compose/api/05_test_alias_interfaces.sh
Executable file
111
tests/compose/api/05_test_alias_interfaces.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
# try create, find, lookup, delete
|
||||
|
||||
echo "Start 05_test_alias_interfaces.sh"
|
||||
|
||||
# Try creating a new alias /alias
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/alias' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "test alias for user@mailu.io and admin@mailu.io",
|
||||
"destination": [
|
||||
"user@mailu.io",
|
||||
"admin@mailu.io"
|
||||
],
|
||||
"wildcard": false,
|
||||
"email": "test@mailu.io"
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Created alias test@mailu.io succcessfully for user@mailu.io and admin@mailu.io"
|
||||
|
||||
curl --silent --insecure -X 'POST' \
|
||||
'https://localhost/api/v1/alias' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "test2 alias for user@mailu.io",
|
||||
"destination": [
|
||||
"user@mailu.io"
|
||||
],
|
||||
"wildcard": false,
|
||||
"email": "test2@mailu.io"
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Created alias test2@mailu.io succcessfully for user@mailu.io "
|
||||
|
||||
# Try retrieving all aliases /alias. We expect to retrieve 2
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/alias' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o '"destination":' | grep -c '"destination":' | grep 2
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Successfully retrieved 2 aliases"
|
||||
|
||||
# Try looking up the aliases for a specific domain /alias/destination/{domain}. We expect to retrieve 2
|
||||
curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/alias/destination/mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep -o '"destination":' | grep -c '"destination":' | grep 2
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Successfully retrieved 2 aliases"
|
||||
|
||||
# Try deleting a specific alias /alias/{alias}
|
||||
curl --silent --insecure -X 'DELETE' \
|
||||
'https://localhost/api/v1/alias/test2%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
| grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Deleted alias test2@mailu.io succcessfully"
|
||||
|
||||
# Try updating a specific alias /alias/{alias}
|
||||
curl --silent --insecure -X 'PATCH' \
|
||||
'https://localhost/api/v1/alias/test%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"comment": "updated_comment",
|
||||
"destination": [
|
||||
"user@mailu.io"
|
||||
],
|
||||
"wildcard": true
|
||||
}' | grep 200
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Updated alias test2@mailu.io succcessfully"
|
||||
|
||||
# Try looking up a specific alias /alias/{alias}.
|
||||
#Check if values were updated correctyly in previous step.
|
||||
response=$(curl --silent --insecure -X 'GET' \
|
||||
'https://localhost/api/v1/alias/test%40mailu.io' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: apitest')
|
||||
echo $response | grep 'admin@mailu.io'
|
||||
if [ $? -ne 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed that destination admin@mailu.io is removed from alias test@mailu.io"
|
||||
echo $response | grep 'updated_comment'
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed that comment attribute is updated successfully"
|
||||
|
||||
echo "Finished 05_test_alias_interfaces.sh"
|
||||
112
tests/compose/api/docker-compose.yml
Normal file
112
tests/compose/api/docker-compose.yml
Normal file
@@ -0,0 +1,112 @@
|
||||
# This file is auto-generated by the Mailu configuration wizard.
|
||||
# Please read the documentation before attempting any change.
|
||||
# Generated for compose flavor
|
||||
|
||||
version: '3.6'
|
||||
|
||||
services:
|
||||
|
||||
# External dependencies
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- "/mailu/redis:/data"
|
||||
|
||||
# Core services
|
||||
front:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-local}
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
logging:
|
||||
driver: json-file
|
||||
ports:
|
||||
- "127.0.0.1:80:80"
|
||||
- "127.0.0.1:443:443"
|
||||
- "127.0.0.1:25:25"
|
||||
- "127.0.0.1:465:465"
|
||||
- "127.0.0.1:587:587"
|
||||
- "127.0.0.1:110:110"
|
||||
- "127.0.0.1:995:995"
|
||||
- "127.0.0.1:143:143"
|
||||
- "127.0.0.1:993:993"
|
||||
- "127.0.0.1:4190:4190"
|
||||
volumes:
|
||||
- "/mailu/certs:/certs"
|
||||
|
||||
admin:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}admin:${MAILU_VERSION:-local}
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
volumes:
|
||||
- "/mailu/data:/data"
|
||||
- "/mailu/dkim:/dkim"
|
||||
dns:
|
||||
- 192.168.203.254
|
||||
depends_on:
|
||||
- redis
|
||||
- resolver
|
||||
|
||||
imap:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}dovecot:${MAILU_VERSION:-local}
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
volumes:
|
||||
- "/mailu/mail:/mail"
|
||||
- "/mailu/overrides:/overrides"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
smtp:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}postfix:${MAILU_VERSION:-local}
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
volumes:
|
||||
- "/mailu/overrides:/overrides"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
oletools:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}oletools:${MAILU_VERSION:-local}
|
||||
hostname: oletools
|
||||
restart: always
|
||||
networks:
|
||||
- noinet
|
||||
|
||||
antispam:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-local}
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
networks:
|
||||
- default
|
||||
- noinet
|
||||
volumes:
|
||||
- "/mailu/filter:/var/lib/rspamd"
|
||||
- "/mailu/dkim:/dkim"
|
||||
- "/mailu/overrides/rspamd:/etc/rspamd/override.d"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
# Optional services
|
||||
|
||||
resolver:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local}
|
||||
env_file: mailu.env
|
||||
restart: always
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 192.168.203.254
|
||||
|
||||
# Webmail
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 192.168.203.0/24
|
||||
noinet:
|
||||
driver: bridge
|
||||
internal: true
|
||||
151
tests/compose/api/mailu.env
Normal file
151
tests/compose/api/mailu.env
Normal file
@@ -0,0 +1,151 @@
|
||||
# Mailu main configuration file
|
||||
#
|
||||
# Generated for compose flavor
|
||||
#
|
||||
# This file is autogenerated by the configuration management wizard.
|
||||
# For a detailed list of configuration variables, see the documentation at
|
||||
# https://mailu.io
|
||||
|
||||
###################################
|
||||
# Common configuration variables
|
||||
###################################
|
||||
|
||||
# Set this to the path where Mailu data and configuration is stored
|
||||
# This variable is now set directly in `docker-compose.yml by the setup utility
|
||||
# ROOT=/mailu
|
||||
|
||||
# Mailu version to run (1.0, 1.1, etc. or master)
|
||||
#VERSION=master
|
||||
|
||||
# Set to a randomly generated 16 bytes string
|
||||
SECRET_KEY=HGZCYGVI6FVG31HS
|
||||
|
||||
# Address where listening ports should bind
|
||||
# This variables are now set directly in `docker-compose.yml by the setup utility
|
||||
# PUBLIC_IPV4= 127.0.0.1 (default: 127.0.0.1)
|
||||
# PUBLIC_IPV6= (default: ::1)
|
||||
|
||||
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
|
||||
SUBNET=192.168.203.0/24
|
||||
|
||||
# Main mail domain
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with commas
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
||||
# Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt)
|
||||
TLS_FLAVOR=cert
|
||||
|
||||
# Authentication rate limit (per source IP address)
|
||||
AUTH_RATELIMIT=10/minute;1000/hour
|
||||
|
||||
# Opt-out of statistics, replace with "True" to opt out
|
||||
DISABLE_STATISTICS=False
|
||||
|
||||
###################################
|
||||
# Optional features
|
||||
###################################
|
||||
|
||||
# Expose the admin interface (value: true, false)
|
||||
ADMIN=true
|
||||
|
||||
# Choose which webmail to run if any (values: roundcube, snappymail, none)
|
||||
WEBMAIL=none
|
||||
|
||||
# Dav server implementation (value: radicale, none)
|
||||
WEBDAV=none
|
||||
|
||||
# Antivirus solution (value: clamav, none)
|
||||
#ANTIVIRUS=none
|
||||
|
||||
#Antispam solution
|
||||
ANTISPAM=none
|
||||
|
||||
#RESTful API
|
||||
API=true
|
||||
|
||||
# Scan Macros solution (value: true, false)
|
||||
SCAN_MACROS=True
|
||||
|
||||
###################################
|
||||
# Mail settings
|
||||
###################################
|
||||
|
||||
# Message size limit in bytes
|
||||
# Default: accept messages up to 50MB
|
||||
MESSAGE_SIZE_LIMIT=50000000
|
||||
|
||||
# Networks granted relay permissions
|
||||
# Use this with care, all hosts in this networks will be able to send mail without authentication!
|
||||
RELAYNETS=
|
||||
|
||||
# Will relay all outgoing mails if configured
|
||||
RELAYHOST=
|
||||
|
||||
# Show fetchmail functionality in admin interface
|
||||
FETCHMAIL_ENABLED=false
|
||||
|
||||
# Fetchmail delay
|
||||
FETCHMAIL_DELAY=600
|
||||
|
||||
# Recipient delimiter, character used to delimiter localpart from custom address part
|
||||
RECIPIENT_DELIMITER=+
|
||||
|
||||
# DMARC rua and ruf email
|
||||
DMARC_RUA=admin
|
||||
DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
||||
###################################
|
||||
# Web settings
|
||||
###################################
|
||||
|
||||
# Path to the admin interface if enabled
|
||||
WEB_ADMIN=/admin
|
||||
|
||||
# Path to the webmail if enabled
|
||||
WEB_WEBMAIL=/webmail
|
||||
|
||||
WEB_API=/api
|
||||
|
||||
# Website name
|
||||
SITENAME=Mailu
|
||||
|
||||
# Linked Website URL
|
||||
WEBSITE=https://mailu.io
|
||||
|
||||
|
||||
|
||||
###################################
|
||||
# Advanced settings
|
||||
###################################
|
||||
|
||||
# Log driver for front service. Possible values:
|
||||
# json-file (default)
|
||||
# journald (On systemd platforms, useful for Fail2Ban integration)
|
||||
# syslog (Non systemd platforms, Fail2Ban integration. Disables `docker compose log` for front!)
|
||||
# LOG_DRIVER=json-file
|
||||
|
||||
# Docker-compose project name, this will prepended to containers names.
|
||||
COMPOSE_PROJECT_NAME=mailu
|
||||
|
||||
# Header to take the real ip from
|
||||
REAL_IP_HEADER=
|
||||
|
||||
# IPs for nginx set_real_ip_from (CIDR list separated by commas)
|
||||
REAL_IP_FROM=
|
||||
|
||||
# choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no)
|
||||
REJECT_UNLISTED_RECIPIENT=
|
||||
|
||||
API_TOKEN=apitest
|
||||
Reference in New Issue
Block a user