diff --git a/schema b/schema index 9fb856a..95fa6c5 160000 --- a/schema +++ b/schema @@ -1 +1 @@ -Subproject commit 9fb856a4dbe8f1e68c77afa3dbff606a27342e7c +Subproject commit 95fa6c5eabc8fded8b69ce8e3e87bc5d9c37c027 diff --git a/src/auth.rs b/src/auth.rs index dba4a33..5fe1297 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -12,15 +12,18 @@ use rsasl::{ ReturnCode, Callback, SaslCtx, + Step, }; +use capnp::capability::{Params, Results, Promise}; + use crate::error::Result; use crate::config::Settings; pub use crate::schema::auth_capnp; -struct AppData; -struct SessionData; +pub struct AppData; +pub struct SessionData; struct CB; impl Callback for CB { @@ -63,6 +66,96 @@ impl Auth { } } +use auth_capnp::authentication::*; +impl auth_capnp::authentication::Server for Auth { + fn mechanisms(&mut self, + _: Params, + mut res: Results + ) -> Promise<(), capnp::Error> { + let mechs = match self.ctx.server_mech_list() { + Ok(m) => m, + Err(e) => { + return Promise::err(capnp::Error { + kind: capnp::ErrorKind::Failed, + description: format!("SASL Failure: {}", e), + }) + }, + }; + + let mechvec: Vec<&str> = mechs.iter().collect(); + + let mut res_mechs = res.get().init_mechs(mechvec.len() as u32); + for (i, m) in mechvec.into_iter().enumerate() { + res_mechs.set(i as u32, m); + } + + Promise::ok(()) + } + + // TODO: return Outcome instead of exceptions + fn start(&mut self, + params: Params, + mut res: Results + ) -> Promise<(), capnp::Error> { + let req = pry!(pry!(params.get()).get_request()); + + // Extract the MECHANISM the client wants to use and start a session. + // Or fail at that and thrown an exception TODO: return Outcome + let mech = pry!(req.get_mechanism()); + let mut session = match self.ctx.server_start(mech) { + Ok(s) => s, + Err(e) => + return Promise::err(capnp::Error { + kind: capnp::ErrorKind::Failed, + description: format!("SASL error: {}", e), + }), + }; + + // If the client has provided initial data go use that + use auth_capnp::request::initial_response::Which; + let step_res = match req.get_initial_response().which() { + Err(capnp::NotInSchema(_)) => + return Promise::err(capnp::Error { + kind: capnp::ErrorKind::Failed, + description: "Initial data is badly formatted".to_string(), + }), + + Ok(Which::None(_)) => { + // FIXME: Actually this needs to indicate NO data instead of SOME data of 0 length + session.step(&[]) + } + Ok(Which::Initial(data)) => { + session.step(pry!(data)) + } + }; + + // The step may either return an error, a success or the need for more data + match step_res { + Ok(Step::Done(b)) => { + use auth_capnp::response::Result; + + let mut outcome = pry!(res.get().get_response()).init_outcome(); + outcome.reborrow().set_result(Result::Successful); + if b.len() != 0 { + outcome.init_additional_data().set_additional(&b); + } + Promise::ok(()) + }, + Ok(Step::NeedsMore(b)) => { + pry!(res.get().get_response()).set_challence(&b); + Promise::ok(()) + } + // TODO: This should really be an outcome because this is failed auth just as much atm. + Err(e) => + return Promise::err(capnp::Error { + kind: capnp::ErrorKind::Failed, + description: format!("SASL error: {}", e), + }), + } + + } +} + pub async fn init(log: Logger, config: Settings) -> Result { Ok(Auth::new()) } @@ -82,9 +175,19 @@ struct AuthCId(String); /// This identity is internal to FabAccess and completely independent from the authentication /// method or source struct AuthZId { + /// Main User ID. Generally an user name or similar uid: String, + /// 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 subuid: String, - domain: String, + /// Realm this account originates. + /// + /// The Realm is usually described by a domain name but local policy may dictate an unrelated + /// mapping + realm: String, } // What is a man?! A miserable little pile of secrets! @@ -93,7 +196,7 @@ struct AuthZId { /// This struct contains the user as is passed to the actual authentication/authorization /// subsystems /// -struct User { +pub struct User { /// Contains the Authentication ID used /// /// The authentication ID is an identifier for the authentication exchange. This is different @@ -133,11 +236,13 @@ struct User { // 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, -enum AuthError { +pub enum AuthError { /// Authentication ID is bad/unknown/.. BadAuthcid, - /// Authorization ID is bad/unknown/.. + /// Authorization ID is unknown/.. BadAuthzid, + /// Authorization ID is not of form user+uid@realm + MalformedAuthzid, /// User may not use that authorization id NotAllowedAuthzid, diff --git a/src/connection.rs b/src/connection.rs index 0bcae6f..1424884 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -69,11 +69,9 @@ pub async fn handle_connection(log: Logger, mut stream: TcpStream) -> Result<()> match w { Request(Ok(r)) => { let m = r.get_mechanism().unwrap(); - println!("Client wants to AUTH using {:?}", - m); - let cm = std::ffi::CString::new(m).unwrap(); + println!("Client wants to AUTH using {:?}", &m); let mut sasl = auth::Auth::new(); - let mut sess = sasl.ctx.server_start(&cm).unwrap(); + let mut sess = sasl.ctx.server_start(&m).unwrap(); use crate::auth::auth_capnp::request::initial_response::*; match r.get_initial_response().which() {