diff --git a/src/api.rs b/src/api.rs index 7fa5589..c01c196 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,6 +6,7 @@ use crate::schema::connection_capnp; use crate::connection::Session; use crate::db::Databases; +use crate::db::user::UserId; use crate::network::Network; @@ -55,11 +56,27 @@ impl connection_capnp::bootstrap::Server for Bootstrap { _: MachineSystemParams, mut res: MachineSystemResults ) -> Promise<(), capnp::Error> { - // TODO actual permission check and stuff - let c = capnp_rpc::new_client(Machines::new(self.session.clone(), self.db.clone(), self.nw.clone())); - res.get().set_machine_system(c); + let session = self.session.clone(); + let accessdb = self.db.access.clone(); + let nw = self.nw.clone(); + let f = async move { + // Ensure the lock is dropped as soon as possible + if let Some(user) = { session.user.lock().await.clone() } { + let perms = accessdb.collect_permrules(&user.data) + .map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))?; - Promise::ok(()) + // TODO actual permission check and stuff + // Right now we only check that the user has authenticated at all. + let c = capnp_rpc::new_client(Machines::new(user.id, perms, nw)); + res.get().set_machine_system(c); + } + + // Promise is Ok either way, just the machine system may not be set, indicating as + // usual a lack of permission. + Ok(()) + }; + + Promise::from_future(f) } } diff --git a/src/api/auth.rs b/src/api/auth.rs index 419a32d..66bc60f 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -64,6 +64,7 @@ impl Callback for CB { if b { if let Some(s) = cap_session { if let Ok(Some(user)) = appdata.userdb.get_user(authid) { + // FIXME: This should set the userid outside the callback s.user.try_lock().unwrap().replace(user); } } @@ -116,7 +117,7 @@ impl authentication_system::Server for Auth { _: authentication_system::MechanismsParams, mut res: authentication_system::MechanismsResults ) -> Promise<(), capnp::Error> { - let mechs = match self.ctx.server_mech_list() { + /*let mechs = match self.ctx.server_mech_list() { Ok(m) => m, Err(e) => { return Promise::err(capnp::Error { @@ -131,7 +132,10 @@ impl authentication_system::Server for Auth { 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); - } + }*/ + // For now, only PLAIN + let mut res_mechs = res.get().init_mechs(1); + res_mechs.set(0, "PLAIN"); Promise::ok(()) } @@ -146,6 +150,13 @@ impl authentication_system::Server for Auth { // 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()); + if pry!(req.get_mechanism()) != "PLAIN" { + return Promise::err(capnp::Error { + kind: capnp::ErrorKind::Failed, + description: format!("Invalid SASL mech"), + }) + } + let mut session = match self.ctx.server_start(mech) { Ok(s) => s, Err(e) => diff --git a/src/api/machine.rs b/src/api/machine.rs index 7a791d3..393593a 100644 --- a/src/api/machine.rs +++ b/src/api/machine.rs @@ -1,137 +1,115 @@ use std::sync::Arc; -use std::ops::Deref; use capnp::capability::Promise; use capnp::Error; use futures::FutureExt; -use crate::schema::machine_capnp::machine::*; -use crate::connection::Session; -use crate::db::Databases; -use crate::db::machine::{Status, MachineState}; -use crate::machine::{Machine as NwMachine, ReturnToken}; +use crate::db::access::{PrivilegesBuf, PermRule}; + +use crate::db::machine::Status; +use crate::machine::Machine as NwMachine; +use crate::schema::machine_capnp::machine::*; + +#[derive(Clone, Copy)] +pub struct Perms { + pub disclose: bool, + pub read: bool, + pub write: bool, + pub manage: bool, +} + +impl Perms { + pub fn get_for<'a, I: Iterator>(privs: &'a PrivilegesBuf, rules: I) -> Self { + let mut disclose = false; + let mut read = false; + let mut write = false; + let mut manage = false; + for rule in rules { + if rule.match_perm(&privs.disclose) { + disclose = true; + } + if rule.match_perm(&privs.read) { + read = true; + } + if rule.match_perm(&privs.write) { + write = true; + } + if rule.match_perm(&privs.manage) { + manage = true; + } + } + + Self { disclose, read, write, manage } + } +} -#[derive(Clone)] pub struct Machine { - session: Arc, + perms: Perms, machine: NwMachine, - db: Databases, } impl Machine { - pub fn new(session: Arc, machine: NwMachine, db: Databases) -> Self { - Machine { session, machine, db } - } - - pub fn fill(self: Arc, builder: &mut Builder) { - builder.set_manage(capnp_rpc::new_client(Manage(self.clone()))); - builder.set_admin(capnp_rpc::new_client(Admin(self.clone()))); + pub fn new(perms: Perms, machine: NwMachine) -> Self { + Self { perms, machine } } } -#[derive(Clone)] -pub struct Read(Arc); +impl info::Server for Machine { + fn get_machine_info_extended( + &mut self, + _: info::GetMachineInfoExtendedParams, + _results: info::GetMachineInfoExtendedResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + /*if self.perms.manage { + let mut builder = results.get(); + let mut extinfo = builder.init_machine_info_extended(); + let mut current = extinfo.init_current_user(); + // FIXME fill user + } + Promise::ok(())*/ -impl Read { - pub fn new(inner: Arc) -> Self { - Self(inner) + Promise::err(capnp::Error::unimplemented("Extended Infos are unavailable".to_string())) + } + + fn get_reservation_list( + &mut self, + _: info::GetReservationListParams, + mut results: info::GetReservationListResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + Promise::err(capnp::Error::unimplemented("Reservations are unavailable".to_string())) + } + + fn get_property_list( + &mut self, + _: info::GetPropertyListParams, + mut results: info::GetPropertyListResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + Promise::err(capnp::Error::unimplemented("Extended Properties are unavailable".to_string())) } } -impl info::Server for Read { -} - -struct Write(Arc); - -impl use_::Server for Write { - fn use_(&mut self, - _params: use_::UseParams, - mut results: use_::UseResults) - -> Promise<(), Error> - { - let uid = self.0.session.user.try_lock().unwrap().as_ref().map(|u| u.id.clone()); - let new_state = MachineState::used(uid.clone()); - let this = self.0.clone(); - let f = async move { - let res_token = this.machine.request_state_change( - this.session.user.try_lock().unwrap().as_ref(), - new_state - ).await; - - match res_token { - // TODO: Do something with the token we get returned - Ok(tok) => { - return Ok(()); - }, - Err(e) => Err(capnp::Error::failed(format!("State change request returned {}", e))), - } - }; - - Promise::from_future(f) - } - - fn reserve(&mut self, - _params: use_::ReserveParams, - _results: use_::ReserveResults) - -> Promise<(), Error> - { - unimplemented!() - } - -} - -impl in_use::Server for Write { - fn give_back(&mut self, - _params: in_use::GiveBackParams, - mut results: in_use::GiveBackResults) - -> Promise<(), Error> - { - let this = self.0.clone(); - - let f = async move { - let status = this.machine.get_status().await; - let sess = this.session.clone(); - - match status { - Status::InUse(Some(uid)) => { - let user = sess.user.lock().await; - if let Some(u) = user.as_ref() { - if u.id == uid { - } - } - }, - // Machine not in use - _ => { - } - } - }; - - Promise::from_future(f.map(|_| Ok(()))) +impl use_::Server for Machine { + fn use_( + &mut self, + _: use_::UseParams, + _: use_::UseResults + ) -> capnp::capability::Promise<(), capnp::Error> { + Promise::ok(()) } } -struct Manage(Arc); - -impl manage::Server for Manage { +impl in_use::Server for Machine { } -struct Admin(Arc); - -impl admin::Server for Admin { - fn force_set_state(&mut self, - _params: admin::ForceSetStateParams, - _results: admin::ForceSetStateResults) - -> Promise<(), Error> - { - unimplemented!() - } - - fn force_set_user(&mut self, - _params: admin::ForceSetUserParams, - _results: admin::ForceSetUserResults) - -> Promise<(), Error> - { - unimplemented!() - } +impl transfer::Server for Machine { +} + +impl check::Server for Machine { +} + +impl manage::Server for Machine { +} + +impl admin::Server for Machine { } diff --git a/src/api/machines.rs b/src/api/machines.rs index 91bf6ce..25bf646 100644 --- a/src/api/machines.rs +++ b/src/api/machines.rs @@ -3,35 +3,39 @@ use std::sync::Arc; use capnp::capability::Promise; use capnp::Error; +use crate::db::machine::Status; +use crate::api::machine::*; +use crate::schema::machine_capnp::machine::MachineState; use crate::schema::machinesystem_capnp::machine_system; use crate::schema::machinesystem_capnp::machine_system::info as machines; -use crate::connection::Session; - -use crate::db::Databases; - use crate::network::Network; - -use super::machine::*; +use crate::db::user::UserId; +use crate::db::access::{PermRule, admin_perm}; /// An implementation of the `Machines` API +#[derive(Clone)] pub struct Machines { - /// A reference to the connection — as long as at least one API endpoint is - /// still alive the session has to be as well. - session: Arc, - - db: Databases, + user: UserId, + permissions: Vec, network: Arc, } impl Machines { - pub fn new(session: Arc, db: Databases, network: Arc) -> Self { - info!(session.log, "Machines created"); - Self { session, db, network } + pub fn new(user: UserId, permissions: Vec, network: Arc) -> Self { + Self { user, permissions, network } } } impl machine_system::Server for Machines { - + // This function shouldn't exist. See fabaccess-api issue #16 + fn info(&mut self, + _:machine_system::InfoParams, + mut results: machine_system::InfoResults + ) -> capnp::capability::Promise<(), capnp::Error> + { + results.get().set_info(capnp_rpc::new_client(self.clone())); + Promise::ok(()) + } } impl machines::Server for Machines { @@ -41,18 +45,61 @@ impl machines::Server for Machines { -> Promise<(), Error> { let v: Vec<(String, crate::machine::Machine)> = self.network.machines.iter() - .map(|(n, m)| (n.clone(), m.clone())) + .filter(|(_name, machine)| { + let required_disclose = &machine.desc.privs.disclose; + for perm_rule in self.permissions.iter() { + if perm_rule.match_perm(required_disclose) { + return true; + } + } + + false + }) + .map(|(n,m)| (n.clone(), m.clone())) .collect(); - /*let mut machines = results.get().init_machines(v.len() as u32); + let permissions = self.permissions.clone(); - for (i, (name, machine)) in v.into_iter().enumerate() { - trace!(self.session.log, "Adding machine #{} {}: {:?}", i, name, machine); - let machine = Arc::new(Machine::new(self.session.clone(), machine, self.db.clone())); - let mut builder = machines.reborrow().get(i as u32); - machine.fill(&mut builder); - }*/ + let f = async move { + let mut machines = results.get().init_machine_list(v.len() as u32); + for (i, (name, machine)) in v.into_iter().enumerate() { + let perms = Perms::get_for(&machine.desc.privs, permissions.iter()); - Promise::ok(()) + let mut builder = machines.reborrow().get(i as u32); + builder.set_name(&name); + if let Some(ref desc) = machine.desc.description { + builder.set_description(desc); + } + + let s = match machine.get_status().await { + Status::Free => MachineState::Free, + Status::Disabled => MachineState::Disabled, + Status::Blocked(_) => MachineState::Blocked, + Status::InUse(_) => MachineState::InUse, + Status::Reserved(_) => MachineState::Reserved, + Status::ToCheck(_) => MachineState::ToCheck, + }; + builder.set_state(s); + + if perms.write { + builder.set_use(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + builder.set_inuse(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + } + if perms.manage { + builder.set_transfer(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + builder.set_check(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + builder.set_manage(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + } + if permissions.iter().any(|r| r.match_perm(&admin_perm())) { + builder.set_admin(capnp_rpc::new_client(Machine::new(perms, machine.clone()))); + } + + builder.set_info(capnp_rpc::new_client(Machine::new(perms, machine))); + } + + Ok(()) + }; + + Promise::from_future(f) } } diff --git a/src/db/access.rs b/src/db/access.rs index a3fe9dc..f8ba5b8 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -22,10 +22,15 @@ pub struct AccessControl { pub internal: Internal, } +pub const ADMINPERM: &'static str = "bffh.admin"; +pub fn admin_perm() -> &'static Permission { + Permission::new(ADMINPERM) +} + impl AccessControl { pub fn new(internal: Internal) -> Self { Self { - internal: internal, + internal, } } @@ -47,6 +52,10 @@ impl AccessControl { return Ok(false); } + pub fn collect_permrules(&self, user: &UserData) -> Result> { + self.internal.collect_permrules(user) + } + pub fn dump_roles(&self) -> Result> { self.internal.dump_roles() } @@ -120,6 +129,22 @@ pub trait RoleDB { Ok(()) } + + fn collect_permrules(&self, user: &UserData) -> Result> { + let mut roleset = HashMap::new(); + for role_id in user.roles.iter() { + self.tally_role(&mut roleset, role_id)?; + } + + let mut output = Vec::new(); + + // Iter all unique role->permissions we've found and early return on match. + for (_roleid, role) in roleset.iter() { + output.extend(role.permissions.iter().cloned()) + } + + return Ok(output); + } } /// A "Role" from the Authorization perspective @@ -310,7 +335,7 @@ impl PermissionBuf { self.inner.push_str(perm.as_str()) } - pub fn from_string(inner: String) -> Self { + pub const fn from_string(inner: String) -> Self { Self { inner } } @@ -429,7 +454,7 @@ pub enum PermRule { impl PermRule { // Does this rule match that permission - fn match_perm>(&self, perm: &P) -> bool { + pub fn match_perm>(&self, perm: &P) -> bool { match self { PermRule::Base(ref base) => base.as_permission() == perm.as_ref(), PermRule::Children(ref parent) => parent.as_permission() > perm.as_ref() , diff --git a/src/machine.rs b/src/machine.rs index 6e0c543..fe1884a 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -77,21 +77,19 @@ impl Index { #[derive(Debug, Clone)] pub struct Machine { pub id: uuid::Uuid, - pub name: String, - pub description: String, + pub desc: MachineDescription, inner: Arc>, access: Arc, } impl Machine { - pub fn new(inner: Inner, access: Arc) -> Self { + pub fn new(inner: Inner, desc: MachineDescription, access: Arc) -> Self { Self { id: uuid::Uuid::default(), - name: "".to_string(), - description: "".to_string(), inner: Arc::new(Mutex::new(inner)), - access: access, + access, + desc, } } @@ -102,7 +100,7 @@ impl Machine { , access: Arc ) -> Machine { - Self::new(Inner::new(id, desc, state), access) + Self::new(Inner::new(id, state), desc, access) } pub fn from_file>(path: P, access: Arc) @@ -129,14 +127,14 @@ impl Machine { let f = async move { if let Some(udata) = udata { - let mut guard = this.inner.try_lock().unwrap(); - if this.access.check(&udata, &guard.desc.privs.write).await? { + if this.access.check(&udata, &this.desc.privs.write).await? { + let mut guard = this.inner.lock().await; guard.do_state_change(new_state); return Ok(ReturnToken::new(this.inner.clone())) } } else { if new_state == MachineState::free() { - let mut guard = this.inner.try_lock().unwrap(); + let mut guard = this.inner.lock().await; guard.do_state_change(new_state); return Ok(ReturnToken::new(this.inner.clone())); } @@ -148,6 +146,20 @@ impl Machine { Box::pin(f) } + pub fn do_state_change(&self, new_state: MachineState) + -> BoxFuture<'static, Result> + { + let this = self.clone(); + + let f = async move { + let mut guard = this.inner.lock().await; + guard.do_state_change(new_state); + return Ok(ReturnToken::new(this.inner.clone())) + }; + + Box::pin(f) + } + pub fn create_token(&self) -> ReturnToken { ReturnToken::new(self.inner.clone()) } @@ -161,6 +173,10 @@ impl Machine { let guard = self.inner.try_lock().unwrap(); guard.signal() } + + pub fn get_inner(&self) -> Arc> { + self.inner.clone() + } } impl Deref for Machine { @@ -182,9 +198,6 @@ pub struct Inner { /// Globally unique machine readable identifier pub id: MachineIdentifier, - /// Descriptor of the machine - pub desc: MachineDescription, - /// The state of the machine as bffh thinks the machine *should* be in. /// /// This is a Signal generator. Subscribers to this signal will be notified of changes. In the @@ -195,13 +208,11 @@ pub struct Inner { impl Inner { pub fn new ( id: MachineIdentifier - , desc: MachineDescription , state: MachineState ) -> Inner { Inner { - id: id, - desc: desc, + id, state: Mutable::new(state), reset: None, }