Added: New API Class

This commit is contained in:
TheJoKlLa
2022-05-12 23:08:37 +02:00
parent bb4a74f8c4
commit 0d5ad01496
10 changed files with 514 additions and 536 deletions

View File

@ -1,8 +1,10 @@
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using FabAccessAPI.Schema;
using S22.Sasl;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
@ -13,7 +15,8 @@ namespace FabAccessAPI
public class API : IAPI
{
#region Private Members
private Connection _APIConnection;
private TcpRpcClient _TcpRpcClient;
private IBootstrap _Bootstrap;
#endregion
#region Constructors
@ -25,6 +28,25 @@ namespace FabAccessAPI
#region Events
public event EventHandler<ConnectionStatusChange> ConnectionStatusChanged;
public void OnTcpRpcConnectionChanged(object sender, ConnectionStateChange args)
{
EventHandler<ConnectionStatusChange> eventHandler = ConnectionStatusChanged;
if (eventHandler == null)
{
return;
}
if (args.LastState == ConnectionState.Initializing && args.NewState == ConnectionState.Active)
{
eventHandler(this, ConnectionStatusChange.Connected);
}
if (args.LastState == ConnectionState.Active && args.NewState == ConnectionState.Down)
{
eventHandler(this, ConnectionStatusChange.ConnectionLoss);
}
}
#endregion
#region Members
@ -36,17 +58,11 @@ namespace FabAccessAPI
{
get
{
return _APIConnection != null && _APIConnection.RpcClient.State == ConnectionState.Active;
return _TcpRpcClient != null && _TcpRpcClient.State == ConnectionState.Active;
}
}
public Session Session
{
get
{
throw new NotImplementedException();
}
}
public Session Session { get; private set; }
#endregion
#region Methods
@ -55,20 +71,33 @@ namespace FabAccessAPI
/// </summary>
/// <exception cref="AuthenticationException"></exception>
/// <exception cref="ConnectingFailedException"></exception>
public async Task Connect(ConnectionData connectionData)
public async Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
{
if (IsConnected)
{
await Disconnect();
}
TcpRpcClient rpcClient = await _ConnectAsync(connectionData).ConfigureAwait(false);
_APIConnection = new Connection(rpcClient);
if(tcpRpcClient == null)
{
_TcpRpcClient = new TcpRpcClient();
}
else
{
_TcpRpcClient = tcpRpcClient;
}
try
{
await _Authenticate(connectionData).ConfigureAwait(false);
_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;
}
catch(System.Exception)
{
@ -81,45 +110,68 @@ namespace FabAccessAPI
{
if (IsConnected)
{
_APIConnection.RpcClient?.Dispose();
_TcpRpcClient.Dispose();
}
_APIConnection = null;
_Bootstrap = null;
Session = null;
_TcpRpcClient = null;
ConnectionData = null;
ConnectionInfo = null;
EventHandler<ConnectionStatusChange> eventHandler = ConnectionStatusChanged;
if (eventHandler != null)
{
eventHandler(this, ConnectionStatusChange.Disconnected);
}
return Task.CompletedTask;
}
public Task Reconnect()
public async Task Reconnect()
{
throw new NotImplementedException();
if (ConnectionData != null)
{
await Connect(ConnectionData);
}
EventHandler<ConnectionStatusChange> eventHandler = ConnectionStatusChanged;
if (eventHandler != null)
{
eventHandler(this, ConnectionStatusChange.Reconnected);
}
}
public async Task<ConnectionInfo> TestConnection(ConnectionData connectionData)
public async Task<ConnectionInfo> TestConnection(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
{
try
{
TcpRpcClient rpcClient = await _ConnectAsync(connectionData).ConfigureAwait(false);
Connection testConnection = new Connection(rpcClient);
rpcClient.Dispose();
ConnectionInfo connectionInfo = new ConnectionInfo()
if (tcpRpcClient == null)
{
APIVersion = testConnection.
tcpRpcClient = new TcpRpcClient();
}
return true;
await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false);
IBootstrap testBootstrap = tcpRpcClient.GetMain<IBootstrap>();
ConnectionInfo connectionInfo = await _GetConnectionInfo(testBootstrap).ConfigureAwait(false);
tcpRpcClient.Dispose();
return connectionInfo;
}
catch
{
return null;
throw new ConnectingFailedException();
}
}
#endregion
#region Private Methods
/// <summary>
/// Validate Certificate
/// TODO: Do some validation
/// </summary>
private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// TODO Cert Check
@ -132,9 +184,8 @@ namespace FabAccessAPI
/// <exception cref="AuthenticationException">TLS Error</exception>
/// <exception cref="ConnectingFailedException">Based on RPC Exception</exception>
///
private async Task<TcpRpcClient> _ConnectAsync(ConnectionData connectionData)
private async Task _ConnectAsync(TcpRpcClient rpcClient, ConnectionData connectionData)
{
TcpRpcClient rpcClient = new TcpRpcClient();
rpcClient.InjectMidlayer((tcpstream) =>
{
var sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(RemoteCertificateValidationCallback));
@ -154,8 +205,6 @@ namespace FabAccessAPI
{
rpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port);
await rpcClient.WhenConnected.ConfigureAwait(false);
return rpcClient;
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
@ -163,13 +212,29 @@ namespace FabAccessAPI
}
}
/// <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;
}
/// <summary>
/// Authenticate connection with ConnectionData
/// </summary>
/// <exception cref="UnsupportedMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
private async Task _Authenticate(ConnectionData connectionData)
private async Task<Session> _Authenticate(ConnectionData connectionData)
{
Dictionary<string, object> joinedProperties = new Dictionary<string, object>();
foreach(KeyValuePair<string, object> keyValuePair in connectionData.Properties)
@ -181,7 +246,72 @@ namespace FabAccessAPI
joinedProperties.Add(keyValuePair.Key, keyValuePair.Value);
}
await _APIConnection.Auth(MechanismString.ToString(connectionData.Mechanism), joinedProperties).ConfigureAwait(false);
IAuthentication? authentication = await _Bootstrap.CreateSession(MechanismString.ToString(connectionData.Mechanism)).ConfigureAwait(false);
return await _SASLAuthenticate(authentication, MechanismString.ToString(connectionData.Mechanism), joinedProperties).ConfigureAwait(false);
}
/// <summary>
/// Authenticate Connection to get Session
/// </summary>
/// <exception cref="BadMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
public async Task<Session> _SASLAuthenticate(IAuthentication authentication, string mech, Dictionary<string, object> properties)
{
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();
}
}
#endregion
}

View File

@ -1,91 +0,0 @@
using FabAccessAPI.Schema;
using S22.Sasl;
using System.Collections.Generic;
using System.Threading.Tasks;
using FabAccessAPI.Exceptions;
using System.Linq;
namespace FabAccessAPI
{
/// <summary>
/// Authenticate with SASL
/// </summary>
public class Auth
{
#region Private Fields
private readonly IAuthentication _AuthCap;
#endregion
#region Constructors
public Auth(IAuthentication authCap)
{
_AuthCap = authCap;
}
#endregion
#region Methods
/// <summary>
/// Authenticate Connection to get Session
/// </summary>
/// <exception cref="BadMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
public async Task<Session> Authenticate(string mech, Dictionary<string, object> properties)
{
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 _AuthCap.Step(data);
while (!saslMechanism.IsCompleted)
{
if(response.Failed != null)
{
break;
}
if(response.Challenge != null)
{
byte[]? additional = saslMechanism.GetResponse(response.Challenge.ToArray());
response = await _AuthCap.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();
}
}
#endregion
}
}

View File

@ -1,65 +0,0 @@
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using FabAccessAPI.Schema;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FabAccessAPI
{
public class Connection
{
#region Private Fields
private readonly IBootstrap? _BootstrapCap = null;
private Auth? _Auth = null;
#endregion
#region Constructors
/// <summary>
///
/// </summary>
/// <param name="rpcClient">Should be an already configured and connected TcpRpcClient</param>
public Connection(TcpRpcClient rpcClient)
{
RpcClient = rpcClient;
_BootstrapCap = RpcClient.GetMain<IBootstrap>();
}
#endregion
#region Fields
public TcpRpcClient? RpcClient { get; } = null;
public Session? Session { get; private set; } = null;
#endregion
#region Methods
/// <summary>
/// Authenticate this connection.
/// Calling this more then once is UB
/// </summary>
/// <param name="mech">The desired authentication mechanism</param>
/// <param name="kvs">Key-Value data specific to the mechanism</param>
/// <exception cref="UnsupportedMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
public async Task Auth(string mech, Dictionary<string, object> kvs, CancellationToken cancellationToken_ = default)
{
IReadOnlyList<string>? mechs = await _BootstrapCap.Mechanisms();
if (!mechs.Contains(mech))
{
throw new UnsupportedMechanismException();
}
if (_Auth == null)
{
IAuthentication? authCap = await _BootstrapCap.CreateSession(mech, cancellationToken_).ConfigureAwait(false);
_Auth = new Auth(authCap);
}
Session = await _Auth.Authenticate(mech, kvs);
}
#endregion
}
}

View File

@ -5,7 +5,7 @@ namespace FabAccessAPI
{
public class ConnectionInfo
{
public Version APIVersion;
public Schema.Version APIVersion;
public string ServerName;
public string ServerRelease;
public List<string> Mechanisms;

View File

@ -1,4 +1,5 @@
using FabAccessAPI.Schema;
using Capnp.Rpc;
using FabAccessAPI.Schema;
using System;
using System.Threading.Tasks;
@ -36,7 +37,7 @@ namespace FabAccessAPI
/// Connect to BFFH Server
/// </summary>
/// <param name="connectionData"></param>
Task Connect(ConnectionData connectionData);
Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient);
/// <summary>
/// Disconnect from BFFH Server
@ -52,6 +53,6 @@ namespace FabAccessAPI
/// Connect to Server and get ConnectionInfo.
/// The Connection is not maintained.
/// </summary>
ConnectionInfo TestConnection(ConnectionData connectionData);
Task<ConnectionInfo> TestConnection(ConnectionData connectionData, TcpRpcClient tcpRpcClient);
}
}