diff --git a/app/api/__init__.py b/app/api/__init__.py index 34ebbc77a0..956c1b46bc 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -6,3 +6,4 @@ from flask import Blueprint bp = Blueprint("api", __name__) from app.api import sco_api +from app.api import tokens diff --git a/app/api/auth.py b/app/api/auth.py index 24348aab83..bb8464e0d4 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -33,6 +33,7 @@ token_auth = HTTPTokenAuth() @basic_auth.verify_password def verify_password(username, password): + # breakpoint() user = User.query.filter_by(user_name=username).first() if user and user.check_password(password): return user @@ -51,3 +52,17 @@ def verify_token(token): @token_auth.error_handler def token_auth_error(status): return error_response(status) + + +def token_permission_required(permission): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + scodoc_dept = getattr(g, "scodoc_dept", None) + if not current_user.has_permission(permission, scodoc_dept): + abort(403) + return f(*args, **kwargs) + + return login_required(decorated_function) + + return decorator diff --git a/app/api/sco_api.py b/app/api/sco_api.py index e2619a0b0d..c3ee742405 100644 --- a/app/api/sco_api.py +++ b/app/api/sco_api.py @@ -48,9 +48,9 @@ from app.api.errors import bad_request from app import models -@bp.route("/ScoDoc/api/list_depts", methods=["GET"]) +@bp.route("list_depts", methods=["GET"]) @token_auth.login_required def list_depts(): depts = models.Departement.query.filter_by(visible=True).all() - data = {"items": [d.to_dict() for d in depts]} + data = [d.to_dict() for d in depts] return jsonify(data) diff --git a/app/auth/models.py b/app/auth/models.py index f243f0e794..86ebdb83c6 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -213,6 +213,9 @@ class User(UserMixin, db.Model): @staticmethod def check_token(token): + """Retreive user for given token, chek token's validity + and returns the user object. + """ user = User.query.filter_by(token=token).first() if user is None or user.token_expiration < datetime.utcnow(): return None diff --git a/app/decorators.py b/app/decorators.py index df67751aea..8ebf5deabc 100644 --- a/app/decorators.py +++ b/app/decorators.py @@ -50,9 +50,19 @@ def scodoc(func): @wraps(func) def scodoc_function(*args, **kwargs): + # print("@scodoc") # interdit les POST si pas loggué - if request.method == "POST" and not current_user.is_authenticated: - current_app.logger.info("POST by non authenticated user") + if ( + request.method == "POST" + and not current_user.is_authenticated + and not request.form.get( + "__ac_password" + ) # exception pour compat API ScoDoc7 + ): + current_app.logger.info( + "POST by non authenticated user (request.form=%s)", + str(request.form)[:2048], + ) return redirect( url_for( "auth.login", @@ -68,6 +78,7 @@ def scodoc(func): # current_app.logger.info("setting dept to None") g.scodoc_dept = None g.scodoc_dept_id = -1 # invalide + return func(*args, **kwargs) return scodoc_function @@ -97,8 +108,8 @@ def permission_required_compat_scodoc7(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): - # current_app.logger.warning("PERMISSION; kwargs=%s" % str(kwargs)) # cherche les paramètre d'auth: + # print("@permission_required_compat_scodoc7") auth_ok = False if request.method == "GET": user_name = request.args.get("__ac_name") @@ -113,7 +124,6 @@ def permission_required_compat_scodoc7(permission): 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) @@ -150,6 +160,7 @@ def scodoc7func(func): 2. or be called directly from Python. """ + # print("@scodoc7func") # Détermine si on est appelé via une route ("toplevel") # ou par un appel de fonction python normal. top_level = not hasattr(g, "scodoc7_decorated") diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 2511f0ca50..90733b857b 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -41,7 +41,7 @@ class Identite(db.Model): code_nip = db.Column(db.Text()) code_ine = db.Column(db.Text()) # Ancien id ScoDoc7 pour les migrations de bases anciennes - # ne pas utiliser après migrate_scodoc7_dept_archive + # ne pas utiliser après migrate_scodoc7_dept_archives scodoc7_id = db.Column(db.Text(), nullable=True) # billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic") diff --git a/app/models/formations.py b/app/models/formations.py index bb82291157..c4f81e521c 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -137,6 +137,12 @@ class NotesTag(db.Model): # Association tag <-> module notes_modules_tags = db.Table( "notes_modules_tags", - db.Column("tag_id", db.Integer, db.ForeignKey("notes_tags.id")), - db.Column("module_id", db.Integer, db.ForeignKey("notes_modules.id")), + db.Column( + "tag_id", + db.Integer, + db.ForeignKey("notes_tags.id", ondelete="CASCADE"), + ), + db.Column( + "module_id", db.Integer, db.ForeignKey("notes_modules.id", ondelete="CASCADE") + ), ) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 73830b0656..94f0188fad 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -79,7 +79,7 @@ class FormSemestre(db.Model): ) # Ancien id ScoDoc7 pour les migrations de bases anciennes - # ne pas utiliser après migrate_scodoc7_dept_archive + # ne pas utiliser après migrate_scodoc7_dept_archives scodoc7_id = db.Column(db.Text(), nullable=True) def __init__(self, **kwargs): diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 0a88f648ad..98f3a917e4 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -987,7 +987,7 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr): bcc = copy_addr.strip() else: bcc = "" - msg = Message(subject, sender=sender, recipients=recipients, bcc=bcc) + msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc]) msg.body = hea # Attach pdf diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 2935bdb3ad..92b00a67d8 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -1949,6 +1949,15 @@ class BasePreferences(object): "name": name, }, ) + if len(pdb) > 1: + # suppress buggy duplicates (may come from corrupted database for ice ages) + log( + f"**oups** detected duplicated preference !\n({self.dept_id}, {formsemestre_id}, {name}, {value})" + ) + for obj in pdb[1:]: + self._editor.delete(cnx, obj["id"]) + pdb = [pdb[0]] + if not pdb: # crée préférence log("create pref sem=%s %s=%s" % (formsemestre_id, name, value)) @@ -1962,10 +1971,8 @@ class BasePreferences(object): }, ) modif = True - log("create pref sem=%s %s=%s" % (formsemestre_id, name, value)) else: # edit existing value - existing_value = pdb[0]["value"] # old stored value if ( (existing_value != value) diff --git a/app/templates/auth/change_password.html b/app/templates/auth/change_password.html index 975e8bc970..2bec776221 100644 --- a/app/templates/auth/change_password.html +++ b/app/templates/auth/change_password.html @@ -1,9 +1,13 @@ {% extends "base.html" %} {% import 'bootstrap/wtf.html' as wtf %} -{% macro render_field(field) %} +{% macro render_field(field, auth_name=None) %}
Identifiez-vous avez votre mot de passe actuel
-Vous pouvez changer le mot de passe et/ou l'adresse email.
-Les champs vides ne seront pas changés.