""" Gebruikers & rechten. - Administrators: alle gebruikers, alle scholen, rol (Administrator/Gebruiker) wijzigen. - Gebruikers met het recht 'gebruikersbeheer' in een school: gebruikers aanmaken en hun rechten in DIE school(en) instellen. Ze kunnen geen administrators wijzigen. Gebruikers staan in de centrale database (één account voor alle scholen); de rechten per school in Gebruiker_rechten. """ from collections import OrderedDict from flask import Blueprint, render_template, request, session, redirect, url_for, flash from werkzeug.security import generate_password_hash import rechten from rechten import ADMIN, GEBRUIKER, VEILIGHEIDSGROEPEN, ONDERDELEN, is_admin from database import central_query, central_insert from tenancy import alle_scholen, school_op_code admin_bp = Blueprint('admin', __name__) MIN_WACHTWOORD = 8 # ------------------------------------------------------------------ # Hulpfuncties # ------------------------------------------------------------------ def beheerbare_scholen(): """Scholen waarvan de ingelogde gebruiker de rechten mag instellen.""" scholen = alle_scholen(ook_inactief=True) if is_admin(): return scholen gid = session.get('gebruiker_id') if not gid: return [] rows = central_query( "SELECT r.School_ID FROM Gebruiker_rechten r JOIN Onderdelen o ON o.Onderdeel_ID = r.Onderdeel_ID " "WHERE r.Gebruiker_ID = %s AND o.Code = 'gebruikersbeheer'", (gid,)) ids = {r['School_ID'] for r in rows} return [s for s in scholen if s.id in ids] def _toegang(): """None als de gebruiker hier mag komen, anders een redirect.""" if not session.get('auth') or not session.get('school'): return redirect(url_for('auth.login')) if not is_admin() and not rechten.heeft_recht('gebruikersbeheer'): flash("Je hebt geen rechten om gebruikers te beheren.", "error") return redirect(url_for('main.dashboard')) return None def _aantal_admins(behalve=None): rows = central_query("SELECT COUNT(*) AS n FROM Gebruikers WHERE Veiligheidsgroep = %s AND Actief = 1 " "AND Gebruiker_ID <> %s", (ADMIN, behalve or 0)) return rows[0]['n'] def _onderdelen_per_categorie(): groepen = OrderedDict() for code, naam, cat in ONDERDELEN: groepen.setdefault(cat, []).append((code, naam)) return groepen def _wachtwoord_ok(pw, pw2): if pw != pw2: return "De wachtwoorden komen niet overeen." if len(pw) < MIN_WACHTWOORD: return f"Het wachtwoord moet minstens {MIN_WACHTWOORD} tekens hebben." return None # ------------------------------------------------------------------ # Overzicht + nieuwe gebruiker # ------------------------------------------------------------------ @admin_bp.route("/settings", methods=["GET"]) def settings(): weg = _toegang() if weg: return weg users = central_query( "SELECT g.Gebruiker_ID, g.Gebruikersnaam, g.Emailadres, g.Veiligheidsgroep, g.Actief, g.Last_login, " " v.Naam AS Rol " "FROM Gebruikers g JOIN Veiligheidsgroepen v ON v.Veiligheidsgroep = g.Veiligheidsgroep " "ORDER BY g.Veiligheidsgroep, g.Gebruikersnaam") if not is_admin(): users = [u for u in users if int(u['Veiligheidsgroep']) != ADMIN] # Aantal rechten per gebruiker per school (voor het overzicht) per = {} for r in central_query("SELECT Gebruiker_ID, School_ID, COUNT(*) AS n FROM Gebruiker_rechten " "GROUP BY Gebruiker_ID, School_ID"): per.setdefault(r['Gebruiker_ID'], {})[r['School_ID']] = r['n'] scholen = {s.id: s for s in alle_scholen(ook_inactief=True)} for u in users: u['scholen'] = sorted(((scholen[sid].naam, n) for sid, n in per.get(u['Gebruiker_ID'], {}).items() if sid in scholen), key=lambda x: (-x[1], x[0].lower())) return render_template("admin_accounts.html", users=users, groepen=VEILIGHEIDSGROEPEN, ADMIN=ADMIN, totaal_onderdelen=len(ONDERDELEN)) @admin_bp.route("/gebruiker/nieuw", methods=["POST"]) def nieuw(): weg = _toegang() if weg: return weg naam = (request.form.get('username') or '').strip() email = (request.form.get('email') or '').strip().lower() or None pw = request.form.get('password') or '' pw2 = request.form.get('password_confirm') or '' groep = int(request.form.get('veiligheidsgroep') or GEBRUIKER) if groep not in VEILIGHEIDSGROEPEN or (groep == ADMIN and not is_admin()): groep = GEBRUIKER fout = None if not naam: fout = "Vul een gebruikersnaam in." elif not pw and not email: fout = "Geef een wachtwoord, of een e-mailadres om met Google in te loggen." elif pw: fout = _wachtwoord_ok(pw, pw2) if not fout and rechten.zoek_gebruiker(naam): fout = f"Er bestaat al een gebruiker '{naam}'." if not fout and email and rechten.zoek_gebruiker(email): fout = f"Het e-mailadres {email} is al in gebruik." if fout: flash(fout, "error") return redirect(url_for('admin.settings', nieuw=1)) nieuw_id = central_insert( "INSERT INTO Gebruikers (Gebruikersnaam, Veiligheidsgroep, Passwoordhash, Emailadres, Actief) " "VALUES (%s, %s, %s, %s, 1)", (naam, groep, generate_password_hash(pw) if pw else None, email)) rechten.log(actie=f"Gebruiker {naam} aangemaakt") if groep == ADMIN: flash(f"Administrator '{naam}' aangemaakt.", "success") return redirect(url_for('admin.settings')) flash(f"Gebruiker '{naam}' aangemaakt. Vink nu aan wat hij per school mag.", "success") return redirect(url_for('admin.gebruiker_rechten', gid=nieuw_id)) # ------------------------------------------------------------------ # Eén gebruiker: account (gegevens, rol, wachtwoord) # ------------------------------------------------------------------ def _gebruiker_of_terug(gid): """(gebruiker, None) of (None, redirect).""" u = rechten.gebruiker_op_id(gid) if not u: flash("Onbekende gebruiker.", "error") return None, redirect(url_for('admin.settings')) if int(u['Veiligheidsgroep']) == ADMIN and not is_admin(): flash("Enkel een administrator kan een administrator wijzigen.", "error") return None, redirect(url_for('admin.settings')) return u, None @admin_bp.route("/gebruiker/", methods=["GET", "POST"]) def gebruiker(gid): weg = _toegang() if weg: return weg u, weg = _gebruiker_of_terug(gid) if weg: return weg zelf = gid == session.get('gebruiker_id') if request.method == 'POST': return _bewaar_account(u, zelf) return render_template("admin_gebruiker.html", u=u, groepen=VEILIGHEIDSGROEPEN, ADMIN=ADMIN, zelf=zelf, min_wachtwoord=MIN_WACHTWOORD) # ------------------------------------------------------------------ # Eén gebruiker: rechten, één school tegelijk # ------------------------------------------------------------------ @admin_bp.route("/gebruiker//rechten", methods=["GET", "POST"]) def gebruiker_rechten(gid): weg = _toegang() if weg: return weg u, weg = _gebruiker_of_terug(gid) if weg: return weg scholen = beheerbare_scholen() per_id = {s.id: s for s in scholen} aantallen = {} for r in central_query("SELECT School_ID, COUNT(*) AS n FROM Gebruiker_rechten WHERE Gebruiker_ID = %s " "GROUP BY School_ID", (gid,)): aantallen[r['School_ID']] = r['n'] if request.method == 'POST': sid = request.form.get('school_id', type=int) if sid not in per_id: flash("Je mag de rechten in deze school niet instellen.", "error") return redirect(url_for('admin.gebruiker_rechten', gid=gid)) codes = [c for c in rechten.CODES if request.form.get(f"r|{c}")] doel = [sid] if request.form.get('ook_alle'): doel = list(per_id) else: doel += [int(x) for x in request.form.getlist('ook') if x.isdigit() and int(x) in per_id] doel = list(dict.fromkeys(doel)) door = session.get('user') for school_id in doel: rechten.zet_rechten(gid, school_id, codes, door=door) if len(doel) == 1: rechten.log(actie=f"Rechten van {u['Gebruikersnaam']} in {per_id[sid].naam} gewijzigd") flash(f"Rechten van {u['Gebruikersnaam']} in {per_id[sid].naam} opgeslagen.", "success") else: rechten.log(actie=f"Rechten van {u['Gebruikersnaam']} in {len(doel)} scholen gewijzigd") flash(f"Rechten van {u['Gebruikersnaam']} opgeslagen in {len(doel)} scholen.", "success") return redirect(url_for('admin.gebruiker_rechten', gid=gid, school=sid)) # Welke school tonen: gevraagd > huidige school > eerste school met rechten > eerste school gekozen = request.args.get('school', type=int) if gekozen not in per_id: huidig = school_op_code(session.get('school') or '') huidig_id = huidig.id if huidig else None if huidig_id in per_id and (aantallen.get(huidig_id) or not aantallen): gekozen = huidig_id else: met = [s.id for s in scholen if aantallen.get(s.id)] gekozen = met[0] if met else (scholen[0].id if scholen else None) lijst = sorted(scholen, key=lambda s: (not aantallen.get(s.id), s.naam.lower())) return render_template("admin_rechten.html", u=u, scholen=lijst, gekozen=per_id.get(gekozen), aantallen=aantallen, huidig=rechten.rechten_van(gid, gekozen) if gekozen else set(), categorieen=_onderdelen_per_categorie(), ADMIN=ADMIN, totaal=len(ONDERDELEN)) def _bewaar_account(u, zelf): gid = u['Gebruiker_ID'] email = (request.form.get('email') or '').strip().lower() or None pw = request.form.get('new_password') or '' pw2 = request.form.get('new_password_confirm') or '' actief = 1 if request.form.get('actief') else 0 groep = int(u['Veiligheidsgroep']) if is_admin() and request.form.get('veiligheidsgroep'): groep = int(request.form['veiligheidsgroep']) if groep not in VEILIGHEIDSGROEPEN: groep = int(u['Veiligheidsgroep']) terug = redirect(url_for('admin.gebruiker', gid=gid)) if zelf and (not actief or groep != int(u['Veiligheidsgroep'])): flash("Je kan je eigen rol niet wijzigen en jezelf niet uitschakelen.", "error") return terug if int(u['Veiligheidsgroep']) == ADMIN and (groep != ADMIN or not actief) and _aantal_admins(behalve=gid) == 0: flash("Er moet minstens één actieve administrator overblijven.", "error") return terug if email: ander = rechten.zoek_gebruiker(email) if ander and ander['Gebruiker_ID'] != gid: flash(f"Het e-mailadres {email} is al in gebruik.", "error") return terug if pw: fout = _wachtwoord_ok(pw, pw2) if fout: flash(fout, "error") return terug central_query("UPDATE Gebruikers SET Passwoordhash = %s WHERE Gebruiker_ID = %s", (generate_password_hash(pw), gid), commit=True) central_query("UPDATE Gebruikers SET Emailadres = %s, Veiligheidsgroep = %s, Actief = %s WHERE Gebruiker_ID = %s", (email, groep, actief, gid), commit=True) rechten.log(actie=f"Account {u['Gebruikersnaam']} gewijzigd") flash(f"Account van {u['Gebruikersnaam']} opgeslagen.", "success") return terug @admin_bp.route("/gebruiker//verwijderen", methods=["POST"]) def verwijderen(gid): weg = _toegang() if weg: return weg u = rechten.gebruiker_op_id(gid) if not u: return redirect(url_for('admin.settings')) if not is_admin(): flash("Enkel een administrator kan gebruikers verwijderen.", "error") elif gid == session.get('gebruiker_id'): flash("Je kan jezelf niet verwijderen.", "error") elif int(u['Veiligheidsgroep']) == ADMIN and _aantal_admins(behalve=gid) == 0: flash("Er moet minstens één actieve administrator overblijven.", "error") else: central_query("DELETE FROM Gebruikers WHERE Gebruiker_ID = %s", (gid,), commit=True) rechten.log(actie=f"Gebruiker {u['Gebruikersnaam']} verwijderd") flash(f"Gebruiker '{u['Gebruikersnaam']}' is verwijderd.", "success") return redirect(url_for('admin.settings')) return redirect(url_for('admin.gebruiker', gid=gid))