mirror of
https://gitlab.com/fabinfra/fabaccess/borepin.git
synced 2025-06-11 11:03:23 +02:00
Added: New API Class
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user