using LehrerApp.Sync.Crypto; using System.Text.Json; namespace LehrerApp.Desktop.Services; internal class WebUntisSettingsConfig { public bool Enabled { get; set; } public string? EncryptedIcalUrl { get; set; } public DateTime? LastSyncAt { get; set; } public string LastSyncStatus { get; set; } = ""; public string? EncryptedApiCredentials { get; set; } public int? TeacherUntisId { get; set; } } public sealed record WebUntisCredentials(string School, string Host, string Username, string Password); /// /// Einstellungen für den WebUntis-iCal-Abgleich (Nutzer-Feedback, siehe TODO.md). Liegt wie /// AiSettingsService/SyncSettingsService in LehrerApp.Desktop statt LehrerApp.Core, da die /// Verschlüsselung über aus LehrerApp.Sync läuft — Core bleibt bewusst /// frei von Abhängigkeiten außerhalb von .NET selbst (siehe CLAUDE.md). /// /// Die iCal-URL trägt ein eingebettetes Auth-Token und wird deshalb wie ein Passwort behandelt: /// nie im Klartext persistiert, nur AES-256-GCM-verschlüsselt (gleicher Mechanismus wie beim /// KI-Backend-Token) mit einem eigenen, dateirechte-geschützten Schlüssel. /// public class WebUntisSettingsService { private readonly string _configPath; private readonly string _keyPath; private readonly byte[] _urlKey; private WebUntisSettingsConfig _config; public bool Enabled => _config.Enabled; public bool IsConfigured => _config.EncryptedIcalUrl is not null; public bool ApiIsConfigured => _config.EncryptedApiCredentials is not null; public int? TeacherUntisId => _config.TeacherUntisId; public DateTime? LastSyncAt => _config.LastSyncAt; public string LastSyncStatus => _config.LastSyncStatus; public WebUntisSettingsService(string appDataPath) { _configPath = Path.Combine(appDataPath, "webuntis-settings.json"); _keyPath = Path.Combine(appDataPath, "webuntis-url.key"); _urlKey = SyncCrypto.LoadKey(_keyPath) ?? GenerateAndSaveKey(); _config = Load(); } public void SetEnabled(bool enabled) { _config.Enabled = enabled; Save(); } public void SetIcalUrl(string url) { _config.EncryptedIcalUrl = SyncCrypto.EncryptObject(url, _urlKey); Save(); } public string? GetIcalUrl() => _config.EncryptedIcalUrl is null ? null : SyncCrypto.DecryptObject(_config.EncryptedIcalUrl, _urlKey); public void ClearIcalUrl() { _config.EncryptedIcalUrl = null; _config.Enabled = false; Save(); } public void SetApiCredentials(WebUntisCredentials credentials) { _config.EncryptedApiCredentials = SyncCrypto.EncryptObject(credentials, _urlKey); Save(); } public WebUntisCredentials? GetApiCredentials() => _config.EncryptedApiCredentials is null ? null : SyncCrypto.DecryptObject(_config.EncryptedApiCredentials, _urlKey); public void ClearApiCredentials() { _config.EncryptedApiCredentials = null; _config.TeacherUntisId = null; Save(); } public void SetTeacherUntisId(int? teacherUntisId) { _config.TeacherUntisId = teacherUntisId; Save(); } public void SetLastSync(DateTime at, string status) { _config.LastSyncAt = at; _config.LastSyncStatus = status; Save(); } private byte[] GenerateAndSaveKey() { var key = SyncCrypto.GenerateKey(); SyncCrypto.SaveKey(key, _keyPath); return key; } private void Save() => File.WriteAllText(_configPath, JsonSerializer.Serialize(_config)); private WebUntisSettingsConfig Load() { try { if (File.Exists(_configPath)) return JsonSerializer.Deserialize(File.ReadAllText(_configPath)) ?? new WebUntisSettingsConfig(); } catch { /* beschädigte Konfiguration -> Standardwert */ } return new WebUntisSettingsConfig(); } }