2021-09-18 17:18:24 +02:00

177 lines
5.9 KiB
C#

using FabAccessAPI.Schema;
using S22.Sasl;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Exception = System.Exception;
namespace FabAccessAPI
{
/// Authentication Identity
///
/// Under the hood a string because the form depends heavily on the method
public struct AuthCId
{
public string Id { get; private set; }
public AuthCId(string id) : this() { Id = id; }
}
/// Authorization Identity
///
/// This identity is internal to FabAccess and completely independent from the authentication
/// method or source
public struct AuthZId
{
/// Main User ID. Generally an user name or similar
public string Uid;
/// Sub user ID.
///
/// Can change scopes for permissions, e.g. having a +admin account with more permissions than
/// the default account and +dashboard et.al. accounts that have restricted permissions for
/// their applications
public string Subuid;
/// Realm this account originates.
///
/// The Realm is usually described by a domain name but local policy may dictate an unrelated
/// mapping
public string Realm;
}
/// Authentication/Authorization user object.
///
/// This struct contains the user as is passed to the actual authentication/authorization
/// subsystems
///
public struct AuthUser
{
/// Contains the Authentication ID used
///
/// The authentication ID is an identifier for the authentication exchange. This is different
/// than the ID of the user to be authenticated; for example when using x509 the authcid is
/// the dn of the certificate, when using GSSAPI the authcid is of form `<userid>@<REALM>`
public AuthCId Authcid;
/// Contains the Authorization ID
///
/// This is the identifier of the user to *authenticate as*. This in several cases is different
/// to the `authcid`:
/// If somebody wants to authenticate as somebody else, su-style.
/// If a person wants to authenticate as a higher-permissions account, e.g. foo may set authzid foo+admin
/// to split normal user and "admin" accounts.
/// If a method requires a specific authcid that is different from the identifier of the user
/// to authenticate as, e.g. GSSAPI, x509 client certificates, API TOKEN authentication.
public AuthZId Authzid;
/// Contains the authentication method used
///
/// For the most part this is the SASL method
public string AuthMethod;
/// Method-specific key-value pairs
///
/// Each method can use their own key-value pairs.
/// E.g. EXTERNAL encodes the actual method used (x509 client certs, UID/GID for unix sockets,
/// ...)
public Dictionary<string, string> Kvs;
}
// Authentication has two parts: Granting the authentication itself and then performing the
// authentication.
// Granting the authentication checks if
// a) the given authcid fits with the given (authMethod, kvs). In general a failure here indicates
// a programming failure — the authcid come from the same source as that tuple
// b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate
// has been configured for that user, if a GSSAPI user maps to a given user,
public enum AuthError
{
/// Authentication ID is bad/unknown/..
BadAuthcid,
/// Authorization ID is unknown/..
BadAuthzid,
/// Authorization ID is not of form user+uid@realm
MalformedAuthzid,
/// User may not use that authorization id
NotAllowedAuthzid,
}
public class UnauthorizedException : Exception { }
public class UnsupportedMechanismException : Exception { }
/// <summary>
/// THIS IS VERY INCOMPLETE!
/// </summary>
public class Auth
{
#region Log
//private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endregion
private IAuthenticationSystem _authCap;
public Auth(IAuthenticationSystem authCap)
{
_authCap = authCap;
}
public Task<IReadOnlyList<string>> GetMechanisms()
{
return _authCap.Mechanisms();
}
public async Task<bool> Authenticate(string mech, Dictionary<string, object> properties)
{
var m = SaslFactory.Create(mech);
foreach (KeyValuePair<string, object> entry in properties)
{
m.Properties.Add(entry.Key, entry.Value);
}
var initialResponse = new Request.initialResponse();
if (m.HasInitial)
{
initialResponse.Initial = m.GetResponse(new byte[0]);
}
var req = new Request
{
Mechanism = m.Name,
InitialResponse = initialResponse
};
var resp = await _authCap.Start(req);
while (!m.IsCompleted)
{
if (resp.which == Response.WHICH.Challence)
{
var additional = m.GetResponse(resp.Challence.ToArray());
resp = await _authCap.Step(additional);
}
else
{
break;
}
}
if (resp.which == Response.WHICH.Outcome)
{
if (resp.Outcome.Result == Response.Result.successful)
{
return true;
}
else
{
//TODO: Provide meaningful info about auth failure
return false;
}
}
return false;
}
}
}