mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
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:
parent
ce2eae3ed3
commit
75ad3ced51
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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? })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue