292 lines
12 KiB
Python
292 lines
12 KiB
Python
"""
|
|
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/<int:gid>", 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/<int:gid>/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/<int:gid>/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))
|