ajout gestion token et basic auth sur apikaz

This commit is contained in:
HPL 2024-08-19 23:36:49 +02:00
parent 7c52d4844b
commit 94cbbc1006
2 changed files with 331 additions and 99 deletions

View File

@ -14,6 +14,8 @@ from flask import Flask, jsonify, send_from_directory, request, abort, json, Res
from flask_mail import Mail, Message
from flasgger import Swagger
from flask_restful import Api, Resource
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
from passlib.hash import sha512_crypt
from unidecode import unidecode
from email_validator import validate_email, EmailNotValidError
@ -23,6 +25,7 @@ from bs4 import BeautifulSoup
from datetime import datetime
app = Flask(__name__)
jwt = JWTManager(app)
api = Api(app)
app.logger.setLevel(logging.DEBUG)
@ -33,9 +36,42 @@ swagger = Swagger(app, template={
"title": "L'API Kaz de la mort qui tue",
"version": "0.2.0",
"description": "Permettre des opérations de gestion des services kaz avec des écrans Ouaib"
},
"tags": [
{"name": "Authentication", "description": "Auth related operations"},
{"name": "Test", "description": "pour tester des conneries"},
{"name": "Password", "description": "Gestion Mdp"},
{"name": "Paheko", "description": "Gestion Paheko"},
{"name": "Mattermost", "description": "Gestion Mattermost Authent"},
{"name": "Mattermost User", "description": "Gestion Mattermost User"},
{"name": "Mattermost Team", "description": "Gestion Mattermost Team"},
{"name": "Ldap", "description": "Gestion Ldap"},
{"name": "Cloud", "description": "Gestion Cloud Général"},
{"name": "Sympa", "description": "Gestion Sympa"},
{"name": "Quota", "description": "Gestion Quota"},
{"name": "Dns", "description": "Gestion Dns"},
{"name": "Kaz User", "description": "Gestion Kaz User"}
],
"securityDefinitions": {
"basicAuth": {
"type": "basic",
"description": "Basic Authentication with username and password"
},
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header",
"description": "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'"
}
}
})
#TODO:
# check variables
# fail2ban (ou alors sur traefik)
# découper app.py en service
# quels scripts bash garder ?
#*************************************************
# Configuration du logger
@ -58,44 +94,17 @@ trusted_ips = [
"80.67.176.91",
"89.234.177.119",
"78.127.1.19",
"80.215.236.243",
"78.117.86.68",
"80.215.236.168"
"80.215.236.243"
]
@app.before_request
def limit_remote_addr():
if request.environ['HTTP_X_FORWARDED_FOR'] not in trusted_ips:
abort(jsonify(message="Et pis quoi encore "+request.environ['HTTP_X_FORWARDED_FOR']), 400)
#*************************************************
@app.route('/print_env')
def print_environment():
# Crée une chaîne de caractères pour stocker les variables d'environnement
env_string = ""
# Itère sur les variables d'environnement et les ajoute à la chaîne de caractères
for key, value in os.environ.items():
env_string += f"{key}: {value}\n" + "<br>"
# Retourne la chaîne de caractères contenant les variables d'environnement
return env_string
#*************************************************
#***** DEBUT Quelques fonctions utiles ***********
#*************************************************
#pour injecter la date dans dans le contexte des template
@app.context_processor
def inject_now():
return {'now': datetime.now}
#*************************************************
#***** FIN Quelques fonctions utiles ***********
#*************************************************
#variables globales
#*************************************************
#le secret pour générer les tokens
#app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY')
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY', 'your_jwt_secret_key')
#le paheko de kaz
paheko_ident=os.environ.get('paheko_API_USER')
@ -148,6 +157,57 @@ MAIL_USERNAME=app.config['MAIL_USERNAME']
serveur_imap = os.environ.get('serveur_imap')
mot_de_passe_mail=os.environ.get('mot_de_passe_mail')
#*************************************************
@app.before_request
def limit_remote_addr():
if request.environ['HTTP_X_FORWARDED_FOR'] not in trusted_ips:
abort(jsonify(message="Et pis quoi encore ?"), 400)
#*************************************************
#authent mdp/pass basique
def check_auth(username, password):
return username == os.environ.get('apikaz_doc_user') and password == os.environ.get('apikaz_doc_password')
def authenticate():
return Response('tssssss.\n', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
@app.before_request
def require_basic_auth():
if request.path.startswith('/apidocs') or request.path.startswith('/print_env'):
#if request.path.startswith('/'):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
#*************************************************
#DANGER: ne jamais mettre print_env en PROD
@app.route('/print_env')
def print_environment():
# Crée une chaîne de caractères pour stocker les variables d'environnement
env_string = ""
# Itère sur les variables d'environnement et les ajoute à la chaîne de caractères
for key, value in os.environ.items():
env_string += f"{key}: {value}\n" + "<br>"
# Retourne la chaîne de caractères contenant les variables d'environnement
return env_string
#*************************************************
#***** DEBUT Quelques fonctions utiles ***********
#*************************************************
#pour injecter la date dans dans le contexte des template
@app.context_processor
def inject_now():
return {'now': datetime.now}
#*************************************************
#***** FIN Quelques fonctions utiles ***********
#*************************************************
#*************************************************
@app.route('/favicon.ico')
@ -155,22 +215,58 @@ def favicon():
# return send_from_directory(os.path.join(app.root_path, 'static'),'favicon.ico')
return '', 204
#*************************************************
#la page d'accueil est vide
@app.route('/')
def silence():
return ""
#*************************************************
# obtenir un token
@app.route('/get_token', methods=['GET'])
def get_token():
"""
Get JWT token with basic auth
---
tags:
- Authentication
security:
- basicAuth: []
responses:
200:
description: Token generated successfully
schema:
type: object
properties:
access_token:
type: string
description: JWT access token
401:
description: Unauthorized
"""
auth = request.authorization
if auth and check_auth(auth.username, auth.password):
# Créez un token JWT après une authentification réussie
access_token = create_access_token(identity=auth.username)
return jsonify(access_token=access_token)
else:
return authenticate()
#*************************************************
#*******MDP***************************************
#*************************************************
class Password_create(Resource):
@jwt_required()
def get(self):
"""
créer un password qui colle avec les appli kaz
---
tags:
- Password
security:
- Bearer: []
parameters: []
responses:
200:
@ -196,12 +292,16 @@ api.add_resource(Password_create, '/password/create')
#*************************************************
class Paheko_categories(Resource):
@jwt_required()
def get(self):
"""
Récupérer les catégories Paheko avec le compteur associé
---
tags:
- Paheko
security:
- Bearer: []
parameters: []
responses:
200:
@ -227,12 +327,16 @@ api.add_resource(Paheko_categories, '/paheko/user/categories')
#*************************************************
class Paheko_users(Resource):
@jwt_required()
def get(self,categorie):
"""
Afficher les membres d'une catégorie Paheko
---
tags:
- Paheko
security:
- Bearer: []
parameters:
- in: path
name: categorie
@ -248,6 +352,9 @@ class Paheko_users(Resource):
global paheko_ident, paheko_pass, paheko_url
auth = (paheko_ident, paheko_pass)
if not categorie.isdigit():
return 'Id de category non valide', 400
api_url = paheko_url + '/api/user/category/'+categorie+'.json'
response = requests.get(api_url, auth=auth)
@ -263,6 +370,7 @@ api.add_resource(Paheko_users, '/paheko/user/category/<categorie>')
#*************************************************
class Paheko_user(Resource):
def __init__(self):
global paheko_ident, paheko_pass, paheko_url
self.paheko_ident = paheko_ident
@ -270,12 +378,15 @@ class Paheko_user(Resource):
self.paheko_url = paheko_url
self.auth = (self.paheko_ident, self.paheko_pass)
@jwt_required()
def get(self,ident):
"""
Afficher un membre de Paheko par son email kaz ou son numéro ou le non court de l'orga
---
tags:
- Paheko
security:
- Bearer: []
parameters:
- in: path
name: ident
@ -289,8 +400,10 @@ class Paheko_user(Resource):
description: N'existe pas
"""
if '@' in ident:
data = { "sql": f"select * from users where email='{ident}'" }
emailmatchregexp = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
if emailmatchregexp.match(ident):
data = { "sql": f"select * from users where email='{ident}' or alias = '{ident}'" }
api_url = self.paheko_url + '/api/sql/'
response = requests.post(api_url, auth=self.auth, data=data)
#TODO: if faut Rechercher count et vérifier que = 1 et supprimer le count=1 dans la réponse
@ -298,26 +411,35 @@ class Paheko_user(Resource):
api_url = self.paheko_url + '/api/user/'+ident
response = requests.get(api_url, auth=self.auth)
else:
data = { "sql": f"select * from users where admin_orga=1 and nom_orga='{ident}'" }
nomorga = re.sub(r'\W+', '', ident) # on vire les caractères non alphanumérique
data = { "sql": f"select * from users where admin_orga=1 and nom_orga='{nomorga}'" }
api_url = self.paheko_url + '/api/sql/'
response = requests.post(api_url, auth=self.auth, data=data)
#TODO:if faut Rechercher count et vérifier que = 1 et supprimer le count=1 dans la réponse
if response.status_code == 200:
data = response.json()
return jsonify(data)
if data["count"] == 1:
return jsonify(data["results"][0])
elif data["count"] == 0:
return "pas de résultat", 400
else:
return "Plusieurs utilisateurs correspondent ?!", 400
else:
#return jsonify({'error': 'La requête a échoué'}), response.status_code
return "pas de résultat", response.status_code
#*************************************************
@jwt_required()
def put(self,ident,field,new_value):
"""
Modifie la valeur d'un champ d'un membre paheko (ident= numéro paheko ou email kaz)
---
tags:
- Paheko
security:
- Bearer: []
parameters:
- in: path
name: ident
@ -345,7 +467,8 @@ class Paheko_user(Resource):
"""
#récupérer le numero paheko si on fournit un email kaz
if '@' in ident:
emailmatchregexp = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
if emailmatchregexp.match(ident):
data = { "sql": f"select id from users where email='{ident}'" }
api_url = self.paheko_url + '/api/sql/'
response = requests.post(api_url, auth=self.auth, data=data)
@ -361,16 +484,23 @@ class Paheko_user(Resource):
ident = data['results'][0]['id']
else:
return "pas de résultat", response.status_code
elif not ident.isdigit():
return "Identifiant utilisateur invalide", response.status_code
regexp = re.compile("[^a-zA-Z0-9 \\r\\n\\t" + re.escape(string.punctuation) + "]")
valeur = regexp.sub('',new_value) # mouais, il faudrait être beaucoup plus précis ici en fonction des champs qu'on accepte...
champ = re.sub(r'\W+','',field) # pas de caractères non alphanumériques ici, dans l'idéal, c'est à choisir dans une liste plutot
api_url = self.paheko_url + '/api/user/'+str(ident)
payload = {field: new_value}
payload = {champ: valeur}
response = requests.post(api_url, auth=self.auth, data=payload)
return response.json(),response.status_code
#*************************************************
api.add_resource(Paheko_user, '/paheko/user/<ident>', endpoint='paheko_get_user', methods=['GET'])
api.add_resource(Paheko_user, '/paheko/user/<ident>/<field>/<new_value>', endpoint='paheko_maj_user', methods=['PUT'])
api.add_resource(Paheko_user, '/paheko/user/<ident>/<string:field>/<string:new_value>', endpoint='paheko_maj_user', methods=['PUT'])
#*************************************************
@ -382,12 +512,15 @@ class Paheko_users_action(Resource):
self.paheko_pass = paheko_pass
self.paheko_url = paheko_url
@jwt_required()
def get(self, action):
"""
retourne tous les membres de paheko avec une action à mener (création du compte kaz / modification...)
---
tags:
- Paheko
security:
- Bearer: []
parameters:
- in: path
name: action
@ -431,12 +564,16 @@ def Mattermost_authenticate():
#*************************************************
class Mattermost_message(Resource):
@jwt_required()
def post(self,message,equipe="kaz",canal="creation-comptes"):
"""
Envoyer un message dans une Equipe/Canal de MM
---
tags:
- Mattermost
security:
- Bearer: []
parameters:
- in: path
name: equipe
@ -476,12 +613,15 @@ class Mattermost_user(Resource):
Mattermost_authenticate()
#*************************************************
@jwt_required()
def get(self,user):
"""
Le user existe t-il sur MM ?
---
tags:
- Mattermost User
security:
- Bearer: []
parameters:
- in: path
name: user
@ -503,13 +643,15 @@ class Mattermost_user(Resource):
return 404 # Le nom d'utilisateur n'existe pas
#*************************************************
@jwt_required()
def post(self,user,email,password):
"""
Créer un utilisateur sur MM
---
tags:
- Mattermost User
security:
- Bearer: []
parameters:
- in: path
name: user
@ -541,12 +683,15 @@ class Mattermost_user(Resource):
#*************************************************
@jwt_required()
def delete(self,email):
"""
Supprimer un utilisateur sur MM
---
tags:
- Mattermost User
security:
- Bearer: []
parameters:
- in: path
name: email
@ -568,12 +713,16 @@ class Mattermost_user(Resource):
return e.output.decode("utf-8"), 400
#*************************************************
@jwt_required()
def put(self,email,new_password):
"""
Changer un password pour un utilisateur de MM
---
tags:
- Mattermost User
security:
- Bearer: []
parameters:
- in: path
name: email
@ -607,12 +756,16 @@ api.add_resource(Mattermost_user, '/mattermost/user/change/password/<string:emai
#*************************************************
class Mattermost_user_team(Resource):
@jwt_required()
def post(self,email,equipe):
"""
Affecte un utilisateur à une équipe MM
---
tags:
- Mattermost Team
security:
- Bearer: []
parameters:
- in: path
name: email
@ -643,12 +796,15 @@ api.add_resource(Mattermost_user_team, '/mattermost/user/team/<string:email>/<st
#*************************************************
class Mattermost_user_channel(Resource):
@jwt_required()
def post(self,email,equipe,canal):
"""
Affecte un utilisateur à un canal MM
---
tags:
- Mattermost
security:
- Bearer: []
parameters:
- in: path
name: email
@ -688,12 +844,16 @@ class Mattermost_team(Resource):
Mattermost_authenticate()
#*************************************************
@jwt_required()
def get(self):
"""
Lister les équipes sur MM
---
tags:
- Mattermost Team
security:
- Bearer: []
parameters: []
responses:
200:
@ -715,12 +875,15 @@ class Mattermost_team(Resource):
return e.output.decode("utf-8"), 400
#*************************************************
@jwt_required()
def post(self,equipe,email):
"""
Créer une équipe sur MM et affecter un admin si email est renseigné (set admin marche )
---
tags:
- Mattermost Team
security:
- Bearer: []
parameters:
- in: path
name: equipe
@ -752,12 +915,15 @@ class Mattermost_team(Resource):
return e.output.decode("utf-8"), 400
#*************************************************
@jwt_required()
def delete(self,equipe):
"""
Supprimer une équipe sur MM
---
tags:
- Mattermost Team
security:
- Bearer: []
parameters:
- in: path
name: equipe
@ -807,12 +973,15 @@ class Ldap_user(Resource):
allowed_fields = ['mailDeSecours', 'mailEnabled', 'nextcloudEnabled', 'mobilizonEnabled', 'agoraEnabled', 'userPassword', 'identifiantKaz', 'mailAlias', 'quota']
return field in allowed_fields
@jwt_required()
def get(self, email):
"""
Vérifier si un utilisateur avec cet email existe dans le LDAP soit comme mail principal soit comme alias
---
tags:
- Ldap
security:
- Bearer: []
parameters:
- in: path
name: email
@ -857,13 +1026,15 @@ class Ldap_user(Resource):
#*************************************************
@jwt_required()
def delete(self, email):
"""
Supprimer un utilisateur du LDAP par son adresse e-mail
---
tags:
- Ldap
security:
- Bearer: []
parameters:
- in: path
name: email
@ -907,13 +1078,15 @@ class Ldap_user(Resource):
#*************************************************
@jwt_required()
def post(self, email):
"""
Ajouter, supprimer ou modifier un champ pour l'utilisateur LDAP
---
tags:
- Ldap
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1007,13 +1180,15 @@ class Ldap_user(Resource):
return str(e), 400
#*************************************************
@jwt_required()
def put(self, email, **kwargs):
"""
Créer une nouvelle entrée dans le LDAP pour un nouvel utilisateur. QUESTION: A QUOI SERVENT PRENOM/NOM/IDENT_KAZ DANS LE LDAP ? POURQUOI 3 QUOTA ?
---
tags:
- Ldap
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1126,12 +1301,16 @@ api.add_resource(Ldap_user, '/ldap/user/add/<string:email>', endpoint='ldap_user
#TODO: pas réussi à faire une seule classe Cloud_user avec 2 méthodes get/delete
class Cloud_user(Resource):
@jwt_required()
def get(self, email):
"""
Existe dans le cloud général ?
---
tags:
- Cloud
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1167,6 +1346,8 @@ api.add_resource(Cloud_user, '/cloud/user/<string:email>')
#*************************************************
class Cloud_user_delete(Resource):
@jwt_required()
def delete(self, email):
"""
Supprime le compte dans le cloud général
@ -1174,6 +1355,8 @@ class Cloud_user_delete(Resource):
---
tags:
- Cloud
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1209,12 +1392,15 @@ api.add_resource(Cloud_user_delete, '/cloud/user/delete/<string:email>')
#*************************************************
# class Cloud_user_change(Resource):
# @jwt_required()
# def put(self, email, new_password):
# """
# Modifie le mot de passe d'un Utilisateur dans le cloud général: QUESTION: A PRIORI INUTILE CAR LIE AU LDAP
# ---
# tags:
# - Cloud
# security:
# - Bearer: []
# parameters:
# - in: path
# name: email
@ -1278,12 +1464,15 @@ class Sympa_user(Resource):
except subprocess.CalledProcessError as e:
return e.output.decode("utf-8"), 400 # Retourne la sortie de la commande et un code d'erreur 400
@jwt_required()
def post(self, email, liste):
"""
Ajouter un email dans une liste sympa
---
tags:
- Sympa
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1302,12 +1491,15 @@ class Sympa_user(Resource):
output, status_code = self._execute_sympa_command(email, liste, 'add')
return output, status_code
@jwt_required()
def delete(self, email, liste):
"""
Supprimer un email dans une liste sympa
---
tags:
- Sympa
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1340,13 +1532,15 @@ class Quota(Resource):
# sur kazkouil.fr, j'ai modifié /etc/dovecot/conf.d/20-lmtp.conf
#mail_plugins = $mail_plugins sieve quota
@jwt_required()
def get(self, email):
"""
Récupérer la place prise par une BAL
Récupérer la place prise par une BAL (EN COURS)
---
tags:
- Quota (EN COURS)
- Quota
security:
- Bearer: []
parameters:
- in: path
name: email
@ -1402,12 +1596,15 @@ class Dns_serveurs(Resource):
self.gandi_key = gandi_key
self.gandi_url_api = gandi_url_api
@jwt_required()
def get(self):
"""
Renvoie tous les serveurs kaz de la zone dns
---
tags:
- Dns
security:
- Bearer: []
responses:
200:
description: Succès, liste des serveurs
@ -1440,13 +1637,15 @@ class Dns(Resource):
self.dns_serveurs_resource = Dns_serveurs()
#*************************************************
@jwt_required()
def get(self,sdomaine):
"""
Le sous-domaine existe t-il dans la zone dns avec un enreg CNAME ?
---
tags:
- Dns
security:
- Bearer: []
parameters:
- in: path
name: sdomaine
@ -1466,12 +1665,15 @@ class Dns(Resource):
#*************************************************
@jwt_required()
def delete(self,sdomaine):
"""
suppression du sdomaine
---
tags:
- Dns
security:
- Bearer: []
parameters:
- in: path
name: sdomaine
@ -1490,12 +1692,15 @@ class Dns(Resource):
#*************************************************
@jwt_required()
def post(self,sdomaine,serveur):
"""
Créé le sous-domaine de type CNAME qui pointe sur serveur
---
tags:
- Dns
security:
- Bearer: []
parameters:
- in: path
name: sdomaine
@ -1564,12 +1769,15 @@ class Kaz_user(Resource):
#********************************************************************************************
@jwt_required()
def delete(self):
"""
Utile pour les tests de createUser. Avant le POST de /kaz/create/users. Ça permet de supprimer/maj les comptes.
---
tags:
- Kaz
- Kaz User
security:
- Bearer: []
parameters: []
responses:
201:
@ -1608,12 +1816,15 @@ class Kaz_user(Resource):
#********************************************************************************************
@jwt_required()
def post(self):
"""
Créé un nouveau kaznaute: inscription sur MM / Cloud / email + msg sur MM + email à partir de action="a créer" sur paheko
---
tags:
- Kaz
- Kaz User
security:
- Bearer: []
parameters: []
responses:
201:
@ -1770,12 +1981,15 @@ class Test(Resource):
#global mattermost_url, sympa_url, webmail_url, mdp_url, site_url, nc_url
#********************************************************************************************
@jwt_required()
def get(self):
"""
Pour tester des conneries
Pour tester des conneries: # test lançement de cmde ssh sur des serveurs distants:
---
tags:
- a simple test
- Test
security:
- Bearer: []
parameters: []
responses:
201:
@ -1784,6 +1998,23 @@ class Test(Resource):
description: KO
"""
#********************************************************************************************
# test lançcement de cmde ssh sur des serveurs distants:
# il faut au préalable que la clé publique de root du conteneur apikaz soit dans authorized key du user fabricer de la machine 163.172.94.54
# clé à créer dans le Dockerfile
# risque sécu ?
cmd="ssh -p 2201 fabricer@163.172.94.54 mkdir -p /tmp/toto"
try:
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
return "ok",200
except subprocess.CalledProcessError as e:
return e.output.decode("utf-8"), 400
#********************************************************************************************
# #***** test suppression de toutes les équipes de MM sauf KAZ
# res,status=self.mattermost_team_resource=Mattermost_team().get()
# for equipe in res:
@ -1794,39 +2025,39 @@ class Test(Resource):
#**** test messagerie
NOM="toto"
EMAIL_SOUHAITE='f@kaz.bzh'
PASSWORD="toto"
QUOTA="1"
ADMIN_ORGA="0"
context = {
'ADMIN_ORGA': ADMIN_ORGA,
'NOM': NOM,
'EMAIL_SOUHAITE': EMAIL_SOUHAITE,
'PASSWORD': PASSWORD,
'QUOTA': QUOTA,
'URL_WEBMAIL': webmail_url,
'URL_AGORA': mattermost_url,
'URL_MDP': mdp_url,
'URL_LISTE': sympa_url,
'URL_SITE': site_url,
'URL_CLOUD': cloud_url
}
subject = "KAZ: confirmation d'inscription !"
sender=app.config['MAIL_USERNAME']
reply_to = app.config['MAIL_REPLY_TO']
msg = Message(subject=subject, sender=sender, reply_to=reply_to, recipients=[EMAIL_SOUHAITE])
msg.html = render_template('email_inscription.html', **context)
# Parsez le contenu HTML avec BeautifulSoup
soup = BeautifulSoup(msg.html, 'html.parser')
msg.body = soup.get_text()
mail.send(msg)
return "Message envoyé!"
# NOM="toto"
# EMAIL_SOUHAITE='f@kaz.bzh'
# PASSWORD="toto"
# QUOTA="1"
# ADMIN_ORGA="0"
#
# context = {
# 'ADMIN_ORGA': ADMIN_ORGA,
# 'NOM': NOM,
# 'EMAIL_SOUHAITE': EMAIL_SOUHAITE,
# 'PASSWORD': PASSWORD,
# 'QUOTA': QUOTA,
# 'URL_WEBMAIL': webmail_url,
# 'URL_AGORA': mattermost_url,
# 'URL_MDP': mdp_url,
# 'URL_LISTE': sympa_url,
# 'URL_SITE': site_url,
# 'URL_CLOUD': cloud_url
# }
#
# subject = "KAZ: confirmation d'inscription !"
# sender=app.config['MAIL_USERNAME']
# reply_to = app.config['MAIL_REPLY_TO']
#
# msg = Message(subject=subject, sender=sender, reply_to=reply_to, recipients=[EMAIL_SOUHAITE])
# msg.html = render_template('email_inscription.html', **context)
#
# # Parsez le contenu HTML avec BeautifulSoup
# soup = BeautifulSoup(msg.html, 'html.parser')
# msg.body = soup.get_text()
#
# mail.send(msg)
# return "Message envoyé!"
#********************************************************************************************
# #**** test ms erreur
@ -1847,8 +2078,7 @@ class Test(Resource):
# sleep(20)
# return str(lock_file), 201
api.add_resource(Test, '/atest', endpoint='atest', methods=['GET'])
api.add_resource(Test, '/test', endpoint='test', methods=['GET'])
#*************************************************
#*************************************************

View File

@ -7,3 +7,5 @@ passlib
unidecode
email-validator
python-ldap
flask-jwt-extended
BeautifulSoup4