""" Beheer van PIVOT vanaf de opdrachtregel. python beheer.py init Maakt de tabellen van de centrale database aan (Scholen, Gebruikers, rechten, Logs, Bugs). python beheer.py nieuwe-schooldatabase DATABASE Maakt een lege schooldatabase met de standaardstructuur (sql/school.sql). python beheer.py gebruiker NAAM [--wachtwoord W] [--email E] [--admin] Maakt een PIVOT-gebruiker aan, of zet een nieuw wachtwoord / maakt hem administrator. python beheer.py rechten NAAM SCHOOLCODE [onderdeel ...|alles|geen] Toont of zet de rechten van een gebruiker in een school. python beheer.py importeer-config OUDE_CONFIG.ini [--google-sleutel sleutel.json] [--code kamxm] [--db Pivot_26-27] Zet een school uit een config.ini van PIVOT 1.0.x in het scholenregister (alle wachtwoorden en koppelingen worden versleuteld overgenomen). python beheer.py lijst Toont alle scholen. python beheer.py kopieer-school BRONCODE NIEUWE_DATABASE Maakt een nieuwe database als volledige kopie (tabellen + gegevens) van de database van BRONCODE. Registreer ze daarna als school via PIVOT (Nieuwe school › Database aanmaken? › Nee, bestaat al). python beheer.py nieuwe-sleutel Toont een nieuwe versleutelingssleutel (voor PIVOT_SLEUTEL). """ import argparse import configparser import json import os import sys from cryptography.fernet import Fernet # Oude config.ini → velden in het scholenregister MAPPING = { ('schoolgegevens', 'admin_email'): 'ADMIN_EMAIL', ('schooljaar', 'huidig_schooljaar'): 'SCHOOLYEAR', ('google', 'ou_nieuwe_lln'): 'STUDENT_OU', ('google', 'google_sso_clientid'): 'GOOGLE_SSO_CLIENTID', ('google', 'google_sso_clientsecret'): 'GOOGLE_SSO_CLIENTSECRET', ('google', 'google_smtp_server'): 'GOOGLE_SMTP_SERVER', ('google', 'google_smtp_port'): 'GOOGLE_SMTP_PORT', ('google', 'google_sender_adres'): 'GOOGLE_SMTP_SENDER_adres', ('google', 'google_email_passsword'): 'GOOGLE_SMTP_EMAIL_PASSWORD', ('google', 'google_api_scopes'): 'GOOGLE_API_SCOPES_RAW', ('google', 'tweestaps_uitzondering_groep'): 'TWEESTAPS_UITZONDERING_GROEP', ('google', 'tweestaps_ous'): 'TWEESTAPS_OUS', ('google', 'tweestaps_groepen'): 'TWEESTAPS_GROEPEN_RAW', ('informat', 'client_id'): 'INFORMAT_ID', ('informat', 'client_secret'): 'INFORMAT_SECRET', ('informat', 'intitute_number'): 'INFORMAT_INST_NUMBER', ('informat', 'autho_url'): 'INFORMAT_AUTH_URL', ('informat', 'api_base'): 'INFORMAT_API_BASE', ('informat', 'scope'): 'INFORMAT_SCOPE', ('smartschool', 'ss_client_id'): 'SS_CLIENT_ID', ('smartschool', 'ss_client_secret'): 'SS_CLIENT_SECRET', ('smartschool', 'ss_api_pw'): 'SS_API_PW', ('smartschool', 'ss_api_platform'): 'SS_API_PLATFORM', ('smartschool', 'afwezigheid_codes'): 'AFWEZIGHEID_CODES', ('smartschool', 'afwezigheid_sync_uur'): 'AFWEZIGHEID_SYNC_UUR', ('CO2_sensor', 'co2_netwerk'): 'CO2_NETWORK', ('CO2_sensor', 'co2_beginadres'): 'CO2_START_ADDR', ('CO2_sensor', 'co2_eindadres'): 'CO2_END_ADDR', ('CO2_sensor', 'co2_waardedrempel'): 'CO2_THRESHOLD', ('CO2_sensor', 'co2_scan_interval'): 'CO2_INTERVAL', ('personeel', 'personeel_ous'): 'PERSONEEL_OUS', ('personeel', 'directie'): 'PERSONEEL_DIRECTIE', ('personeel', 'secretariaat'): 'PERSONEEL_SECRETARIAAT', ('personeel', 'secretariaat_mail'): 'PERSONEEL_SECRETARIAAT_MAIL', ('personeel', 'mails_versturen'): 'PERSONEEL_MAILS', ('m4s', 'm4s_base_data_url'): 'M4S_BASE_URL', ('m4s', 'm4s_auth_url'): 'M4S_AUTH_URL', ('m4s', 'm4s_clientid'): 'M4S_CLIENT_ID', ('m4s', 'm4s_clientsecret'): 'M4S_CLIENT_SECRET', ('m4s', 'm4s_institutionguid'): 'M4S_INSTITUTIONGUID', ('m4s', 'm4s_repair_location_type'): 'M4S_REPAIR_LOCATION_TYPE', ('m4s', 'm4s_repair_status'): 'M4S_REPAIR_STATUS', ('m4s', 'm4s_truth_statement'): 'M4S_TRUTH_STATEMENT', ('m4s', 'm4s_repair_at_institution'): 'M4S_REPAIR_AT_INTITUTION', ('m4s', 'm4s_contact_first_name'): 'M4S_CONTACT_FIRST_NAME', ('m4s', 'm4s_contact_last_name'): 'M4S_CONTACT_LAST_NAME', ('m4s', 'm4s_contact_email'): 'M4S_CONTACT_EMAIL', ('m4s', 'm4s_contact_phone'): 'M4S_CONTACT_PHONE', ('m4s', 'm4s_repair_street'): 'M4S_REPAIR_STREET', ('m4s', 'm4s_repair_streetnumber'): 'M4S_REPAIR_STREETNUMBER', ('m4s', 'm4s_repair_zipcode'): 'M4S_REPAIR_ZIPCODE', ('m4s', 'm4s_repair_city'): 'M4S_REPAIIR_CITY', ('m4s', 'm4s_repair_countrycode'): 'M4S_REPAIR_COUNTRYCODE', ('m4s', 'm4s_language_code'): 'M4S_LANGUAGE_CODE', ('m4s', 'm4s_billing_first_name'): 'M4S_BILLING_FIRST_NAME', ('m4s', 'm4s_billing_last_name'): 'M4S_BILLING_LAST_NAME', ('m4s', 'm4s_billing_email'): 'M4S_BILLING_EMAIL', ('m4s', 'm4s_billing_phone'): 'M4S_BILLING_PHONE', } def lees_oude_config(pad): cp = configparser.ConfigParser() if not cp.read(pad, encoding='utf-8'): sys.exit(f"Kan {pad} niet lezen.") waarden = {} for (sectie, key), veld in MAPPING.items(): if cp.has_option(sectie, key): v = cp.get(sectie, key).strip() if v and v != 'None': waarden[veld] = v # Smartschool-platform zonder https:// en pad if 'SS_API_PLATFORM' in waarden: waarden['SS_API_PLATFORM'] = waarden['SS_API_PLATFORM'].replace('https://', '').replace('http://', '').split('/')[0] return cp, waarden def cmd_importeer(args): import tenancy from config import Config cp, waarden = lees_oude_config(args.config) code = (args.code or cp.get('schoolgegevens', 'school_afkorting', fallback='')).strip().lower() naam = cp.get('schoolgegevens', 'schoolnaam', fallback=code).strip() domein = cp.get('schoolgegevens', 'schooldomein', fallback='').strip().lower() db_naam = args.db or cp.get('database', 'database_name', fallback='').strip() if not code or not domein or not db_naam: sys.exit("Code, schooldomein of databasenaam ontbreekt (gebruik --code / --db).") # Eigen databaseserver/gebruiker enkel overnemen als die verschilt van de nieuwe config.ini for key, veld, standaard in [('host', 'DB_HOST', Config.DB_HOST), ('user', 'DB_USER', Config.DB_USER), ('password', 'DB_PASSWORD', Config.DB_PASSWORD)]: v = cp.get('database', key, fallback='').strip() if v and v != standaard: waarden[veld] = v # Google-serviceaccount sleutel = None kandidaten = [args.google_sleutel] if args.google_sleutel else [ cp.get('google', 'location_linux', fallback=''), cp.get('google', 'location', fallback='')] for pad in kandidaten: if pad and os.path.exists(pad): with open(pad, encoding='utf-8') as f: sleutel = json.load(f) print(f"Google-sleutel gelezen uit {pad}") break if not sleutel: print("⚠ Geen Google-sleutel gevonden; upload hem later via /scholen (of gebruik --google-sleutel).") tenancy.ensure_schema() bestaand = tenancy.school_op_code(code, ook_inactief=True) if bestaand and not args.overschrijven: sys.exit(f"School '{code}' bestaat al. Gebruik --overschrijven om bij te werken.") tenancy.opslaan(code, naam, domein, '', db_naam, True, waarden, sleutel, gebruiker='beheer.py', nieuw=not bestaand) print(f"✓ School {naam} ({code}) opgeslagen: domein {domein}, database {db_naam}, " f"{len(waarden)} instellingen{', Google-sleutel' if sleutel else ''}.") def cmd_lijst(args): import tenancy tenancy.ensure_schema() scholen = tenancy.alle_scholen(ook_inactief=True) if not scholen: print("Nog geen scholen.") for s in scholen: print(f"{s.code:<12} {s.naam:<40} {','.join(s.domeinen):<30} db={s.db_naam:<20} " f"{'actief' if s.actief else 'INACTIEF'}{' google✓' if s.google_sleutel else ''}") def cmd_kopieer(args): import tenancy from blueprints.scholen_bp import kopieer_school_db bron = tenancy.school_op_code(args.bron, ook_inactief=True) if not bron: sys.exit(f"Onbekende school {args.bron}") ok, msg = kopieer_school_db(bron, args.database, {}) print(msg) sys.exit(0 if ok else 1) def cmd_nieuwe_schooldatabase(args): from blueprints.scholen_bp import maak_schooldatabase ok, msg = maak_schooldatabase(args.database) print(msg) sys.exit(0 if ok else 1) def cmd_gebruiker(args): import getpass import tenancy import rechten from database import central_query from werkzeug.security import generate_password_hash tenancy.ensure_schema() pw = args.wachtwoord bestaand = rechten.zoek_gebruiker(args.naam) if pw is None and not bestaand: pw = getpass.getpass("Wachtwoord: ") groep = rechten.ADMIN if args.admin else None if bestaand: gid = bestaand['Gebruiker_ID'] if pw: central_query("UPDATE Gebruikers SET Passwoordhash = %s WHERE Gebruiker_ID = %s", (generate_password_hash(pw), gid), commit=True) if args.email: central_query("UPDATE Gebruikers SET Emailadres = %s WHERE Gebruiker_ID = %s", (args.email.lower(), gid), commit=True) if groep: central_query("UPDATE Gebruikers SET Veiligheidsgroep = %s, Actief = 1 WHERE Gebruiker_ID = %s", (groep, gid), commit=True) print(f"✓ Gebruiker {bestaand['Gebruikersnaam']} bijgewerkt.") else: central_query("INSERT INTO Gebruikers (Gebruikersnaam, Veiligheidsgroep, Passwoordhash, Emailadres) " "VALUES (%s, %s, %s, %s)", (args.naam, groep or rechten.GEBRUIKER, generate_password_hash(pw) if pw else None, args.email.lower() if args.email else None), commit=True) print(f"✓ Gebruiker {args.naam} aangemaakt ({'Administrator' if groep else 'Gebruiker'}).") def cmd_rechten(args): import tenancy import rechten g = rechten.zoek_gebruiker(args.naam) school = tenancy.school_op_code(args.school, ook_inactief=True) if not g or not school: sys.exit("Onbekende gebruiker of school.") if args.onderdelen: if args.onderdelen == ['alles']: codes = rechten.CODES elif args.onderdelen == ['geen']: codes = [] else: onbekend = [c for c in args.onderdelen if c not in rechten.CODES] if onbekend: sys.exit(f"Onbekende onderdelen: {', '.join(onbekend)}\nMogelijk: {', '.join(rechten.CODES)}") codes = args.onderdelen rechten.zet_rechten(g['Gebruiker_ID'], school.id, codes, door='beheer.py') huidig = rechten.rechten_van(g['Gebruiker_ID'], school.id) print(f"{g['Gebruikersnaam']} in {school.naam}:") for code, naam, cat in rechten.ONDERDELEN: print(f" [{'x' if code in huidig else ' '}] {code:<26} {naam}") def main(): p = argparse.ArgumentParser(description="PIVOT beheer") sub = p.add_subparsers(dest='cmd', required=True) sub.add_parser('init') i = sub.add_parser('importeer-config') i.add_argument('config') i.add_argument('--code') i.add_argument('--db') i.add_argument('--google-sleutel') i.add_argument('--overschrijven', action='store_true') sub.add_parser('lijst') k = sub.add_parser('kopieer-school', aliases=['kopieer-schema']) k.add_argument('bron') k.add_argument('database') sub.add_parser('nieuwe-sleutel') n = sub.add_parser('nieuwe-schooldatabase') n.add_argument('database') gb = sub.add_parser('gebruiker') gb.add_argument('naam') gb.add_argument('--wachtwoord') gb.add_argument('--email') gb.add_argument('--admin', action='store_true') r = sub.add_parser('rechten') r.add_argument('naam') r.add_argument('school') r.add_argument('onderdelen', nargs='*') args = p.parse_args() if args.cmd == 'nieuwe-sleutel': print(Fernet.generate_key().decode()) elif args.cmd == 'init': import tenancy tenancy.ensure_schema() print("✓ Centrale tabellen aangemaakt (of bestonden al).") elif args.cmd == 'importeer-config': cmd_importeer(args) elif args.cmd == 'lijst': cmd_lijst(args) elif args.cmd in ('kopieer-school', 'kopieer-schema'): cmd_kopieer(args) elif args.cmd == 'nieuwe-schooldatabase': cmd_nieuwe_schooldatabase(args) elif args.cmd == 'gebruiker': cmd_gebruiker(args) elif args.cmd == 'rechten': cmd_rechten(args) if __name__ == '__main__': main()