using C4IT.FASD.Base; using C4IT.FASD.Cockpit; using C4IT.FASD.Cockpit.Communication; using C4IT.Graphics; using C4IT.Logging; using C4IT.MultiLanguage; using C4IT.Security; using FasdCockpitBase.Models; using FasdDesktopUi.Pages.SettingsPage; using FasdDesktopUi.Pages.SplashScreenView; using Microsoft.Win32; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows.Threading; using static C4IT.Logging.cLogManager; namespace FasdDesktopUi.Basics.Models { public class cConnectionStatusHelper { public enum enumOnlineStatus { notSpecified = 0, offline, connectionError, incompatibleServerVersion, serverNotConfigured, serverStarting, illegalConfig, unauthorized, online } public enum enumCheckReason { firstStart = 0, lateInit, heartBeat } public bool IsActive = true; public readonly Version MinServerVersion = new Version("0.0.0.0"); private enum enumCheckRunning { no = 0, running, again }; private enumCheckReason checkReason = enumCheckReason.firstStart; public delegate void ConnectionStatusDelegate(enumOnlineStatus? Status); public static event ConnectionStatusDelegate ApiConnectionStatusChanged; public delegate cF4SdUserInfoChange M42FormBasedAuthenticationDelegate(); public event M42FormBasedAuthenticationDelegate M42FormBasedAuthentication; public static cConnectionStatusHelper Instance { get; set; } public bool IsAuthorizationSupported { get; private set; } = false; private System.Timers.Timer timer; #region Lock Elements Connecion Status private readonly object connectionStatusCheckLock = new object(); private enumCheckRunning IsConnectionStatusCheckRunning = enumCheckRunning.no; #endregion public enumOnlineStatus ApiConnectionStatus = enumOnlineStatus.notSpecified; public cConnectionStatusHelper() { ApiConnectionStatus = enumOnlineStatus.offline; cFasdCockpitCommunicationBase.Instance.CheckConnectionStatus = RunConnectionStatusCheckAsync; timer = new System.Timers.Timer(); timer.Elapsed += Timer_Elapsed; Assembly assembly = Assembly.GetExecutingAssembly(); var arrMinClientVersion = assembly.GetCustomAttributes(typeof(AssemblyMinServerVersion), false); if (arrMinClientVersion != null && arrMinClientVersion.Length >= 1) { var attrMinClientVersion = (AssemblyMinServerVersion)(arrMinClientVersion[0]); MinServerVersion = new Version(attrMinClientVersion.minServerVersion); } } private async void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) => await RunConnectionStatusCheckAsync(); public async Task RunConnectionStatusCheckAsync() { await RunConnectionStatusCheckAsync(null); } private (int timerInterval, int shortInterval) GetTimerIntervalFromRegistry(int defaultInterval, int defaultShortInterval) { int timerInterval = defaultInterval; int shortInterval = defaultShortInterval; if (!cLogManager.DefaultLogger.IsDebug) return (timerInterval, shortInterval); try { var regBase = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); var regKey = regBase.OpenSubKey("SOFTWARE\\Consulting4IT GmbH\\First Aid Service Desk\\Cockpit", false); if (regKey is null || !int.TryParse(regKey.GetValue("DebugConnectionCheck", 0).ToString(), out var regValue)) return (timerInterval, shortInterval); if (regValue <= 0) return (timerInterval, shortInterval); timerInterval = regValue * 1000; shortInterval = timerInterval / 10; } catch { } return (timerInterval, shortInterval); } private void HandleConnectionStatus(enumConnectionStatus status, ref int timerInterval, int timerInteralShort) { switch (status) { case enumConnectionStatus.unknown: case enumConnectionStatus.serverNotFound: if (ApiConnectionStatus != enumOnlineStatus.offline) ApiConnectionStatus = enumOnlineStatus.offline; timerInterval = timerInteralShort; LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'serverNotFound'"); break; case enumConnectionStatus.serverResponseError: ApiConnectionStatus = enumOnlineStatus.connectionError; timerInterval = timerInteralShort; LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'serverResponseError'"); break; case enumConnectionStatus.incompatibleServerVersion: LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'incompatibleServerVersion'"); ApiConnectionStatus = enumOnlineStatus.incompatibleServerVersion; break; case enumConnectionStatus.serverStarting: ApiConnectionStatus = enumOnlineStatus.serverStarting; break; case enumConnectionStatus.serverNotConfigured: ApiConnectionStatus = enumOnlineStatus.serverNotConfigured; break; case enumConnectionStatus.connected: if (cCockpitConfiguration.Instance == null || cF4SDCockpitXmlConfig.Instance == null) ApiConnectionStatus = enumOnlineStatus.illegalConfig; else if (ApiConnectionStatus != enumOnlineStatus.online) ApiConnectionStatus = enumOnlineStatus.online; break; } } public async Task RunConnectionStatusCheckAsync(SplashScreenView splashScreen) { if (!IsActive) return; var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { (int timerInterval, int shortTimerInterval) = GetTimerIntervalFromRegistry(300000, 30000); enumOnlineStatus oldConnectionStatus = ApiConnectionStatus; try { timer.Stop(); lock (connectionStatusCheckLock) { switch (IsConnectionStatusCheckRunning) { case enumCheckRunning.again: LogEntry("RunConnectionStatusCheckAsync is already running. Status is 'again'."); return; case enumCheckRunning.running: LogEntry("RunConnectionStatusCheckAsync is already running. Status is 'running'."); IsConnectionStatusCheckRunning = enumCheckRunning.again; return; default: IsConnectionStatusCheckRunning = enumCheckRunning.running; break; } } cCheckConnectionResult connectionResult = await cFasdCockpitCommunicationBase.Instance.CheckConnection(MinServerVersion); IsAuthorizationSupported = connectionResult?.ApiConnectionInfo?.SupportAuthorisation ?? false; HandleConnectionStatus(connectionResult.ConnectionStatus, ref timerInterval, shortTimerInterval); if (connectionResult.ConnectionStatus != enumConnectionStatus.connected) return; if (checkReason < enumCheckReason.heartBeat && ApiConnectionStatus == enumOnlineStatus.illegalConfig) { // download the xml config files Task loadConfigFilesTask = Task.Run(async () => await cFasdCockpitConfig.Instance.LoadConfigFilesAsync(cFasdCockpitCommunicationBase.Instance)); // get the cockpit base configuration from server Task getCockpitConfig = Task.Run(async () => await cFasdCockpitConfig.Instance.GetCockpitConfigurationAsync()); Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.LoadConfigs"))); var configTasks = await Task.WhenAll(loadConfigFilesTask, getCockpitConfig); if (configTasks.Any(t => t == false)) return; if (cFasdCockpitConfig.Instance?.Global != null && cCockpitConfiguration.Instance?.GlobalConfig != null) { cFasdCockpitConfig.Instance.Global.Load(cCockpitConfiguration.Instance.GlobalConfig); cFasdCockpitConfig.Instance.Global.Save(); } } cF4sdUserInfo userInfo; lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock) { userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo; } if (IsAuthorizationSupported) { if (userInfo is null || DateTime.UtcNow > userInfo.RenewUntil) { Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.AuthenticateUser"))); ApiConnectionStatus = enumOnlineStatus.unauthorized; const string cockpitUserRole = "Cockpit.User"; userInfo = await cFasdCockpitCommunicationBase.Instance.WinLogon(); lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock) { cFasdCockpitCommunicationBase.CockpitUserInfo = userInfo; } if (userInfo?.Roles is null || !userInfo.Roles.Contains(cockpitUserRole)) { Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.NoAuthorization"))); LogEntry($"Cockpit User ({userInfo?.Name} with Id {userInfo?.Id}, has not the required permissions.", LogLevels.Error); } else { await Task.Run(async () => await cFasdCockpitConfig.Instance.InstantiateAnalyticsAsync(cFasdCockpitConfig.SessionId)); ApiConnectionStatus = enumOnlineStatus.online; } } } else { if (userInfo == null) { string cockpitUserName = Environment.UserName; string cockpitUserDomain = Environment.UserDomainName; Guid cockpitUserId = await cFasdCockpitCommunicationBase.Instance.GetUserIdByAccount(cockpitUserName, cockpitUserDomain); if (cockpitUserId == Guid.Empty) LogEntry($"Could not get UserId for cockpit user '{cockpitUserName}@{cockpitUserDomain}'.", LogLevels.Warning); else { userInfo = new cF4sdUserInfo() { Id = cockpitUserId, AccountType = cF4sdUserInfo.enumAccountType.unknown, }; lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock) { cFasdCockpitCommunicationBase.CockpitUserInfo = userInfo; } } } ApiConnectionStatus = enumOnlineStatus.online; } if (App.M42OptionMenuItem != null) App.M42OptionMenuItem.Enabled = userInfo != null; // check, if the are logons needed bool m42Valid = await CheckAndRefreshM42LogonAsync(); await cFasdCockpitConfig.Instance.CheckAgentScriptAvailabilityAsync(); await cFasdCockpitConfig.Instance.CheckServerQuickActionAvailabilityAsync(); await cFasdCockpitCommunicationBase.Instance.InitializeAfterOnlineAsync(); cFasdCockpitConfig.Instance.OnUiSettingsChanged(); } catch (Exception E) { LogException(E); } finally { try { lock (connectionStatusCheckLock) { if (IsConnectionStatusCheckRunning == enumCheckRunning.again) timerInterval = 1; IsConnectionStatusCheckRunning = enumCheckRunning.no; } } catch (Exception E) { LogException(E); } if (IsActive) { if (ApiConnectionStatus == enumOnlineStatus.online) NotifyerSupport.SetNotifyIcon("Default", null, NotifyerSupport.enumIconAlignment.BottomRight); else NotifyerSupport.SetNotifyIcon("Default", "OverlayOffline", NotifyerSupport.enumIconAlignment.BottomRight); if (ApiConnectionStatus != oldConnectionStatus) OnApiConnectionStatusChanged(); } if (timer != null) { timer.Interval = timerInterval; timer.Start(); } } } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } } private async Task CheckAndRefreshM42LogonAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo; if (userInfo == null) return false; // do we have a valid M42 token? if (userInfo?.ValidLogonsUntil != null && userInfo.ValidLogonsUntil.TryGetValue(enumAdditionalAuthentication.M42WinLogon, out var logonPeriod)) { if (logonPeriod.renewUntil > DateTime.UtcNow) return true; } // do we need a logon? var isNeeded = false; if (cFasdCockpitConfig.Instance?.M42Config == null) return false; cF4sdCockpitConfigM42 config = cFasdCockpitConfig.Instance.M42Config; if (config.Control == enumM42AuthenticationControl.always) isNeeded = true; else if (config.Control == enumM42AuthenticationControl.auto) { if (userInfo?.additionalLogons == null) return false; if (userInfo.additionalLogons.Contains(enumAdditionalAuthentication.M42WinLogon)) isNeeded = true; } if (!isNeeded) return false; // yes, we need a new logon cF4sdCockpitM42BearerTokenInfo tokenInfo = null; cF4SdUserInfoChange userChange = null; switch (config.Method) { case enumM42AuthenticationMethod.passthrough: tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonPassthrough(); break; case enumM42AuthenticationMethod.basic: var user = config.BasicUser; var pw = PrivateSecurePassword.Instance.Decode(config.BasicPassword); tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonBasic(user, pw); break; case enumM42AuthenticationMethod.token: var token = PrivateSecurePassword.Instance.Decode(config.ApiToken); tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonToken(token); break; case enumM42AuthenticationMethod.forms: var FormBasedAuth = M42FormBasedAuthentication; userChange = FormBasedAuth?.Invoke(); break; } if (tokenInfo?.Token != null && tokenInfo.ValidUntil > DateTime.UtcNow) { var tokenRegistration = new cF4SDTokenRegistration() { UserId = userInfo.Id, TokenType = cF4SDTokenRegistration.enumTokenType.M42Bearer, Secret = tokenInfo.Token }; userChange = await cFasdCockpitCommunicationBase.Instance.RegisterExternalTokenAsync(tokenRegistration); } if (userChange != null) { lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock) { userChange.ChangeUserInfo(cFasdCockpitCommunicationBase.CockpitUserInfo); } return true; } } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return false; } public void OnApiConnectionStatusChanged() { ConnectionStatusDelegate handler = ApiConnectionStatusChanged; handler?.Invoke(ApiConnectionStatus); } } }