mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
245 ldap compat (#247)
Add's improvements to ldap compatibility. This stabilises DN formats and how they are returned, and adds the name2uuid index to help speed up binds and entry resolves on large queries. Even on the largest queries, this is able to process them rapidly, and the ldap interface now has single operation times of sub 0.001 second even on 100k entry databases with references.
This commit is contained in:
parent
70fa17f3a1
commit
d47d4fed0a
|
@ -14,7 +14,7 @@ static PORT_ALLOC: AtomicUsize = AtomicUsize::new(8080);
|
|||
// Test external behaviours of the service.
|
||||
|
||||
pub fn run_test(test_fn: fn(KanidmClient) -> ()) {
|
||||
// ::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
|
||||
// ::std::env::set_var("RUST_LOG", "actix_web=warn,kanidm=error");
|
||||
let _ = env_logger::builder()
|
||||
.format_timestamp(None)
|
||||
.format_level(false)
|
||||
|
|
|
@ -472,6 +472,7 @@ fn test_default_entries_rbac_admins_schema_entries() {
|
|||
"badlist_password",
|
||||
"loginshell",
|
||||
"unix_password",
|
||||
"nsuniqueid",
|
||||
]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
|
@ -485,7 +486,8 @@ fn test_default_entries_rbac_admins_schema_entries() {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
assert_eq!(default_attributenames, attributenames);
|
||||
// I wonder if this should be a subset op?
|
||||
assert!(default_attributenames.is_subset(&attributenames));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ pub enum OperationError {
|
|||
Backend,
|
||||
NoMatchingEntries,
|
||||
CorruptedEntry(u64),
|
||||
CorruptedIndex(String),
|
||||
ConsistencyError(Vec<Result<(), ConsistencyError>>),
|
||||
SchemaViolation(SchemaError),
|
||||
Plugin(PluginError),
|
||||
|
@ -73,6 +74,7 @@ pub enum OperationError {
|
|||
InvalidAttributeName(String),
|
||||
InvalidAttribute(String),
|
||||
InvalidDBState,
|
||||
InvalidValueState,
|
||||
InvalidEntryID,
|
||||
InvalidRequestState,
|
||||
InvalidState,
|
||||
|
|
|
@ -41,14 +41,14 @@ URL = CONFIG.get('kanidm_client', 'url')
|
|||
AUTH_URL = "%s/v1/auth" % URL
|
||||
|
||||
def _authenticate(s, acct, pw):
|
||||
init_auth = {"step": { "Init": [acct, None]}}
|
||||
init_auth = {"step": { "init": [acct, None]}}
|
||||
|
||||
r = s.post(AUTH_URL, json=init_auth, verify=CA, timeout=TIMEOUT)
|
||||
if r.status_code != 200:
|
||||
print(r.json())
|
||||
raise Exception("AuthInitFailed")
|
||||
|
||||
cred_auth = {"step": { "Creds": [{"Password": pw}]}}
|
||||
cred_auth = {"step": { "creds": [{"Password": pw}]}}
|
||||
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT)
|
||||
if r.status_code != 200:
|
||||
print(r.json())
|
||||
|
|
|
@ -19,7 +19,7 @@ const TESTACCOUNT1_PASSWORD_B: &str = "password b for account1 test";
|
|||
const TESTACCOUNT1_PASSWORD_INC: &str = "never going to work";
|
||||
|
||||
fn run_test(fix_fn: fn(&KanidmClient) -> (), test_fn: fn(CacheLayer, KanidmAsyncClient) -> ()) {
|
||||
// ::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
|
||||
// ::std::env::set_var("RUST_LOG", "actix_web=warn,kanidm=error");
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
||||
|
|
|
@ -473,15 +473,12 @@ impl Handler<InternalRadiusReadMessage> for QueryServerReadV1 {
|
|||
|| {
|
||||
let mut qs_read = self.qs.read();
|
||||
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
// Make an event from the request
|
||||
let srch = match SearchEvent::from_target_uuid_request(
|
||||
|
@ -538,16 +535,13 @@ impl Handler<InternalRadiusTokenReadMessage> for QueryServerReadV1 {
|
|||
|| {
|
||||
let mut idm_read = self.idms.proxy_read();
|
||||
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => idm_read
|
||||
.qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = idm_read
|
||||
.qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
// Make an event from the request
|
||||
let rate = match RadiusAuthTokenEvent::from_parts(
|
||||
|
@ -594,7 +588,7 @@ impl Handler<InternalUnixUserTokenReadMessage> for QueryServerReadV1 {
|
|||
let target_uuid = Uuid::parse_str(msg.uuid_or_name.as_str()).or_else(|_| {
|
||||
idm_read
|
||||
.qs_read
|
||||
.posixid_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving as gidnumber continuing ...");
|
||||
e
|
||||
|
@ -647,7 +641,7 @@ impl Handler<InternalUnixGroupTokenReadMessage> for QueryServerReadV1 {
|
|||
let target_uuid = Uuid::parse_str(msg.uuid_or_name.as_str()).or_else(|_| {
|
||||
idm_read
|
||||
.qs_read
|
||||
.posixid_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving as gidnumber continuing ...");
|
||||
e
|
||||
|
@ -692,15 +686,12 @@ impl Handler<InternalSshKeyReadMessage> for QueryServerReadV1 {
|
|||
|| {
|
||||
let mut qs_read = self.qs.read();
|
||||
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = qs_read
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
// Make an event from the request
|
||||
let srch = match SearchEvent::from_target_uuid_request(
|
||||
|
@ -762,15 +753,12 @@ impl Handler<InternalSshKeyTagReadMessage> for QueryServerReadV1 {
|
|||
|| {
|
||||
let mut qs_read = self.qs.read();
|
||||
|
||||
let target_uuid = match Uuid::parse_str(uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_read
|
||||
.name_to_uuid(&mut audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = qs_read
|
||||
.name_to_uuid(&mut audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
// Make an event from the request
|
||||
let srch = match SearchEvent::from_target_uuid_request(
|
||||
|
@ -836,7 +824,7 @@ impl Handler<IdmAccountUnixAuthMessage> for QueryServerReadV1 {
|
|||
let target_uuid = Uuid::parse_str(msg.uuid_or_name.as_str()).or_else(|_| {
|
||||
idm_write
|
||||
.qs_read
|
||||
.posixid_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving as gidnumber continuing ...");
|
||||
e
|
||||
|
|
|
@ -347,15 +347,12 @@ impl QueryServerWriteV1 {
|
|||
) -> Result<(), OperationError> {
|
||||
let mut qs_write = self.qs.write(duration_from_epoch_now());
|
||||
|
||||
let target_uuid = match Uuid::parse_str(uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_write
|
||||
.name_to_uuid(audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = qs_write
|
||||
.name_to_uuid(audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
let mdf =
|
||||
match ModifyEvent::from_parts(audit, uat, target_uuid, proto_ml, filter, &mut qs_write)
|
||||
|
@ -384,15 +381,12 @@ impl QueryServerWriteV1 {
|
|||
) -> Result<(), OperationError> {
|
||||
let mut qs_write = self.qs.write(duration_from_epoch_now());
|
||||
|
||||
let target_uuid = match Uuid::parse_str(uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_write
|
||||
.name_to_uuid(audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = qs_write
|
||||
.name_to_uuid(audit, uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
let mdf = match ModifyEvent::from_internal_parts(
|
||||
audit,
|
||||
|
@ -615,16 +609,14 @@ impl Handler<InternalCredentialSetMessage> for QueryServerWriteV1 {
|
|||
// We can either do this by trying to parse the name or by creating a filter
|
||||
// to find the entry - there are risks to both TBH ... especially when the uuid
|
||||
// is also an entries name, but that they aren't the same entry.
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => idms_prox_write
|
||||
.qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
|
||||
let target_uuid = idms_prox_write
|
||||
.qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
// What type of auth set did we recieve?
|
||||
match msg.sac {
|
||||
|
@ -777,16 +769,13 @@ impl Handler<InternalRegenerateRadiusMessage> for QueryServerWriteV1 {
|
|||
let mut idms_prox_write = self.idms.proxy_write(ct.clone());
|
||||
idms_prox_write.expire_mfareg_sessions(ct);
|
||||
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => idms_prox_write
|
||||
.qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
let target_uuid = idms_prox_write
|
||||
.qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
let rrse = RegenerateRadiusSecretEvent::from_parts(
|
||||
&mut audit,
|
||||
|
@ -826,15 +815,13 @@ impl Handler<PurgeAttributeMessage> for QueryServerWriteV1 {
|
|||
"actors::v1_write::handle<PurgeAttributeMessage>",
|
||||
|| {
|
||||
let mut qs_write = self.qs.write(duration_from_epoch_now());
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
|
||||
let target_uuid = qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
let mdf = match ModifyEvent::from_target_uuid_attr_purge(
|
||||
&mut audit,
|
||||
|
@ -876,15 +863,13 @@ impl Handler<RemoveAttributeValueMessage> for QueryServerWriteV1 {
|
|||
"actors::v1_write::handle<RemoveAttributeValueMessage>",
|
||||
|| {
|
||||
let mut qs_write = self.qs.write(duration_from_epoch_now());
|
||||
let target_uuid = match Uuid::parse_str(msg.uuid_or_name.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(_) => qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(&mut audit, "Error resolving id to target");
|
||||
e
|
||||
})?,
|
||||
};
|
||||
|
||||
let target_uuid = qs_write
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Error resolving id to target");
|
||||
e
|
||||
})?;
|
||||
|
||||
let proto_ml =
|
||||
ProtoModifyList::new_list(vec![ProtoModify::Removed(msg.attr, msg.value)]);
|
||||
|
@ -1175,7 +1160,7 @@ impl Handler<IdmAccountUnixSetCredMessage> for QueryServerWriteV1 {
|
|||
let target_uuid = Uuid::parse_str(msg.uuid_or_name.as_str()).or_else(|_| {
|
||||
idms_prox_write
|
||||
.qs_write
|
||||
.posixid_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str())
|
||||
.map_err(|e| {
|
||||
ladmin_info!(&mut audit, "Error resolving as gidnumber continuing ...");
|
||||
e
|
||||
|
|
|
@ -47,7 +47,7 @@ macro_rules! audit_log {
|
|||
use crate::audit::LogTag;
|
||||
/*
|
||||
if cfg!(test) || cfg!(debug_assertions) {
|
||||
error!($($arg)*)
|
||||
eprintln!($($arg)*)
|
||||
}
|
||||
*/
|
||||
$audit.log_event(
|
||||
|
@ -61,11 +61,9 @@ macro_rules! audit_log {
|
|||
|
||||
macro_rules! lqueue {
|
||||
($au:expr, $tag:expr, $($arg:tt)*) => ({
|
||||
/*
|
||||
if cfg!(test) || cfg!(debug_assertions) {
|
||||
error!($($arg)*)
|
||||
if cfg!(test) {
|
||||
println!($($arg)*)
|
||||
}
|
||||
*/
|
||||
use std::fmt;
|
||||
use crate::audit::LogTag;
|
||||
$au.log_event(
|
||||
|
@ -79,7 +77,7 @@ macro_rules! lqueue {
|
|||
|
||||
macro_rules! ltrace {
|
||||
($au:expr, $($arg:tt)*) => ({
|
||||
if log_enabled!(log::Level::Debug) {
|
||||
if log_enabled!(log::Level::Debug) || cfg!(test) {
|
||||
lqueue!($au, LogTag::Trace, $($arg)*)
|
||||
}
|
||||
})
|
||||
|
@ -87,7 +85,7 @@ macro_rules! ltrace {
|
|||
|
||||
macro_rules! lfilter {
|
||||
($au:expr, $($arg:tt)*) => ({
|
||||
if log_enabled!(log::Level::Info) {
|
||||
if log_enabled!(log::Level::Info) || cfg!(test) {
|
||||
lqueue!($au, LogTag::Filter, $($arg)*)
|
||||
}
|
||||
})
|
||||
|
@ -115,15 +113,13 @@ macro_rules! ladmin_error {
|
|||
|
||||
macro_rules! ladmin_warning {
|
||||
($au:expr, $($arg:tt)*) => ({
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
lqueue!($au, LogTag::AdminWarning, $($arg)*)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! ladmin_info {
|
||||
($au:expr, $($arg:tt)*) => ({
|
||||
if log_enabled!(log::Level::Info) {
|
||||
if log_enabled!(log::Level::Info) || cfg!(test) {
|
||||
lqueue!($au, LogTag::AdminInfo, $($arg)*)
|
||||
}
|
||||
})
|
||||
|
@ -149,7 +145,7 @@ macro_rules! lsecurity_access {
|
|||
|
||||
macro_rules! lperf_segment {
|
||||
($au:expr, $id:expr, $fun:expr) => {{
|
||||
if log_enabled!(log::Level::Debug) {
|
||||
if log_enabled!(log::Level::Debug) || cfg!(test) {
|
||||
use std::time::Instant;
|
||||
|
||||
// start timer.
|
||||
|
@ -315,7 +311,7 @@ impl PerfProcessed {
|
|||
prefix.push_str("| ");
|
||||
}
|
||||
};
|
||||
debug!(
|
||||
eprintln!(
|
||||
"{}|--> {} {2:.9} {3:.3}%",
|
||||
prefix, self.id, df, self.percent
|
||||
);
|
||||
|
@ -367,19 +363,39 @@ impl AuditScope {
|
|||
|
||||
pub fn write_log(self) {
|
||||
let uuid_ref = self.uuid.to_hyphenated_ref();
|
||||
error!("[- event::start] {}", uuid_ref);
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
eprintln!("[- event::start] {}", uuid_ref);
|
||||
}
|
||||
self.events.iter().for_each(|e| match e.tag {
|
||||
LogTag::AdminError | LogTag::RequestError | LogTag::FilterError => {
|
||||
error!("[{} {}] {}", e.time, e.tag, e.data)
|
||||
LogTag::AdminError => eprintln!("[{} {}] {} {}", e.time, e.tag, uuid_ref, e.data),
|
||||
LogTag::RequestError | LogTag::FilterError => {
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
eprintln!("[{} {}] {}", e.time, e.tag, e.data)
|
||||
}
|
||||
}
|
||||
LogTag::AdminWarning
|
||||
| LogTag::Security
|
||||
| LogTag::SecurityAccess
|
||||
| LogTag::FilterWarning => warn!("[{} {}] {}", e.time, e.tag, e.data),
|
||||
LogTag::AdminInfo | LogTag::Filter => info!("[{} {}] {}", e.time, e.tag, e.data),
|
||||
LogTag::Trace => debug!("[{} {}] {}", e.time, e.tag, e.data),
|
||||
| LogTag::FilterWarning => {
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
eprintln!("[{} {}] {}", e.time, e.tag, e.data)
|
||||
}
|
||||
}
|
||||
LogTag::AdminInfo | LogTag::Filter => {
|
||||
if log_enabled!(log::Level::Info) {
|
||||
eprintln!("[{} {}] {}", e.time, e.tag, e.data)
|
||||
}
|
||||
}
|
||||
LogTag::Trace => {
|
||||
if log_enabled!(log::Level::Debug) {
|
||||
eprintln!("[{} {}] {}", e.time, e.tag, e.data)
|
||||
}
|
||||
}
|
||||
});
|
||||
error!("[- event::end] {}", uuid_ref);
|
||||
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
eprintln!("[- event::end] {}", uuid_ref);
|
||||
}
|
||||
// First, we pre-process all the perf events to order them
|
||||
let mut proc_perf: Vec<_> = self.perf.iter().map(|pe| pe.process()).collect();
|
||||
|
||||
|
@ -390,7 +406,9 @@ impl AuditScope {
|
|||
proc_perf
|
||||
.iter()
|
||||
.for_each(|pe| pe.int_write_fmt(0, &uuid_ref));
|
||||
error!("[- perf::end] {}", uuid_ref);
|
||||
if log_enabled!(log::Level::Debug) {
|
||||
eprintln!("[- perf::trace] end: {}", uuid_ref);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_event(&mut self, tag: LogTag, data: String) {
|
||||
|
|
|
@ -65,6 +65,7 @@ pub enum DbValueV1 {
|
|||
SP(String, String),
|
||||
UI(u32),
|
||||
CI(DbCidV1),
|
||||
NU(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -5,10 +5,12 @@ use crate::be::idl_sqlite::{
|
|||
use crate::be::{IdRawEntry, IDL};
|
||||
use crate::entry::{Entry, EntryCommitted, EntrySealed};
|
||||
use crate::value::IndexType;
|
||||
use crate::value::Value;
|
||||
use concread::cache::arc::{Arc, ArcReadTxn, ArcWriteTxn};
|
||||
use concread::cowcell::*;
|
||||
use idlset::IDLBitRange;
|
||||
use kanidm_proto::v1::{ConsistencyError, OperationError};
|
||||
use std::collections::BTreeSet;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -16,9 +18,24 @@ use uuid::Uuid;
|
|||
|
||||
const DEFAULT_CACHE_TARGET: usize = 10240;
|
||||
const DEFAULT_IDL_CACHE_RATIO: usize = 32;
|
||||
const DEFAULT_NAME_CACHE_RATIO: usize = 8;
|
||||
const DEFAULT_CACHE_RMISS: usize = 8;
|
||||
const DEFAULT_CACHE_WMISS: usize = 8;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
enum NameCacheKey {
|
||||
Name2Uuid(String),
|
||||
Uuid2Rdn(Uuid),
|
||||
Uuid2Spn(Uuid),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum NameCacheValue {
|
||||
U(Uuid),
|
||||
R(String),
|
||||
S(Value),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
struct IdlCacheKey {
|
||||
a: String,
|
||||
|
@ -47,6 +64,7 @@ pub struct IdlArcSqlite {
|
|||
db: IdlSqlite,
|
||||
entry_cache: Arc<u64, Box<Entry<EntrySealed, EntryCommitted>>>,
|
||||
idl_cache: Arc<IdlCacheKey, Box<IDLBitRange>>,
|
||||
name_cache: Arc<NameCacheKey, NameCacheValue>,
|
||||
op_ts_max: CowCell<Option<Duration>>,
|
||||
}
|
||||
|
||||
|
@ -54,12 +72,14 @@ pub struct IdlArcSqliteReadTransaction<'a> {
|
|||
db: IdlSqliteReadTransaction,
|
||||
entry_cache: ArcReadTxn<'a, u64, Box<Entry<EntrySealed, EntryCommitted>>>,
|
||||
idl_cache: ArcReadTxn<'a, IdlCacheKey, Box<IDLBitRange>>,
|
||||
name_cache: ArcReadTxn<'a, NameCacheKey, NameCacheValue>,
|
||||
}
|
||||
|
||||
pub struct IdlArcSqliteWriteTransaction<'a> {
|
||||
db: IdlSqliteWriteTransaction,
|
||||
entry_cache: ArcWriteTxn<'a, u64, Box<Entry<EntrySealed, EntryCommitted>>>,
|
||||
idl_cache: ArcWriteTxn<'a, IdlCacheKey, Box<IDLBitRange>>,
|
||||
name_cache: ArcWriteTxn<'a, NameCacheKey, NameCacheValue>,
|
||||
op_ts_max: CowCellWriteTxn<'a, Option<Duration>>,
|
||||
}
|
||||
|
||||
|
@ -168,6 +188,81 @@ macro_rules! get_idl {
|
|||
}};
|
||||
}
|
||||
|
||||
macro_rules! name2uuid {
|
||||
(
|
||||
$self:expr,
|
||||
$audit:expr,
|
||||
$name:expr
|
||||
) => {{
|
||||
lperf_segment!($audit, "be::idl_arc_sqlite::name2uuid", || {
|
||||
let cache_key = NameCacheKey::Name2Uuid($name.to_string());
|
||||
let cache_r = $self.name_cache.get(&cache_key);
|
||||
if let Some(NameCacheValue::U(uuid)) = cache_r {
|
||||
ltrace!($audit, "Got cached uuid for name2uuid");
|
||||
return Ok(Some(uuid.clone()));
|
||||
}
|
||||
|
||||
let db_r = $self.db.name2uuid($audit, $name)?;
|
||||
if let Some(uuid) = db_r {
|
||||
$self
|
||||
.name_cache
|
||||
.insert(cache_key, NameCacheValue::U(uuid.clone()))
|
||||
}
|
||||
Ok(db_r)
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! uuid2spn {
|
||||
(
|
||||
$self:expr,
|
||||
$audit:expr,
|
||||
$uuid:expr
|
||||
) => {{
|
||||
lperf_segment!($audit, "be::idl_arc_sqlite::name2uuid", || {
|
||||
let cache_key = NameCacheKey::Uuid2Spn(*$uuid);
|
||||
let cache_r = $self.name_cache.get(&cache_key);
|
||||
if let Some(NameCacheValue::S(ref spn)) = cache_r {
|
||||
ltrace!($audit, "Got cached spn for uuid2spn");
|
||||
return Ok(Some(spn.clone()));
|
||||
}
|
||||
|
||||
let db_r = $self.db.uuid2spn($audit, $uuid)?;
|
||||
if let Some(ref data) = db_r {
|
||||
$self
|
||||
.name_cache
|
||||
.insert(cache_key, NameCacheValue::S(data.clone()))
|
||||
}
|
||||
Ok(db_r)
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! uuid2rdn {
|
||||
(
|
||||
$self:expr,
|
||||
$audit:expr,
|
||||
$uuid:expr
|
||||
) => {{
|
||||
lperf_segment!($audit, "be::idl_arc_sqlite::name2uuid", || {
|
||||
let cache_key = NameCacheKey::Uuid2Rdn(*$uuid);
|
||||
let cache_r = $self.name_cache.get(&cache_key);
|
||||
if let Some(NameCacheValue::R(ref rdn)) = cache_r {
|
||||
ltrace!($audit, "Got cached rdn for uuid2rdn");
|
||||
return Ok(Some(rdn.clone()));
|
||||
}
|
||||
|
||||
let db_r = $self.db.uuid2rdn($audit, $uuid)?;
|
||||
if let Some(ref data) = db_r {
|
||||
$self
|
||||
.name_cache
|
||||
.insert(cache_key, NameCacheValue::R(data.clone()))
|
||||
}
|
||||
Ok(db_r)
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
pub trait IdlArcSqliteTransaction {
|
||||
fn get_identry(
|
||||
&mut self,
|
||||
|
@ -201,6 +296,24 @@ pub trait IdlArcSqliteTransaction {
|
|||
fn get_db_d_uuid(&self) -> Result<Option<Uuid>, OperationError>;
|
||||
|
||||
fn verify(&self) -> Vec<Result<(), ConsistencyError>>;
|
||||
|
||||
fn name2uuid(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Option<Uuid>, OperationError>;
|
||||
|
||||
fn uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError>;
|
||||
|
||||
fn uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<String>, OperationError>;
|
||||
}
|
||||
|
||||
impl<'a> IdlArcSqliteTransaction for IdlArcSqliteReadTransaction<'a> {
|
||||
|
@ -250,6 +363,30 @@ impl<'a> IdlArcSqliteTransaction for IdlArcSqliteReadTransaction<'a> {
|
|||
fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
|
||||
self.db.verify()
|
||||
}
|
||||
|
||||
fn name2uuid(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Option<Uuid>, OperationError> {
|
||||
name2uuid!(self, audit, name)
|
||||
}
|
||||
|
||||
fn uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError> {
|
||||
uuid2spn!(self, audit, uuid)
|
||||
}
|
||||
|
||||
fn uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<String>, OperationError> {
|
||||
uuid2rdn!(self, audit, uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IdlArcSqliteTransaction for IdlArcSqliteWriteTransaction<'a> {
|
||||
|
@ -299,6 +436,30 @@ impl<'a> IdlArcSqliteTransaction for IdlArcSqliteWriteTransaction<'a> {
|
|||
fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
|
||||
self.db.verify()
|
||||
}
|
||||
|
||||
fn name2uuid(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Option<Uuid>, OperationError> {
|
||||
name2uuid!(self, audit, name)
|
||||
}
|
||||
|
||||
fn uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError> {
|
||||
uuid2spn!(self, audit, uuid)
|
||||
}
|
||||
|
||||
fn uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<String>, OperationError> {
|
||||
uuid2rdn!(self, audit, uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
||||
|
@ -308,11 +469,13 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
|||
db,
|
||||
entry_cache,
|
||||
idl_cache,
|
||||
name_cache,
|
||||
op_ts_max,
|
||||
} = self;
|
||||
// Undo the caches in the reverse order.
|
||||
db.commit(audit).and_then(|r| {
|
||||
op_ts_max.commit();
|
||||
name_cache.commit();
|
||||
idl_cache.commit();
|
||||
entry_cache.commit();
|
||||
Ok(r)
|
||||
|
@ -407,8 +570,88 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
|||
self.db.create_name2uuid(audit)
|
||||
}
|
||||
|
||||
pub fn create_uuid2name(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
self.db.create_uuid2name(audit)
|
||||
pub fn write_name2uuid_add(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
add: BTreeSet<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
lperf_segment!(audit, "be::idl_arc_sqlite::write_name2uuid_add", || {
|
||||
self.db
|
||||
.write_name2uuid_add(audit, uuid, &add)
|
||||
.and_then(|_| {
|
||||
add.into_iter().for_each(|k| {
|
||||
let cache_key = NameCacheKey::Name2Uuid(k);
|
||||
let cache_value = NameCacheValue::U(*uuid);
|
||||
self.name_cache.insert(cache_key, cache_value)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_name2uuid_rem(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
rem: BTreeSet<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
lperf_segment!(audit, "be::idl_arc_sqlite::write_name2uuid_add", || {
|
||||
self.db.write_name2uuid_rem(audit, &rem).and_then(|_| {
|
||||
rem.into_iter().for_each(|k| {
|
||||
let cache_key = NameCacheKey::Name2Uuid(k);
|
||||
self.name_cache.remove(cache_key)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_uuid2spn(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
self.db.create_uuid2spn(audit)
|
||||
}
|
||||
|
||||
pub fn write_uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
k: Option<Value>,
|
||||
) -> Result<(), OperationError> {
|
||||
lperf_segment!(audit, "be::idl_arc_sqlite::write_uuid2spn", || {
|
||||
self.db
|
||||
.write_uuid2spn(audit, uuid, k.as_ref())
|
||||
.and_then(|_| {
|
||||
let cache_key = NameCacheKey::Uuid2Spn(uuid.clone());
|
||||
match k {
|
||||
Some(v) => self.name_cache.insert(cache_key, NameCacheValue::S(v)),
|
||||
None => self.name_cache.remove(cache_key),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_uuid2rdn(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
self.db.create_uuid2rdn(audit)
|
||||
}
|
||||
|
||||
pub fn write_uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
k: Option<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
lperf_segment!(audit, "be::idl_arc_sqlite::write_uuid2rdn", || {
|
||||
self.db
|
||||
.write_uuid2rdn(audit, uuid, k.as_ref())
|
||||
.and_then(|_| {
|
||||
let cache_key = NameCacheKey::Uuid2Rdn(uuid.clone());
|
||||
match k {
|
||||
Some(s) => self.name_cache.insert(cache_key, NameCacheValue::R(s)),
|
||||
None => self.name_cache.remove(cache_key),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_idx(
|
||||
|
@ -491,12 +734,20 @@ impl IdlArcSqlite {
|
|||
DEFAULT_CACHE_WMISS,
|
||||
);
|
||||
|
||||
let name_cache = Arc::new(
|
||||
DEFAULT_CACHE_TARGET * DEFAULT_NAME_CACHE_RATIO,
|
||||
pool_size as usize,
|
||||
DEFAULT_CACHE_RMISS,
|
||||
DEFAULT_CACHE_WMISS,
|
||||
);
|
||||
|
||||
let op_ts_max = CowCell::new(None);
|
||||
|
||||
Ok(IdlArcSqlite {
|
||||
db,
|
||||
entry_cache,
|
||||
idl_cache,
|
||||
name_cache,
|
||||
op_ts_max,
|
||||
})
|
||||
}
|
||||
|
@ -505,11 +756,13 @@ impl IdlArcSqlite {
|
|||
// IMPORTANT! Always take entrycache FIRST
|
||||
let entry_cache_read = self.entry_cache.read();
|
||||
let idl_cache_read = self.idl_cache.read();
|
||||
let name_cache_read = self.name_cache.read();
|
||||
let db_read = self.db.read();
|
||||
IdlArcSqliteReadTransaction {
|
||||
db: db_read,
|
||||
entry_cache: entry_cache_read,
|
||||
idl_cache: idl_cache_read,
|
||||
name_cache: name_cache_read,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,12 +770,14 @@ impl IdlArcSqlite {
|
|||
// IMPORTANT! Always take entrycache FIRST
|
||||
let entry_cache_write = self.entry_cache.write();
|
||||
let idl_cache_write = self.idl_cache.write();
|
||||
let name_cache_write = self.name_cache.write();
|
||||
let op_ts_max_write = self.op_ts_max.write();
|
||||
let db_write = self.db.write();
|
||||
IdlArcSqliteWriteTransaction {
|
||||
db: db_write,
|
||||
entry_cache: entry_cache_write,
|
||||
idl_cache: idl_cache_write,
|
||||
name_cache: name_cache_write,
|
||||
op_ts_max: op_ts_max_write,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::audit::AuditScope;
|
||||
use crate::be::{IdRawEntry, IDL};
|
||||
use crate::entry::{Entry, EntryCommitted, EntrySealed};
|
||||
use crate::value::IndexType;
|
||||
use crate::value::{IndexType, Value};
|
||||
use idlset::IDLBitRange;
|
||||
use kanidm_proto::v1::{ConsistencyError, OperationError};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::OptionalExtension;
|
||||
use rusqlite::NO_PARAMS;
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
@ -137,6 +138,13 @@ pub trait IdlSqliteTransaction {
|
|||
// I have now is probably really bad :(
|
||||
let mut results = Vec::new();
|
||||
|
||||
// TODO: Can this actually just load in a single select?
|
||||
/*
|
||||
let decompressed: Result<Vec<i64>, _> = idli.into_iter()
|
||||
.map(|u| i64::try_from(u).map_err(|_| OperationError::InvalidEntryID))
|
||||
.collect();
|
||||
*/
|
||||
|
||||
for id in idli {
|
||||
let iid = i64::try_from(id).map_err(|_| OperationError::InvalidEntryID)?;
|
||||
let id2entry_iter = stmt
|
||||
|
@ -246,15 +254,106 @@ pub trait IdlSqliteTransaction {
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
fn get_name2uuid(&self, name: &str) -> Result<Uuid, OperationError> {
|
||||
unimplemented!();
|
||||
fn name2uuid(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Option<Uuid>, OperationError> {
|
||||
lperf_segment!(audit, "be::idl_sqlite::name2uuid", || {
|
||||
// The table exists - lets now get the actual index itself.
|
||||
let mut stmt = try_audit!(
|
||||
audit,
|
||||
self.get_conn()
|
||||
.prepare("SELECT uuid FROM idx_name2uuid WHERE name = :name",),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
let uuid_raw: Option<String> = try_audit!(
|
||||
audit,
|
||||
stmt.query_row_named(&[(":name", &name)], |row| row.get(0))
|
||||
// We don't mind if it doesn't exist
|
||||
.optional(),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
|
||||
let uuid = uuid_raw.as_ref().and_then(|u| Uuid::parse_str(u).ok());
|
||||
ltrace!(audit, "Got uuid for index name {} -> {:?}", name, uuid);
|
||||
|
||||
Ok(uuid)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_uuid2name(&self, uuid: &Uuid) -> Result<String, OperationError> {
|
||||
unimplemented!();
|
||||
fn uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError> {
|
||||
lperf_segment!(audit, "be::idl_sqlite::uuid2spn", || {
|
||||
let uuids = uuid.to_hyphenated_ref().to_string();
|
||||
// The table exists - lets now get the actual index itself.
|
||||
let mut stmt = try_audit!(
|
||||
audit,
|
||||
self.get_conn()
|
||||
.prepare("SELECT spn FROM idx_uuid2spn WHERE uuid = :uuid",),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
let spn_raw: Option<Vec<u8>> = try_audit!(
|
||||
audit,
|
||||
stmt.query_row_named(&[(":uuid", &uuids)], |row| row.get(0))
|
||||
// We don't mind if it doesn't exist
|
||||
.optional(),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
|
||||
let spn: Option<Value> = match spn_raw {
|
||||
Some(d) => {
|
||||
let dbv = serde_cbor::from_slice(d.as_slice())
|
||||
.map_err(|_| OperationError::SerdeCborError)?;
|
||||
let spn = Value::from_db_valuev1(dbv)
|
||||
.map_err(|_| OperationError::CorruptedIndex("uuid2spn".to_string()))?;
|
||||
Some(spn)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
ltrace!(audit, "Got spn for uuid {:?} -> {:?}", uuid, spn);
|
||||
|
||||
Ok(spn)
|
||||
})
|
||||
}
|
||||
|
||||
fn uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<String>, OperationError> {
|
||||
lperf_segment!(audit, "be::idl_sqlite::uuid2rdn", || {
|
||||
let uuids = uuid.to_hyphenated_ref().to_string();
|
||||
// The table exists - lets now get the actual index itself.
|
||||
let mut stmt = try_audit!(
|
||||
audit,
|
||||
self.get_conn()
|
||||
.prepare("SELECT rdn FROM idx_uuid2rdn WHERE uuid = :uuid",),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
let rdn: Option<String> = try_audit!(
|
||||
audit,
|
||||
stmt.query_row_named(&[(":uuid", &uuids)], |row| row.get(0))
|
||||
// We don't mind if it doesn't exist
|
||||
.optional(),
|
||||
"SQLite Error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
|
||||
ltrace!(audit, "Got rdn for uuid {:?} -> {:?}", uuid, rdn);
|
||||
|
||||
Ok(rdn)
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
fn get_db_s_uuid(&self) -> Result<Option<Uuid>, OperationError> {
|
||||
// Try to get a value.
|
||||
|
@ -604,11 +703,52 @@ impl IdlSqliteWriteTransaction {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_uuid2name(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
pub fn write_name2uuid_add(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
add: &BTreeSet<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
let uuids = uuid.to_hyphenated_ref().to_string();
|
||||
|
||||
add.iter().try_for_each(|k| {
|
||||
self.conn
|
||||
.execute_named(
|
||||
"INSERT OR REPLACE INTO idx_name2uuid (name, uuid) VALUES(:name, :uuid)",
|
||||
&[(":name", &k), (":uuid", &uuids)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_name2uuid_rem(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
rem: &BTreeSet<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
rem.iter().try_for_each(|k| {
|
||||
self.conn
|
||||
.execute_named(
|
||||
"DELETE FROM idx_name2uuid WHERE name = :name",
|
||||
&[(":name", &k)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_uuid2spn(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
try_audit!(
|
||||
audit,
|
||||
self.conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS idx_uuid2name (uuid TEXT PRIMARY KEY, name TEXT)",
|
||||
"CREATE TABLE IF NOT EXISTS idx_uuid2spn (uuid TEXT PRIMARY KEY, spn BLOB)",
|
||||
NO_PARAMS
|
||||
),
|
||||
"sqlite error {:?}",
|
||||
|
@ -617,6 +757,89 @@ impl IdlSqliteWriteTransaction {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_uuid2spn(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
k: Option<&Value>,
|
||||
) -> Result<(), OperationError> {
|
||||
let uuids = uuid.to_hyphenated_ref().to_string();
|
||||
match k {
|
||||
Some(k) => {
|
||||
let dbv1 = k.to_db_valuev1();
|
||||
let data =
|
||||
serde_cbor::to_vec(&dbv1).map_err(|_e| OperationError::SerdeCborError)?;
|
||||
self.conn
|
||||
.execute_named(
|
||||
"INSERT OR REPLACE INTO idx_uuid2spn (uuid, spn) VALUES(:uuid, :spn)",
|
||||
&[(":uuid", &uuids), (":spn", &data)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
})
|
||||
}
|
||||
None => self
|
||||
.conn
|
||||
.execute_named(
|
||||
"DELETE FROM idx_uuid2spn WHERE uuid = :uuid",
|
||||
&[(":uuid", &uuids)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_uuid2rdn(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
|
||||
try_audit!(
|
||||
audit,
|
||||
self.conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS idx_uuid2rdn (uuid TEXT PRIMARY KEY, rdn TEXT)",
|
||||
NO_PARAMS
|
||||
),
|
||||
"sqlite error {:?}",
|
||||
OperationError::SQLiteError
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_uuid2rdn(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
k: Option<&String>,
|
||||
) -> Result<(), OperationError> {
|
||||
let uuids = uuid.to_hyphenated_ref().to_string();
|
||||
match k {
|
||||
Some(k) => self
|
||||
.conn
|
||||
.execute_named(
|
||||
"INSERT OR REPLACE INTO idx_uuid2rdn (uuid, rdn) VALUES(:uuid, :rdn)",
|
||||
&[(":uuid", &uuids), (":rdn", &k)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
}),
|
||||
None => self
|
||||
.conn
|
||||
.execute_named(
|
||||
"DELETE FROM idx_uuid2rdn WHERE uuid = :uuid",
|
||||
&[(":uuid", &uuids)],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "SQLite Error {:?}", e);
|
||||
OperationError::SQLiteError
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_idx(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
|
@ -884,7 +1107,7 @@ impl IdlSqliteWriteTransaction {
|
|||
OperationError::SQLiteError
|
||||
);
|
||||
dbv_id2entry = 1;
|
||||
ltrace!(
|
||||
ladmin_info!(
|
||||
audit,
|
||||
"dbv_id2entry migrated (id2entry, db_sid) -> {}",
|
||||
dbv_id2entry
|
||||
|
@ -906,7 +1129,7 @@ impl IdlSqliteWriteTransaction {
|
|||
OperationError::SQLiteError
|
||||
);
|
||||
dbv_id2entry = 2;
|
||||
ltrace!(audit, "dbv_id2entry migrated (db_did) -> {}", dbv_id2entry);
|
||||
ladmin_info!(audit, "dbv_id2entry migrated (db_did) -> {}", dbv_id2entry);
|
||||
}
|
||||
// * if v2 -> add the op max ts table.
|
||||
if dbv_id2entry == 2 {
|
||||
|
@ -924,13 +1147,25 @@ impl IdlSqliteWriteTransaction {
|
|||
OperationError::SQLiteError
|
||||
);
|
||||
dbv_id2entry = 3;
|
||||
ltrace!(
|
||||
ladmin_info!(
|
||||
audit,
|
||||
"dbv_id2entry migrated (db_op_ts) -> {}",
|
||||
dbv_id2entry
|
||||
);
|
||||
}
|
||||
// * if v3 -> complete.
|
||||
if dbv_id2entry == 3 {
|
||||
self.create_name2uuid(audit)
|
||||
.and_then(|_| self.create_uuid2spn(audit))
|
||||
.and_then(|_| self.create_uuid2rdn(audit))?;
|
||||
dbv_id2entry = 4;
|
||||
ladmin_info!(
|
||||
audit,
|
||||
"dbv_id2entry migrated (name2uuid, uuid2spn, uuid2rdn) -> {}",
|
||||
dbv_id2entry
|
||||
);
|
||||
}
|
||||
// * if v4 -> complete.
|
||||
|
||||
try_audit!(
|
||||
audit,
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::audit::AuditScope;
|
|||
use crate::be::dbentry::DbEntry;
|
||||
use crate::entry::{Entry, EntryCommitted, EntryNew, EntrySealed};
|
||||
use crate::filter::{Filter, FilterPlan, FilterResolved, FilterValidResolved};
|
||||
use crate::value::Value;
|
||||
use idlset::AndNot;
|
||||
use idlset::IDLBitRange;
|
||||
use kanidm_proto::v1::{ConsistencyError, OperationError};
|
||||
|
@ -582,6 +583,30 @@ pub trait BackendTransaction {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name2uuid(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Option<Uuid>, OperationError> {
|
||||
self.get_idlayer().name2uuid(audit, name)
|
||||
}
|
||||
|
||||
fn uuid2spn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError> {
|
||||
self.get_idlayer().uuid2spn(audit, uuid)
|
||||
}
|
||||
|
||||
fn uuid2rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<String>, OperationError> {
|
||||
self.get_idlayer().uuid2rdn(audit, uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BackendTransaction for BackendReadTransaction<'a> {
|
||||
|
@ -740,26 +765,114 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
pre: Option<&Entry<EntrySealed, EntryCommitted>>,
|
||||
post: Option<&Entry<EntrySealed, EntryCommitted>>,
|
||||
) -> Result<(), OperationError> {
|
||||
let e_id = match (pre, post) {
|
||||
let (e_uuid, e_id, uuid_same) = match (pre, post) {
|
||||
(None, None) => {
|
||||
ltrace!(audit, "Invalid call to entry_index - no entries provided");
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
(Some(pre), None) => {
|
||||
ltrace!(audit, "Attempting to remove indexes");
|
||||
pre.get_id()
|
||||
ltrace!(audit, "Attempting to remove entry indexes");
|
||||
(pre.get_uuid(), pre.get_id(), true)
|
||||
}
|
||||
(None, Some(post)) => {
|
||||
ltrace!(audit, "Attempting to update indexes");
|
||||
post.get_id()
|
||||
ltrace!(audit, "Attempting to create entry indexes");
|
||||
(post.get_uuid(), post.get_id(), true)
|
||||
}
|
||||
(Some(pre), Some(post)) => {
|
||||
ltrace!(audit, "Attempting to modify indexes");
|
||||
ltrace!(audit, "Attempting to modify entry indexes");
|
||||
assert!(pre.get_id() == post.get_id());
|
||||
post.get_id()
|
||||
(
|
||||
post.get_uuid(),
|
||||
post.get_id(),
|
||||
pre.get_uuid() == post.get_uuid(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Update the names/uuid maps. These have to mask out entries
|
||||
// that are recycled or tombstones, so these pretend as "deleted"
|
||||
// and can trigger correct actions.
|
||||
//
|
||||
|
||||
let mask_pre = pre.and_then(|e| e.mask_recycled_ts());
|
||||
let mask_pre = if !uuid_same {
|
||||
// Okay, so if the uuids are different this is probably from
|
||||
// a replication conflict. We can't just use the normal none/some
|
||||
// check from the Entry::idx functions as they only yield partial
|
||||
// changes. Because the uuid is changing, we have to treat pre
|
||||
// as a deleting entry, regardless of what state post is in.
|
||||
let uuid = mask_pre
|
||||
.map(|e| e.get_uuid())
|
||||
.expect("Not possible to fail");
|
||||
let (n2u_add, n2u_rem) = Entry::idx_name2uuid_diff(mask_pre, None);
|
||||
// There will never be content to add.
|
||||
assert!(n2u_add.is_none());
|
||||
|
||||
let u2s_act = Entry::idx_uuid2spn_diff(mask_pre, None);
|
||||
let u2r_act = Entry::idx_uuid2rdn_diff(mask_pre, None);
|
||||
|
||||
ltrace!(audit, "!uuid_same n2u_rem -> {:?}", n2u_rem);
|
||||
ltrace!(audit, "!uuid_same u2s_act -> {:?}", u2s_act);
|
||||
ltrace!(audit, "!uuid_same u2r_act -> {:?}", u2r_act);
|
||||
|
||||
// Write the changes out to the backend
|
||||
match n2u_rem {
|
||||
Some(rem) => self.idlayer.write_name2uuid_rem(audit, rem)?,
|
||||
None => {}
|
||||
}
|
||||
|
||||
match u2s_act {
|
||||
None => {}
|
||||
Some(Ok(k)) => self.idlayer.write_uuid2spn(audit, uuid, Some(k))?,
|
||||
Some(Err(_)) => self.idlayer.write_uuid2spn(audit, uuid, None)?,
|
||||
}
|
||||
|
||||
match u2r_act {
|
||||
None => {}
|
||||
Some(Ok(k)) => self.idlayer.write_uuid2rdn(audit, uuid, Some(k))?,
|
||||
Some(Err(_)) => self.idlayer.write_uuid2rdn(audit, uuid, None)?,
|
||||
}
|
||||
// Return none, mask_pre is now completed.
|
||||
None
|
||||
} else {
|
||||
// Return the state.
|
||||
mask_pre
|
||||
};
|
||||
|
||||
let mask_post = post.and_then(|e| e.mask_recycled_ts());
|
||||
let (n2u_add, n2u_rem) = Entry::idx_name2uuid_diff(mask_pre, mask_post);
|
||||
|
||||
let u2s_act = Entry::idx_uuid2spn_diff(mask_pre, mask_post);
|
||||
let u2r_act = Entry::idx_uuid2rdn_diff(mask_pre, mask_post);
|
||||
|
||||
ltrace!(audit, "n2u_add -> {:?}", n2u_add);
|
||||
ltrace!(audit, "n2u_rem -> {:?}", n2u_rem);
|
||||
ltrace!(audit, "u2s_act -> {:?}", u2s_act);
|
||||
ltrace!(audit, "u2r_act -> {:?}", u2r_act);
|
||||
|
||||
// Write the changes out to the backend
|
||||
match n2u_add {
|
||||
Some(add) => self.idlayer.write_name2uuid_add(audit, e_uuid, add)?,
|
||||
None => {}
|
||||
}
|
||||
|
||||
match n2u_rem {
|
||||
Some(rem) => self.idlayer.write_name2uuid_rem(audit, rem)?,
|
||||
None => {}
|
||||
}
|
||||
|
||||
match u2s_act {
|
||||
None => {}
|
||||
Some(Ok(k)) => self.idlayer.write_uuid2spn(audit, e_uuid, Some(k))?,
|
||||
Some(Err(_)) => self.idlayer.write_uuid2spn(audit, e_uuid, None)?,
|
||||
}
|
||||
|
||||
match u2r_act {
|
||||
None => {}
|
||||
Some(Ok(k)) => self.idlayer.write_uuid2rdn(audit, e_uuid, Some(k))?,
|
||||
Some(Err(_)) => self.idlayer.write_uuid2rdn(audit, e_uuid, None)?,
|
||||
}
|
||||
|
||||
// Extremely Cursed - Okay, we know that self.idxmeta will NOT be changed
|
||||
// in this function, but we need to borrow self as mut for the caches in
|
||||
// get_idl to work. As a result, this causes a double borrow. To work around
|
||||
|
@ -845,8 +958,11 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
ltrace!(audit, "Creating index -> name2uuid");
|
||||
self.idlayer.create_name2uuid(audit)?;
|
||||
|
||||
ltrace!(audit, "Creating index -> uuid2name");
|
||||
self.idlayer.create_uuid2name(audit)?;
|
||||
ltrace!(audit, "Creating index -> uuid2spn");
|
||||
self.idlayer.create_uuid2spn(audit)?;
|
||||
|
||||
ltrace!(audit, "Creating index -> uuid2rdn");
|
||||
self.idlayer.create_uuid2rdn(audit)?;
|
||||
|
||||
self.idxmeta
|
||||
.iter()
|
||||
|
@ -861,7 +977,9 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
let dbv = self.get_db_index_version();
|
||||
ladmin_info!(audit, "upgrade_reindex -> dbv: {} v: {}", dbv, v);
|
||||
if dbv < v {
|
||||
eprintln!("NOTICE: A system reindex is required. This may take a long time ...");
|
||||
self.reindex(audit)?;
|
||||
eprintln!("NOTICE: System reindex complete");
|
||||
self.set_db_index_version(v)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -881,14 +999,23 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
let idl = IDL::ALLIDS;
|
||||
let entries = try_audit!(audit, self.idlayer.get_identry(audit, &idl));
|
||||
|
||||
// WHEN do we update name2uuid and uuid2name?
|
||||
// Do they become attrs of the idx_cache? Should that be a struct?
|
||||
let mut count = 0;
|
||||
|
||||
try_audit!(
|
||||
audit,
|
||||
entries
|
||||
.iter()
|
||||
.try_for_each(|e| self.entry_index(audit, None, Some(e)))
|
||||
.try_for_each(|e| {
|
||||
count += 1;
|
||||
if count % 1000 == 0 {
|
||||
eprint!("{}", count);
|
||||
} else if count % 100 == 0 {
|
||||
eprint!(".");
|
||||
}
|
||||
self.entry_index(audit, None, Some(e))
|
||||
})
|
||||
);
|
||||
eprintln!(" ✅");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1121,11 +1248,11 @@ impl Backend {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use idlset::IDLBitRange;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::iter::FromIterator;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::super::audit::AuditScope;
|
||||
use super::super::entry::{Entry, EntryInit, EntryNew};
|
||||
|
@ -1157,7 +1284,6 @@ mod tests {
|
|||
idxmeta.insert(("tb".to_string(), IndexType::EQUALITY));
|
||||
let mut be_txn = be.write(&idxmeta);
|
||||
|
||||
// Could wrap another future here for the future::ok bit...
|
||||
let r = $test_fn(&mut audit, &mut be_txn);
|
||||
// Commit, to guarantee it worked.
|
||||
assert!(be_txn.commit(&mut audit).is_ok());
|
||||
|
@ -1479,14 +1605,13 @@ mod tests {
|
|||
fn test_be_reindex_data() {
|
||||
run_test!(|audit: &mut AuditScope, be: &mut BackendWriteTransaction| {
|
||||
// Add some test data?
|
||||
// TODO: Test reindex duplicate eq?
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("name", &Value::from("william"));
|
||||
e1.add_ava("name", &Value::new_iname_s("william"));
|
||||
e1.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
|
||||
let e1 = unsafe { e1.into_sealed_new() };
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("name", &Value::from("claire"));
|
||||
e2.add_ava("name", &Value::new_iname_s("claire"));
|
||||
e2.add_ava("uuid", &Value::from("bd651620-00dd-426b-aaa0-4494f7b7906f"));
|
||||
let e2 = unsafe { e2.into_sealed_new() };
|
||||
|
||||
|
@ -1588,7 +1713,18 @@ mod tests {
|
|||
assert_eq!(uuid_p_idl, None);
|
||||
|
||||
// Check name2uuid
|
||||
// check uuid2name
|
||||
let claire_uuid = Uuid::parse_str("bd651620-00dd-426b-aaa0-4494f7b7906f").unwrap();
|
||||
let william_uuid = Uuid::parse_str("db237e8a-0079-4b8c-8a56-593b22aa44d1").unwrap();
|
||||
|
||||
assert!(be.name2uuid(audit, "claire") == Ok(Some(claire_uuid)));
|
||||
assert!(be.name2uuid(audit, "william") == Ok(Some(william_uuid)));
|
||||
assert!(be.name2uuid(audit, "db237e8a-0079-4b8c-8a56-593b22aa44d1") == Ok(None));
|
||||
// check uuid2spn
|
||||
assert!(be.uuid2spn(audit, &claire_uuid) == Ok(Some(Value::new_iname_s("claire"))));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(Some(Value::new_iname_s("william"))));
|
||||
// check uuid2rdn
|
||||
assert!(be.uuid2rdn(audit, &claire_uuid) == Ok(Some("name=claire".to_string())));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(Some("name=william".to_string())));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1628,6 +1764,11 @@ mod tests {
|
|||
|
||||
idl_state!(audit, be, "uuid", IndexType::PRESENCE, "_", Some(vec![1]));
|
||||
|
||||
let william_uuid = Uuid::parse_str("db237e8a-0079-4b8c-8a56-593b22aa44d1").unwrap();
|
||||
assert!(be.name2uuid(audit, "william") == Ok(Some(william_uuid)));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(Some(Value::from("william"))));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(Some("name=william".to_string())));
|
||||
|
||||
// == Now we delete, and assert we removed the items.
|
||||
be.delete(audit, &rset).unwrap();
|
||||
|
||||
|
@ -1666,6 +1807,10 @@ mod tests {
|
|||
"_",
|
||||
Some(Vec::new())
|
||||
);
|
||||
|
||||
assert!(be.name2uuid(audit, "william") == Ok(None));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(None));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(None));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1721,6 +1866,22 @@ mod tests {
|
|||
);
|
||||
|
||||
idl_state!(audit, be, "uuid", IndexType::PRESENCE, "_", Some(vec![2]));
|
||||
|
||||
let claire_uuid = Uuid::parse_str("bd651620-00dd-426b-aaa0-4494f7b7906f").unwrap();
|
||||
let william_uuid = Uuid::parse_str("db237e8a-0079-4b8c-8a56-593b22aa44d1").unwrap();
|
||||
let lucy_uuid = Uuid::parse_str("7b23c99d-c06b-4a9a-a958-3afa56383e1d").unwrap();
|
||||
|
||||
assert!(be.name2uuid(audit, "claire") == Ok(Some(claire_uuid)));
|
||||
assert!(be.uuid2spn(audit, &claire_uuid) == Ok(Some(Value::from("claire"))));
|
||||
assert!(be.uuid2rdn(audit, &claire_uuid) == Ok(Some("name=claire".to_string())));
|
||||
|
||||
assert!(be.name2uuid(audit, "william") == Ok(None));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(None));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(None));
|
||||
|
||||
assert!(be.name2uuid(audit, "lucy") == Ok(None));
|
||||
assert!(be.uuid2spn(audit, &lucy_uuid) == Ok(None));
|
||||
assert!(be.uuid2rdn(audit, &lucy_uuid) == Ok(None));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1767,6 +1928,13 @@ mod tests {
|
|||
idl_state!(audit, be, "tb", IndexType::EQUALITY, "test", Some(vec![1]));
|
||||
|
||||
idl_state!(audit, be, "ta", IndexType::EQUALITY, "test", Some(vec![]));
|
||||
|
||||
// let claire_uuid = Uuid::parse_str("bd651620-00dd-426b-aaa0-4494f7b7906f").unwrap();
|
||||
let william_uuid = Uuid::parse_str("db237e8a-0079-4b8c-8a56-593b22aa44d1").unwrap();
|
||||
assert!(be.name2uuid(audit, "william") == Ok(None));
|
||||
assert!(be.name2uuid(audit, "claire") == Ok(Some(william_uuid)));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(Some(Value::from("claire"))));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(Some("name=claire".to_string())));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1830,6 +1998,15 @@ mod tests {
|
|||
"william",
|
||||
Some(Vec::new())
|
||||
);
|
||||
|
||||
let claire_uuid = Uuid::parse_str("04091a7a-6ce4-42d2-abf5-c2ce244ac9e8").unwrap();
|
||||
let william_uuid = Uuid::parse_str("db237e8a-0079-4b8c-8a56-593b22aa44d1").unwrap();
|
||||
assert!(be.name2uuid(audit, "william") == Ok(None));
|
||||
assert!(be.name2uuid(audit, "claire") == Ok(Some(claire_uuid)));
|
||||
assert!(be.uuid2spn(audit, &william_uuid) == Ok(None));
|
||||
assert!(be.uuid2rdn(audit, &william_uuid) == Ok(None));
|
||||
assert!(be.uuid2spn(audit, &claire_uuid) == Ok(Some(Value::from("claire"))));
|
||||
assert!(be.uuid2rdn(audit, &claire_uuid) == Ok(Some("name=claire".to_string())));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ pub use crate::constants::system_config::*;
|
|||
pub use crate::constants::uuids::*;
|
||||
|
||||
// Increment this as we add new schema types and values!!!
|
||||
pub const SYSTEM_INDEX_VERSION: i64 = 8;
|
||||
pub const SYSTEM_INDEX_VERSION: i64 = 10;
|
||||
// On test builds, define to 60 seconds
|
||||
#[cfg(test)]
|
||||
pub const PURGE_FREQUENCY: u64 = 60;
|
||||
|
|
|
@ -411,6 +411,37 @@ pub const JSON_SCHEMA_ATTR_UNIX_PASSWORD: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_NSUNIQUEID: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"attributetype"
|
||||
],
|
||||
"description": [
|
||||
"A unique id compatibility for 389-ds/dsee"
|
||||
],
|
||||
"index": [
|
||||
"EQUALITY"
|
||||
],
|
||||
"unique": [
|
||||
"true"
|
||||
],
|
||||
"multivalue": [
|
||||
"false"
|
||||
],
|
||||
"attributename": [
|
||||
"nsuniqueid"
|
||||
],
|
||||
"syntax": [
|
||||
"NSUNIQUEID"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000067"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_PERSON: &str = r#"
|
||||
{
|
||||
"valid": {
|
||||
|
|
|
@ -101,6 +101,12 @@ pub const UUID_SCHEMA_ATTR_LAST_MOD_CID: &str = "00000000-0000-0000-0000-ffff000
|
|||
pub const UUID_SCHEMA_ATTR_PHANTOM: &str = "00000000-0000-0000-0000-ffff00000064";
|
||||
pub const UUID_SCHEMA_ATTR_CLAIM: &str = "00000000-0000-0000-0000-ffff00000065";
|
||||
pub const UUID_SCHEMA_ATTR_PASSWORD_IMPORT: &str = "00000000-0000-0000-0000-ffff00000066";
|
||||
pub const UUID_SCHEMA_ATTR_NSUNIQUEID: &str = "00000000-0000-0000-0000-ffff00000067";
|
||||
|
||||
pub const UUID_SCHEMA_ATTR_DN: &str = "00000000-0000-0000-0000-ffff00000068";
|
||||
pub const UUID_SCHEMA_ATTR_NICE: &str = "00000000-0000-0000-0000-ffff00000069";
|
||||
pub const UUID_SCHEMA_ATTR_ENTRYUUID: &str = "00000000-0000-0000-0000-ffff00000070";
|
||||
pub const UUID_SCHEMA_ATTR_OBJECTCLASS: &str = "00000000-0000-0000-0000-ffff00000071";
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for fucking up these allocations.
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
use crate::audit::AuditScope;
|
||||
use crate::credential::Credential;
|
||||
use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved};
|
||||
use crate::ldap::ldap_attr_entry_map;
|
||||
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
|
||||
use crate::repl::cid::Cid;
|
||||
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
|
||||
|
@ -85,6 +86,8 @@ use uuid::Uuid;
|
|||
|
||||
lazy_static! {
|
||||
static ref CLASS_EXTENSIBLE: PartialValue = PartialValue::new_class("extensibleobject");
|
||||
static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone");
|
||||
static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled");
|
||||
}
|
||||
|
||||
pub struct EntryClasses<'a> {
|
||||
|
@ -958,6 +961,177 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_name2uuid_cands(&self) -> BTreeSet<String> {
|
||||
// The cands are:
|
||||
// * spn
|
||||
// * name
|
||||
// * gidnumber
|
||||
|
||||
let cands = ["spn", "name", "gidnumber"];
|
||||
cands
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
self.attrs
|
||||
.get(*c)
|
||||
.map(|avs| avs.iter().map(|v| v.to_proto_string_clone()))
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uuid2spn(&self) -> Value {
|
||||
self.attrs
|
||||
.get("spn")
|
||||
.and_then(|vs| vs.iter().take(1).next().map(|v| v.clone()))
|
||||
.or_else(|| {
|
||||
self.attrs
|
||||
.get("name")
|
||||
.and_then(|vs| vs.iter().take(1).next().map(|v| v.clone()))
|
||||
})
|
||||
.unwrap_or_else(|| Value::new_uuidr(self.get_uuid()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uuid2rdn(&self) -> String {
|
||||
self.attrs
|
||||
.get("spn")
|
||||
.and_then(|vs| {
|
||||
vs.iter()
|
||||
.take(1)
|
||||
.next()
|
||||
.map(|v| format!("spn={}", v.to_proto_string_clone()))
|
||||
})
|
||||
.or_else(|| {
|
||||
self.attrs.get("name").and_then(|vs| {
|
||||
vs.iter()
|
||||
.take(1)
|
||||
.next()
|
||||
.map(|v| format!("name={}", v.to_proto_string_clone()))
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| format!("uuid={}", self.get_uuid().to_hyphenated_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn mask_recycled_ts(&self) -> Option<&Self> {
|
||||
// Only when cls has ts/rc then None, else lways Some(self).
|
||||
match self.attrs.get("class") {
|
||||
Some(cls) => {
|
||||
if cls.contains(&PVCLASS_TOMBSTONE as &PartialValue)
|
||||
|| cls.contains(&PVCLASS_RECYCLED as &PartialValue)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
None => Some(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the required values for a name2uuid index. IE this is
|
||||
/// ALL possible names this entry COULD be known uniquely by!
|
||||
pub(crate) fn idx_name2uuid_diff(
|
||||
pre: Option<&Self>,
|
||||
post: Option<&Self>,
|
||||
) -> (
|
||||
// Add
|
||||
Option<BTreeSet<String>>,
|
||||
// Remove
|
||||
Option<BTreeSet<String>>,
|
||||
) {
|
||||
// needs to return gid for posix conversion
|
||||
match (pre, post) {
|
||||
(None, None) => {
|
||||
// No action required
|
||||
(None, None)
|
||||
}
|
||||
(None, Some(b)) => {
|
||||
// We are adding this entry (or restoring it),
|
||||
// so we need to add the values.
|
||||
(Some(b.get_name2uuid_cands()), None)
|
||||
}
|
||||
(Some(a), None) => {
|
||||
// Removing the entry, remove all values.
|
||||
(None, Some(a.get_name2uuid_cands()))
|
||||
}
|
||||
(Some(a), Some(b)) => {
|
||||
let pre_set = a.get_name2uuid_cands();
|
||||
let post_set = b.get_name2uuid_cands();
|
||||
|
||||
// what is in post, but not pre (added)
|
||||
let add_set: BTreeSet<_> = post_set.difference(&pre_set).cloned().collect();
|
||||
// what is in pre, but not post (removed)
|
||||
let rem_set: BTreeSet<_> = pre_set.difference(&post_set).cloned().collect();
|
||||
(Some(add_set), Some(rem_set))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn idx_uuid2spn_diff(
|
||||
pre: Option<&Self>,
|
||||
post: Option<&Self>,
|
||||
) -> Option<Result<Value, ()>> {
|
||||
match (pre, post) {
|
||||
(None, None) => {
|
||||
// no action
|
||||
None
|
||||
}
|
||||
(None, Some(b)) => {
|
||||
// add
|
||||
Some(Ok(b.get_uuid2spn()))
|
||||
}
|
||||
(Some(_a), None) => {
|
||||
// remove
|
||||
Some(Err(()))
|
||||
}
|
||||
(Some(a), Some(b)) => {
|
||||
let ia = a.get_uuid2spn();
|
||||
let ib = b.get_uuid2spn();
|
||||
if ia != ib {
|
||||
// Add (acts as replace)
|
||||
Some(Ok(ib))
|
||||
} else {
|
||||
// no action
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn idx_uuid2rdn_diff(
|
||||
pre: Option<&Self>,
|
||||
post: Option<&Self>,
|
||||
) -> Option<Result<String, ()>> {
|
||||
match (pre, post) {
|
||||
(None, None) => {
|
||||
// no action
|
||||
None
|
||||
}
|
||||
(None, Some(b)) => {
|
||||
// add
|
||||
Some(Ok(b.get_uuid2rdn()))
|
||||
}
|
||||
(Some(_a), None) => {
|
||||
// remove
|
||||
Some(Err(()))
|
||||
}
|
||||
(Some(a), Some(b)) => {
|
||||
let ia = a.get_uuid2rdn();
|
||||
let ib = b.get_uuid2rdn();
|
||||
if ia != ib {
|
||||
// Add (acts as replace)
|
||||
Some(Ok(ib))
|
||||
} else {
|
||||
// no action
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is an associated method, not on & self so we can take options on
|
||||
// both sides.
|
||||
pub(crate) fn idx_diff<'a>(
|
||||
|
@ -1340,33 +1514,22 @@ impl Entry<EntryReduced, EntryCommitted> {
|
|||
qs: &mut QueryServerReadTransaction,
|
||||
basedn: &str,
|
||||
) -> Result<LdapSearchResultEntry, OperationError> {
|
||||
let (attr, rdn) = self
|
||||
.get_ava_single("spn")
|
||||
.map(|v| ("spn", v.to_proto_string_clone()))
|
||||
.or_else(|| {
|
||||
self.get_ava_single("name")
|
||||
.map(|v| ("name", v.to_proto_string_clone()))
|
||||
})
|
||||
.unwrap_or_else(|| ("uuid", self.get_uuid().to_hyphenated_ref().to_string()));
|
||||
let rdn = qs.uuid_to_rdn(audit, self.get_uuid())?;
|
||||
|
||||
let dn = format!("{}={},{}", attr, rdn, basedn);
|
||||
let dn = format!("{},{}", rdn, basedn);
|
||||
|
||||
let attributes: Result<Vec<_>, _> = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, vs)| {
|
||||
let pvs: Result<Vec<String>, _> =
|
||||
vs.iter().map(|v| qs.resolve_value(audit, v)).collect();
|
||||
let pvs: Result<Vec<String>, _> = vs
|
||||
.iter()
|
||||
.map(|v| qs.resolve_value_ldap(audit, v, basedn))
|
||||
.collect();
|
||||
let pvs = pvs?;
|
||||
let pvs = if k == "memberof" || k == "member" {
|
||||
pvs.into_iter()
|
||||
.map(|s| format!("spn={},{}", s, basedn))
|
||||
.collect()
|
||||
} else {
|
||||
pvs
|
||||
};
|
||||
let ks = ldap_attr_entry_map(k.as_str());
|
||||
Ok(LdapPartialAttribute {
|
||||
atype: k.clone(),
|
||||
atype: ks,
|
||||
vals: pvs,
|
||||
})
|
||||
})
|
||||
|
@ -2229,4 +2392,185 @@ mod tests {
|
|||
);
|
||||
debug!("{:?}", chg_r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_mask_recycled_ts() {
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("class", &Value::new_class("person"));
|
||||
let e1 = unsafe { e1.into_sealed_committed() };
|
||||
assert!(e1.mask_recycled_ts().is_some());
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("class", &Value::new_class("person"));
|
||||
e2.add_ava("class", &Value::new_class("recycled"));
|
||||
let e2 = unsafe { e2.into_sealed_committed() };
|
||||
assert!(e2.mask_recycled_ts().is_none());
|
||||
|
||||
let mut e3: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e3.add_ava("class", &Value::new_class("tombstone"));
|
||||
let e3 = unsafe { e3.into_sealed_committed() };
|
||||
assert!(e3.mask_recycled_ts().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_idx_name2uuid_diff() {
|
||||
// none, none,
|
||||
let r = Entry::idx_name2uuid_diff(None, None);
|
||||
assert!(r == (None, None));
|
||||
|
||||
// none, some - test adding an entry gives back add sets
|
||||
{
|
||||
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e.add_ava("class", &Value::new_class("person"));
|
||||
let e = unsafe { e.into_sealed_committed() };
|
||||
|
||||
assert!(Entry::idx_name2uuid_diff(None, Some(&e)) == (Some(BTreeSet::new()), None));
|
||||
}
|
||||
|
||||
{
|
||||
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e.add_ava("class", &Value::new_class("person"));
|
||||
e.add_ava("gidnumber", &Value::new_uint32(1300));
|
||||
e.add_ava("name", &Value::new_iname_s("testperson"));
|
||||
e.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
e.add_ava(
|
||||
"uuid",
|
||||
&Value::new_uuids("9fec0398-c46c-4df4-9df5-b0016f7d563f").unwrap(),
|
||||
);
|
||||
let e = unsafe { e.into_sealed_committed() };
|
||||
|
||||
// Note the uuid isn't present!
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(None, Some(&e))
|
||||
== (
|
||||
Some(btreeset![
|
||||
"1300".to_string(),
|
||||
"testperson".to_string(),
|
||||
"testperson@example.com".to_string()
|
||||
]),
|
||||
None
|
||||
)
|
||||
);
|
||||
// some, none,
|
||||
// Check delete, swap the order of args
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(Some(&e), None)
|
||||
== (
|
||||
None,
|
||||
Some(btreeset![
|
||||
"1300".to_string(),
|
||||
"testperson".to_string(),
|
||||
"testperson@example.com".to_string()
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
// some, some (same), should be empty changes.
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(Some(&e), Some(&e))
|
||||
== (Some(BTreeSet::new()), Some(BTreeSet::new()))
|
||||
);
|
||||
}
|
||||
// some, some (diff)
|
||||
|
||||
{
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("class", &Value::new_class("person"));
|
||||
e1.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
let e1 = unsafe { e1.into_sealed_committed() };
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("class", &Value::new_class("person"));
|
||||
e2.add_ava("name", &Value::new_iname_s("testperson"));
|
||||
e2.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
let e2 = unsafe { e2.into_sealed_committed() };
|
||||
|
||||
// One attr added
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(Some(&e1), Some(&e2))
|
||||
== (
|
||||
Some(btreeset!["testperson".to_string()]),
|
||||
Some(BTreeSet::new())
|
||||
)
|
||||
);
|
||||
|
||||
// One removed
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(Some(&e2), Some(&e1))
|
||||
== (
|
||||
Some(BTreeSet::new()),
|
||||
Some(btreeset!["testperson".to_string()])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Value changed, remove old, add new.
|
||||
{
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("class", &Value::new_class("person"));
|
||||
e1.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
let e1 = unsafe { e1.into_sealed_committed() };
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("class", &Value::new_class("person"));
|
||||
e2.add_ava("spn", &Value::new_spn_str("renameperson", "example.com"));
|
||||
let e2 = unsafe { e2.into_sealed_committed() };
|
||||
|
||||
assert!(
|
||||
Entry::idx_name2uuid_diff(Some(&e1), Some(&e2))
|
||||
== (
|
||||
Some(btreeset!["renameperson@example.com".to_string()]),
|
||||
Some(btreeset!["testperson@example.com".to_string()])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_idx_uuid2spn_diff() {
|
||||
assert!(Entry::idx_uuid2spn_diff(None, None) == None);
|
||||
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
let e1 = unsafe { e1.into_sealed_committed() };
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("spn", &Value::new_spn_str("renameperson", "example.com"));
|
||||
let e2 = unsafe { e2.into_sealed_committed() };
|
||||
|
||||
assert!(
|
||||
Entry::idx_uuid2spn_diff(None, Some(&e1))
|
||||
== Some(Ok(Value::new_spn_str("testperson", "example.com")))
|
||||
);
|
||||
assert!(Entry::idx_uuid2spn_diff(Some(&e1), None) == Some(Err(())));
|
||||
assert!(Entry::idx_uuid2spn_diff(Some(&e1), Some(&e1)) == None);
|
||||
assert!(
|
||||
Entry::idx_uuid2spn_diff(Some(&e1), Some(&e2))
|
||||
== Some(Ok(Value::new_spn_str("renameperson", "example.com")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_idx_uuid2rdn_diff() {
|
||||
assert!(Entry::idx_uuid2rdn_diff(None, None) == None);
|
||||
|
||||
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e1.add_ava("spn", &Value::new_spn_str("testperson", "example.com"));
|
||||
let e1 = unsafe { e1.into_sealed_committed() };
|
||||
|
||||
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
|
||||
e2.add_ava("spn", &Value::new_spn_str("renameperson", "example.com"));
|
||||
let e2 = unsafe { e2.into_sealed_committed() };
|
||||
|
||||
assert!(
|
||||
Entry::idx_uuid2rdn_diff(None, Some(&e1))
|
||||
== Some(Ok("spn=testperson@example.com".to_string()))
|
||||
);
|
||||
assert!(Entry::idx_uuid2rdn_diff(Some(&e1), None) == Some(Err(())));
|
||||
assert!(Entry::idx_uuid2rdn_diff(Some(&e1), Some(&e1)) == None);
|
||||
assert!(
|
||||
Entry::idx_uuid2rdn_diff(Some(&e1), Some(&e2))
|
||||
== Some(Ok("spn=renameperson@example.com".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
use crate::audit::AuditScope;
|
||||
use crate::event::{Event, EventOrigin};
|
||||
use crate::ldap::ldap_attr_filter_map;
|
||||
use crate::schema::SchemaTransaction;
|
||||
use crate::server::{
|
||||
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
||||
|
@ -692,9 +693,11 @@ impl FilterComp {
|
|||
),
|
||||
LdapFilter::Not(l) => FilterComp::AndNot(Box::new(Self::from_ldap_ro(audit, l, qs)?)),
|
||||
LdapFilter::Equality(a, v) => {
|
||||
FilterComp::Eq(a.clone(), qs.clone_partialvalue(audit, a, v)?)
|
||||
let a = ldap_attr_filter_map(a);
|
||||
let v = qs.clone_partialvalue(audit, a.as_str(), v)?;
|
||||
FilterComp::Eq(a, v)
|
||||
}
|
||||
LdapFilter::Present(a) => FilterComp::Pres(a.clone()),
|
||||
LdapFilter::Present(a) => FilterComp::Pres(ldap_attr_filter_map(a)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
#[cfg(test)]
|
||||
macro_rules! entry_str_to_account {
|
||||
($entry_str:expr) => {{
|
||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||
use crate::idm::account::Account;
|
||||
use crate::value::Value;
|
||||
|
||||
let mut e: Entry<EntryInvalid, EntryNew> =
|
||||
unsafe { Entry::unsafe_from_entry_str($entry_str).into_invalid_new() };
|
||||
// Add spn, because normally this is generated but in tests we can't.
|
||||
let spn = e
|
||||
.get_ava_single_str("name")
|
||||
.map(|s| Value::new_spn_str(s, "example.com"))
|
||||
.expect("Failed to munge spn from name!");
|
||||
e.set_avas("spn", vec![spn]);
|
||||
|
||||
let e = unsafe { e.into_sealed_committed() };
|
||||
|
||||
Account::try_from_entry_no_groups(e).expect("Account conversion failure")
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! run_idm_test {
|
||||
($test_fn:expr) => {{
|
||||
use crate::audit::AuditScope;
|
||||
use crate::be::Backend;
|
||||
use crate::idm::server::IdmServer;
|
||||
use crate::schema::Schema;
|
||||
use crate::server::QueryServer;
|
||||
use crate::utils::duration_from_epoch_now;
|
||||
|
||||
use env_logger;
|
||||
::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
|
||||
let _ = env_logger::builder()
|
||||
.format_timestamp(None)
|
||||
.format_level(false)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let mut audit = AuditScope::new("run_test", uuid::Uuid::new_v4());
|
||||
|
||||
let be = Backend::new(&mut audit, "", 1).expect("Failed to init be");
|
||||
let schema_outer = Schema::new(&mut audit).expect("Failed to init schema");
|
||||
|
||||
let test_server = QueryServer::new(be, schema_outer);
|
||||
test_server
|
||||
.initialise_helper(&mut audit, duration_from_epoch_now())
|
||||
.expect("init failed");
|
||||
|
||||
let test_idm_server = IdmServer::new(test_server.clone());
|
||||
|
||||
$test_fn(&test_server, &test_idm_server, &mut audit);
|
||||
// Any needed teardown?
|
||||
// Make sure there are no errors.
|
||||
assert!(test_server.verify(&mut audit).len() == 0);
|
||||
}};
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub(crate) mod account;
|
||||
pub(crate) mod authsession;
|
||||
pub(crate) mod claim;
|
||||
|
|
|
@ -469,9 +469,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
ltrace!(au, "processing change {:?}", modlist);
|
||||
// given the new credential generate a modify
|
||||
// We use impersonate here to get the event from ae
|
||||
try_audit!(
|
||||
au,
|
||||
self.qs_write.impersonate_modify(
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
au,
|
||||
// Filter as executed
|
||||
filter!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
|
||||
|
@ -480,7 +479,10 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
modlist,
|
||||
&pce.event,
|
||||
)
|
||||
);
|
||||
.map_err(|e| {
|
||||
lrequest_error!(au, "error -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -521,9 +523,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
ltrace!(au, "processing change {:?}", modlist);
|
||||
// given the new credential generate a modify
|
||||
// We use impersonate here to get the event from ae
|
||||
try_audit!(
|
||||
au,
|
||||
self.qs_write.impersonate_modify(
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
au,
|
||||
// Filter as executed
|
||||
filter!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
|
||||
|
@ -532,7 +533,10 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
modlist,
|
||||
&pce.event,
|
||||
)
|
||||
);
|
||||
.map_err(|e| {
|
||||
lrequest_error!(au, "error -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -604,9 +608,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
ltrace!(au, "processing change {:?}", modlist);
|
||||
|
||||
// Apply it.
|
||||
try_audit!(
|
||||
au,
|
||||
self.qs_write.impersonate_modify(
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
au,
|
||||
// Filter as executed
|
||||
filter!(f_eq("uuid", PartialValue::new_uuidr(&rrse.target))),
|
||||
|
@ -616,7 +619,10 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
// Provide the event to impersonate
|
||||
&rrse.event,
|
||||
)
|
||||
);
|
||||
.map_err(|e| {
|
||||
lrequest_error!(au, "error -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(cleartext)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ pub struct LdapServer {
|
|||
rootdse: LdapSearchResultEntry,
|
||||
basedn: String,
|
||||
dnre: Regex,
|
||||
binddnre: Regex,
|
||||
}
|
||||
|
||||
impl LdapServer {
|
||||
|
@ -55,6 +56,9 @@ impl LdapServer {
|
|||
let dnre = Regex::new(format!("^((?P<attr>[^=]+)=(?P<val>[^=]+),)?{}$", basedn).as_str())
|
||||
.map_err(|_| OperationError::InvalidEntryState)?;
|
||||
|
||||
let binddnre = Regex::new(format!("^(([^=,]+)=)?(?P<val>[^=,]+)(,{})?$", basedn).as_str())
|
||||
.map_err(|_| OperationError::InvalidEntryState)?;
|
||||
|
||||
let rootdse = LdapSearchResultEntry {
|
||||
dn: "".to_string(),
|
||||
attributes: vec![
|
||||
|
@ -89,6 +93,7 @@ impl LdapServer {
|
|||
basedn,
|
||||
rootdse,
|
||||
dnre,
|
||||
binddnre,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -100,8 +105,10 @@ impl LdapServer {
|
|||
uat: &LdapBoundToken,
|
||||
// eventid: &Uuid,
|
||||
) -> Result<Vec<LdapMsg>, OperationError> {
|
||||
ladmin_info!(au, "Attempt LDAP Search for {}", uat.spn);
|
||||
// If the request is "", Base, Present("objectclass"), [], then we want the rootdse.
|
||||
if sr.base == "" && sr.scope == LdapSearchScope::Base {
|
||||
ladmin_info!(au, "LDAP Search success - RootDSE");
|
||||
Ok(vec![
|
||||
sr.gen_result_entry(self.rootdse.clone()),
|
||||
sr.gen_success(),
|
||||
|
@ -118,6 +125,7 @@ impl LdapServer {
|
|||
caps.name("val").map(|v| v.as_str().to_string()),
|
||||
),
|
||||
None => {
|
||||
lrequest_error!(au, "LDAP Search failure - invalid basedn");
|
||||
return Err(OperationError::InvalidRequestState);
|
||||
}
|
||||
};
|
||||
|
@ -126,6 +134,7 @@ impl LdapServer {
|
|||
(Some(a), Some(v)) => Some((a, v)),
|
||||
(None, None) => None,
|
||||
_ => {
|
||||
lrequest_error!(au, "LDAP Search failure - invalid rdn");
|
||||
return Err(OperationError::InvalidRequestState);
|
||||
}
|
||||
};
|
||||
|
@ -177,7 +186,7 @@ impl LdapServer {
|
|||
None
|
||||
} else {
|
||||
// if list, add to the search
|
||||
Some(a.clone())
|
||||
Some(ldap_attr_filter_map(a))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -188,7 +197,7 @@ impl LdapServer {
|
|||
}
|
||||
};
|
||||
|
||||
ltrace!(au, "Attrs -> {:?}", attrs);
|
||||
ladmin_info!(au, "LDAP Search Request Attrs -> {:?}", attrs);
|
||||
|
||||
lperf_segment!(au, "ldap::do_search<core>", || {
|
||||
// Now start the txn - we need it for resolving filter components.
|
||||
|
@ -221,7 +230,7 @@ impl LdapServer {
|
|||
]),
|
||||
};
|
||||
|
||||
ltrace!(au, "ldapfilter -> {:?}", lfilter);
|
||||
ladmin_info!(au, "LDAP Search Filter -> {:?}", lfilter);
|
||||
|
||||
// Kanidm Filter from LdapFilter
|
||||
let filter =
|
||||
|
@ -267,6 +276,12 @@ impl LdapServer {
|
|||
e
|
||||
})?;
|
||||
|
||||
ladmin_info!(
|
||||
au,
|
||||
"LDAP Search Success -> number of entries {}",
|
||||
lres.len()
|
||||
);
|
||||
|
||||
Ok(lres)
|
||||
})
|
||||
}
|
||||
|
@ -279,15 +294,46 @@ impl LdapServer {
|
|||
dn: &str,
|
||||
pw: &str,
|
||||
) -> Result<Option<LdapBoundToken>, OperationError> {
|
||||
lsecurity!(
|
||||
au,
|
||||
"Attempt LDAP Bind for {}",
|
||||
if dn == "" { "anonymous" } else { dn }
|
||||
);
|
||||
let mut idm_write = idms.write();
|
||||
|
||||
let target_uuid: Uuid = if dn == "" && pw == "" {
|
||||
UUID_ANONYMOUS.clone()
|
||||
let target_uuid: Uuid = if dn == "" {
|
||||
if pw == "" {
|
||||
lsecurity!(au, "✅ LDAP Bind success anonymous");
|
||||
UUID_ANONYMOUS.clone()
|
||||
} else {
|
||||
lsecurity!(au, "❌ LDAP Bind failure anonymous");
|
||||
// Yeah-nahhhhh
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
idm_write.qs_read.name_to_uuid(au, dn).map_err(|e| {
|
||||
ladmin_info!(au, "Error resolving id to target {:?}", e);
|
||||
e
|
||||
})?
|
||||
let rdn = match self
|
||||
.binddnre
|
||||
.captures(dn)
|
||||
.and_then(|caps| caps.name("val").map(|v| v.as_str().to_string()))
|
||||
{
|
||||
Some(r) => r,
|
||||
None => return Err(OperationError::NoMatchingEntries),
|
||||
};
|
||||
|
||||
ltrace!(au, "rdn val is -> {:?}", rdn);
|
||||
|
||||
if rdn == "" {
|
||||
// That's weird ...
|
||||
return Err(OperationError::NoMatchingEntries);
|
||||
}
|
||||
|
||||
idm_write
|
||||
.qs_read
|
||||
.name_to_uuid(au, rdn.as_str())
|
||||
.map_err(|e| {
|
||||
lrequest_error!(au, "Error resolving rdn to target {:?} {:?}", rdn, e);
|
||||
e
|
||||
})?
|
||||
};
|
||||
|
||||
let ct = SystemTime::now()
|
||||
|
@ -295,9 +341,16 @@ impl LdapServer {
|
|||
.expect("Clock failure!");
|
||||
|
||||
let lae = LdapAuthEvent::from_parts(au, target_uuid, pw.to_string())?;
|
||||
idm_write
|
||||
.auth_ldap(au, lae, ct)
|
||||
.and_then(|r| idm_write.commit(au).map(|_| r))
|
||||
idm_write.auth_ldap(au, lae, ct).and_then(|r| {
|
||||
idm_write.commit(au).map(|_| {
|
||||
if r.is_some() {
|
||||
lsecurity!(au, "✅ LDAP Bind success {}", dn);
|
||||
} else {
|
||||
lsecurity!(au, "❌ LDAP Bind failure {}", dn);
|
||||
};
|
||||
r
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn do_op(
|
||||
|
@ -392,3 +445,189 @@ fn operationerr_to_ldapresultcode(e: OperationError) -> (LdapResultCode, String)
|
|||
e => (LdapResultCode::Other, format!("{:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn ldap_attr_filter_map(input: &str) -> String {
|
||||
let lin = input.to_lowercase();
|
||||
match lin.as_str() {
|
||||
"entryuuid" => "uuid",
|
||||
"objectclass" => "class",
|
||||
a => a,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn ldap_attr_entry_map(attr: &str) -> String {
|
||||
match attr {
|
||||
"uuid" => "entryuuid",
|
||||
"class" => "objectclass",
|
||||
ks => ks,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::constants::{STR_UUID_ADMIN, UUID_ADMIN, UUID_ANONYMOUS};
|
||||
use crate::event::ModifyEvent;
|
||||
use crate::idm::event::UnixPasswordChangeEvent;
|
||||
use crate::modify::{Modify, ModifyList};
|
||||
use crate::value::{PartialValue, Value};
|
||||
// use kanidm_proto::v1::OperationError;
|
||||
// use crate::audit::AuditScope;
|
||||
// use crate::idm::server::IdmServer;
|
||||
// use crate::server::QueryServer;
|
||||
// use crate::utils::duration_from_epoch_now;
|
||||
// use uuid::Uuid;
|
||||
use crate::ldap::LdapServer;
|
||||
|
||||
const TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍";
|
||||
|
||||
#[test]
|
||||
fn test_ldap_simple_bind() {
|
||||
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
|
||||
let ldaps = LdapServer::new(au, idms).expect("failed to start ldap");
|
||||
|
||||
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
|
||||
// make the admin a valid posix account
|
||||
let me_posix = unsafe {
|
||||
ModifyEvent::new_internal_invalid(
|
||||
filter!(f_eq("name", PartialValue::new_iname("admin"))),
|
||||
ModifyList::new_list(vec![
|
||||
Modify::Present("class".to_string(), Value::new_class("posixaccount")),
|
||||
Modify::Present("gidnumber".to_string(), Value::new_uint32(2001)),
|
||||
]),
|
||||
)
|
||||
};
|
||||
assert!(idms_prox_write.qs_write.modify(au, &me_posix).is_ok());
|
||||
|
||||
let pce = UnixPasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
|
||||
|
||||
assert!(idms_prox_write.set_unix_account_password(au, &pce).is_ok());
|
||||
assert!(idms_prox_write.commit(au).is_ok());
|
||||
|
||||
let anon_t = ldaps.do_bind(au, idms, "", "").unwrap().unwrap();
|
||||
assert!(anon_t.uuid == *UUID_ANONYMOUS);
|
||||
assert!(ldaps.do_bind(au, idms, "", "test").unwrap().is_none());
|
||||
|
||||
// Bad password
|
||||
assert!(ldaps.do_bind(au, idms, "admin", "test").unwrap().is_none());
|
||||
|
||||
// Now test the admin and various DN's
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "admin", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "admin@example.com", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, STR_UUID_ADMIN, TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "name=admin,dc=example,dc=com", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
"spn=admin@example.com,dc=example,dc=com",
|
||||
TEST_PASSWORD,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
format!("uuid={},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
||||
TEST_PASSWORD,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "name=admin", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "spn=admin@example.com", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
format!("uuid={}", STR_UUID_ADMIN).as_str(),
|
||||
TEST_PASSWORD,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
|
||||
let admin_t = ldaps
|
||||
.do_bind(au, idms, "admin,dc=example,dc=com", TEST_PASSWORD)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
"admin@example.com,dc=example,dc=com",
|
||||
TEST_PASSWORD,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
let admin_t = ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
format!("{},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
||||
TEST_PASSWORD,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
||||
|
||||
// Non-existant and invalid DNs
|
||||
assert!(ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
"spn=admin@example.com,dc=clownshoes,dc=example,dc=com",
|
||||
TEST_PASSWORD
|
||||
)
|
||||
.is_err());
|
||||
assert!(ldaps
|
||||
.do_bind(
|
||||
au,
|
||||
idms,
|
||||
"spn=claire@example.com,dc=example,dc=com",
|
||||
TEST_PASSWORD
|
||||
)
|
||||
.is_err());
|
||||
assert!(ldaps
|
||||
.do_bind(au, idms, ",dc=example,dc=com", TEST_PASSWORD)
|
||||
.is_err());
|
||||
assert!(ldaps
|
||||
.do_bind(au, idms, "dc=example,dc=com", TEST_PASSWORD)
|
||||
.is_err());
|
||||
|
||||
assert!(ldaps.do_bind(au, idms, "claire", "test").is_err());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,66 @@ macro_rules! run_test {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! entry_str_to_account {
|
||||
($entry_str:expr) => {{
|
||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||
use crate::idm::account::Account;
|
||||
use crate::value::Value;
|
||||
|
||||
let mut e: Entry<EntryInvalid, EntryNew> =
|
||||
unsafe { Entry::unsafe_from_entry_str($entry_str).into_invalid_new() };
|
||||
// Add spn, because normally this is generated but in tests we can't.
|
||||
let spn = e
|
||||
.get_ava_single_str("name")
|
||||
.map(|s| Value::new_spn_str(s, "example.com"))
|
||||
.expect("Failed to munge spn from name!");
|
||||
e.set_avas("spn", vec![spn]);
|
||||
|
||||
let e = unsafe { e.into_sealed_committed() };
|
||||
|
||||
Account::try_from_entry_no_groups(e).expect("Account conversion failure")
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! run_idm_test {
|
||||
($test_fn:expr) => {{
|
||||
use crate::audit::AuditScope;
|
||||
use crate::be::Backend;
|
||||
use crate::idm::server::IdmServer;
|
||||
use crate::schema::Schema;
|
||||
use crate::server::QueryServer;
|
||||
use crate::utils::duration_from_epoch_now;
|
||||
|
||||
use env_logger;
|
||||
::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
|
||||
let _ = env_logger::builder()
|
||||
.format_timestamp(None)
|
||||
.format_level(false)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let mut audit = AuditScope::new("run_test", uuid::Uuid::new_v4());
|
||||
|
||||
let be = Backend::new(&mut audit, "", 1).expect("Failed to init be");
|
||||
let schema_outer = Schema::new(&mut audit).expect("Failed to init schema");
|
||||
|
||||
let test_server = QueryServer::new(be, schema_outer);
|
||||
test_server
|
||||
.initialise_helper(&mut audit, duration_from_epoch_now())
|
||||
.expect("init failed");
|
||||
|
||||
let test_idm_server = IdmServer::new(test_server.clone());
|
||||
|
||||
$test_fn(&test_server, &test_idm_server, &mut audit);
|
||||
// Any needed teardown?
|
||||
// Make sure there are no errors.
|
||||
assert!(test_server.verify(&mut audit).len() == 0);
|
||||
audit.write_log();
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! modlist {
|
||||
|
|
|
@ -196,6 +196,7 @@ impl SchemaAttribute {
|
|||
SyntaxType::SERVICE_PRINCIPLE_NAME => v.is_spn(),
|
||||
SyntaxType::UINT32 => v.is_uint32(),
|
||||
SyntaxType::CID => v.is_cid(),
|
||||
SyntaxType::NSUNIQUEID => v.is_nsuniqueid(),
|
||||
};
|
||||
if r {
|
||||
Ok(())
|
||||
|
@ -362,6 +363,15 @@ impl SchemaAttribute {
|
|||
}
|
||||
})
|
||||
}),
|
||||
SyntaxType::NSUNIQUEID => ava.iter().fold(Ok(()), |acc, v| {
|
||||
acc.and_then(|_| {
|
||||
if v.is_nsuniqueid() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
||||
}
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1117,6 +1127,50 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
},
|
||||
);
|
||||
|
||||
// LDAP Masking Phantoms
|
||||
self.attributes.insert(
|
||||
String::from("dn"),
|
||||
SchemaAttribute {
|
||||
name: String::from("dn"),
|
||||
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_DN).expect("unable to parse const uuid"),
|
||||
description: String::from("An LDAP Compatible DN"),
|
||||
multivalue: false,
|
||||
unique: false,
|
||||
phantom: true,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
String::from("entryuuid"),
|
||||
SchemaAttribute {
|
||||
name: String::from("entryuuid"),
|
||||
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ENTRYUUID)
|
||||
.expect("unable to parse const uuid"),
|
||||
description: String::from("An LDAP Compatible entryUUID"),
|
||||
multivalue: false,
|
||||
unique: false,
|
||||
phantom: true,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::UUID,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
String::from("objectclass"),
|
||||
SchemaAttribute {
|
||||
name: String::from("objectclass"),
|
||||
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_OBJECTCLASS)
|
||||
.expect("unable to parse const uuid"),
|
||||
description: String::from("An LDAP Compatible objectClass"),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: true,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
|
||||
},
|
||||
);
|
||||
// end LDAP masking phantoms
|
||||
|
||||
self.classes.insert(
|
||||
String::from("attributetype"),
|
||||
SchemaClass {
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::event::{
|
|||
CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent,
|
||||
SearchEvent,
|
||||
};
|
||||
use crate::filter::{f_eq, Filter, FilterInvalid, FilterValid};
|
||||
use crate::filter::{Filter, FilterInvalid, FilterValid};
|
||||
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
|
||||
use crate::plugins::Plugins;
|
||||
use crate::repl::cid::Cid;
|
||||
|
@ -186,50 +186,19 @@ pub trait QueryServerTransaction {
|
|||
// Remember, we don't care if the name is invalid, because search
|
||||
// will validate/normalise the filter we construct for us. COOL!
|
||||
fn name_to_uuid(&mut self, audit: &mut AuditScope, name: &str) -> Result<Uuid, OperationError> {
|
||||
lperf_segment!(audit, "server::name_to_uuid", || {
|
||||
// For now this just constructs a filter and searches, but later
|
||||
// we could actually improve this to contact the backend and do
|
||||
// index searches, completely bypassing id2entry.
|
||||
|
||||
// construct the filter
|
||||
// Internal search - DO NOT SEARCH TOMBSTONES AND RECYCLE
|
||||
let filt = filter!(f_spn_name(name));
|
||||
|
||||
ltrace!(audit, "name_to_uuid: name -> {:?}", name);
|
||||
|
||||
let res = match self.internal_search(audit, filt) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
ladmin_error!(audit, "name_to_uuid search failure -> {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
ltrace!(audit, "name_to_uuid: results -- {:?}", res);
|
||||
|
||||
if res.is_empty() {
|
||||
// If result len == 0, error no such result
|
||||
ladmin_warning!(audit, "name_to_uuid no matching results -> {}", name);
|
||||
return Err(OperationError::NoMatchingEntries);
|
||||
} else if res.len() >= 2 {
|
||||
// if result len >= 2, error, invaid entry state.
|
||||
ladmin_error!(
|
||||
audit,
|
||||
"name_to_uuid invalid db state, multiple results for name -> {}",
|
||||
name
|
||||
);
|
||||
return Err(OperationError::InvalidDBState);
|
||||
// Is it just a uuid?
|
||||
match Uuid::parse_str(name) {
|
||||
Ok(u) => return Ok(u),
|
||||
Err(_) => {
|
||||
let lname = name.to_lowercase();
|
||||
self.get_be_txn()
|
||||
.name2uuid(audit, lname.as_str())
|
||||
.and_then(|v| match v {
|
||||
Some(u) => Ok(u),
|
||||
None => Err(OperationError::NoMatchingEntries),
|
||||
})
|
||||
}
|
||||
|
||||
// error should never be triggered due to the len checks above.
|
||||
let e = res.first().ok_or(OperationError::NoMatchingEntries)?;
|
||||
// Get the uuid from the entry. Again, check it exists, and only one.
|
||||
let uuid_res: Uuid = *e.get_uuid();
|
||||
|
||||
ltrace!(audit, "name_to_uuid: uuid <- {:?}", uuid_res);
|
||||
|
||||
Ok(uuid_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn uuid_to_spn(
|
||||
|
@ -237,87 +206,28 @@ pub trait QueryServerTransaction {
|
|||
audit: &mut AuditScope,
|
||||
uuid: &Uuid,
|
||||
) -> Result<Option<Value>, OperationError> {
|
||||
lperf_segment!(audit, "server::uuid_to_spn", || {
|
||||
// construct the filter
|
||||
let filt = filter!(f_eq("uuid", PartialValue::new_uuidr(uuid)));
|
||||
ltrace!(audit, "uuid_to_spn: uuid -> {:?}", uuid);
|
||||
let r = self.get_be_txn().uuid2spn(audit, uuid)?;
|
||||
|
||||
// Internal search - DO NOT SEARCH TOMBSTONES AND RECYCLE
|
||||
let res = match self.internal_search(audit, filt) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
ltrace!(audit, "uuid_to_spn: results -- {:?}", res);
|
||||
|
||||
if res.is_empty() {
|
||||
// If result len == 0, error no such result
|
||||
ltrace!(audit, "uuid_to_spn: 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);
|
||||
match &r {
|
||||
Some(n) => {
|
||||
debug_assert!(n.is_spn() || n.is_iname());
|
||||
}
|
||||
|
||||
// fine for 0/1 case, but check len for >= 2 to eliminate that case.
|
||||
let e = res.first().ok_or(OperationError::NoMatchingEntries)?;
|
||||
// Get the uuid from the entry. Again, check it exists, and only one.
|
||||
let name_res = e
|
||||
.get_ava(&String::from("spn"))
|
||||
.or_else(|| e.get_ava(&String::from("name")))
|
||||
.and_then(|vas| vas.first().map(|u| (*u).clone()))
|
||||
.ok_or(OperationError::InvalidEntryState)?;
|
||||
|
||||
ltrace!(audit, "uuid_to_spn: name <- {:?}", name_res);
|
||||
|
||||
// Make sure it's the right type ... (debug only)
|
||||
debug_assert!(name_res.is_spn() || name_res.is_iname());
|
||||
|
||||
Ok(Some(name_res))
|
||||
})
|
||||
None => {}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn posixid_to_uuid(
|
||||
fn uuid_to_rdn(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
name: &str,
|
||||
) -> Result<Uuid, OperationError> {
|
||||
lperf_segment!(audit, "server::posixid_to_uuid", || {
|
||||
let f_name = Some(f_eq("name", PartialValue::new_iname(name)));
|
||||
|
||||
let f_spn = PartialValue::new_spn_s(name).map(|v| f_eq("spn", v));
|
||||
|
||||
let f_gidnumber = PartialValue::new_uint32_str(name).map(|v| f_eq("gidnumber", v));
|
||||
|
||||
let x = vec![f_name, f_spn, f_gidnumber];
|
||||
|
||||
let filt = filter!(f_or(x.into_iter().filter_map(|v| v).collect()));
|
||||
ltrace!(audit, "posixid_to_uuid: name -> {:?}", name);
|
||||
|
||||
let res = match self.internal_search(audit, filt) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
ltrace!(audit, "posixid_to_uuid: results -- {:?}", res);
|
||||
|
||||
if res.is_empty() {
|
||||
// If result len == 0, error no such result
|
||||
return Err(OperationError::NoMatchingEntries);
|
||||
} else if res.len() >= 2 {
|
||||
// if result len >= 2, error, invaid entry state.
|
||||
return Err(OperationError::InvalidDBState);
|
||||
}
|
||||
|
||||
// error should never be triggered due to the len checks above.
|
||||
let e = res.first().ok_or(OperationError::NoMatchingEntries)?;
|
||||
// Get the uuid from the entry. Again, check it exists, and only one.
|
||||
let uuid_res: Uuid = *e.get_uuid();
|
||||
|
||||
ltrace!(audit, "posixid_to_uuid: uuid <- {:?}", uuid_res);
|
||||
|
||||
Ok(uuid_res)
|
||||
})
|
||||
uuid: &Uuid,
|
||||
) -> Result<String, OperationError> {
|
||||
self.get_be_txn()
|
||||
.uuid2rdn(audit, uuid)
|
||||
.and_then(|v| match v {
|
||||
Some(u) => Ok(u),
|
||||
None => Ok(format!("uuid={}", uuid.to_hyphenated_ref())),
|
||||
})
|
||||
}
|
||||
|
||||
// From internal, generate an exists event and dispatch
|
||||
|
@ -541,6 +451,7 @@ pub trait QueryServerTransaction {
|
|||
SyntaxType::UINT32 => Value::new_uint32_str(value)
|
||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
|
||||
SyntaxType::CID => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
|
||||
SyntaxType::NSUNIQUEID => Ok(Value::new_nsuniqueid_s(value)),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
@ -620,14 +531,15 @@ pub trait QueryServerTransaction {
|
|||
SyntaxType::SSHKEY => Ok(PartialValue::new_sshkey_tag_s(value)),
|
||||
SyntaxType::SERVICE_PRINCIPLE_NAME => PartialValue::new_spn_s(value)
|
||||
.ok_or_else(|| {
|
||||
OperationError::InvalidAttribute("Invalid SPN syntax".to_string())
|
||||
OperationError::InvalidAttribute("Invalid spn syntax".to_string())
|
||||
}),
|
||||
SyntaxType::UINT32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute("Invalid Uint32 syntax".to_string())
|
||||
OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
|
||||
}),
|
||||
SyntaxType::CID => PartialValue::new_cid_s(value).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute("Invalid Cid syntax".to_string())
|
||||
OperationError::InvalidAttribute("Invalid cid syntax".to_string())
|
||||
}),
|
||||
SyntaxType::NSUNIQUEID => Ok(PartialValue::new_nsuniqueid_s(value)),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
@ -656,6 +568,25 @@ pub trait QueryServerTransaction {
|
|||
// Not? Okay, do the to string.
|
||||
Ok(value.to_proto_string_clone())
|
||||
}
|
||||
|
||||
fn resolve_value_ldap(
|
||||
&mut self,
|
||||
audit: &mut AuditScope,
|
||||
value: &Value,
|
||||
basedn: &str,
|
||||
) -> Result<String, OperationError> {
|
||||
if let Some(ur) = value.to_ref_uuid() {
|
||||
let rdn = self.uuid_to_rdn(audit, ur)?;
|
||||
Ok(format!("{},{}", rdn, basedn))
|
||||
} else if value.is_sshkey() {
|
||||
value
|
||||
.get_sshkey()
|
||||
.ok_or_else(|| OperationError::InvalidValueState)
|
||||
} else {
|
||||
// Not? Okay, do the to string.
|
||||
Ok(value.to_proto_string_clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryServerReadTransaction<'a> {
|
||||
|
@ -1854,6 +1785,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_SCHEMA_CLASS_POSIXACCOUNT,
|
||||
JSON_SCHEMA_CLASS_POSIXGROUP,
|
||||
JSON_SCHEMA_CLASS_SYSTEM_CONFIG,
|
||||
JSON_SCHEMA_ATTR_NSUNIQUEID,
|
||||
];
|
||||
|
||||
let r: Result<Vec<()>, _> = idm_schema
|
||||
|
@ -2925,6 +2857,133 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qs_uuid_to_rdn() {
|
||||
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
|
||||
let mut server_txn = server.write(duration_from_epoch_now());
|
||||
|
||||
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"valid": null,
|
||||
"state": null,
|
||||
"attrs": {
|
||||
"class": ["object", "person", "account"],
|
||||
"name": ["testperson1"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson1"]
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
let cr = server_txn.create(audit, &ce);
|
||||
assert!(cr.is_ok());
|
||||
|
||||
// Name doesn't exist
|
||||
let r1 = server_txn.uuid_to_rdn(
|
||||
audit,
|
||||
&Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(),
|
||||
);
|
||||
// There is nothing.
|
||||
assert!(r1.unwrap() == "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
|
||||
// Name does exist
|
||||
let r3 = server_txn.uuid_to_rdn(
|
||||
audit,
|
||||
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
|
||||
);
|
||||
println!("{:?}", r3);
|
||||
assert!(r3.unwrap() == "spn=testperson1@example.com");
|
||||
// Uuid is not syntax normalised (but exists)
|
||||
let r4 = server_txn.uuid_to_rdn(
|
||||
audit,
|
||||
&Uuid::parse_str("CC8E95B4-C24F-4D68-BA54-8BED76F63930").unwrap(),
|
||||
);
|
||||
assert!(r4.unwrap() == "spn=testperson1@example.com");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qs_uuid_to_star_recycle() {
|
||||
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
|
||||
let mut server_txn = server.write(duration_from_epoch_now());
|
||||
|
||||
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "person", "account"],
|
||||
"name": ["testperson1"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson1"]
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
let tuuid = Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap();
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
let cr = server_txn.create(audit, &ce);
|
||||
assert!(cr.is_ok());
|
||||
|
||||
assert!(
|
||||
server_txn.uuid_to_rdn(audit, &tuuid)
|
||||
== Ok("spn=testperson1@example.com".to_string())
|
||||
);
|
||||
|
||||
assert!(
|
||||
server_txn.uuid_to_spn(audit, &tuuid)
|
||||
== Ok(Some(Value::new_spn_str("testperson1", "example.com")))
|
||||
);
|
||||
|
||||
assert!(server_txn.name_to_uuid(audit, "testperson1") == Ok(tuuid));
|
||||
|
||||
// delete
|
||||
let de_sin = unsafe {
|
||||
DeleteEvent::new_internal_invalid(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("testperson1")
|
||||
)))
|
||||
};
|
||||
assert!(server_txn.delete(audit, &de_sin).is_ok());
|
||||
|
||||
// all should fail
|
||||
assert!(
|
||||
server_txn.uuid_to_rdn(audit, &tuuid)
|
||||
== Ok("uuid=cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string())
|
||||
);
|
||||
|
||||
assert!(server_txn.uuid_to_spn(audit, &tuuid) == Ok(None));
|
||||
|
||||
assert!(server_txn.name_to_uuid(audit, "testperson1").is_err());
|
||||
|
||||
// revive
|
||||
let admin = server_txn
|
||||
.internal_search_uuid(audit, &UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let rre_rc = unsafe {
|
||||
ReviveRecycledEvent::new_impersonate_entry(
|
||||
admin,
|
||||
filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
|
||||
)
|
||||
};
|
||||
assert!(server_txn.revive_recycled(audit, &rre_rc).is_ok());
|
||||
|
||||
// all checks pass
|
||||
|
||||
assert!(
|
||||
server_txn.uuid_to_rdn(audit, &tuuid)
|
||||
== Ok("spn=testperson1@example.com".to_string())
|
||||
);
|
||||
|
||||
assert!(
|
||||
server_txn.uuid_to_spn(audit, &tuuid)
|
||||
== Ok(Some(Value::new_spn_str("testperson1", "example.com")))
|
||||
);
|
||||
|
||||
assert!(server_txn.name_to_uuid(audit, "testperson1") == Ok(tuuid));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qs_clone_value() {
|
||||
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
|
||||
|
|
|
@ -24,6 +24,8 @@ lazy_static! {
|
|||
// | \- must not contain whitespace, @, ',', =
|
||||
// \- must not start with _
|
||||
// Them's be the rules.
|
||||
static ref NSUNIQUEID_RE: Regex =
|
||||
Regex::new("^[0-9a-fA-F]{8}-[0-9a-fA-F]{8}-[0-9a-fA-F]{8}-[0-9a-fA-F]{8}$").expect("Invalid Nsunique regex found");
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
|
@ -115,6 +117,7 @@ pub enum SyntaxType {
|
|||
SERVICE_PRINCIPLE_NAME,
|
||||
UINT32,
|
||||
CID,
|
||||
NSUNIQUEID,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for SyntaxType {
|
||||
|
@ -138,6 +141,7 @@ impl TryFrom<&str> for SyntaxType {
|
|||
"SERVICE_PRINCIPLE_NAME" => Ok(SyntaxType::SERVICE_PRINCIPLE_NAME),
|
||||
"UINT32" => Ok(SyntaxType::UINT32),
|
||||
"CID" => Ok(SyntaxType::CID),
|
||||
"NSUNIQUEID" => Ok(SyntaxType::NSUNIQUEID),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +167,7 @@ impl TryFrom<usize> for SyntaxType {
|
|||
12 => Ok(SyntaxType::UINT32),
|
||||
13 => Ok(SyntaxType::CID),
|
||||
14 => Ok(SyntaxType::UTF8STRING_INAME),
|
||||
15 => Ok(SyntaxType::NSUNIQUEID),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +191,7 @@ impl SyntaxType {
|
|||
SyntaxType::UINT32 => 12,
|
||||
SyntaxType::CID => 13,
|
||||
SyntaxType::UTF8STRING_INAME => 14,
|
||||
SyntaxType::NSUNIQUEID => 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +217,7 @@ impl fmt::Display for SyntaxType {
|
|||
SyntaxType::SERVICE_PRINCIPLE_NAME => "SERVICE_PRINCIPLE_NAME",
|
||||
SyntaxType::UINT32 => "UINT32",
|
||||
SyntaxType::CID => "CID",
|
||||
SyntaxType::NSUNIQUEID => "NSUNIQUEID",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -253,6 +260,7 @@ pub enum PartialValue {
|
|||
Spn(String, String),
|
||||
Uint32(u32),
|
||||
Cid(Cid),
|
||||
Nsuniqueid(String),
|
||||
}
|
||||
|
||||
impl PartialValue {
|
||||
|
@ -504,6 +512,17 @@ impl PartialValue {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_nsuniqueid_s(s: &str) -> Self {
|
||||
PartialValue::Nsuniqueid(s.to_lowercase())
|
||||
}
|
||||
|
||||
pub fn is_nsuniqueid(&self) -> bool {
|
||||
match self {
|
||||
PartialValue::Nsuniqueid(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_str(&self) -> Option<&str> {
|
||||
match self {
|
||||
PartialValue::Utf8(s) => Some(s.as_str()),
|
||||
|
@ -536,7 +555,10 @@ impl PartialValue {
|
|||
|
||||
pub fn get_idx_eq_key(&self) -> String {
|
||||
match &self {
|
||||
PartialValue::Utf8(s) | PartialValue::Iutf8(s) | PartialValue::Iname(s) => s.clone(),
|
||||
PartialValue::Utf8(s)
|
||||
| PartialValue::Iutf8(s)
|
||||
| PartialValue::Iname(s)
|
||||
| PartialValue::Nsuniqueid(s) => s.clone(),
|
||||
PartialValue::Refer(u) | PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
||||
PartialValue::Bool(b) => b.to_string(),
|
||||
PartialValue::Syntax(syn) => syn.to_string(),
|
||||
|
@ -1019,6 +1041,17 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_nsuniqueid_s(s: &str) -> Self {
|
||||
Value {
|
||||
pv: PartialValue::new_nsuniqueid_s(s),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_nsuniqueid(&self) -> bool {
|
||||
self.pv.is_nsuniqueid()
|
||||
}
|
||||
|
||||
pub fn contains(&self, s: &PartialValue) -> bool {
|
||||
self.pv.contains(s)
|
||||
}
|
||||
|
@ -1108,6 +1141,10 @@ impl Value {
|
|||
}),
|
||||
data: None,
|
||||
}),
|
||||
DbValueV1::NU(s) => Ok(Value {
|
||||
pv: PartialValue::Nsuniqueid(s),
|
||||
data: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1172,6 +1209,7 @@ impl Value {
|
|||
s: c.s_uuid,
|
||||
t: c.ts,
|
||||
}),
|
||||
PartialValue::Nsuniqueid(s) => DbValueV1::NU(s.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1259,7 +1297,10 @@ impl Value {
|
|||
|
||||
pub(crate) fn to_proto_string_clone(&self) -> String {
|
||||
match &self.pv {
|
||||
PartialValue::Utf8(s) | PartialValue::Iutf8(s) | PartialValue::Iname(s) => s.clone(),
|
||||
PartialValue::Utf8(s)
|
||||
| PartialValue::Iutf8(s)
|
||||
| PartialValue::Iname(s)
|
||||
| PartialValue::Nsuniqueid(s) => s.clone(),
|
||||
PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
||||
PartialValue::Bool(b) => b.to_string(),
|
||||
PartialValue::Syntax(syn) => syn.to_string(),
|
||||
|
@ -1338,15 +1379,17 @@ impl Value {
|
|||
},
|
||||
None => false,
|
||||
},
|
||||
PartialValue::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_idx_eq_keys(&self) -> Vec<String> {
|
||||
match &self.pv {
|
||||
PartialValue::Utf8(s) | PartialValue::Iutf8(s) | PartialValue::Iname(s) => {
|
||||
vec![s.clone()]
|
||||
}
|
||||
PartialValue::Utf8(s)
|
||||
| PartialValue::Iutf8(s)
|
||||
| PartialValue::Iname(s)
|
||||
| PartialValue::Nsuniqueid(s) => vec![s.clone()],
|
||||
PartialValue::Refer(u) | PartialValue::Uuid(u) => {
|
||||
vec![u.to_hyphenated_ref().to_string()]
|
||||
}
|
||||
|
@ -1537,6 +1580,23 @@ mod tests {
|
|||
assert!(val4.validate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_nsuniqueid() {
|
||||
// nsunique
|
||||
// d765e707-48e111e6-8c9ebed8-f7926cc3
|
||||
// uuid
|
||||
// d765e707-48e1-11e6-8c9e-bed8f7926cc3
|
||||
let val1 = Value::new_nsuniqueid_s("d765e707-48e111e6-8c9ebed8-f7926cc3");
|
||||
let val2 = Value::new_nsuniqueid_s("D765E707-48E111E6-8C9EBED8-F7926CC3");
|
||||
let inv1 = Value::new_nsuniqueid_s("d765e707-48e1-11e6-8c9e-bed8f7926cc3");
|
||||
let inv2 = Value::new_nsuniqueid_s("xxxx");
|
||||
|
||||
assert!(!inv1.validate());
|
||||
assert!(!inv2.validate());
|
||||
assert!(val1.validate());
|
||||
assert!(val2.validate());
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn test_schema_syntax_json_filter() {
|
||||
|
|
Loading…
Reference in a new issue