diff --git a/dockers/apikaz/source/app.py b/dockers/apikaz/source/app.py
index 6fc40ad..50d1afe 100644
--- a/dockers/apikaz/source/app.py
+++ b/dockers/apikaz/source/app.py
@@ -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,8 +36,41 @@ 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 ?
#*************************************************
@@ -47,7 +83,7 @@ swagger = Swagger(app, template={
#TODO: au lieu d'avoir les IP en dur, prendre le fichier allow_ip'
trusted_ips = [
-"82.64.20.246",
+"82.64.20.246",
"31.39.14.228",
"51.75.112.172",
"80.11.47.59",
@@ -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" + "
"
-
- # 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,12 +157,64 @@ 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" + "
"
+
+ # 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')
def favicon():
# return send_from_directory(os.path.join(app.root_path, 'static'),'favicon.ico')
return '', 204
+
#*************************************************
#la page d'accueil est vide
@@ -161,16 +222,51 @@ def favicon():
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:
@@ -184,7 +280,7 @@ class Password_create(Resource):
try:
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
new_password="_"+output.decode("utf-8")+"_"
- return new_password,200
+ return new_password,200
except subprocess.CalledProcessError as e:
return e.output.decode("utf-8"), 400
@@ -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,19 +370,23 @@ api.add_resource(Paheko_users, '/paheko/user/category/')
#*************************************************
class Paheko_user(Resource):
+
def __init__(self):
global paheko_ident, paheko_pass, paheko_url
self.paheko_ident = paheko_ident
self.paheko_pass = paheko_pass
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/', endpoint='paheko_get_user', methods=['GET'])
-api.add_resource(Paheko_user, '/paheko/user///', endpoint='paheko_maj_user', methods=['PUT'])
+api.add_resource(Paheko_user, '/paheko/user///', 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
+ - Paheko
+ security:
+ - Bearer: []
parameters:
- in: path
name: action
@@ -411,7 +544,7 @@ class Paheko_users_action(Resource):
else:
return "pas de résultat", response.status_code
-api.add_resource(Paheko_users_action, '/paheko/users/')
+api.add_resource(Paheko_users_action, '/paheko/users/')
#*************************************************
#*******MATTERMOST********************************
@@ -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//', 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/')
#*************************************************
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/')
#*************************************************
# 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'])
#*************************************************
#*************************************************
diff --git a/dockers/apikaz/source/requirements.txt b/dockers/apikaz/source/requirements.txt
index 62ab067..f0c8dd3 100644
--- a/dockers/apikaz/source/requirements.txt
+++ b/dockers/apikaz/source/requirements.txt
@@ -7,3 +7,5 @@ passlib
unidecode
email-validator
python-ldap
+flask-jwt-extended
+BeautifulSoup4