""" Instellingen. config.ini bevat enkel nog wat voor ALLE scholen geldt (database-server, centrale database, gedeelde Google-login, superbeheerders). Alles wat per school verschilt staat in de centrale database (zie tenancy.py) en wordt beheerd via /scholen. `Config.SS_API_PW`, `Config.SCHOOL_DOMAIN`, ... geven de waarde van de school waarvoor de huidige request (of achtergrondtaak) loopt. Geheimen kunnen ook via omgevingsvariabelen gezet worden (handig voor Docker): PIVOT_DB_PASSWORD, FLASK_SECRET_KEY, PIVOT_SLEUTEL, PIVOT_SSO_CLIENT_SECRET """ import os import configparser base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.environ.get('PIVOT_CONFIG', os.path.join(base_dir, 'config.ini')) config_file = configparser.ConfigParser() config_file.read(config_path, encoding='utf-8') def _get(section, key, fallback='', env=None): if env and os.environ.get(env): return os.environ[env] return config_file.get(section, key, fallback=fallback) def _versie(): """Versienummer uit het bestand VERSION (wordt bij elke commit verhoogd met verhoog_versie.py, zie CLAUDE.md).""" try: with open(os.path.join(base_dir, 'VERSION'), encoding='utf-8') as f: return f.read().strip() except OSError: return _get('algemeen', 'app_version', 'onbekend') def _secret_key(): """FLASK_SECRET_KEY, config.ini, of anders een willekeurige sleutel die bewaard wordt in geheimen/flask.key. (Nooit een vaste standaardsleutel: daarmee kan iemand een sessie voor eender welke school vervalsen.)""" sleutel = os.environ.get("FLASK_SECRET_KEY") or _get('algemeen', 'secret_key') if sleutel: return sleutel import secrets pad = os.path.join(os.environ.get('PIVOT_GEHEIMEN', os.path.join(base_dir, 'geheimen')), 'flask.key') if os.path.exists(pad): with open(pad) as f: return f.read().strip() os.makedirs(os.path.dirname(pad), exist_ok=True) sleutel = secrets.token_hex(32) with open(pad, 'w') as f: f.write(sleutel) return sleutel class _SchoolConfigMeta(type): """Onbekende attributen op de klasse → instelling van de huidige school.""" def __getattr__(cls, naam): if naam.startswith('__'): raise AttributeError(naam) if naam == 'GOOGLE_API_LOCATION': raise AttributeError("GOOGLE_API_LOCATION bestaat niet meer: gebruik tenancy.google_credentials()") from tenancy import huidige_school, VELD, AFGELEID if naam not in VELD and naam not in AFGELEID: raise AttributeError(naam) return huidige_school().get(naam) class Config(metaclass=_SchoolConfigMeta): """Algemene instellingen (klasse-attributen) + per-school instellingen (via de metaclass).""" # Flask SECRET_KEY = _secret_key() DEBUG = _get('algemeen', 'debug', 'nee').strip().lower() in ('ja', 'yes', 'true', '1', 'on') SESSION_COOKIE_PATH = '/' SESSION_PERMANENT = True # Database-server: standaard voor alle schooldatabases + de centrale database DB_HOST = _get('database', 'host', 'localhost', env='PIVOT_DB_HOST') DB_PORT = int(_get('database', 'port', '3306', env='PIVOT_DB_PORT')) DB_USER = _get('database', 'user', '', env='PIVOT_DB_USER') DB_PASSWORD = _get('database', 'password', '', env='PIVOT_DB_PASSWORD') DB_CENTRAAL = _get('database', 'centrale_database', 'pivot_centraal', env='PIVOT_DB_CENTRAAL') # Verbindingen per database in de pool. Elke pool opent die meteen allemaal: aantal scholen x pool_size # (+ centraal) moet onder max_connections van MySQL blijven (standaard 151). DB_POOL_SIZE = max(1, min(32, int(_get('database', 'pool_size', '3', env='PIVOT_DB_POOL_SIZE') or 3))) # Gedeelde Google-login (één OAuth-client voor alle scholen; per school te overschrijven) SSO_CLIENT_ID = _get('google_sso', 'client_id', env='PIVOT_SSO_CLIENT_ID') SSO_CLIENT_SECRET = _get('google_sso', 'client_secret', env='PIVOT_SSO_CLIENT_SECRET') # Superbeheerders: mogen scholen beheren en tussen scholen wisselen (Google-login) SUPERBEHEERDERS = [x.strip().lower() for x in _get('algemeen', 'superbeheerders', env='PIVOT_SUPERBEHEERDERS').replace(';', ',').split(',') if x.strip()] APP_VERSION = _versie() # Elke bezochte pagina bewaren in de tabel Logs (centrale database) LOGBOEK = _get('algemeen', 'logboek', 'ja').strip().lower() in ('ja', 'yes', 'true', '1', 'on') # Aantal reverse proxies (nginx, IIS, firewall met https, ...) vóór PIVOT. 0 = PIVOT staat rechtstreeks op het net. # Nodig om het echte IP-adres van de bezoeker te kennen (IP-blokkades, logboek, inlogbeveiliging). try: PROXY_AANTAL = max(0, int(_get('algemeen', 'proxy_aantal', '0') or 0)) except ValueError: PROXY_AANTAL = 0 def __getattr__(self, naam): # Config().SS_API_PW werkt ook (zoals in de bestaande services) return getattr(type(self), naam) @property def DB_CONFIG(self): from tenancy import huidige_school return huidige_school().db_config()