Files
C4IT-F4SD-Collector/F4SD-Cockpit-ServerCore/DataHistoryCollectorTokenCache.cs
2025-11-11 11:12:05 +01:00

406 lines
16 KiB
C#

using C4IT.FASD.Base;
using C4IT.Logging;
using C4IT.Security;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using static C4IT.Logging.cLogManager;
namespace C4IT.DataHistoryProvider
{
public class cDataHistoryCollectorTokenCache
{
private const int timerCleanupInterval = 15 * 60 * 1000;
private System.Threading.Timer cleanupTimer;
private cDataHistoryCollector Collector;
private Dictionary<Guid, Dictionary<cF4SDTokenRegistration.enumTokenType, cTokenInfo>> dicToken = null;
public class cTokenInfo
{
public Guid id;
public cF4SDTokenRegistration.enumTokenType type;
public string name;
public string name2;
public string secret;
public DateTime validUntil;
public DateTime renewUntil;
public bool IsValid()
{
if (id == Guid.Empty)
return false;
if (validUntil < DateTime.UtcNow + TimeSpan.FromMinutes(cDataHistoryCollector.constMinutesUntilTokenIsInvalid))
return false;
return true;
}
public cTokenInfo GetNewInstance()
{
var _sec = secret;
if (HasTokenTypeLongLifetime(type))
_sec = cSecurePassword.Instance.Decode(secret);
var _ret = new cTokenInfo()
{
id = id,
type = type,
name = name,
name2 = name2,
secret = _sec,
validUntil = validUntil,
renewUntil = renewUntil
};
return _ret;
}
}
private static class TokenCacheCriticalSection
{
private static SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
public static async Task EnterAsync()
{
await Semaphore.WaitAsync();
}
public static void Leave()
{
Semaphore.Release();
}
}
private static bool HasTokenTypeLongLifetime(cF4SDTokenRegistration.enumTokenType tokenType)
{
switch (tokenType)
{
case cF4SDTokenRegistration.enumTokenType.M42Api:
return true;
default:
return false;
}
}
public cDataHistoryCollectorTokenCache(cDataHistoryCollector Collector, cF4sdWebRequestInfo requestInfo, int LogDeep)
{
this.Collector = Collector;
cleanupTimer = new System.Threading.Timer(async (x) => await DoCleanup(requestInfo, 0), null, timerCleanupInterval, timerCleanupInterval);
}
private bool privInsert(cTokenInfo info, bool overwite)
{
if (dicToken == null)
return false;
if (!dicToken.TryGetValue(info.id, out var _entyId))
{
_entyId = new Dictionary<cF4SDTokenRegistration.enumTokenType, cTokenInfo>();
dicToken.Add(info.id, _entyId);
}
if (!overwite && _entyId.ContainsKey(info.type))
return false;
_entyId[info.type] = info;
return true;
}
public async Task LoadAsync(cDbConnection DbConn, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
var _startTime = DateTime.UtcNow;
try
{
if (dicToken == null)
dicToken = new Dictionary<Guid, Dictionary<cF4SDTokenRegistration.enumTokenType, cTokenInfo>>();
if (!DataHistorySqlHelper.GetWellKnownSqlStatement("GetAllExternalTokens", out var Query))
return;
var sqlerror = 0;
var sqlStartTime = DateTime.UtcNow;
await TokenCacheCriticalSection.EnterAsync();
try
{
dicToken.Clear();
using (var _reader = await DataHistorySqlHelper.GetDataReaderAsync(DbConn, Query, null, Token))
{
if (_reader != null)
try
{
if (_reader.HasRows)
{
var _toDelete = new List<cTokenInfo>();
while (await _reader.ReadAsync())
{
try
{
var _entry = new cTokenInfo();
_entry.id = _reader.GetGuid(0);
_entry.type = (cF4SDTokenRegistration.enumTokenType)_reader.GetInt32(1);
_entry.name = _reader.IsDBNull(2) ? null : _reader.GetString(2);
_entry.name2 = _reader.IsDBNull(3) ? null : _reader.GetString(3);
var _secret = _reader.IsDBNull(4) ? null : _reader.GetString(4);
if (HasTokenTypeLongLifetime(_entry.type))
_entry.secret = _secret;
else
_entry.secret = cSecurePassword.Instance.Decode(_secret);
_entry.validUntil = _reader.IsDBNull(5) ? DateTime.MinValue : _reader.GetDateTime(5);
_entry.renewUntil = _reader.IsDBNull(6) ? DateTime.MinValue : _reader.GetDateTime(6);
if (_entry.IsValid())
{
if (!privInsert(_entry, false))
_toDelete.Add(_entry);
}
else
_toDelete.Add(_entry);
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
if (Token.IsCancellationRequested)
return;
}
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
}
}
catch (Exception E)
{
sqlerror = E.HResult;
cLogManager.DefaultLogger.LogException(E);
}
finally
{
TokenCacheCriticalSection.Leave();
if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, sqlStartTime, "", sqlerror);
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
if (CM != null) LogMethodEnd(CM);
}
}
public async Task SetAsync(cF4SDTokenRegistration _tokenRegistration, cF4SDTokenValidityPeriod validityPeriod, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
var _startTime = DateTime.UtcNow;
try
{
// encryption of the secret for non-temporary tokens
string _sec = _tokenRegistration.Secret;
string _secEnc = cSecurePassword.Instance.Encode(_tokenRegistration.Secret);
if (HasTokenTypeLongLifetime(_tokenRegistration.TokenType))
_sec = _secEnc;
var _tokenInfo = new cTokenInfo()
{
id = _tokenRegistration.UserId,
type = _tokenRegistration.TokenType,
name = _tokenRegistration.Name,
name2 = _tokenRegistration.Name2,
secret = _sec,
validUntil = validityPeriod.validUntil,
renewUntil = validityPeriod.renewUntil
};
if (!DataHistorySqlHelper.GetWellKnownSqlStatement("InsertOrUpdateExternalToken", out var Query))
return;
var Params = new Dictionary<string, object>()
{
{ "Id", _tokenInfo.id },
{ "Type", (int)_tokenInfo.type },
{ "Name", _tokenInfo.name },
{ "Name2", _tokenInfo.name2 },
{ "Secret", _secEnc },
{ "ValidUntil", _tokenInfo.validUntil },
{ "RenewUntil", _tokenInfo.renewUntil },
};
var sqlerror = 0;
var sqlStartTime = DateTime.UtcNow;
await TokenCacheCriticalSection.EnterAsync();
try
{
privInsert(_tokenInfo, true);
using (var Conn = new cDbConnection(Collector.mainDbConnection))
{
if (!Conn.IsOpen)
{
LogEntry($"Could not open main database '{Collector.mainDbConnection.Database}', aborting query'", LogLevels.Error);
return;
}
await DataHistorySqlHelper.ExecuteAsync(Conn, Query, Params, requestInfo, Token);
}
}
catch (Exception E)
{
sqlerror = E.HResult;
LogException(E);
}
finally
{
TokenCacheCriticalSection.Leave();
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
if (CM != null) LogMethodEnd(CM);
}
}
public async Task<cTokenInfo> GetAsync(Guid UserId, cF4SDTokenRegistration.enumTokenType TokenType, cF4sdWebRequestInfo requestInfo, int LogDeep)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (dicToken == null)
return null;
var doCleanup = false;
try
{
await TokenCacheCriticalSection.EnterAsync();
if (dicToken.TryGetValue(UserId, out var _entry))
if (_entry.TryGetValue(TokenType, out var _tokenInfo))
{
if (_tokenInfo.validUntil < DateTime.UtcNow)
return null;
return _tokenInfo.GetNewInstance();
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
TokenCacheCriticalSection.Leave();
if (doCleanup)
await DoCleanup(requestInfo, LogDeep + 1);
if (CM != null) LogMethodEnd(CM);
}
return null;
}
public async Task DoCleanup(cF4sdWebRequestInfo requestInfo, int LogDeep)
{
if (dicToken == null)
return;
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
var _startTime = DateTime.UtcNow;
try
{
var _current = DateTime.UtcNow;
// remove all expiered tokens in the cache
await TokenCacheCriticalSection.EnterAsync();
cleanupTimer.Change(Timeout.Infinite, Timeout.Infinite);
var _toRemove1 = new List<Guid>();
foreach (var _entry in dicToken)
{
var _toRemove2 = new List<cF4SDTokenRegistration.enumTokenType>();
foreach (var _tokenInfoPair in _entry.Value)
{
if (_tokenInfoPair.Value.validUntil < _current)
_toRemove2.Add(_tokenInfoPair.Key);
}
foreach (var token in _toRemove2)
_entry.Value.Remove(token);
if (_entry.Value.Count == 0)
_toRemove1.Add(_entry.Key);
}
foreach (var _entry in _toRemove1)
dicToken.Remove(_entry);
// synchronize the sql table with the cache
if (!DataHistorySqlHelper.GetWellKnownSqlStatement("RemoveExpieredExternalTokens", out var Query))
return;
// remove the expired tokens
var Params = new Dictionary<string, object>()
{
{ "ValidUntil", _current }
};
var sqlStartTime = DateTime.UtcNow;
try
{
using (var Conn = new cDbConnection(Collector.mainDbConnection))
{
if (!Conn.IsOpen)
{
LogEntry($"Could not open main database '{Collector.mainDbConnection.Database}', aborting query'", LogLevels.Error);
return;
}
var _n = await DataHistorySqlHelper.ExecuteAsync(Conn, Query, Params, requestInfo, CancellationToken.None);
}
}
catch (Exception E)
{
LogException(E);
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
TokenCacheCriticalSection.Leave();
cleanupTimer.Change(timerCleanupInterval, timerCleanupInterval);
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
if (CM != null) LogMethodEnd(CM);
}
}
}
}