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.
This commit is contained in:
Firstyear 2019-09-14 23:37:05 +10:00 committed by GitHub
parent ce2eae3ed3
commit 75ad3ced51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 67 deletions

View file

@ -12,4 +12,6 @@ path = "src/main.rs"
rsidm_client = { path = "../rsidm_client" } rsidm_client = { path = "../rsidm_client" }
rpassword = "0.4" rpassword = "0.4"
structopt = { version = "0.2", default-features = false } structopt = { version = "0.2", default-features = false }
log = "0.4"
env_logger = "0.6"

View file

@ -2,9 +2,14 @@ extern crate structopt;
use rsidm_client::RsidmClient; use rsidm_client::RsidmClient;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
extern crate env_logger;
#[macro_use]
extern crate log;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
struct CommonOpt { struct CommonOpt {
#[structopt(short = "d", long = "debug")]
debug: bool,
#[structopt(short = "H", long = "url")] #[structopt(short = "H", long = "url")]
addr: String, addr: String,
#[structopt(short = "D", long = "name")] #[structopt(short = "D", long = "name")]
@ -26,9 +31,24 @@ enum ClientOpt {
Whoami(CommonOpt), Whoami(CommonOpt),
} }
impl ClientOpt {
fn debug(&self) -> bool {
match self {
ClientOpt::Whoami(copt) => copt.debug,
}
}
}
fn main() { fn main() {
let opt = ClientOpt::from_args(); 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 { match opt {
ClientOpt::Whoami(copt) => { ClientOpt::Whoami(copt) => {
let client = copt.to_client(); let client = copt.to_client();
@ -46,7 +66,8 @@ fn main() {
match client.whoami() { match client.whoami() {
Ok(o_ent) => match o_ent { Ok(o_ent) => match o_ent {
Some((_ent, uat)) => { Some((ent, uat)) => {
debug!("{:?}", ent);
println!("{}", uat); println!("{}", uat);
} }
None => println!("Unauthenticated"), None => println!("Unauthenticated"),

View file

@ -185,9 +185,7 @@ impl Handler<SearchMessage> for QueryServerV1 {
match qs_read.search_ext(&mut audit, &srch) { match qs_read.search_ext(&mut audit, &srch) {
Ok(entries) => { Ok(entries) => {
let sr = SearchResult::new(entries); SearchResult::new(&mut audit, &qs_read, entries).map(|ok_sr| ok_sr.response())
// Now convert to a response, and return
Ok(sr.response())
} }
Err(e) => Err(e), Err(e) => Err(e),
} }
@ -365,8 +363,8 @@ impl Handler<WhoamiMessage> for QueryServerV1 {
1 => { 1 => {
let e = entries.pop().expect("Entry length mismatch!!!"); let e = entries.pop().expect("Entry length mismatch!!!");
// Now convert to a response, and return // Now convert to a response, and return
let wr = WhoamiResult::new(e, uat); WhoamiResult::new(&mut audit, &qs_read, e, uat)
Ok(wr.response()) .map(|ok_wr| ok_wr.response())
} }
// Somehow we matched multiple, which should be impossible. // Somehow we matched multiple, which should be impossible.
_ => Err(OperationError::InvalidState), _ => Err(OperationError::InvalidState),

View file

@ -154,7 +154,7 @@ pub static JSON_IDM_ADMINS_ACP_MANAGE_V1: &'static str = r#"{
"acp_targetscope": [ "acp_targetscope": [
"{\"Pres\":\"class\"}" "{\"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_class": ["person"],
"acp_modify_removedattr": ["class", "displayname", "name", "description"], "acp_modify_removedattr": ["class", "displayname", "name", "description"],
"acp_modify_presentattr": ["class", "displayname", "name", "description"], "acp_modify_presentattr": ["class", "displayname", "name", "description"],

View file

@ -4,7 +4,9 @@ use crate::credential::Credential;
use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved}; use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved};
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid}; use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction}; 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::{IndexType, SyntaxType};
use crate::value::{PartialValue, Value}; use crate::value::{PartialValue, Value};
use rsidm_proto::v1::Entry as ProtoEntry; use rsidm_proto::v1::Entry as ProtoEntry;
@ -1046,18 +1048,23 @@ impl<STATE> Entry<EntryValid, STATE> {
} }
impl Entry<EntryReduced, EntryCommitted> { impl Entry<EntryReduced, EntryCommitted> {
pub fn into_pe(&self) -> ProtoEntry { pub fn into_pe(
&self,
audit: &mut AuditScope,
qs: &QueryServerReadTransaction,
) -> Result<ProtoEntry, OperationError> {
// Turn values -> Strings. // Turn values -> Strings.
ProtoEntry { let attrs: Result<_, _> = self
attrs: self
.attrs .attrs
.iter() .iter()
.map(|(k, vs)| { .map(|(k, vs)| {
let pvs: Vec<_> = vs.iter().map(|v| v.to_proto_string_clone()).collect(); let pvs: Result<Vec<String>, _> =
(k.clone(), pvs) vs.iter().map(|v| qs.resolve_value(audit, v)).collect();
let pvs = pvs?;
Ok((k.clone(), pvs))
}) })
.collect(), .collect();
} Ok(ProtoEntry { attrs: attrs? })
} }
} }

View file

@ -32,18 +32,21 @@ pub struct SearchResult {
} }
impl SearchResult { impl SearchResult {
pub fn new(entries: Vec<Entry<EntryReduced, EntryCommitted>>) -> Self { pub fn new(
SearchResult { audit: &mut AuditScope,
entries: entries qs: &QueryServerReadTransaction,
entries: Vec<Entry<EntryReduced, EntryCommitted>>,
) -> Result<Self, OperationError> {
let entries: Result<_, _> = entries
.iter() .iter()
.map(|e| { .map(|e| {
// All the needed transforms for this result are done // All the needed transforms for this result are done
// in search_ext. This is just an entry -> protoentry // in search_ext. This is just an entry -> protoentry
// step. // step.
e.into_pe() e.into_pe(audit, qs)
}) })
.collect(), .collect();
} Ok(SearchResult { entries: entries? })
} }
// Consume self into a search response // Consume self into a search response
@ -755,11 +758,16 @@ pub struct WhoamiResult {
} }
impl WhoamiResult { impl WhoamiResult {
pub fn new(e: Entry<EntryReduced, EntryCommitted>, uat: UserAuthToken) -> Self { pub fn new(
WhoamiResult { audit: &mut AuditScope,
youare: e.into_pe(), qs: &QueryServerReadTransaction,
e: Entry<EntryReduced, EntryCommitted>,
uat: UserAuthToken,
) -> Result<Self, OperationError> {
Ok(WhoamiResult {
youare: e.into_pe(audit, qs)?,
uat: uat, uat: uat,
} })
} }
pub fn response(self) -> WhoamiResponse { pub fn response(self) -> WhoamiResponse {

View file

@ -197,7 +197,11 @@ pub trait QueryServerTransaction {
Ok(uuid_res) Ok(uuid_res)
} }
fn uuid_to_name(&self, audit: &mut AuditScope, uuid: &Uuid) -> Result<Value, OperationError> { fn uuid_to_name(
&self,
audit: &mut AuditScope,
uuid: &Uuid,
) -> Result<Option<Value>, OperationError> {
// construct the filter // construct the filter
let filt = filter!(f_eq("uuid", PartialValue::new_uuidr(uuid))); let filt = filter!(f_eq("uuid", PartialValue::new_uuidr(uuid)));
audit_log!(audit, "uuid_to_name: uuid -> {:?}", uuid); audit_log!(audit, "uuid_to_name: uuid -> {:?}", uuid);
@ -212,7 +216,8 @@ pub trait QueryServerTransaction {
if res.len() == 0 { if res.len() == 0 {
// If result len == 0, error no such result // 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 { } else if res.len() >= 2 {
// if result len >= 2, error, invaid entry state. // if result len >= 2, error, invaid entry state.
return Err(OperationError::InvalidDBState); return Err(OperationError::InvalidDBState);
@ -224,17 +229,22 @@ pub trait QueryServerTransaction {
let name_res = match e.get_ava(&String::from("name")) { let name_res = match e.get_ava(&String::from("name")) {
Some(vas) => match vas.first() { Some(vas) => match vas.first() {
Some(u) => (*u).clone(), Some(u) => (*u).clone(),
// Name is in an invalid state in the db
None => return Err(OperationError::InvalidEntryState), 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); audit_log!(audit, "uuid_to_name: name <- {:?}", name_res);
// Make sure it's the right type ... // Make sure it's the right type ... (debug only)
assert!(name_res.is_insensitive_utf8()); debug_assert!(name_res.is_insensitive_utf8());
Ok(name_res) Ok(Some(name_res))
} }
// From internal, generate an exists event and dispatch // 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 // In the opposite direction, we can resolve values for presentation
fn resolve_value(&self, _value: &Value) -> Result<String, OperationError> { fn resolve_value(
// Ok(value.to_proto_string_clone()) &self,
unimplemented!(); audit: &mut AuditScope,
value: &Value,
) -> Result<String, OperationError> {
// 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, audit,
&Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(), &Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(),
); );
assert!(r1.is_err()); // There is nothing.
assert!(r1 == Ok(None));
// Name does exist // Name does exist
let r3 = server_txn.uuid_to_name( let r3 = server_txn.uuid_to_name(
audit, 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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] #[test]
fn test_qs_dynamic_schema_class() { fn test_qs_dynamic_schema_class() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| { run_test!(|server: &QueryServer, audit: &mut AuditScope| {

View file

@ -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> { pub fn to_str(&self) -> Option<&str> {
match &self.pv { match &self.pv {
PartialValue::Utf8(s) => Some(s.as_str()), PartialValue::Utf8(s) => Some(s.as_str()),
@ -853,6 +831,28 @@ impl Value {
self.pv.clone() 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 { pub fn validate(&self) -> bool {
// Validate that extra-data constraints on the type exist and are // Validate that extra-data constraints on the type exist and are
// valid. IE json filter is really a filter, or cred types have supplemental // valid. IE json filter is really a filter, or cred types have supplemental