2021-05-29 18:22:51 +02:00
|
|
|
# -*- coding: UTF-8 -*
|
|
|
|
"""Decorators for permissions, roles and ScoDoc7 Zope compatibility
|
|
|
|
"""
|
|
|
|
from functools import wraps
|
|
|
|
import inspect
|
2022-04-02 10:56:10 +02:00
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
|
2021-06-21 14:40:58 +02:00
|
|
|
import werkzeug
|
2021-05-29 18:22:51 +02:00
|
|
|
import flask
|
2021-10-13 21:00:03 +02:00
|
|
|
from flask import g, current_app, request
|
|
|
|
from flask import abort, url_for, redirect
|
2021-05-29 18:22:51 +02:00
|
|
|
from flask_login import current_user
|
|
|
|
from flask_login import login_required
|
2021-09-09 16:11:05 +02:00
|
|
|
import flask_login
|
2021-06-25 18:25:46 +02:00
|
|
|
|
|
|
|
import app
|
2021-09-09 16:11:05 +02:00
|
|
|
from app.auth.models import User
|
2021-09-27 16:42:14 +02:00
|
|
|
import app.scodoc.sco_utils as scu
|
2023-01-17 14:57:49 +01:00
|
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ZUser(object):
|
|
|
|
"Emulating Zope User"
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"create, based on `flask_login.current_user`"
|
2021-06-26 21:57:54 +02:00
|
|
|
self.username = current_user.user_name
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.username
|
|
|
|
|
2021-06-24 10:59:03 +02:00
|
|
|
def has_permission(self, perm, dept=None):
|
2021-05-29 18:22:51 +02:00
|
|
|
"""check if this user as the permission `perm`
|
|
|
|
in departement given by `g.scodoc_dept`.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
2021-08-13 00:34:58 +02:00
|
|
|
def scodoc(func):
|
|
|
|
"""Décorateur pour toutes les fonctions ScoDoc
|
|
|
|
Affecte le département à g
|
|
|
|
et ouvre la connexion à la base
|
|
|
|
|
|
|
|
Set `g.scodoc_dept` and `g.scodoc_dept_id` if `scodoc_dept` is present
|
|
|
|
in the argument (for routes like
|
|
|
|
`/<scodoc_dept>/Scolarite/sco_exemple`).
|
2023-10-11 14:45:06 +02:00
|
|
|
Else set scodoc_dept=None, scodoc_dept_id=-1.
|
2021-08-13 00:34:58 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
@wraps(func)
|
|
|
|
def scodoc_function(*args, **kwargs):
|
2021-11-01 16:59:56 +01:00
|
|
|
# print("@scodoc")
|
2021-10-13 21:00:03 +02:00
|
|
|
# interdit les POST si pas loggué
|
2021-11-01 16:59:56 +01:00
|
|
|
if (
|
|
|
|
request.method == "POST"
|
|
|
|
and not current_user.is_authenticated
|
|
|
|
and not request.form.get(
|
|
|
|
"__ac_password"
|
|
|
|
) # exception pour compat API ScoDoc7
|
|
|
|
):
|
2021-10-26 00:13:42 +02:00
|
|
|
current_app.logger.info(
|
|
|
|
"POST by non authenticated user (request.form=%s)",
|
|
|
|
str(request.form)[:2048],
|
|
|
|
)
|
2021-10-13 21:00:03 +02:00
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"auth.login",
|
|
|
|
message="La page a expiré. Identifiez-vous et recommencez l'opération",
|
|
|
|
)
|
|
|
|
)
|
2021-08-13 00:34:58 +02:00
|
|
|
if "scodoc_dept" in kwargs:
|
|
|
|
dept_acronym = kwargs["scodoc_dept"]
|
2021-08-19 10:28:35 +02:00
|
|
|
# current_app.logger.info("setting dept to " + dept_acronym)
|
2021-08-13 00:34:58 +02:00
|
|
|
app.set_sco_dept(dept_acronym)
|
|
|
|
del kwargs["scodoc_dept"]
|
|
|
|
elif not hasattr(g, "scodoc_dept"):
|
2021-08-19 10:28:35 +02:00
|
|
|
# current_app.logger.info("setting dept to None")
|
2021-08-13 00:34:58 +02:00
|
|
|
g.scodoc_dept = None
|
|
|
|
g.scodoc_dept_id = -1 # invalide
|
2021-10-28 00:52:23 +02:00
|
|
|
|
2021-08-13 00:34:58 +02:00
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
|
|
|
return scodoc_function
|
|
|
|
|
|
|
|
|
2021-05-31 00:14:15 +02:00
|
|
|
def permission_required(permission):
|
2024-07-17 12:03:08 +02:00
|
|
|
"""Vérifie les permissions"""
|
|
|
|
|
|
|
|
# Attention: l'API utilise api_permission_required
|
2021-05-31 00:14:15 +02:00
|
|
|
def decorator(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated_function(*args, **kwargs):
|
2021-07-28 08:42:22 +02:00
|
|
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
|
|
|
if not current_user.has_permission(permission, scodoc_dept):
|
2022-04-30 06:10:45 +02:00
|
|
|
return current_app.login_manager.unauthorized()
|
2021-05-31 00:14:15 +02:00
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
2022-04-30 06:10:45 +02:00
|
|
|
return decorated_function
|
2021-05-31 00:14:15 +02:00
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2023-03-01 19:10:37 +01:00
|
|
|
def permission_required_compat_scodoc7(permission): # XXX TODO A SUPPRIMER
|
2021-10-13 21:00:03 +02:00
|
|
|
"""Décorateur pour les fonctions utilisées comme API dans ScoDoc 7
|
2021-09-09 16:11:05 +02:00
|
|
|
Comme @permission_required mais autorise de passer directement
|
|
|
|
les informations d'auth en paramètres:
|
|
|
|
__ac_name, __ac_password
|
|
|
|
"""
|
|
|
|
|
|
|
|
def decorator(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
# cherche les paramètre d'auth:
|
2021-11-01 16:59:56 +01:00
|
|
|
# print("@permission_required_compat_scodoc7")
|
2021-09-09 16:11:05 +02:00
|
|
|
auth_ok = False
|
|
|
|
if request.method == "GET":
|
|
|
|
user_name = request.args.get("__ac_name")
|
|
|
|
user_password = request.args.get("__ac_password")
|
|
|
|
elif request.method == "POST":
|
|
|
|
user_name = request.form.get("__ac_name")
|
|
|
|
user_password = request.form.get("__ac_password")
|
|
|
|
else:
|
|
|
|
abort(405) # method not allowed
|
|
|
|
if user_name and user_password:
|
2023-03-03 17:17:14 +01:00
|
|
|
# Ancienne API: va être supprimée courant mars 2023
|
|
|
|
current_app.logger.warning(
|
|
|
|
"using DEPRECATED ScoDoc7 authentication method !"
|
|
|
|
)
|
2021-09-09 16:11:05 +02:00
|
|
|
u = User.query.filter_by(user_name=user_name).first()
|
|
|
|
if u and u.check_password(user_password):
|
|
|
|
auth_ok = True
|
|
|
|
flask_login.login_user(u)
|
|
|
|
# reprend le chemin classique:
|
|
|
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
|
|
|
|
|
|
|
if not current_user.has_permission(permission, scodoc_dept):
|
|
|
|
abort(403)
|
|
|
|
if auth_ok:
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
else:
|
|
|
|
return login_required(f)(*args, **kwargs)
|
|
|
|
|
|
|
|
return decorated_function
|
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2021-05-31 00:14:15 +02:00
|
|
|
def admin_required(f):
|
2021-06-28 10:45:00 +02:00
|
|
|
from app.auth.models import Permission
|
|
|
|
|
2021-05-31 00:14:15 +02:00
|
|
|
return permission_required(Permission.ScoSuperAdmin)(f)
|
|
|
|
|
|
|
|
|
2021-08-21 00:24:51 +02:00
|
|
|
def scodoc7func(func):
|
2021-05-29 18:22:51 +02:00
|
|
|
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7.
|
|
|
|
Les paramètres de la query string deviennent des (keywords) paramètres de la fonction.
|
|
|
|
"""
|
|
|
|
|
2021-08-21 00:24:51 +02:00
|
|
|
@wraps(func)
|
|
|
|
def scodoc7func_decorator(*args, **kwargs):
|
|
|
|
"""Decorator allowing legacy Zope published methods to be called via Flask
|
|
|
|
routes without modification.
|
|
|
|
|
|
|
|
There are two cases: the function can be called
|
|
|
|
1. via a Flask route ("top level call")
|
|
|
|
2. or be called directly from Python.
|
|
|
|
|
|
|
|
"""
|
2021-11-01 16:59:56 +01:00
|
|
|
# print("@scodoc7func")
|
2021-08-21 00:24:51 +02:00
|
|
|
# Détermine si on est appelé via une route ("toplevel")
|
|
|
|
# ou par un appel de fonction python normal.
|
2021-09-27 10:20:10 +02:00
|
|
|
top_level = not hasattr(g, "scodoc7_decorated")
|
2021-08-21 00:24:51 +02:00
|
|
|
if not top_level:
|
|
|
|
# ne "redécore" pas
|
|
|
|
return func(*args, **kwargs)
|
2021-09-27 10:20:10 +02:00
|
|
|
g.scodoc7_decorated = True
|
2021-08-21 00:24:51 +02:00
|
|
|
# --- Emulate Zope's REQUEST
|
2021-09-27 10:20:10 +02:00
|
|
|
# REQUEST = ZRequest()
|
|
|
|
# g.zrequest = REQUEST
|
|
|
|
# args from query string (get) or form (post)
|
2021-09-27 16:42:14 +02:00
|
|
|
req_args = scu.get_request_args()
|
2021-09-27 10:20:10 +02:00
|
|
|
## --- Add positional arguments
|
2021-08-21 00:24:51 +02:00
|
|
|
pos_arg_values = []
|
|
|
|
argspec = inspect.getfullargspec(func)
|
|
|
|
# current_app.logger.info("argspec=%s" % str(argspec))
|
|
|
|
nb_default_args = len(argspec.defaults) if argspec.defaults else 0
|
|
|
|
if nb_default_args:
|
|
|
|
arg_names = argspec.args[:-nb_default_args]
|
|
|
|
else:
|
|
|
|
arg_names = argspec.args
|
2021-09-27 16:42:14 +02:00
|
|
|
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
2023-01-17 14:57:49 +01:00
|
|
|
# peut produire une KeyError s'il manque un argument attendu:
|
2023-09-05 21:49:10 +02:00
|
|
|
try:
|
|
|
|
v = req_args[arg_name]
|
|
|
|
except KeyError as exc:
|
|
|
|
raise ScoValueError(f"argument {arg_name} manquant") from exc
|
2023-01-17 14:57:49 +01:00
|
|
|
# try to convert all arguments to INTEGERS
|
|
|
|
# necessary for db ids and boolean values
|
|
|
|
try:
|
|
|
|
v = int(v) if v else v
|
|
|
|
except (ValueError, TypeError) as exc:
|
|
|
|
if arg_name in {
|
|
|
|
"etudid",
|
|
|
|
"formation_id",
|
|
|
|
"formsemestre_id",
|
|
|
|
"module_id",
|
|
|
|
"moduleimpl_id",
|
|
|
|
"partition_id",
|
|
|
|
"ue_id",
|
|
|
|
}:
|
|
|
|
raise ScoValueError("page introuvable (id invalide)") from exc
|
|
|
|
pos_arg_values.append(v)
|
2021-08-21 00:24:51 +02:00
|
|
|
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
|
|
|
# current_app.logger.info("req_args=%s" % req_args)
|
|
|
|
# Add keyword arguments
|
|
|
|
if nb_default_args:
|
|
|
|
for arg_name in argspec.args[-nb_default_args:]:
|
2021-09-27 10:20:10 +02:00
|
|
|
# if arg_name == "REQUEST": # special case
|
|
|
|
# kwargs[arg_name] = REQUEST
|
|
|
|
if arg_name in req_args:
|
2021-08-21 00:24:51 +02:00
|
|
|
# set argument kw optionnel
|
2021-08-09 10:08:24 +02:00
|
|
|
v = req_args[arg_name]
|
|
|
|
# try to convert all arguments to INTEGERS
|
|
|
|
# necessary for db ids and boolean values
|
|
|
|
try:
|
|
|
|
v = int(v)
|
2021-08-21 00:24:51 +02:00
|
|
|
except (ValueError, TypeError):
|
2021-08-09 10:08:24 +02:00
|
|
|
pass
|
2021-08-21 00:24:51 +02:00
|
|
|
kwargs[arg_name] = v
|
|
|
|
# current_app.logger.info(
|
|
|
|
# "scodoc7func_decorator: top_level=%s, pos_arg_values=%s, kwargs=%s"
|
|
|
|
# % (top_level, pos_arg_values, kwargs)
|
|
|
|
# )
|
|
|
|
value = func(*pos_arg_values, **kwargs)
|
|
|
|
|
|
|
|
if not top_level:
|
|
|
|
return value
|
|
|
|
else:
|
|
|
|
if isinstance(value, werkzeug.wrappers.response.Response):
|
|
|
|
return value # redirected
|
|
|
|
# Build response, adding collected http headers:
|
|
|
|
headers = []
|
|
|
|
kw = {"response": value, "status": 200}
|
2021-09-27 10:20:10 +02:00
|
|
|
# if g.zrequest:
|
|
|
|
# headers = g.zrequest.RESPONSE.headers
|
|
|
|
# if not headers:
|
|
|
|
# # no customized header, speedup:
|
|
|
|
# return value
|
|
|
|
# if "content-type" in headers:
|
|
|
|
# kw["mimetype"] = headers["content-type"]
|
2021-08-21 00:24:51 +02:00
|
|
|
r = flask.Response(**kw)
|
|
|
|
for h in headers:
|
|
|
|
r.headers[h] = headers[h]
|
|
|
|
return r
|
|
|
|
|
|
|
|
return scodoc7func_decorator
|