private async Task EstablishSessionAsync(IServerChannel channel, CancellationToken cancellationToken) { try { cancellationToken.ThrowIfCancellationRequested(); _serverConnectedNodesDictionary.Add(channel.SessionId, channel); var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var newSession = await channel.ReceiveNewSessionAsync( CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token).Token); timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var negotiatedSession = await channel.NegotiateSessionAsync( channel.Transport.GetSupportedCompression(), channel.Transport.GetSupportedEncryption(), CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token).Token); if (negotiatedSession.State == SessionState.Negotiating && negotiatedSession.Compression != null && negotiatedSession.Encryption != null) { await channel.SendNegotiatingSessionAsync( negotiatedSession.Compression.Value, negotiatedSession.Encryption.Value ); timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)); if (channel.Transport.Compression != negotiatedSession.Compression.Value) { await channel.Transport.SetCompressionAsync( negotiatedSession.Compression.Value, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token).Token); } if (channel.Transport.Encryption != negotiatedSession.Encryption.Value) { await channel.Transport.SetEncryptionAsync( negotiatedSession.Encryption.Value, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token).Token); } timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var authenticatedSession = await channel.AuthenticateSessionAsync( new AuthenticationScheme[] { AuthenticationScheme.Plain, AuthenticationScheme.Transport }, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token).Token); if (authenticatedSession.Authentication != null && authenticatedSession.From != null && authenticatedSession.From.Domain.Equals(_serverNode.Domain, StringComparison.OrdinalIgnoreCase)) { if (authenticatedSession.Authentication is PlainAuthentication) { var plainAuthentication = authenticatedSession.Authentication as PlainAuthentication; string password; if (_identityPasswordDictionary.TryGetValue(authenticatedSession.From.ToIdentity(), out password) && password.Equals(plainAuthentication.GetFromBase64Password())) { await RegisterChannel(channel, authenticatedSession.From, cancellationToken); } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "Invalid username or password" }); } } else if (authenticatedSession.Authentication is TransportAuthentication) { var transportAuthentication = authenticatedSession.Authentication as PlainAuthentication; if (channel.Transport is IAuthenticatableTransport) { var authenticableTransport = channel.Transport as IAuthenticatableTransport; if (await authenticableTransport.AuthenticateAsync(authenticatedSession.From.ToIdentity()) != DomainRole.Unknown) { await RegisterChannel(channel, authenticatedSession.From, cancellationToken); } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "The authentication failed" }); } } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "The current transport doesn't support authentication" }); } } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "Unsupported authenticaiton scheme" }); } } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "Invalid user" }); } } else { await channel.SendFailedSessionAsync( new Reason() { Code = 1, Description = "Invalid selected negotiation options" }); } } finally { channel.DisposeIfDisposable(); } }
private static async Task AuthenticateSessionAsync( IServerChannel channel, AuthenticationScheme[] schemeOptions, Func <Identity, Authentication, CancellationToken, Task <AuthenticationResult> > authenticationFunc, Func <Node, IServerChannel, CancellationToken, Task <Node> > registrationFunc, CancellationToken cancellationToken) { // Sends the authentication options and awaits for the authentication var receivedSession = await channel.AuthenticateSessionAsync(schemeOptions, cancellationToken); while (receivedSession.State == SessionState.Authenticating && receivedSession.From != null && receivedSession.Authentication != null && receivedSession.Scheme != null && schemeOptions.Contains(receivedSession.Scheme.Value)) { cancellationToken.ThrowIfCancellationRequested(); if (receivedSession.Authentication is TransportAuthentication transportAuthentication) { await AuthenticateAsTransportAsync(channel, transportAuthentication, receivedSession.From.ToIdentity()); } var authenticationResult = await authenticationFunc( receivedSession.From.ToIdentity(), receivedSession.Authentication, cancellationToken); if (authenticationResult.DomainRole != DomainRole.Unknown) { var registeredNode = await registrationFunc(receivedSession.From, channel, cancellationToken); if (registeredNode != null) { await channel.SendEstablishedSessionAsync(registeredNode, cancellationToken); } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_REGISTRATION_ERROR, Description = "The session instance registration failed" }, cancellationToken); } break; } if (authenticationResult.Roundtrip != null) { receivedSession = await channel.AuthenticateSessionAsync(authenticationResult.Roundtrip, cancellationToken); } else { await channel.SendFailedSessionAsync( new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "The session authentication failed" }, cancellationToken); break; } } }
/// <summary> /// Establishes a server channel with transport options negotiation and authentication. /// </summary> /// <param name="channel">The channel.</param> /// <param name="enabledCompressionOptions">The enabled compression options.</param> /// <param name="enabledEncryptionOptions">The enabled encryption options.</param> /// <param name="schemeOptions">The scheme options.</param> /// <param name="authenticateFunc">The authenticate function.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException"> /// </exception> /// <exception cref="System.ArgumentException"> /// The transport doesn't support one or more of the specified compression options /// or /// The transport doesn't support one or more of the specified compression options /// or /// The authentication scheme options is mandatory /// </exception> public static async Task EstablishSessionAsync( this IServerChannel channel, SessionCompression[] enabledCompressionOptions, SessionEncryption[] enabledEncryptionOptions, AuthenticationScheme[] schemeOptions, Func <Node, Authentication, AuthenticationResult> authenticateFunc, CancellationToken cancellationToken) { if (channel == null) { throw new ArgumentNullException(nameof(channel)); } if (enabledCompressionOptions == null || enabledCompressionOptions.Length == 0 || enabledCompressionOptions.Any(o => !channel.Transport.GetSupportedCompression().Contains(o))) { throw new ArgumentException("The transport doesn't support one or more of the specified compression options", nameof(enabledCompressionOptions)); } if (enabledEncryptionOptions == null || enabledEncryptionOptions.Length == 0 || enabledEncryptionOptions.Any(o => !channel.Transport.GetSupportedEncryption().Contains(o))) { throw new ArgumentException("The transport doesn't support one or more of the specified compression options", nameof(enabledEncryptionOptions)); } if (schemeOptions == null) { throw new ArgumentNullException(nameof(schemeOptions)); } if (schemeOptions.Length == 0) { throw new ArgumentException("The authentication scheme options is mandatory", nameof(schemeOptions)); } if (authenticateFunc == null) { throw new ArgumentNullException(nameof(authenticateFunc)); } // Awaits for the 'new' session envelope var receivedSession = await channel.ReceiveNewSessionAsync(cancellationToken).ConfigureAwait(false); if (receivedSession.State == SessionState.New) { // Check if there's any transport option to negotiate var compressionOptions = enabledCompressionOptions.Intersect(channel.Transport.GetSupportedCompression()).ToArray(); var encryptionOptions = enabledEncryptionOptions.Intersect(channel.Transport.GetSupportedEncryption()).ToArray(); if (compressionOptions.Length > 1 || encryptionOptions.Length > 1) { // Negotiate the transport options receivedSession = await channel.NegotiateSessionAsync( compressionOptions, encryptionOptions, cancellationToken).ConfigureAwait(false); // Validate the selected options if (receivedSession.State == SessionState.Negotiating && receivedSession.Compression != null && compressionOptions.Contains(receivedSession.Compression.Value) && receivedSession.Encryption != null && encryptionOptions.Contains(receivedSession.Encryption.Value)) { await channel.SendNegotiatingSessionAsync( receivedSession.Compression.Value, receivedSession.Encryption.Value, cancellationToken); if (channel.Transport.Compression != receivedSession.Compression.Value) { await channel.Transport.SetCompressionAsync( receivedSession.Compression.Value, cancellationToken); } if (channel.Transport.Encryption != receivedSession.Encryption.Value) { await channel.Transport.SetEncryptionAsync( receivedSession.Encryption.Value, cancellationToken); } } else { await channel.SendFailedSessionAsync(new Reason() { Code = ReasonCodes.SESSION_NEGOTIATION_INVALID_OPTIONS, Description = "An invalid negotiation option was selected" }, cancellationToken); } } if (channel.State != SessionState.Failed) { // Sends the authentication options and awaits for the authentication receivedSession = await channel.AuthenticateSessionAsync(schemeOptions, cancellationToken); if (receivedSession.State == SessionState.Authenticating && receivedSession.Authentication != null && receivedSession.Scheme != null && schemeOptions.Contains(receivedSession.Scheme.Value)) { while (channel.State == SessionState.Authenticating) { cancellationToken.ThrowIfCancellationRequested(); var authenticationResult = authenticateFunc(receivedSession.From, receivedSession.Authentication); if (authenticationResult.Roundtrip != null) { receivedSession = await channel.AuthenticateSessionAsync(authenticationResult.Roundtrip, cancellationToken); } else if (authenticationResult.Node != null) { await channel.SendEstablishedSessionAsync(authenticationResult.Node, cancellationToken); } else { await channel.SendFailedSessionAsync(new Reason() { Code = ReasonCodes.SESSION_AUTHENTICATION_FAILED, Description = "The session authentication failed" }, cancellationToken); } } } } } }