first commit
This commit is contained in:
405
F4SD-Cockpit-ServerCore/DataHistoryCollectorTokenCache.cs
Normal file
405
F4SD-Cockpit-ServerCore/DataHistoryCollectorTokenCache.cs
Normal file
@@ -0,0 +1,405 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user