using FishNet.Connection;
using FishNet.Example.Authenticating;
using FishNet.Managing;
using FishNet.Transporting;
using System;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
namespace FishNet.Authenticating
{
///
/// This authenticator is an example of how to let host bypass the authentication process.
/// When checking to authenticate on the client side call AuthenticateAsHost, and if returned true skip normal authentication.
///
public abstract class HostAuthenticator : Authenticator
{
#region Serialized.
///
/// True to enable use of AuthenticateAsHost.
///
[Tooltip("True to enable use of AuthenticateAsHost.")]
[SerializeField]
private bool _allowHostAuthentication;
///
/// Sets if to allow host authentication.
///
///
public void SetAllowHostAuthentication(bool value) => _allowHostAuthentication = value;
///
/// Returns if AllowHostAuthentication is enabled.
///
///
public bool GetAllowHostAuthentication() => _allowHostAuthentication;
#endregion
#region Private.
///
/// A random hash which only exist if the server is started.
///
private static string _hostHash = string.Empty;
#endregion
///
/// Initializes this script for use.
///
///
public override void InitializeOnce(NetworkManager networkManager)
{
base.InitializeOnce(networkManager);
// Listen for connection state of local server to set hash.
NetworkManager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
// Listen for broadcast from client. Be sure to set requireAuthentication to false.
NetworkManager.ServerManager.RegisterBroadcast(OnHostPasswordBroadcast, false);
}
///
/// Called after the local server connection state changes.
///
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj)
{
int length = obj.ConnectionState == LocalConnectionState.Started ? 25 : 0;
SetHostHash(length);
}
///
/// Received on server when a client sends the password broadcast message.
///
/// Connection sending broadcast.
///
private void OnHostPasswordBroadcast(NetworkConnection conn, HostPasswordBroadcast hpb, Channel channel)
{
// Not accepting host authentications. This could be an attack.
if (!_allowHostAuthentication)
{
conn.Disconnect(true);
return;
}
/* If client is already authenticated this could be an attack. Connections
* are removed when a client disconnects so there is no reason they should
* already be considered authenticated. */
if (conn.IsAuthenticated)
{
conn.Disconnect(true);
return;
}
bool correctPassword = hpb.Password == _hostHash;
OnHostAuthenticationResult(conn, correctPassword);
}
///
/// Called after handling a host authentication result.
///
/// Connection authenticating.
/// True if authentication passed.
protected abstract void OnHostAuthenticationResult(NetworkConnection conn, bool authenticated);
///
/// Sets a host hash of length.
///
/// https://stackoverflow.com/questions/32932679/using-rngcryptoserviceprovider-to-generate-random-string
private void SetHostHash(int length)
{
if (length <= 0)
{
_hostHash = string.Empty;
}
else
{
const string charPool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()";
StringBuilder result = new();
using (RNGCryptoServiceProvider rng = new())
{
byte[] uintBuffer = new byte[sizeof(uint)];
while (length-- > 0)
{
rng.GetBytes(uintBuffer);
uint num = BitConverter.ToUInt32(uintBuffer, 0);
result.Append(charPool[(int)(num % (uint)charPool.Length)]);
}
}
_hostHash = result.ToString();
}
}
///
/// Returns true if authentication was sent as the clientHost.
///
/// This should only be called from the client side when receiving an authentication request.
protected bool TryAuthenticateAsClientHost()
{
if (!_allowHostAuthentication)
return false;
/* Host hash would only exist if also
* the server. */
if (_hostHash == string.Empty)
return false;
/* Send the password as it is only on the host. */
HostPasswordBroadcast hpb = new()
{
Password = _hostHash
};
NetworkManager.ClientManager.Broadcast(hpb);
return true;
}
}
}