Make Authentication return an authenticated Bootstrap instead

Fixes: #12
Fixes: #14
This commit is contained in:
Nadja Reitzenstein 2021-09-23 22:33:56 +02:00
parent 1441b59145
commit 8de6cd1924
3 changed files with 113 additions and 98 deletions

View File

@ -1,135 +1,112 @@
@0xb9cffd29ac983e9f; @0xb9cffd29ac983e9f;
using Rust = import "programming_language/rust.capnp"; using Rust = import "programming_language/rust.capnp";
$Rust.parentModule("schema"); $Rust.parentModule("schema");
using CSharp = import "programming_language/csharp.capnp"; using CSharp = import "programming_language/csharp.capnp";
$CSharp.namespace("FabAccessAPI.Schema"); $CSharp.namespace("FabAccessAPI.Schema");
using User = import "user.capnp".User; using L10NString = import "utils.capnp".L10NString;
using Session = import "connection.capnp".Session;
interface AuthenticationSystem {
mechanisms @0 () -> ( mechs :List(Text) );
# Get the list of mechanisms supported by the server
start @1 ( request :Request ) -> ( response :Response );
# Initiate an authentication exchange
# NOTE: Calling start() after an authentication exchange has already
# finished is undefined behaviour. If you want to double-authenticate call
# auth() from connection.capnp again to get a fresh capability you can use.
# This may however return NULL if you are not allowed to authenticate twice.
step @2 ( response :Data ) -> ( response :Response );
# Respond to a challenge with more data
# NOTE: As with start() calling this after having received an outcome is
# undefined behaviour.
abort @3 () -> ();
# Abort the current exchange. This may be sent by both client and server
# at any point during the exchange. It MUST not be sent by a server
# after sending an outcome or by a client after receiving an outcome.
# A server receiving an abort after sending an outcome but before
# receiving any other message MUST respect the abort.
whoami @4 () -> ( you :User, dummy :UInt8 = 0 );
# Returns NULL if not authenticated and an User object if authenticated.
}
struct Request {
mechanism @0 :Text; # The SASL mechanism name.
initialResponse :union {
# A client may send some intial data when requesting an auth exchange.
# According to RFC4422 section 4.3a an implementation MUST be able to
# distinguish between an empty initial reponse and no initial response.
none @1 :Void;
# No initial reponse is being sent.
# The lowest numbered field in an union is the default value in Cap'n
# Proto.
initial @2 :Data;
# A response may be any sequence of octets, including zero-length
# sequences and zero-valued octets.
}
}
struct Response { struct Response {
enum Result { enum Error {
# Result code of the outcome aborted @0;
successful @0; # This authentication exchange was aborted by either side.
badMechanism @1; badMechanism @1;
# The server does not support this mechanism in this context. # The server does not support this mechanism in this context.
unwilling @2; unwilling @2;
# Generic "I'm sorry dave, I can't do that" response. MAY be set for any # Generic "I'm sorry dave, I can't do that" response. MAY be set for any reason, all reasons
# reason, all reasons or no reason at all. # or no reason at all. This code will for example be used if a client is being rate limited.
# A server SHOULD set the `action` and `helpText` fields as appropiate. # A server SHOULD set the `action` and `description` fields as appropiate.
# This code SHOULD only be sent if no other value is more fitting. # This code SHOULD only be sent if no other value is more fitting.
invalidCredentials @3; invalidCredentials @3;
# The exchange was valid, but the provided credentials are invalid. This # The exchange was valid, but the provided credentials are invalid. This may mean that the
# may mean that the authcid is not known to the server or that the # authcid is not known to the server or that the password/certificate/key/ticket/etc is not
# password/certificate/key/etc. is not correct. # correct.
unauthorized @4; unauthorized @4;
# The given authcid is not authorized to act as the requested authzid. # The given authcid is not authorized to act as the requested authzid. This MAY also be
# This MAY also be returned for the cases `authzid == NULL` or # returned for the cases `authzid == NULL` or `authzid == authcid`, for example because
# `authzid == authcid`, for example because login is disabled for that # login is disabled for that authcid.
# authcid.
malformedAuthZid @5; malformedAuthZid @5;
# The provided authzid is malformed in some way. # The provided authzid is malformed in some way.
failed @6; failed @6;
# A generic failed result. A server sending this result MUST set the # A generic failed result. A server sending this result MUST set the `action` field to
# `action` field to indicate whether this is a temporary or permanent # indicate whether this is a temporary or permanent failure and SHOULD set `description` to
# failure and SHOULD set `helpText` to a human-readable error message. # a human-readable error message.
} }
enum Action { enum Action {
# In case of a unsuccessful outcome, how should a client proceed? # In case of a unsuccessful outcome, how should a client proceed?
unset @0; unset @0;
# Not a unsuccessful outcome, or the server doesn't know either. # The server doesn't know either. A client SHOULD treat this either as as `retry` or as
# Also, the default value # `wait`.
retry @1; retry @1;
# A client should just try again, potentially after prompting the user # A client SHOULD try again, depending on the `Error` after prompting the user for their
# to enter their credentials again. # credentials once more.
wait @2; wait @2;
# The client should wait and try again later. This MAY mean that # The client SHOULD wait and retry after a grace period. A client MUST wait at least 500ms
# whatever failure happened was temporary or the server is applying # and SHOULD implement exponential backoff up to no less than 64s.
# This MAY mean that whatever failure happened was temporary or the server is applying
# rate-limiting to the connection. # rate-limiting to the connection.
permanent @3; permanent @3;
# The issue appears to the server as being permanent. Another try is # The issue appears to the server as being permanent. Another try is very likely to return
# very likely to return the exact same result. In most cases the user # the exact same result. In most cases the user should notify the responsible system
# should notify the responsible system administrator. # administrator. A client SHOULD NOT try to authenticate to the server again until the user
# manually indicates a retry.
} }
union { union {
challence @0 :Data; error :group {
outcome :group { # Some kind of error happened. This in the first entry in the union because it is the
result @1 :Result; # Result code # default set value meaning if a server fails to set any of the values, indicating some
# pretty severe server bugs, it is parsed as an "aborted" error.
action @2 :Action; # Hints for the client how to proceed in case of an error result @0 :Error;
action @1 :Action;
description @2 :L10NString;
# A human-readable error description that is designed to be shown to the user in case of
# failure. Clients MAY NOT display this message if they understand the error e.g. for
# the error/action "invalidCredentials/retry".
}
challenge @3 :Data;
# The data provided so far is not enough to authenticate the user. The data MAY be a
# NULL-ptr or a non-NULL list ptr of zero bytes which clients MUST pass to their SASL
# implementation as "no data" and "some data of zero length" respectively.
successful :group {
# The exchange was successful and a new session has been created for the authzid that
# was established by the SASL exchange.
helpText @3 :Text; # Human-readable further information in case of an error session @4 :Session;
additionalData @5 :Data;
additionalData :union { # SASL may send additional data with the successful result. This MAY be a NULL-ptr or a
# Additional data that may be sent by the server to the client after a # non-NULL list ptr of zero bytes which clients MUST pass to their SASL implementation
# successful authentication exchange. # as "no additional data" and "some additional data of zero length" respectively.
none @4 :Void;
# No additional data is being sent. This MUST be set on unsuccessful
# outcomes.
additional @5 :Data;
# Additional data may be any sequence of octets, including zero-length
# sequences and zero-value octets.
}
} }
} }
} }
interface Authentication {
step @0 ( data: Data ) -> ( response: Response );
# Respond to a challenge with more data. A client MUST NOT call this after having received an
# "successful" response.
abort @1 () -> ();
# Abort the current exchange. This will invalidate the Authentication making all further calls
# to `step` return an error response. A client MUST NOT call this function after
# having received an "successful" response.
# A server will indicate that they have aborted an authentication exchange by replying with an
# "aborted" Error to the next `step` call. A server SHOULD directly terminate the underlying stream
# after sending this response. The server MAY after a short grace period terminate the stream
# without sending a response if no call to `step` was received by the client.
}

View File

@ -6,18 +6,27 @@ $Rust.parentModule("schema");
using CSharp = import "programming_language/csharp.capnp"; using CSharp = import "programming_language/csharp.capnp";
$CSharp.namespace("FabAccessAPI.Schema"); $CSharp.namespace("FabAccessAPI.Schema");
using AuthenticationSystem = import "authenticationsystem.capnp".AuthenticationSystem; using Authentication = import "authenticationsystem.capnp".Authentication;
using MachineSystem = import "machinesystem.capnp".MachineSystem; using MachineSystem = import "machinesystem.capnp".MachineSystem;
using UserSystem = import "usersystem.capnp".UserSystem; using UserSystem = import "usersystem.capnp".UserSystem;
using PermissionSystem = import "permissionsystem.capnp".PermissionSystem; using PermissionSystem = import "permissionsystem.capnp".PermissionSystem;
interface Bootstrap interface Bootstrap
{ {
authenticationSystem @0 () -> ( authenticationSystem : AuthenticationSystem ); mechanisms @0 () -> ( mechs: List(Text) );
# Get a list of Mechanisms this server allows in this context.
machineSystem @1 () -> ( machineSystem : MachineSystem ); createSession @1 ( mechanism :Text, initialData :Data ) -> ( authentication :Authentication);
# Create a new session with the server that you wish to authenticate using `mechanism`.
userSystem @2 () -> ( userSystem : UserSystem ); # If the mechanism is a client-first mechanism you MAY set `initialData` to contain the data you
# want to send. If the mechanism is server-first or you do not wish to send initial data, make
permissionSystem @3 () -> ( permissionSystem : PermissionSystem ); # initialData a NULL-pointer.
}
struct Session {
me @0 :Text;
machineSystem @1 : MachineSystem;
userSystem @2 : UserSystem;
permissionSystem @3 : PermissionSystem;
} }

29
utils.capnp Normal file
View File

@ -0,0 +1,29 @@
@0xed0c02f41fea6b5a;
interface L10NString {
# Any string type that is intended to be displayed to an user that is more than an identifier to
# be used as-is must be able to be localized into the users preferred language. This includes
# description, help messages, etc. but of course does not extend to usernames.
# TODO: Potentially make generic over the localized content (e.g. dates)? Can be done after the
# fact without braking protocol, so no big issue.
get @0 ( lang :Text ) -> ( lang :Text, content :Text );
# Retrieve the string in the given locale. The input parameter MUST be a RFC5646-formatted
# locale identifier (e.g: "en-US", "de-DE", "az-Arab-IR").
# A server MUST set the outputs `lang` to the exact tag that the content it sends was written in
# and `content` to the localized string.
# If a server can't find a localized version it SHOULD try to substitute it. Substitution SHOULD
# only search for close matches, e.g. returning "en-UK" for a string requested in "en-US" or
# returning "es-ES" for a requested "es-VE". Substitution MUST NOT return a localization that an
# user can not be expected to understand unless the server has a priori knowledge that the user
# can read and understand said language.
# If a server sends a substituted string it MUST set the output `lang` to the tag of the
# substitute it is sending.
# If a server can't find a suitable substitute or is not doing substitution it MUST set the
# output `content` to a NULL pointer and set the output `lang` to the input `lang` it was
# passed.
# If a server can't parse a given `lang` tag it MUST set the output `lang` to a NULL pointer.
available @1 () -> ( langs :List(Text) );
# Returns the list of locales this content is available in.
}