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" }
rpassword = "0.4"
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 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"),

View file

@ -185,9 +185,7 @@ impl Handler<SearchMessage> 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<WhoamiMessage> 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),

View file

@ -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"],

View file

@ -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<STATE> Entry<EntryValid, STATE> {
}
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.
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<Vec<String>, _> =
vs.iter().map(|v| qs.resolve_value(audit, v)).collect();
let pvs = pvs?;
Ok((k.clone(), pvs))
})
.collect();
Ok(ProtoEntry { attrs: attrs? })
}
}

View file

@ -32,18 +32,21 @@ pub struct SearchResult {
}
impl SearchResult {
pub fn new(entries: Vec<Entry<EntryReduced, EntryCommitted>>) -> 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<Entry<EntryReduced, EntryCommitted>>,
) -> Result<Self, OperationError> {
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<EntryReduced, EntryCommitted>, uat: UserAuthToken) -> Self {
WhoamiResult {
youare: e.into_pe(),
pub fn new(
audit: &mut AuditScope,
qs: &QueryServerReadTransaction,
e: Entry<EntryReduced, EntryCommitted>,
uat: UserAuthToken,
) -> Result<Self, OperationError> {
Ok(WhoamiResult {
youare: e.into_pe(audit, qs)?,
uat: uat,
}
})
}
pub fn response(self) -> WhoamiResponse {

View file

@ -197,7 +197,11 @@ pub trait QueryServerTransaction {
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
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<String, OperationError> {
// Ok(value.to_proto_string_clone())
unimplemented!();
fn resolve_value(
&self,
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,
&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<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]
fn test_qs_dynamic_schema_class() {
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> {
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