using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading; using ArchestrAServices.Common; using Asb.Base.V2.ContractTypes; namespace Asb.Base.V2; [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)] public abstract class BaseV2Shim : IAuthenticateAsb { private sealed class ConnectionContext : IDisposable { private readonly TimeSpan keepAliveTimeout; private readonly Timer timer; private bool disposed; public IContextChannel ConnectionChannel { get; private set; } public IBaseV2 ConnectionImplementation { get; } public ConnectionContext(IBaseV2 implementation, TimerCallback keepAliveTimeoutCallback, Guid connectionId, TimeSpan keepAliveTimeout) { ConnectionChannel = ((OperationContext.Current == null) ? null : OperationContext.Current.Channel); ConnectionImplementation = implementation; timer = new Timer(keepAliveTimeoutCallback, connectionId, (int)keepAliveTimeout.TotalMilliseconds, -1); this.keepAliveTimeout = keepAliveTimeout; } ~ConnectionContext() { Dispose(disposing: false); } public void RestartTimer() { timer.Change((int)keepAliveTimeout.TotalMilliseconds, -1); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposed) { return; } disposed = true; if (disposing) { if (ConnectionChannel != null) { ConnectionChannel.Close(); ConnectionChannel = null; } if (timer != null) { timer.Dispose(); } } } } private static readonly ReaderWriterLockSlim implementationLock = new ReaderWriterLockSlim(); private static readonly Dictionary implementations = new Dictionary(); private static readonly TimeSpan defaultKeepAliveTimeout = TimeSpan.FromSeconds(30.0); public virtual ConnectionState DownstreamConnectionState { get; set; } protected virtual TimeSpan KeepAliveTimeout => defaultKeepAliveTimeout; public ConnectResponse Connect(ConnectRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("Connect entry [ConId]", request.ConnectionId); ServiceTrace.LogResume("Connect entry, connection Id {0} ", request.ConnectionId); ConnectResponse connectResponse = SystemAuthenticationServiceAuthentication.ProcessClientConnection(request); ServiceTrace.LogVerbose("BaseV2Shim.Connect started new SysAuthenticator for connection Id {0}", connectResponse.ConnectionValidator.ConnectionId); ServiceTrace.LogSuspend("Connect exit, connection Id {0}, success = {1}", request.ConnectionId, connectResponse.Result.Success); ServiceTrace.LogCsv("Connect exit [ConId Success]", request.ConnectionId, connectResponse.Result.Success); return connectResponse; } public AuthenticateMeResponse AuthenticateMe(AuthenticateMeRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("AuthenticateMe entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("AuthenticateMe entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); ArchestrAResult archestrAResult = ArchestrAResult.MakeResult(ArchestrAError.OperationFailed, 0); SystemAuthenticationServiceAuthentication serviceAuthenticator = SysAuthenticatorServiceCache.GetServiceAuthenticator(request.ConnectionValidator.ConnectionId); if (serviceAuthenticator != null) { if (serviceAuthenticator.ProcessClientAuthenticateMe(request)) { ServiceTrace.LogVerbose("AuthenticateMe found authenticator and validated request message, establishing serivce implementation for connect Id {0})", serviceAuthenticator.ConnectionId); ConsumerMetadata metadata = ConsumerMetadata.Create(request.ConsumerMetaData); archestrAResult = CheckConsumerMetadata(metadata, serviceAuthenticator.ConnectionId); if (archestrAResult.Success) { archestrAResult = CreateServiceImplmentation(serviceAuthenticator, metadata); } } else { archestrAResult = ArchestrAResult.MakeResult(ArchestrAError.ApplicationAuthenticationError, 0); string message = string.Format(CultureInfo.CurrentCulture, "AuthenticateMe unable to authenticate client for ConnectionId {0}", new object[1] { serviceAuthenticator.ConnectionId }); ServiceTrace.LogWarning(message); archestrAResult.AddErrorMessage(message); } } if (!archestrAResult.Success) { OnDisconnect(request.ConnectionValidator.ConnectionId); } ServiceTrace.LogSuspend("AuthenticateMe exit, connection Id {0}, success = {1}", request.ConnectionValidator.ConnectionId, archestrAResult.Success); ServiceTrace.LogCsv("AuthenticateMe exit [ConId Success]", request.ConnectionValidator.ConnectionId, archestrAResult.Success); return new AuthenticateMeResponse(archestrAResult, DownstreamConnectionState); } public KeepAliveResponse KeepAlive(KeepAliveRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("KeepAlive entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("KeepAlive entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); ArchestrAResult result = ArchestrAResult.MakeResult(ArchestrAError.OperationFailed, 0); SystemAuthenticationServiceAuthentication serviceAuthenticator = SysAuthenticatorServiceCache.GetServiceAuthenticator(request.ConnectionValidator.ConnectionId); if (serviceAuthenticator != null && serviceAuthenticator.ValidRequest(request, forceHmac: false)) { implementationLock.EnterReadLock(); bool flag; ConnectionContext value; try { flag = implementations.TryGetValue(request.ConnectionValidator.ConnectionId, out value); } finally { implementationLock.ExitReadLock(); } if (flag && value != null) { value.RestartTimer(); IBaseV2 connectionImplementation = value.ConnectionImplementation; try { result = EnsureValidResult(connectionImplementation.KeepAlive()); } catch (Exception ex) { ServiceTrace.LogWarning("KeepAlive caught exception from implementation for connection Id {0}: {1}", request.ConnectionValidator.ConnectionId, ex.Message); if (ex.InnerException != null) { ServiceTrace.LogWarning(" {0}", ex.InnerException.Message); } } } } ServiceTrace.LogResume("KeepAlive exit, connection Id {0}", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("KeepAlive exit [ConId]", request.ConnectionValidator.ConnectionId); return new KeepAliveResponse(result, DownstreamConnectionState); } public GetStatusItemsResponse GetStatusItems(GetStatusItemsRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("GetStatusItems entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("GetStatusItems entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); GetStatusItemsResponse getStatusItemsResponse = new GetStatusItemsResponse(ArchestrAResult.MakeResult(ArchestrAError.InvalidConnectionId, 0), null, DownstreamConnectionState); IBaseV2 implementation = GetImplementation(request); if (implementation != null) { try { getStatusItemsResponse.Result = EnsureValidResult(implementation.GetStatusItems(out var items)); List list = items.ToList(); list.Add("ServiceLoadTime"); getStatusItemsResponse.Items = list.ToArray(); } catch (Exception ex) { ServiceTrace.LogWarning("GetStatusItems caught exception from implementation for ConnectionId {0}: {1}", request.ConnectionValidator.ConnectionId, ex.Message); getStatusItemsResponse.Result.AddErrorMessage(ex.Message); if (ex.InnerException != null) { ServiceTrace.LogWarning(" {0}", ex.InnerException.Message); getStatusItemsResponse.Result.AddErrorMessage(ex.InnerException.Message); } getStatusItemsResponse.Result.ResultCodeAsError = ArchestrAError.OperationFailed; } } ServiceTrace.LogSuspend("GetStatusItems exit, connection Id {0}, success = {1}", request.ConnectionValidator.ConnectionId, getStatusItemsResponse.Result.Success); ServiceTrace.LogCsv("GetStatusItems exit [ConId Success]", request.ConnectionValidator.ConnectionId, getStatusItemsResponse.Result.Success); return getStatusItemsResponse; } public GetStatusResponse GetStatus(GetStatusRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("GetStatus entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("GetStatus entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); GetStatusResponse getStatusResponse = new GetStatusResponse(ArchestrAResult.MakeResult(ArchestrAError.InvalidConnectionId, 0), null, DownstreamConnectionState); IBaseV2 implementation = GetImplementation(request); if (implementation != null) { try { getStatusResponse.Result = EnsureValidResult(implementation.GetStatus(request.ItemsToReturn, out var items)); List list = items.ToList(); list.Add(new NamedValue { Name = "ServiceLoadTime", Value = Value.Create(implementation.ServiceLoadTime) }); getStatusResponse.Items = list.ToArray(); } catch (Exception ex) { ServiceTrace.LogWarning("GetStatus caught exception from implementation for ConnectionId {0}: {1}", request.ConnectionValidator.ConnectionId, ex.Message); getStatusResponse.Result.AddErrorMessage(ex.Message); if (ex.InnerException != null) { ServiceTrace.LogWarning(" {0}", ex.InnerException.Message); getStatusResponse.Result.AddErrorMessage(ex.InnerException.Message); } getStatusResponse.Result.ResultCodeAsError = ArchestrAError.OperationFailed; } } ServiceTrace.LogSuspend("GetStatus exit, connection Id {0}, success = {1}", request.ConnectionValidator.ConnectionId, getStatusResponse.Result.Success); ServiceTrace.LogCsv("GetStatus exit [ConId Success]", request.ConnectionValidator.ConnectionId, getStatusResponse.Result.Success); return getStatusResponse; } public RenewResponse Renew(RenewRequest request) { if (request == null) { return null; } ServiceTrace.LogCsv("Renew entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("Renew entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); RenewResponse renewResponse = SystemAuthenticationServiceAuthentication.ProcessClientRenew(request); ServiceTrace.LogSuspend("Renew exit, connection Id {0}, success = {1}", request.ConnectionValidator.ConnectionId, renewResponse.Result.Success); ServiceTrace.LogCsv("Renew exit [ConId Success]", request.ConnectionValidator.ConnectionId, renewResponse.Result.Success); return renewResponse; } public void UpdateSystemAuthenticationConfiguration(UpdateSystemAuthenticationConfigurationRequest request) { if (request != null) { ServiceTrace.LogCsv("UpdateSystemAuthenticationConfiguration entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("UpdateSystemAuthenticationConfiguration entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); SystemAuthenticationServiceAuthentication.ProcessClientUpdateSystemAuthenticationConfiguration(request); ServiceTrace.LogSuspend("UpdateSystemAuthenticationConfiguration exit, connection Id {0}", request.ConnectionValidator.ConnectionId); ServiceTrace.LogCsv("UpdateSystemAuthenticationConfiguration exit [ConId]", request.ConnectionValidator.ConnectionId); } } public void Disconnect(DisconnectRequest request) { if (request != null) { ServiceTrace.LogCsv("Disconnect entry [ConnId]", request.ConnectionValidator.ConnectionId); ServiceTrace.LogResume("Disconnect entry, connection Id {0} ", request.ConnectionValidator.ConnectionId); SystemAuthenticationServiceAuthentication serviceAuthenticator = SysAuthenticatorServiceCache.GetServiceAuthenticator(request.ConnectionValidator.ConnectionId); if (serviceAuthenticator != null && serviceAuthenticator.ValidRequest(request, forceHmac: false)) { OnDisconnect(request.ConnectionValidator.ConnectionId); serviceAuthenticator.ProcessClientDisconnect(request); } ServiceTrace.LogSuspend("Disconnect exit, connection Id {0}", request.ConnectionValidator.ConnectionId); ServiceTrace.LogCsv("Disconnect exit [ConId]", request.ConnectionValidator.ConnectionId); } } protected UserToken DecryptUserToken(Guid connectionId, UserTokenContract wireToken) { if (wireToken == null) { return null; } SystemAuthenticationServiceAuthentication serviceAuthenticator = SysAuthenticatorServiceCache.GetServiceAuthenticator(connectionId); if (serviceAuthenticator == null || !serviceAuthenticator.SecureSessionEstablished) { throw new InvalidOperationException("Cannot decrypt user token due to session in wrong state"); } UserToken userToken = new UserToken { Encryption = wireToken.Encryption, HostName = wireToken.HostName, IdType = wireToken.IdType, LocationId = wireToken.LocationId, UserName = wireToken.UserName, Validity = wireToken.Validity }; if (wireToken.Password != null) { byte[] bytes = serviceAuthenticator.Decypher(wireToken.Password, serviceAuthenticator.EncryptionKey); userToken.Password = Encoding.UTF8.GetString(bytes); } if (wireToken.SamlToken != null) { userToken.SamlToken = serviceAuthenticator.Decypher(wireToken.SamlToken, serviceAuthenticator.EncryptionKey); } if (wireToken.JwtToken != null) { byte[] bytes2 = serviceAuthenticator.Decypher(wireToken.JwtToken, serviceAuthenticator.EncryptionKey); userToken.JwtToken = Encoding.UTF8.GetString(bytes2); } if (wireToken.X509Certificate != null) { userToken.X509Certificate = serviceAuthenticator.Decypher(wireToken.X509Certificate, serviceAuthenticator.EncryptionKey); } return userToken; } protected static ArchestrAResult EnsureValidResult(ArchestrAResult result) { if (result == null) { return ArchestrAResult.MakeErrorResult("Service returned no result"); } return result; } private ArchestrAResult CreateServiceImplmentation(SystemAuthenticationServiceAuthentication authenticator, ConsumerMetadata metadata) { ArchestrAResult result = ArchestrAResult.MakeGoodResult(); IBaseV2 implementation = GetImplementation(); if (implementation == null) { result = ArchestrAResult.MakeResult(ArchestrAError.Unknown, 0); string message = string.Format(CultureInfo.CurrentCulture, "AuthenticateMe could not create a service implementation object for ConnectionId {0}", new object[1] { authenticator.ConnectionId }); ServiceTrace.LogWarning(message); result.AddErrorMessage(message); return result; } try { ServiceTrace.LogCsv("AuthenticateMe found authenticator and validated request message, calling implementation OnConnect [ConId]", authenticator.ConnectionId); ServiceTrace.LogVerbose("AuthenticateMe found authenticator and validated request message, calling implementation OnConnect for connection Id {0}", authenticator.ConnectionId); implementation.ServiceLoadTime = DateTime.UtcNow; implementation.ConnectionId = authenticator.ConnectionId; implementation.OnConnect(authenticator.Lifetime, metadata); CreateConnectionContext(authenticator.ConnectionId, implementation); } catch (Exception ex) { result = ArchestrAResult.MakeResult(ArchestrAError.Unknown, 0); ServiceTrace.LogWarning("AuthenticateMe caught exception from implementation for ConnectionId {0}: {1}", authenticator.ConnectionId, ex.Message); result.AddErrorMessage(ex.Message); if (ex.InnerException != null) { ServiceTrace.LogWarning(" {0}", ex.InnerException.Message); result.AddErrorMessage(ex.InnerException.Message); } } return result; } private void CreateConnectionContext(Guid connectionId, IBaseV2 implementation) { implementationLock.EnterWriteLock(); try { implementations.Add(connectionId, new ConnectionContext(implementation, KeepaliveHandler, connectionId, KeepAliveTimeout)); ServiceTrace.LogVerbose("AuthenticateMe added implementation for connection Id {0}", connectionId); } finally { implementationLock.ExitWriteLock(); } } private static void OnDisconnect(Guid connectionId) { IBaseV2 baseV = null; bool flag = false; implementationLock.EnterWriteLock(); try { flag = implementations.TryGetValue(connectionId, out var value); if (flag) { implementations.Remove(connectionId); ServiceTrace.LogVerbose("OnDisconnect removed implementation for ConnectionId {0}", connectionId); baseV = value.ConnectionImplementation; value.Dispose(); } } catch (Exception ex) { ServiceTrace.LogVerbose("OnDisconnect handled an exception: {0}", ex); } finally { implementationLock.ExitWriteLock(); } if (flag && baseV != null) { try { ServiceTrace.LogCsv("OnDisconnect found authenticator and validated request message, calling implementation OnDisconnect Id =", connectionId); baseV.OnDisconnect(); } catch (Exception ex2) { ServiceTrace.LogWarning("OnDisconnect caught exception from implementation for ConnectionId {0}: {1}", connectionId, ex2.Message); if (ex2.InnerException != null) { ServiceTrace.LogWarning(" {0}", ex2.InnerException.Message); } } } SysAuthenticatorServiceCache.RemoveServiceAuthenticator(connectionId); } private static void KeepaliveHandler(object connectionId) { try { OnDisconnect((Guid)connectionId); } catch (Exception ex) { ServiceTrace.LogVerbose("KeepaliveHandler handled an exception {0}", ex); } } protected virtual ArchestrAResult CheckConsumerMetadata(ConsumerMetadata metadata, Guid connectionId) { return ArchestrAResult.MakeGoodResult(); } protected abstract IBaseV2 GetImplementation(); protected static T GetImplementation(ConnectedRequest request) where T : class, IBaseV2 { if (request == null) { return null; } SystemAuthenticationServiceAuthentication serviceAuthenticator = SysAuthenticatorServiceCache.GetServiceAuthenticator(request.ConnectionValidator.ConnectionId); if (serviceAuthenticator != null && serviceAuthenticator.ValidRequest(request, forceHmac: false)) { implementationLock.EnterReadLock(); bool flag; ConnectionContext value; try { flag = implementations.TryGetValue(request.ConnectionValidator.ConnectionId, out value); } finally { implementationLock.ExitReadLock(); } if (flag && value != null) { value.RestartTimer(); return value.ConnectionImplementation as T; } } return null; } }