from datetime import datetime from flask import Blueprint, render_template, request, redirect, url_for, flash, session from tenancy import huidige_school, per_school from blueprints.auth import login_required from rechten import recht_vereist from config import Config from services.tweestaps_service import TweestapsService, TweestapsError tweestaps_bp = Blueprint('tweestaps', __name__) LOG_PER_PAGINA = 25 # standaard; te kiezen: 10 / 25 / 50 / 100 / alles (0) LOG_KEUZES = (10, 25, 50, 100, 0) service = per_school(TweestapsService) _tables_ready = set() # scholen waarvoor de tabellen al gecontroleerd zijn def _ensure_tables(): if huidige_school().code not in _tables_ready: try: service.ensure_tables() _tables_ready.add(huidige_school().code) except Exception as e: flash(f"Kon de 2FA-tabellen niet aanmaken: {e}", "error") def _render(email=None, codes=None, code_actie=None, fout=None): """Bouwt de pagina op; codes worden enkel in deze response getoond, nooit bewaard of gelogd.""" tab = request.args.get('tab', 'hulp') zoek = (request.args.get('zoek') or '').strip() email = email or (request.args.get('email') or '').strip() gebruiker, uitzondering, zoekresultaten, reset_wacht = None, None, [], None if zoek and '@' not in zoek: zoekresultaten = service.search_users(zoek) if not zoekresultaten: fout = fout or f"Geen gebruikers gevonden voor '{zoek}'." elif zoek: email = zoek if email: try: gebruiker = service.get_user(email) uitzondering = service.active_for(gebruiker['email']) reset_wacht = service.reset_bezig(gebruiker['email']) except TweestapsError as e: fout = fout or str(e) try: actief = service.active_exceptions() # Aantal per pagina: uit ?logper=..., anders de vorige keuze (sessie), anders 25 per = request.args.get('logper', type=int) if per in LOG_KEUZES: session['tweestaps_logper'] = per per = session.get('tweestaps_logper', LOG_PER_PAGINA) grootte = per or 100000 # 0 = alles log_pagina = max(1, request.args.get('logpagina', 1, type=int) or 1) logboek, log_totaal = service.recent_log(log_pagina, grootte) log_paginas = max(1, -(-log_totaal // grootte)) if log_pagina > log_paginas: log_pagina = log_paginas logboek, log_totaal = service.recent_log(log_pagina, grootte) except Exception: actief, logboek, log_totaal, log_pagina, log_paginas, per = [], [], 0, 1, 1, LOG_PER_PAGINA lijst, lijst_fout = None, None if tab == 'lijst': try: lijst = service.niet_ingesteld(force=request.args.get('vernieuw') == '1') lijst_fout = service.ou_melding() # sommige OU's bestaan niet: lijst tonen + waarschuwing except TweestapsError as e: lijst_fout = str(e) except Exception as e: lijst_fout = TweestapsService._friendly(e) return render_template('tweestaps.html', tab=tab, zoek=zoek, zoekresultaten=zoekresultaten, gebruiker=gebruiker, uitzondering=uitzondering, reset_wacht=reset_wacht, codes=codes, code_actie=code_actie, fout=fout, actief=actief, logboek=logboek, log_totaal=log_totaal, log_pagina=log_pagina, log_per=per, log_keuzes=LOG_KEUZES, log_paginas=log_paginas, lijst=lijst, lijst_fout=lijst_fout, einde=service.einde_vandaag(), groep=service.exception_group, ous=Config.TWEESTAPS_OUS, nu=datetime.now()) @tweestaps_bp.route('/', methods=['GET']) @login_required @recht_vereist('tweestaps_codes', 'tweestaps_uitzetten') def overzicht(): _ensure_tables() return _render() #--------------------------------------------- # Back-upcodes #--------------------------------------------- @tweestaps_bp.route('/codes', methods=['POST']) @login_required @recht_vereist('tweestaps_codes') def codes(): _ensure_tables() email = request.form.get('email', '') actie = request.form.get('actie', 'tonen') door = session.get('user') try: user = service.get_user(email) if actie == 'ongeldig': service.invalidate_codes(user['email']) service.log(door, 'codes ongeldig gemaakt', user['email']) flash(f"Alle back-upcodes van {user['email']} zijn ongeldig gemaakt.", "success") return redirect(url_for('tweestaps.overzicht', email=user['email'])) bestaand = [] if actie == 'nieuw' else service.list_codes(user['email']) if bestaand: lijst = bestaand service.log(door, 'code getoond', user['email']) else: lijst = service.generate_codes(user['email']) service.log(door, 'nieuwe codes', user['email'], f"{len(lijst)} codes") return _render(email=user['email'], codes=lijst, code_actie='nieuw' if not bestaand else 'bestaand') except TweestapsError as e: return _render(email=email, fout=str(e)) #--------------------------------------------- # Tijdelijke uitzondering #--------------------------------------------- @tweestaps_bp.route('/uitzondering', methods=['POST']) @login_required @recht_vereist('tweestaps_uitzetten') def uitzondering(): _ensure_tables() email = request.form.get('email', '') reden = (request.form.get('reden') or '').strip() eindtijd = service.einde_vandaag() try: user = service.grant_exception(email, eindtijd, session.get('user'), reden) flash(f"{user['email']} kan tot {eindtijd:%H:%M} zonder 2FA inloggen. Daarna zet PIVOT 2FA automatisch " "terug aan. Let op: het kan enkele minuten duren voor Google de uitzondering toepast.", "success") except TweestapsError as e: flash(str(e), "error") return redirect(url_for('tweestaps.overzicht', email=email.strip())) @tweestaps_bp.route('/reset', methods=['POST']) @login_required @recht_vereist('tweestaps_uitzetten') def reset(): """2FA resetten voor iemand met een nieuwe of verloren telefoon.""" _ensure_tables() email = request.form.get('email', '') eindtijd = service.einde_vandaag() try: user = service.reset_2fa(email, eindtijd, session.get('user'), (request.form.get('reden') or '').strip()) if user.get('wacht'): flash(f"{user['email']} staat nu in de uitzonderingsgroep, maar Google past dat pas na enkele minuten toe. " "PIVOT probeert de 2FA-reset elke minuut opnieuw (tot de uitzondering afloopt om " f"{eindtijd:%H:%M}). Je hoeft niets meer te doen; vernieuw deze pagina om de status te zien.", "info") return redirect(url_for('tweestaps.overzicht', email=email.strip())) tekst = f"2FA is uitgezet voor {user['email']}. Hij kan nu inloggen en 2FA opnieuw instellen met zijn nieuwe telefoon" if user.get('uitzondering'): tekst += f" (vóór {eindtijd:%H:%M}; daarna is 2FA weer verplicht)" flash(tekst + ". Het kan enkele minuten duren voor Google dit toepast.", "success") except TweestapsError as e: flash(str(e), "error") return redirect(url_for('tweestaps.overzicht', email=email.strip())) @tweestaps_bp.route('/uitzondering//stop', methods=['POST']) @login_required @recht_vereist('tweestaps_uitzetten') def stop_uitzondering(uitz_id): _ensure_tables() try: email = service.end_exception(uitz_id, session.get('user')) flash(f"2FA is opnieuw verplicht voor {email}.", "success") except TweestapsError as e: flash(str(e), "error") terug = request.form.get('terug') return redirect(url_for('tweestaps.overzicht', email=terug) if terug else url_for('tweestaps.overzicht'))