2022-05-11 15:02:17 +02:00
|
|
|
|
using Capnp.Rpc;
|
|
|
|
|
using FabAccessAPI.Exceptions;
|
|
|
|
|
using FabAccessAPI.Schema;
|
2022-05-16 22:41:29 +02:00
|
|
|
|
using NLog;
|
2022-05-12 23:08:37 +02:00
|
|
|
|
using S22.Sasl;
|
2022-05-10 13:35:23 +02:00
|
|
|
|
using System;
|
2022-05-11 15:02:17 +02:00
|
|
|
|
using System.Collections.Generic;
|
2022-05-12 23:08:37 +02:00
|
|
|
|
using System.Linq;
|
2022-05-11 15:02:17 +02:00
|
|
|
|
using System.Net.Security;
|
|
|
|
|
using System.Security.Authentication;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
using System.Threading.Tasks;
|
2022-05-10 13:35:23 +02:00
|
|
|
|
|
|
|
|
|
namespace FabAccessAPI
|
|
|
|
|
{
|
|
|
|
|
public class API : IAPI
|
|
|
|
|
{
|
2022-05-16 22:41:29 +02:00
|
|
|
|
#region Logger
|
|
|
|
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
|
|
|
|
#endregion
|
|
|
|
|
|
2022-05-10 13:35:23 +02:00
|
|
|
|
#region Private Members
|
2022-05-12 23:08:37 +02:00
|
|
|
|
private TcpRpcClient _TcpRpcClient;
|
|
|
|
|
private IBootstrap _Bootstrap;
|
2022-05-10 13:35:23 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
public API()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
2022-05-10 23:50:04 +02:00
|
|
|
|
|
2022-05-10 13:35:23 +02:00
|
|
|
|
#region Events
|
|
|
|
|
public event EventHandler<ConnectionStatusChange> ConnectionStatusChanged;
|
2022-05-12 23:08:37 +02:00
|
|
|
|
|
|
|
|
|
public void OnTcpRpcConnectionChanged(object sender, ConnectionStateChange args)
|
|
|
|
|
{
|
|
|
|
|
EventHandler<ConnectionStatusChange> eventHandler = ConnectionStatusChanged;
|
|
|
|
|
|
|
|
|
|
if (eventHandler == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (args.LastState == ConnectionState.Initializing && args.NewState == ConnectionState.Active)
|
|
|
|
|
{
|
2022-05-16 22:41:29 +02:00
|
|
|
|
Log.Trace("TcpRpcClient Event Connected");
|
2022-05-12 23:08:37 +02:00
|
|
|
|
eventHandler(this, ConnectionStatusChange.Connected);
|
|
|
|
|
}
|
|
|
|
|
if (args.LastState == ConnectionState.Active && args.NewState == ConnectionState.Down)
|
|
|
|
|
{
|
2022-05-16 22:41:29 +02:00
|
|
|
|
Log.Trace("TcpRpcClient Event ConnectionLoss");
|
2022-05-12 23:08:37 +02:00
|
|
|
|
eventHandler(this, ConnectionStatusChange.ConnectionLoss);
|
2022-05-16 22:41:29 +02:00
|
|
|
|
_TcpRpcClient = null;
|
2022-05-12 23:08:37 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-10 13:35:23 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Members
|
|
|
|
|
public ConnectionData ConnectionData { get; private set; }
|
|
|
|
|
|
|
|
|
|
public ConnectionInfo ConnectionInfo { get; private set; }
|
|
|
|
|
|
|
|
|
|
public bool IsConnected
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
return _TcpRpcClient != null && _TcpRpcClient.State == ConnectionState.Active;
|
2022-05-10 13:35:23 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
public Session Session { get; private set; }
|
2022-05-10 13:35:23 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Methods
|
2022-05-11 15:02:17 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Connect to server with ConnectionData
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <exception cref="AuthenticationException"></exception>
|
|
|
|
|
/// <exception cref="ConnectingFailedException"></exception>
|
2022-05-12 23:08:37 +02:00
|
|
|
|
public async Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
|
2022-05-10 13:35:23 +02:00
|
|
|
|
{
|
2022-05-11 15:02:17 +02:00
|
|
|
|
if (IsConnected)
|
|
|
|
|
{
|
|
|
|
|
await Disconnect();
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
if(tcpRpcClient == null)
|
|
|
|
|
{
|
|
|
|
|
_TcpRpcClient = new TcpRpcClient();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_TcpRpcClient = tcpRpcClient;
|
|
|
|
|
}
|
2022-05-11 15:02:17 +02:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
_TcpRpcClient.ConnectionStateChanged += OnTcpRpcConnectionChanged;
|
|
|
|
|
|
|
|
|
|
await _ConnectAsync(_TcpRpcClient, connectionData).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
_Bootstrap = _TcpRpcClient.GetMain<IBootstrap>();
|
|
|
|
|
ConnectionInfo = await _GetConnectionInfo(_Bootstrap);
|
|
|
|
|
|
|
|
|
|
Session = await _Authenticate(connectionData).ConfigureAwait(false);
|
|
|
|
|
ConnectionData = connectionData;
|
2022-05-16 22:41:29 +02:00
|
|
|
|
|
|
|
|
|
Log.Info("API connected");
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
2022-05-16 22:41:29 +02:00
|
|
|
|
catch(System.Exception ex)
|
2022-05-11 15:02:17 +02:00
|
|
|
|
{
|
|
|
|
|
await Disconnect().ConfigureAwait(false);
|
2022-05-16 22:41:29 +02:00
|
|
|
|
Log.Warn(ex, "API connecting failed");
|
|
|
|
|
throw ex;
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
2022-05-10 13:35:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 15:02:17 +02:00
|
|
|
|
public Task Disconnect()
|
2022-05-10 13:35:23 +02:00
|
|
|
|
{
|
2022-05-11 15:02:17 +02:00
|
|
|
|
if (IsConnected)
|
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
_TcpRpcClient.Dispose();
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
2022-05-12 23:08:37 +02:00
|
|
|
|
|
|
|
|
|
_Bootstrap = null;
|
|
|
|
|
Session = null;
|
|
|
|
|
_TcpRpcClient = null;
|
2022-05-11 15:02:17 +02:00
|
|
|
|
ConnectionData = null;
|
|
|
|
|
ConnectionInfo = null;
|
|
|
|
|
|
2022-05-16 16:07:33 +02:00
|
|
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatusChange.Disconnected);
|
2022-05-12 23:08:37 +02:00
|
|
|
|
|
2022-05-16 22:41:29 +02:00
|
|
|
|
Log.Info("API disconnected");
|
|
|
|
|
|
2022-05-11 15:02:17 +02:00
|
|
|
|
return Task.CompletedTask;
|
2022-05-10 13:35:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
public async Task Reconnect()
|
2022-05-10 13:35:23 +02:00
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
if (ConnectionData != null)
|
|
|
|
|
{
|
|
|
|
|
await Connect(ConnectionData);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-16 16:07:33 +02:00
|
|
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatusChange.Reconnected);
|
2022-05-16 22:41:29 +02:00
|
|
|
|
Log.Info("API reconnected");
|
2022-05-10 13:35:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
public async Task<ConnectionInfo> TestConnection(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
|
2022-05-10 13:35:23 +02:00
|
|
|
|
{
|
2022-05-11 15:02:17 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
if (tcpRpcClient == null)
|
2022-05-11 15:02:17 +02:00
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
tcpRpcClient = new TcpRpcClient();
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false);
|
|
|
|
|
IBootstrap testBootstrap = tcpRpcClient.GetMain<IBootstrap>();
|
|
|
|
|
|
|
|
|
|
ConnectionInfo connectionInfo = await _GetConnectionInfo(testBootstrap).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
tcpRpcClient.Dispose();
|
|
|
|
|
|
|
|
|
|
return connectionInfo;
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
throw new ConnectingFailedException();
|
2022-05-11 15:02:17 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Private Methods
|
2022-05-12 23:08:37 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Validate Certificate
|
|
|
|
|
/// TODO: Do some validation
|
|
|
|
|
/// </summary>
|
2022-05-16 16:07:33 +02:00
|
|
|
|
private static bool _RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
2022-05-11 15:02:17 +02:00
|
|
|
|
{
|
|
|
|
|
// TODO Cert Check
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Connect to server async with ConnectionData
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <exception cref="AuthenticationException">TLS Error</exception>
|
|
|
|
|
/// <exception cref="ConnectingFailedException">Based on RPC Exception</exception>
|
|
|
|
|
///
|
2022-05-12 23:08:37 +02:00
|
|
|
|
private async Task _ConnectAsync(TcpRpcClient rpcClient, ConnectionData connectionData)
|
2022-05-11 15:02:17 +02:00
|
|
|
|
{
|
|
|
|
|
rpcClient.InjectMidlayer((tcpstream) =>
|
|
|
|
|
{
|
2022-05-16 16:07:33 +02:00
|
|
|
|
var sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(_RemoteCertificateValidationCallback));
|
2022-05-11 15:02:17 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sslStream.AuthenticateAsClient("bffhd");
|
|
|
|
|
return sslStream;
|
|
|
|
|
}
|
|
|
|
|
catch (AuthenticationException)
|
|
|
|
|
{
|
|
|
|
|
sslStream.Close();
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
rpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port);
|
|
|
|
|
await rpcClient.WhenConnected.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
throw new ConnectingFailedException("Connecting failed", exception);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 23:08:37 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create ConnectionInfo from Bootstrap
|
|
|
|
|
/// </summary>
|
|
|
|
|
private async Task<ConnectionInfo> _GetConnectionInfo(IBootstrap bootstrap)
|
|
|
|
|
{
|
|
|
|
|
ConnectionInfo connectionInfo = new ConnectionInfo()
|
|
|
|
|
{
|
|
|
|
|
APIVersion = await bootstrap.GetAPIVersion().ConfigureAwait(false),
|
|
|
|
|
Mechanisms = new List<string>(await bootstrap.Mechanisms().ConfigureAwait(false)),
|
|
|
|
|
ServerName = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item1,
|
|
|
|
|
ServerRelease = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item2,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return connectionInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 15:02:17 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Authenticate connection with ConnectionData
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <exception cref="UnsupportedMechanismException"></exception>
|
|
|
|
|
/// <exception cref="InvalidCredentialsException"></exception>
|
|
|
|
|
/// <exception cref="AuthenticationFailedException"></exception>
|
2022-05-12 23:08:37 +02:00
|
|
|
|
private async Task<Session> _Authenticate(ConnectionData connectionData)
|
2022-05-11 15:02:17 +02:00
|
|
|
|
{
|
2022-05-12 23:08:37 +02:00
|
|
|
|
IAuthentication? authentication = await _Bootstrap.CreateSession(MechanismString.ToString(connectionData.Mechanism)).ConfigureAwait(false);
|
|
|
|
|
|
2022-05-16 16:07:33 +02:00
|
|
|
|
return await _SASLAuthenticate(authentication, MechanismString.ToString(connectionData.Mechanism), connectionData.Properties).ConfigureAwait(false);
|
2022-05-12 23:08:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Authenticate Connection to get Session
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <exception cref="BadMechanismException"></exception>
|
|
|
|
|
/// <exception cref="InvalidCredentialsException"></exception>
|
|
|
|
|
/// <exception cref="AuthenticationFailedException"></exception>
|
2022-05-16 16:07:33 +02:00
|
|
|
|
private async Task<Session> _SASLAuthenticate(IAuthentication authentication, string mech, Dictionary<string, object> properties)
|
2022-05-12 23:08:37 +02:00
|
|
|
|
{
|
|
|
|
|
SaslMechanism? saslMechanism = SaslFactory.Create(mech);
|
|
|
|
|
foreach (KeyValuePair<string, object> entry in properties)
|
|
|
|
|
{
|
|
|
|
|
saslMechanism.Properties.Add(entry.Key, entry.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
byte[] data = new byte[0];
|
|
|
|
|
|
|
|
|
|
if (saslMechanism.HasInitial)
|
|
|
|
|
{
|
|
|
|
|
data = saslMechanism.GetResponse(new byte[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response? response = await authentication.Step(data);
|
|
|
|
|
while (!saslMechanism.IsCompleted)
|
|
|
|
|
{
|
|
|
|
|
if(response.Failed != null)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(response.Challenge != null)
|
|
|
|
|
{
|
|
|
|
|
byte[]? additional = saslMechanism.GetResponse(response.Challenge.ToArray());
|
|
|
|
|
response = await authentication.Step(additional);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new AuthenticationFailedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.Successful != null)
|
|
|
|
|
{
|
|
|
|
|
return response.Successful.Session;
|
|
|
|
|
}
|
|
|
|
|
else if (response.Failed != null)
|
|
|
|
|
{
|
|
|
|
|
switch (response.Failed.Code)
|
|
|
|
|
{
|
|
|
|
|
case Response.Error.badMechanism:
|
|
|
|
|
throw new BadMechanismException();
|
|
|
|
|
case Response.Error.invalidCredentials:
|
|
|
|
|
throw new InvalidCredentialsException();
|
|
|
|
|
case Response.Error.aborted:
|
|
|
|
|
case Response.Error.failed:
|
|
|
|
|
default:
|
|
|
|
|
throw new AuthenticationFailedException(response.Failed.AdditionalData.ToArray());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new AuthenticationFailedException();
|
|
|
|
|
}
|
2022-05-10 13:35:23 +02:00
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|