Files
C4IT-F4SD-Client/FasdDesktopUi/Basics/Models/ConnectionStatusHelper.cs
2025-11-11 11:03:42 +01:00

440 lines
20 KiB
C#

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<bool> loadConfigFilesTask = Task.Run(async () => await cFasdCockpitConfig.Instance.LoadConfigFilesAsync(cFasdCockpitCommunicationBase.Instance));
// get the cockpit base configuration from server
Task<bool> 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";
#if isNewFeature
const string cockpitTicketAgentRole = "Cockpit.TicketAgent";
#endif
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;
#if isNewFeature
if (userInfo.Roles.Contains(cockpitTicketAgentRole))
cCockpitConfiguration.Instance.ticketSupport.EditTicket = true;
#endif
}
}
}
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<bool> 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);
}
}
}