Prepare Data History Provider 2.7 integration update

- modularize Citrix and agent integrations and add remote desktop endpoints
- extend ticket overview, ticket links, token validation, and staged relation handling
- update configuration, licensing, project, signing, and publish artifacts
This commit is contained in:
Meik
2026-07-20 09:31:52 +02:00
parent a3357e2a36
commit ccc48c521a
62 changed files with 5267 additions and 2521 deletions

View File

@@ -0,0 +1,95 @@
using C4IT.DataHistoryProvider.Base.Modules.Agent.DTOs;
using C4IT.FASD.Base;
using C4IT.FASD.Communication.Agent;
using C4IT.HTTP;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using static C4IT.Logging.cLogManager;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent
{
internal class AgentApiHandler : cHttpApiBase
{
private readonly Uri _baseUrl;
private const string _agentLoginPath = "connect/token";
private readonly cAgentApiConfiguration _agentConfig;
public AgentApiHandler(cAgentApiConfiguration agentConfig) : base("Agent ID tenant", $"{agentConfig.ApiUrl}/{agentConfig.LogonUrl}")
{
_agentConfig = agentConfig;
_baseUrl = new Uri(_agentConfig.LogonUrl);
AlwaysReturnContent = true;
}
private protected override async Task<bool> privLogonAsync(cOAuthLogonInfo logonInfo)
{
try
{
var postData = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", logonInfo.ClientID),
new KeyValuePair<string, string>("client_secret", logonInfo.ClientSecret)
};
var formData = new FormUrlEncodedContent(postData);
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUrl, _agentLoginPath)) { Content = formData };
using (var httpClient = new HttpClient())
{
var response = await httpClient.SendAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
var responseMessage = await response.Content.ReadAsStreamAsync();
LogEntry($"F4SD Agent Logon failed. StatusCode: {response.StatusCode}");
LogEntry($"F4SD Agent Logon failed. Response: {responseMessage}");
return false;
}
string responseContent = await response.Content.ReadAsStringAsync();
var logonResult = JsonConvert.DeserializeObject<cAgentApiLogonInfo>(responseContent);
if (logonResult.TokenType.Equals("Bearer", StringComparison.OrdinalIgnoreCase) == false)
{
LogEntry("F4SD Agent Access token is not of type 'Bearer'.");
return false;
}
cAgentApiLogonInfo.Instance = logonResult;
AccessToken = logonResult.AccessToken;
TokenExpiresIn = DateTime.UtcNow + TimeSpan.FromSeconds(logonResult.SecondsTillTokenExpires * RelogonFactor);
IsOnline = true;
}
return true;
}
catch (Exception ex)
{
LogException(ex);
return false;
}
}
internal async Task<T> Request<T>(string url, eHttpMethod httpMethod = eHttpMethod.get, object bodyData = null, CancellationToken token = default) where T : AgentApiResult
{
await LogonAsync(new cOAuthLogonInfo() { ClientID = _agentConfig.ClientId, ClientSecret = _agentConfig.ClientSecret, Tenant = "F4SD Agent" });
Uri agentUrl = new Uri(_baseUrl, url);
dynamic requestResult = await privRequestAsync(agentUrl.ToString(), httpMethod, bodyData, token: token);
// todo handle not successfull status
requestResult.ToObject<T>();
string serialized = JsonConvert.SerializeObject(requestResult); // todo find better approach for casting the dynamic
T deserialized = JsonConvert.DeserializeObject<T>(serialized);
return deserialized;
}
internal async Task Request(string url, eHttpMethod httpMethod = eHttpMethod.get, object bodyData = null)
=> await privRequestAsync(url, httpMethod, bodyData);
}
}

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent
{
public class AgentRemoteClientInfo
{
[JsonProperty("orgCode")]
public int OrganisationCode { get; set; }
[JsonProperty("deviceCode")]
public int DeviceCode { get; set; }
[JsonProperty("accountCode")]
public int AccountCode { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using C4IT.FASD.Communication.Agent;
using Newtonsoft.Json;
using System;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent
{
public class AgentRemoteConnectionInfo : AgentApiResult
{
[JsonProperty("connectionId")]
public Guid ConnectionId { get; set; }
[JsonProperty("phoenixServiceUrl")]
public Uri ServiceUrl { get; set; }
[JsonProperty("secret")]
public string Secret { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using C4IT.DataHistoryProvider.Base.DataSources;
using C4IT.FASD.Base;
using C4IT.FASD.Communication.Agent;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent
{
public class AgentRemoteDesktopManager : IRemoteDesktopManager<AgentRemoteConnectionInfo, AgentRemoteClientInfo, AgentRemoteConnectionStatusDto>
{
private readonly AgentApiHandler _agentApiHandler;
public AgentRemoteDesktopManager(cAgentApiConfiguration agentConfig)
{
_agentApiHandler = new AgentApiHandler(agentConfig);
}
public async Task<AgentRemoteConnectionStatusDto> GetConnectionStatus(Guid connectionId)
{
return await _agentApiHandler.Request<AgentRemoteConnectionStatusDto>($"api/management/c4it/phoenix-connections/{connectionId}/status", HTTP.eHttpMethod.get);
}
public async Task<AgentRemoteConnectionInfo> InitiateConnectionOfClient(AgentRemoteClientInfo clientInfos, bool isElevated, CancellationToken token)
{
string url = "api/management/c4it/phoenix-connections";
if (isElevated)
url += "/admin";
return await _agentApiHandler.Request<AgentRemoteConnectionInfo>(url, HTTP.eHttpMethod.post, clientInfos, token);
}
public async Task TerminateConnection(Guid connectionId)
{
await _agentApiHandler.Request($"api/management/c4it/phoenix-connections/{connectionId}", HTTP.eHttpMethod.delete);
}
public bool IsActive() => true;
public Task<HealthInformation> GetHealthInfo()
{
// todo Agent Server has to implement an "health" endpoint and return its result
return Task.FromResult(new HealthInformation(HealthStatus.healthy));
}
}
}

View File

@@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent.DTOs
{
public class AgentErrorDto
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace C4IT.DataHistoryProvider.Base.Modules.Agent.DTOs
{
public class AgentLogonResultDto
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("remote_ip")]
public string RemoteIp { get; set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Net;
using System.Reflection;
using System.Xml;
using C4IT.DataHistoryProvider;
using C4IT.Logging;
using C4IT.Security;
using C4IT.XML;
using static C4IT.Logging.cLogManager;
namespace C4IT.DataHistoryProvider.Base.Modules.Citrix
{
public class cDataHistoryConfigCitrix : IConfigNodeValidation
{
public bool IsValid { get; private set; } = false;
public cDataHistoryScanTiming ScanTiming { get; private set; } = null;
public cDataHistoryConfigCitrix(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
{
var XNode2 = XNode.SelectSingleNode("Citrix");
if (!(XNode2 is XmlElement XCitrix))
{
return;
}
Parser.EnterElement("Citrix");
try
{
ScanTiming = new cDataHistoryScanTiming(XCitrix, TimeSpan.FromHours(24), TimeSpan.FromMinutes(10), Parser);
Systems = cDataHistoryCitrixCloudTenant.LoadFromXml(XCitrix, Credentials, Parser);
if (Systems == null)
Systems = new List<cDataHistoryCitrixSystem>();
var _onPremSystems = cDataHistoryCitrixOnPrem.LoadFromXml(XCitrix, Credentials, Parser);
if (_onPremSystems != null)
Systems.AddRange(_onPremSystems);
IsValid = true;
}
finally
{
Parser.LeaveElement("Citrix");
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
}
public List<cDataHistoryCitrixSystem> Systems { get; private set; } = null; //ToDo: Check all uses of the citrix systems to implement on prem and tenant specific handling if needed
}
public abstract class cDataHistoryCitrixSystem : cConfigNodeNamed
{
public cCredential Credential { get; private set; } = null;
public Guid SiteID { get; set; } = Guid.Empty;
public cDataHistoryCitrixSystem(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser) : base(XNode, Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
{
if (!IsValid)
return;
SiteID = cXmlParser.GetGuidFromXmlAttribute(XNode, "SiteID");
var strCreds = cXmlParser.GetStringFromXmlAttribute(XNode, "Credential");
if (!string.IsNullOrWhiteSpace(strCreds))
{
if (Credentials.TryGetValue(strCreds, out var Cred))
Credential = Cred;
}
if (Credential == null)
{
Parser.AddInvalidAttribute(XNode, null, "Credential");
return;
}
IsValid = true;
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
}
}
public class cDataHistoryCitrixCloudTenant : cDataHistoryCitrixSystem
{
public string Domain { get; private set; } = "";
public string TenantID { get; private set; } = String.Empty;
internal cDataHistoryCitrixCloudTenant(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser) : base(XNode, Credentials, Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
{
if (!IsValid)
return;
Domain = cXmlParser.GetStringFromXmlAttribute(XNode, "Domain");
if (string.IsNullOrWhiteSpace(Domain))
{
Parser.AddInvalidAttribute(XNode, null, "Domain");
return;
}
TenantID = cXmlParser.GetStringFromXmlAttribute(XNode, "TenantID");
if (TenantID == String.Empty)
{
Parser.AddInvalidAttribute(XNode, Domain, "TenantID");
return;
}
IsValid = true;
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
}
internal static List<cDataHistoryCitrixSystem> LoadFromXml(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
var RetVal = new List<cDataHistoryCitrixSystem>();
try
{
var XList = XNode.SelectNodes("Citrix-Cloud-Tenant");
if (XList != null && XList.Count > 0)
{
Parser.EnterElement("Citrix-Cloud-Tenant");
try
{
foreach (var Entry in XList)
{
if (!(Entry is XmlElement XEntry))
continue;
var Node = new cDataHistoryCitrixCloudTenant(XEntry, Credentials, Parser);
if (Node.IsValid)
RetVal.Add(Node);
Parser.SelectElementNext();
}
}
finally
{
Parser.LeaveElement("Citrix-Cloud-Tenant");
}
}
XList = XNode.SelectNodes("Citrix-OnPrem-System");
if (XList != null && XList.Count > 0)
{
Parser.EnterElement("Citrix-OnPrem-System");
try
{
foreach (var Entry in XList)
{
if (!(Entry is XmlElement XEntry))
continue;
var Node = new cDataHistoryCitrixOnPrem(XEntry, Credentials, Parser);
if (Node.IsValid)
RetVal.Add(Node);
Parser.SelectElementNext();
}
}
finally
{
Parser.LeaveElement("Citrix-OnPrem-System");
}
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
return RetVal;
}
}
public class cDataHistoryCitrixOnPrem : cDataHistoryCitrixSystem
{
public string Server { get; private set; } = string.Empty;
internal cDataHistoryCitrixOnPrem(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser) : base(XNode, Credentials, Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
{
Server = cXmlParser.GetStringFromXmlAttribute(XNode, "Server");
if (string.IsNullOrWhiteSpace(Server))
{
Parser.AddInvalidAttribute(XNode, null, "Server");
return;
}
IsValid = true;
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
}
internal static List<cDataHistoryCitrixSystem> LoadFromXml(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
var RetVal = new List<cDataHistoryCitrixSystem>();
try
{
var XList = XNode.SelectNodes("Citrix-OnPrem");
if (XList != null && XList.Count > 0)
{
Parser.EnterElement("Citrix-OnPrem");
try
{
foreach (var Entry in XList)
{
if (!(Entry is XmlElement XEntry))
continue;
var Node = new cDataHistoryCitrixOnPrem(XEntry, Credentials, Parser);
if (Node.IsValid)
RetVal.Add(Node);
Parser.SelectElementNext();
}
}
finally
{
Parser.LeaveElement("Citrix-OnPrem");
}
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
return RetVal;
}
}
}