1
0
mirror of https://github.com/Mailu/Mailu.git synced 2025-01-18 03:21:36 +02:00
2974: Add support for managing authentication tokens to RESTful API r=mergify[bot] a=Diman0

## What type of PR?

enhancement

## What does this PR do?
Authentication tokens could not be managed yet via the RESTful API. Support for this has been added to the RESTful API. It also contains a small bug fix for updating domains. 

### Related issue(s)
n/a1

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

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


Co-authored-by: Dimitri Huisman <diman@huisman.xyz>
This commit is contained in:
bors[bot] 2023-10-10 11:35:20 +00:00 committed by GitHub
commit d21eb8bea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 9 deletions

View File

@ -37,7 +37,8 @@ error_fields = api.model('Error', {
'message': fields.String,
})
from . import domains
from . import domain
from . import alias
from . import relay
from . import user
from . import token

View File

@ -115,13 +115,13 @@ class Domains(Resource):
if 'comment' in data:
domain_new.comment = data['comment']
if 'max_users' in data:
domain_new.comment = data['max_users']
domain_new.max_users = data['max_users']
if 'max_aliases' in data:
domain_new.comment = data['max_aliases']
domain_new.max_aliases = data['max_aliases']
if 'max_quota_bytes' in data:
domain_new.comment = data['max_quota_bytes']
domain_new.max_quota_bytes = data['max_quota_bytes']
if 'signup_enabled' in data:
domain_new.comment = data['signup_enabled']
domain_new.signup_enabled = data['signup_enabled']
models.db.session.add(domain_new)
#apply the changes
db.session.commit()
@ -177,13 +177,13 @@ class Domain(Resource):
if 'comment' in data:
domain_found.comment = data['comment']
if 'max_users' in data:
domain_found.comment = data['max_users']
domain_found.max_users = data['max_users']
if 'max_aliases' in data:
domain_found.comment = data['max_aliases']
domain_found.max_aliases = data['max_aliases']
if 'max_quota_bytes' in data:
domain_found.comment = data['max_quota_bytes']
domain_found.max_quota_bytes = data['max_quota_bytes']
if 'signup_enabled' in data:
domain_found.comment = data['signup_enabled']
domain_found.signup_enabled = data['signup_enabled']
models.db.session.add(domain_found)
#apply the changes

View File

@ -0,0 +1,170 @@
from flask_restx import Resource, fields, marshal
import validators, datetime
import flask
from passlib import pwd
from . import api, response_fields
from .. import common
from ... import models
db = models.db
token = api.namespace('token', description='Token operations')
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'),
'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'),
'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'),
})
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'),
})
token_user_post_response = api.model('TokenPostResponse', {
'id': fields.String(description='The record id of the token (unique identifier)', example='1'),
'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'),
'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(security='Bearer')
@common.api_token_authorization
def get(self):
"""List tokens"""
return models.Token.query.all()
@token.doc('create_token')
@token.expect(token_user_fields_post)
@token.response(200, 'Success', token_user_post_response)
@token.response(400, 'Input validation exception')
@token.response(409, 'Duplicate relay', response_fields)
@token.doc(security='Bearer')
@common.api_token_authorization
def post(self):
""" Create a new token"""
data = api.payload
email = data['email']
if not validators.email(email):
return { 'code': 400, 'message': f'Provided email address {email} 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
tokens = user_found.tokens
token_new = models.Token(user_email=data['email'])
if 'comment' in data:
token_new.comment = data['comment']
if 'AuthorizedIP' in data:
token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
token_new.set_password(raw_password)
models.db.session.add(token_new)
#apply the changes
db.session.commit()
response_dict = {
'id' : token_new.id,
'token' : raw_password,
'email' : token_new.user_email,
'comment' : token_new.comment,
'AuthorizedIP' : token_new.ip,
'Created': str(token_new.created_at),
}
return response_dict
@token.route('user/<string:email>')
class Token(Resource):
@token.doc('find_tokens_of_user')
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
@token.doc(security='Bearer')
@common.api_token_authorization
def get(self, email):
"Find tokens of user"
if not validators.email(email):
return { 'code': 400, 'message': f'Provided email address {email} 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
tokens = user_found.tokens
return tokens
@token.doc('create_token')
@token.expect(token_user_fields_post2)
@token.response(200, 'Success', token_user_post_response)
@token.response(400, 'Input validation exception')
@token.response(409, 'Duplicate relay', response_fields)
@token.doc(security='Bearer')
@common.api_token_authorization
def post(self, email):
""" Create a new token for user"""
data = api.payload
if not validators.email(email):
return { 'code': 400, 'message': f'Provided email address {email} 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
token_new = models.Token(user_email=email)
if 'comment' in data:
token_new.comment = data['comment']
if 'AuthorizedIP' in data:
token_new.ip = token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
token_new.set_password(raw_password)
models.db.session.add(token_new)
#apply the changes
db.session.commit()
response_dict = {
'id' : token_new.id,
'token' : raw_password,
'email' : token_new.user_email,
'comment' : token_new.comment,
'AuthorizedIP' : token_new.ip,
'Created': str(token_new.created_at),
}
return response_dict
@token.route('/<string:token_id>')
class Token(Resource):
@token.doc('find_token')
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
@token.doc(security='Bearer')
@common.api_token_authorization
def get(self, token_id):
"Find token"
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
@token.doc('delete_token')
@token.response(200, 'Success', response_fields)
@token.response(400, 'Input validation exception', response_fields)
@token.response(404, 'Token not found', response_fields)
@token.doc(security='Bearer')
@common.api_token_authorization
def delete(self, token_id):
""" Delete token """
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
db.session.delete(token)
db.session.commit()
return {'code': 200, 'message': f'Token with id {token_id} has been deleted'}, 200

View File

@ -0,0 +1 @@
Enhance RESTful API with functionality for managing authentication tokens of users