2020-11-12 10:12:56 +01:00
|
|
|
|
using Capnp;
|
|
|
|
|
using FabAccessAPI.Schema;
|
|
|
|
|
using S22.Sasl;
|
|
|
|
|
using System;
|
2020-11-07 22:08:40 +01:00
|
|
|
|
using System.Collections.Generic;
|
2020-11-12 10:12:56 +01:00
|
|
|
|
using System.IO;
|
2020-11-07 22:08:40 +01:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
2020-11-12 10:12:56 +01:00
|
|
|
|
using Exception = System.Exception;
|
2020-11-07 22:08:40 +01:00
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
namespace FabAccessAPI {
|
2020-11-07 22:08:40 +01:00
|
|
|
|
/// 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
|
|
|
|
|
///
|
2020-11-12 10:12:56 +01:00
|
|
|
|
public struct AuthUser {
|
2020-11-07 22:08:40 +01:00
|
|
|
|
/// 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,
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
class UnauthorizedException : Exception{}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// THIS IS VERY INCOMPLETE!
|
|
|
|
|
/// </summary>
|
2020-11-07 22:08:40 +01:00
|
|
|
|
class Auth {
|
|
|
|
|
private IAuthentication _authCap;
|
|
|
|
|
public Auth(IAuthentication authCap) {
|
|
|
|
|
_authCap = authCap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<IReadOnlyList<string>> GetMechanisms() {
|
|
|
|
|
return _authCap.Mechanisms();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
public bool Handshake(Stream stream) {
|
2020-11-07 22:08:40 +01:00
|
|
|
|
var host = "localhost";
|
|
|
|
|
var asm = typeof(Api).Assembly;
|
|
|
|
|
var program = $"{asm.FullName}-{asm.GetName().Version}";
|
|
|
|
|
var version = (0u, 1u);
|
|
|
|
|
|
|
|
|
|
var message = new Greeting() {
|
|
|
|
|
Host = host,
|
|
|
|
|
Major = version.Item1,
|
|
|
|
|
Minor = version.Item2,
|
|
|
|
|
Program = program
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var msg = MessageBuilder.Create();
|
2020-11-12 10:12:56 +01:00
|
|
|
|
var root = msg.BuildRoot<Schema.Greeting.WRITER>();
|
2020-11-07 22:08:40 +01:00
|
|
|
|
message.serialize(root);
|
|
|
|
|
|
|
|
|
|
var pump = new FramePump(stream);
|
|
|
|
|
pump.Send(msg.Frame);
|
|
|
|
|
|
|
|
|
|
var frame = Framing.ReadSegments(stream);
|
|
|
|
|
|
|
|
|
|
var deserializer = DeserializerState.CreateRoot(frame);
|
|
|
|
|
var reader = new Greeting.READER(deserializer);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var serverInfo = reader;
|
|
|
|
|
Console.WriteLine($"Server: {serverInfo.Host}");
|
|
|
|
|
Console.WriteLine($"Version: {serverInfo.Program}");
|
|
|
|
|
Console.WriteLine($"API-Version: {serverInfo.Major}.{serverInfo.Minor}");
|
2020-11-12 10:12:56 +01:00
|
|
|
|
|
|
|
|
|
//TODO: Check if we are actually compatible. This will probably need some internal lookup or well defined versioning semantics
|
|
|
|
|
return true;
|
2020-11-07 22:08:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
public async Task<bool> Authenticate(string mech, Dictionary<string, object> properties) {
|
|
|
|
|
//TODO: Perform Handshake to verify that we are compatible with server
|
|
|
|
|
|
2020-11-07 22:08:40 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-07 22:08:40 +01:00
|
|
|
|
|
2020-11-12 10:12:56 +01:00
|
|
|
|
return false;
|
2020-11-07 22:08:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|