From 75ad3ced51a4dc726d54fd0083c359949d53ab8a Mon Sep 17 00:00:00 2001 From: Firstyear Date: Sat, 14 Sep 2019 23:37:05 +1000 Subject: [PATCH] Implement resolving references to names on entry to proto entry convert. (#86) Implement resolving reference uuids to names when they are available on proto entry conversion. --- rsidm_tools/Cargo.toml | 2 + rsidm_tools/src/main.rs | 23 +++++++- rsidmd/src/lib/actors/v1.rs | 8 ++- rsidmd/src/lib/constants.rs | 2 +- rsidmd/src/lib/entry.rs | 31 ++++++----- rsidmd/src/lib/event.rs | 40 ++++++++------ rsidmd/src/lib/server.rs | 104 ++++++++++++++++++++++++++++++++---- rsidmd/src/lib/value.rs | 44 +++++++-------- 8 files changed, 187 insertions(+), 67 deletions(-) diff --git a/rsidm_tools/Cargo.toml b/rsidm_tools/Cargo.toml index 47d07c7ed..81a59e1de 100644 --- a/rsidm_tools/Cargo.toml +++ b/rsidm_tools/Cargo.toml @@ -12,4 +12,6 @@ path = "src/main.rs" rsidm_client = { path = "../rsidm_client" } rpassword = "0.4" structopt = { version = "0.2", default-features = false } +log = "0.4" +env_logger = "0.6" diff --git a/rsidm_tools/src/main.rs b/rsidm_tools/src/main.rs index 705056136..a92ff287d 100644 --- a/rsidm_tools/src/main.rs +++ b/rsidm_tools/src/main.rs @@ -2,9 +2,14 @@ extern crate structopt; use rsidm_client::RsidmClient; use std::path::PathBuf; use structopt::StructOpt; +extern crate env_logger; +#[macro_use] +extern crate log; #[derive(Debug, StructOpt)] struct CommonOpt { + #[structopt(short = "d", long = "debug")] + debug: bool, #[structopt(short = "H", long = "url")] addr: String, #[structopt(short = "D", long = "name")] @@ -26,9 +31,24 @@ enum ClientOpt { Whoami(CommonOpt), } +impl ClientOpt { + fn debug(&self) -> bool { + match self { + ClientOpt::Whoami(copt) => copt.debug, + } + } +} + fn main() { let opt = ClientOpt::from_args(); + if opt.debug() { + ::std::env::set_var("RUST_LOG", "kanidm=debug,rsidm_client=debug"); + } else { + ::std::env::set_var("RUST_LOG", "kanidm=info,rsidm_client=info"); + } + env_logger::init(); + match opt { ClientOpt::Whoami(copt) => { let client = copt.to_client(); @@ -46,7 +66,8 @@ fn main() { match client.whoami() { Ok(o_ent) => match o_ent { - Some((_ent, uat)) => { + Some((ent, uat)) => { + debug!("{:?}", ent); println!("{}", uat); } None => println!("Unauthenticated"), diff --git a/rsidmd/src/lib/actors/v1.rs b/rsidmd/src/lib/actors/v1.rs index d9a6f5437..366368653 100644 --- a/rsidmd/src/lib/actors/v1.rs +++ b/rsidmd/src/lib/actors/v1.rs @@ -185,9 +185,7 @@ impl Handler for QueryServerV1 { match qs_read.search_ext(&mut audit, &srch) { Ok(entries) => { - let sr = SearchResult::new(entries); - // Now convert to a response, and return - Ok(sr.response()) + SearchResult::new(&mut audit, &qs_read, entries).map(|ok_sr| ok_sr.response()) } Err(e) => Err(e), } @@ -365,8 +363,8 @@ impl Handler for QueryServerV1 { 1 => { let e = entries.pop().expect("Entry length mismatch!!!"); // Now convert to a response, and return - let wr = WhoamiResult::new(e, uat); - Ok(wr.response()) + WhoamiResult::new(&mut audit, &qs_read, e, uat) + .map(|ok_wr| ok_wr.response()) } // Somehow we matched multiple, which should be impossible. _ => Err(OperationError::InvalidState), diff --git a/rsidmd/src/lib/constants.rs b/rsidmd/src/lib/constants.rs index 240c4e5ff..1b9918b42 100644 --- a/rsidmd/src/lib/constants.rs +++ b/rsidmd/src/lib/constants.rs @@ -154,7 +154,7 @@ pub static JSON_IDM_ADMINS_ACP_MANAGE_V1: &'static str = r#"{ "acp_targetscope": [ "{\"Pres\":\"class\"}" ], - "acp_search_attr": ["name", "class", "uuid", "classname", "attributename"], + "acp_search_attr": ["name", "class", "uuid", "classname", "attributename", "memberof"], "acp_modify_class": ["person"], "acp_modify_removedattr": ["class", "displayname", "name", "description"], "acp_modify_presentattr": ["class", "displayname", "name", "description"], diff --git a/rsidmd/src/lib/entry.rs b/rsidmd/src/lib/entry.rs index 686c42cd2..28b880201 100644 --- a/rsidmd/src/lib/entry.rs +++ b/rsidmd/src/lib/entry.rs @@ -4,7 +4,9 @@ use crate::credential::Credential; use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved}; use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid}; use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction}; -use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; +use crate::server::{ + QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction, +}; use crate::value::{IndexType, SyntaxType}; use crate::value::{PartialValue, Value}; use rsidm_proto::v1::Entry as ProtoEntry; @@ -1046,18 +1048,23 @@ impl Entry { } impl Entry { - pub fn into_pe(&self) -> ProtoEntry { + pub fn into_pe( + &self, + audit: &mut AuditScope, + qs: &QueryServerReadTransaction, + ) -> Result { // Turn values -> Strings. - ProtoEntry { - attrs: self - .attrs - .iter() - .map(|(k, vs)| { - let pvs: Vec<_> = vs.iter().map(|v| v.to_proto_string_clone()).collect(); - (k.clone(), pvs) - }) - .collect(), - } + let attrs: Result<_, _> = self + .attrs + .iter() + .map(|(k, vs)| { + let pvs: Result, _> = + vs.iter().map(|v| qs.resolve_value(audit, v)).collect(); + let pvs = pvs?; + Ok((k.clone(), pvs)) + }) + .collect(); + Ok(ProtoEntry { attrs: attrs? }) } } diff --git a/rsidmd/src/lib/event.rs b/rsidmd/src/lib/event.rs index 5e2487ce8..9633c0ea6 100644 --- a/rsidmd/src/lib/event.rs +++ b/rsidmd/src/lib/event.rs @@ -32,18 +32,21 @@ pub struct SearchResult { } impl SearchResult { - pub fn new(entries: Vec>) -> Self { - SearchResult { - entries: entries - .iter() - .map(|e| { - // All the needed transforms for this result are done - // in search_ext. This is just an entry -> protoentry - // step. - e.into_pe() - }) - .collect(), - } + pub fn new( + audit: &mut AuditScope, + qs: &QueryServerReadTransaction, + entries: Vec>, + ) -> Result { + let entries: Result<_, _> = entries + .iter() + .map(|e| { + // All the needed transforms for this result are done + // in search_ext. This is just an entry -> protoentry + // step. + e.into_pe(audit, qs) + }) + .collect(); + Ok(SearchResult { entries: entries? }) } // Consume self into a search response @@ -755,11 +758,16 @@ pub struct WhoamiResult { } impl WhoamiResult { - pub fn new(e: Entry, uat: UserAuthToken) -> Self { - WhoamiResult { - youare: e.into_pe(), + pub fn new( + audit: &mut AuditScope, + qs: &QueryServerReadTransaction, + e: Entry, + uat: UserAuthToken, + ) -> Result { + Ok(WhoamiResult { + youare: e.into_pe(audit, qs)?, uat: uat, - } + }) } pub fn response(self) -> WhoamiResponse { diff --git a/rsidmd/src/lib/server.rs b/rsidmd/src/lib/server.rs index 08406b1f8..16f4d6f0d 100644 --- a/rsidmd/src/lib/server.rs +++ b/rsidmd/src/lib/server.rs @@ -197,7 +197,11 @@ pub trait QueryServerTransaction { Ok(uuid_res) } - fn uuid_to_name(&self, audit: &mut AuditScope, uuid: &Uuid) -> Result { + fn uuid_to_name( + &self, + audit: &mut AuditScope, + uuid: &Uuid, + ) -> Result, OperationError> { // construct the filter let filt = filter!(f_eq("uuid", PartialValue::new_uuidr(uuid))); audit_log!(audit, "uuid_to_name: uuid -> {:?}", uuid); @@ -212,7 +216,8 @@ pub trait QueryServerTransaction { if res.len() == 0 { // If result len == 0, error no such result - return Err(OperationError::NoMatchingEntries); + audit_log!(audit, "uuid_to_name: name, no such entry <- Ok(None)"); + return Ok(None); } else if res.len() >= 2 { // if result len >= 2, error, invaid entry state. return Err(OperationError::InvalidDBState); @@ -224,17 +229,22 @@ pub trait QueryServerTransaction { let name_res = match e.get_ava(&String::from("name")) { Some(vas) => match vas.first() { Some(u) => (*u).clone(), + // Name is in an invalid state in the db None => return Err(OperationError::InvalidEntryState), }, - None => return Err(OperationError::InvalidEntryState), + None => { + // No attr name, some types this is valid, IE schema. + // return Err(OperationError::InvalidEntryState), + return Ok(None); + } }; audit_log!(audit, "uuid_to_name: name <- {:?}", name_res); - // Make sure it's the right type ... - assert!(name_res.is_insensitive_utf8()); + // Make sure it's the right type ... (debug only) + debug_assert!(name_res.is_insensitive_utf8()); - Ok(name_res) + Ok(Some(name_res)) } // From internal, generate an exists event and dispatch @@ -469,9 +479,26 @@ pub trait QueryServerTransaction { } // In the opposite direction, we can resolve values for presentation - fn resolve_value(&self, _value: &Value) -> Result { - // Ok(value.to_proto_string_clone()) - unimplemented!(); + fn resolve_value( + &self, + audit: &mut AuditScope, + value: &Value, + ) -> Result { + // Are we a reference type? Try and resolve. + match value.to_ref_uuid() { + Some(ur) => { + let nv = self.uuid_to_name(audit, ur)?; + return match nv { + Some(v) => Ok(v.to_proto_string_clone()), + None => Ok(value.to_proto_string_clone()), + }; + } + // Fall through + None => {} + }; + + // Not? Okay, do the to string. + Ok(value.to_proto_string_clone()) } } @@ -2397,7 +2424,8 @@ mod tests { audit, &Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(), ); - assert!(r1.is_err()); + // There is nothing. + assert!(r1 == Ok(None)); // Name does exist let r3 = server_txn.uuid_to_name( audit, @@ -2464,6 +2492,62 @@ mod tests { }) } + #[test] + fn test_qs_resolve_value() { + run_test!(|server: &QueryServer, audit: &mut AuditScope| { + let mut server_txn = server.write(); + let e1: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["object", "person"], + "name": ["testperson1"], + "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"], + "description": ["testperson"], + "displayname": ["testperson1"] + } + }"#, + ); + let e_ts: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["tombstone", "object"], + "uuid": ["9557f49c-97a5-4277-a9a5-097d17eb8317"] + } + }"#, + ); + let ce = CreateEvent::new_internal(vec![e1, e_ts]); + let cr = server_txn.create(audit, &ce); + assert!(cr.is_ok()); + + // Resolving most times should yield expected results + let t1 = Value::new_utf8s("teststring"); + let r1 = server_txn.resolve_value(audit, &t1); + assert!(r1 == Ok("teststring".to_string())); + + // Resolve UUID with matching name + let t_uuid = Value::new_refer_s("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(); + let r_uuid = server_txn.resolve_value(audit, &t_uuid); + debug!("{:?}", r_uuid); + assert!(r_uuid == Ok("testperson1".to_string())); + + // Resolve UUID non-exist + let t_uuid_non = Value::new_refer_s("b83e98f0-3d2e-41d2-9796-d8d993289c86").unwrap(); + let r_uuid_non = server_txn.resolve_value(audit, &t_uuid_non); + debug!("{:?}", r_uuid_non); + assert!(r_uuid_non == Ok("b83e98f0-3d2e-41d2-9796-d8d993289c86".to_string())); + + // Resolve UUID to tombstone/recycled (same an non-exst) + let t_uuid_ts = Value::new_refer_s("9557f49c-97a5-4277-a9a5-097d17eb8317").unwrap(); + let r_uuid_ts = server_txn.resolve_value(audit, &t_uuid_ts); + debug!("{:?}", r_uuid_ts); + assert!(r_uuid_ts == Ok("9557f49c-97a5-4277-a9a5-097d17eb8317".to_string())); + }) + } + #[test] fn test_qs_dynamic_schema_class() { run_test!(|server: &QueryServer, audit: &mut AuditScope| { diff --git a/rsidmd/src/lib/value.rs b/rsidmd/src/lib/value.rs index 2dc48e69c..239cdb639 100644 --- a/rsidmd/src/lib/value.rs +++ b/rsidmd/src/lib/value.rs @@ -768,28 +768,6 @@ impl Value { } } - /// Convert to a proto/public value that can be read and consumed. - pub(crate) fn to_proto_string_clone(&self) -> String { - match &self.pv { - PartialValue::Utf8(s) => s.clone(), - PartialValue::Iutf8(s) => s.clone(), - PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(), - PartialValue::Bool(b) => b.to_string(), - PartialValue::Syntax(syn) => syn.to_string(), - PartialValue::Index(it) => it.to_string(), - // TODO: These should be uuid_to_name in server - PartialValue::Refer(u) => u.to_hyphenated_ref().to_string(), - PartialValue::JsonFilt(s) => { - serde_json::to_string(s).expect("A json filter value was corrupted during run-time") - } - PartialValue::Cred(tag) => { - // You can't actually read the credential values because we only display the - // tag to the proto side. The credentials private data is stored seperately. - tag.to_string() - } - } - } - pub fn to_str(&self) -> Option<&str> { match &self.pv { PartialValue::Utf8(s) => Some(s.as_str()), @@ -853,6 +831,28 @@ impl Value { self.pv.clone() } + pub(crate) fn to_proto_string_clone(&self) -> String { + match &self.pv { + PartialValue::Utf8(s) => s.clone(), + PartialValue::Iutf8(s) => s.clone(), + PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(), + PartialValue::Bool(b) => b.to_string(), + PartialValue::Syntax(syn) => syn.to_string(), + PartialValue::Index(it) => it.to_string(), + // In resolve value, we bypass this, but we keep it here for complete + // impl sake. + PartialValue::Refer(u) => u.to_hyphenated_ref().to_string(), + PartialValue::JsonFilt(s) => { + serde_json::to_string(s).expect("A json filter value was corrupted during run-time") + } + PartialValue::Cred(tag) => { + // You can't actually read the credential values because we only display the + // tag to the proto side. The credentials private data is stored seperately. + tag.to_string() + } + } + } + pub fn validate(&self) -> bool { // Validate that extra-data constraints on the type exist and are // valid. IE json filter is really a filter, or cred types have supplemental