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" }
|
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"
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"],
|
||||||
|
|
|
@ -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? })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue