using Capnp; using FabAccessAPI.Schema; using S22.Sasl; using System; using System.Collections.Generic; using System.IO; 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 `@` 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 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, } class UnauthorizedException : Exception{} /// /// THIS IS VERY INCOMPLETE! /// class Auth { private IAuthentication _authCap; public Auth(IAuthentication authCap) { _authCap = authCap; } public Task> GetMechanisms() { return _authCap.Mechanisms(); } public bool Handshake(Stream stream) { 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(); var root = msg.BuildRoot(); 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}"); //TODO: Check if we are actually compatible. This will probably need some internal lookup or well defined versioning semantics return true; } public async Task Authenticate(string mech, Dictionary properties) { //TODO: Perform Handshake to verify that we are compatible with server var m = SaslFactory.Create(mech); foreach (KeyValuePair 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; } } }