2022-03-16 00:37:08 +01:00

331 lines
11 KiB
C#

using System;
using Borepin.Model;
using System.Threading.Tasks;
using System.Collections.Generic;
using FabAccessAPI.Schema;
using Borepin.Service.Storage;
using Borepin.Model.Storage;
using Borepin.Service.BFFH.Exceptions;
using Borepin.Service.Storage.Exceptions;
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Security.Authentication;
namespace Borepin.Service.BFFH
{
public class BFFHService : IBFFHService
{
#region Private Fields
private readonly ConnectionStorage _ConnectionStorage;
private readonly ConnectionCredentialStorage _ConnectionCredentialStorage;
private FabAccessAPI.Connection _APIConnection;
private Connection_Plain _CurrentConnection;
#endregion
#region Constructors
public BFFHService(IPreferenceStorageService preferenceStorageService, ISecretStorageService secretStorageService)
{
_ConnectionStorage = new ConnectionStorage(preferenceStorageService);
_ConnectionCredentialStorage = new ConnectionCredentialStorage(secretStorageService);
}
#endregion
#region Fields
/// <summary>
/// Current Connection of Service
/// </summary>
public Connection CurrentConnection
{
get
{
return _CurrentConnection;
}
}
/// <summary>
/// Check if Service is connected to a Server
/// </summary>
public bool IsConnected
{
get
{
if (_APIConnection != null && _APIConnection.RpcClient != null)
{
return _APIConnection.RpcClient.State == Capnp.Rpc.ConnectionState.Active;
}
return false;
}
}
#endregion
#region Method
/// <summary>
/// Get all known Connections from Storage
/// </summary>
public async Task<IList<Connection>> GetConnections()
{
return await _ConnectionStorage.GetConnectionList().ConfigureAwait(false);
}
/// <summary>
/// Remove Connection from Storage
/// </summary>
public async Task RemoveConnection(Connection connection)
{
if (IsConnected && connection.Equals(CurrentConnection))
{
await Disconnect().ConfigureAwait(false);
}
try
{
await _ConnectionCredentialStorage.RemoveCredentials(connection).ConfigureAwait(false);
}
catch (KeyNotFoundException)
{
}
try
{
await _ConnectionStorage.RemoveConnection(connection).ConfigureAwait(false);
}
catch (KeyNotFoundException)
{
}
}
/// <summary>
/// Test a if a Server is reachable
/// </summary>
public async Task<bool> TestConnection(Connection connection)
{
try
{
TcpRpcClient rpcClient = await _ConnectAsync(connection.Address.Host, connection.Address.Port).ConfigureAwait(false);
rpcClient.Dispose();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Connects to Server with Credential from ConnectionCredentialStorage
/// </summary>
/// <exception cref="AllreadyConnectedException"></exception>
/// <exception cref="MissingCredentialsException"></exception>
/// <exception cref="ConnectingFailedException"></exception>
/// <exception cref="AuthenticatingFailedException"></exception>
public async Task Connect(Connection connection)
{
if (IsConnected)
{
throw new AllreadyConnectedException();
}
string password;
try
{
password = await _ConnectionCredentialStorage.GetPassword(connection).ConfigureAwait(false);
}
catch (KeyNotFoundException)
{
await _ConnectionCredentialStorage.RemoveAllCredentials().ConfigureAwait(false);
await _ConnectionStorage.RemoveAllConnections().ConfigureAwait(false);
throw new MissingCredentialsException();
}
try
{
TcpRpcClient rpcClient = await _ConnectAsync(connection.Address.Host, connection.Address.Port).ConfigureAwait(false);
_APIConnection = new FabAccessAPI.Connection(rpcClient);
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
throw new ConnectingFailedException("Connecting failed", exception);
}
if (! await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
{
await Disconnect().ConfigureAwait(false);
throw new AuthenticatingFailedException();
}
_CurrentConnection = new Connection_Plain(connection)
{
Password = password,
};
await _ConnectionStorage.UpdateConnectionTimestamp(_CurrentConnection).ConfigureAwait(false);
}
/// <summary>
/// Connects to Server with Password
/// Connection is saved to Storage if connecting was successfuss
/// </summary>
/// <exception cref="AllreadyConnectedException"></exception>
/// <exception cref="MissingCredentialsException"></exception>
/// <exception cref="ConnectingFailedException"></exception>
/// <exception cref="AuthenticatingFailedException"></exception>
public async Task Connect(Connection connection, string password)
{
if (IsConnected)
{
throw new AllreadyConnectedException();
}
try
{
TcpRpcClient rpcClient = await _ConnectAsync(connection.Address.Host, connection.Address.Port).ConfigureAwait(false);
_APIConnection = new FabAccessAPI.Connection(rpcClient);
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
throw new ConnectingFailedException("Connecting failed", exception);
}
if (!await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
{
await Disconnect().ConfigureAwait(false);
throw new AuthenticatingFailedException();
}
_CurrentConnection = new Connection_Plain(connection)
{
Password = password,
};
try
{
await _ConnectionStorage.AddConnection(_CurrentConnection).ConfigureAwait(false);
}
catch(DuplicateConnectionException)
{
}
await _ConnectionCredentialStorage.AddCredentials(_CurrentConnection, password).ConfigureAwait(false);
await _ConnectionStorage.UpdateConnectionTimestamp(_CurrentConnection).ConfigureAwait(false);
}
/// <summary>
/// Reconnects to server if connection has lost
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ReconnectingFailedException"></exception>
public async Task Reconnect()
{
if (IsConnected || _CurrentConnection == null)
{
throw new InvalidOperationException();
}
try
{
TcpRpcClient rpcClient = await _ConnectAsync(_CurrentConnection.Address.Host, _CurrentConnection.Address.Port).ConfigureAwait(false);
_APIConnection = new FabAccessAPI.Connection(rpcClient);
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
throw new ReconnectingFailedException("Connecting failed", new ConnectingFailedException("Connecting failed", exception));
}
if (! await _AuthenticatePlainAsync(_CurrentConnection.Username, _CurrentConnection.Password).ConfigureAwait(false))
{
throw new ReconnectingFailedException("Authentication failed", new AuthenticatingFailedException());
}
}
/// <summary>
/// Disconnects from Server
/// </summary>
/// <returns></returns>
public Task Disconnect()
{
if (IsConnected)
{
_APIConnection.RpcClient?.Dispose();
}
_APIConnection = null;
_CurrentConnection = null;
return Task.CompletedTask;
}
#region FabAccess API Systems
public async Task<Session> GetSession()
{
if (!IsConnected)
{
await Reconnect().ConfigureAwait(false);
}
return _APIConnection.Session;
}
#endregion
#endregion
#region Private Methods
private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
private async Task<TcpRpcClient> _ConnectAsync(string host, int port)
{
TcpRpcClient rpcClient = new TcpRpcClient();
rpcClient.InjectMidlayer((tcpstream) =>
{
var sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(RemoteCertificateValidationCallback));
try
{
sslStream.AuthenticateAsClient("bffhd");
return sslStream;
}
catch (AuthenticationException)
{
sslStream.Close();
throw;
}
});
rpcClient.Connect(host, port);
await rpcClient.WhenConnected.ConfigureAwait(false);
return rpcClient;
}
private async Task<bool> _AuthenticatePlainAsync(string username, string password)
{
try
{
await _APIConnection.Auth("PLAIN", new Dictionary<string, object>(StringComparer.Ordinal) { { "Username", username }, { "Password", password } }).ConfigureAwait(false);
return await Task.FromResult(true).ConfigureAwait(false);
}
catch(InvalidCredentialsException)
{
return await Task.FromResult(true).ConfigureAwait(false);
}
catch (AuthenticatingFailedException)
{
return await Task.FromResult(true).ConfigureAwait(false);
}
}
#endregion
}
}