mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-01 22:55:06 +02:00
* adding some test coverage because there was some rando panic-inducing thing * ldap constants * documenting a macro * helpful weird errors * the war on strings continues * less json more better * testing things fixing bugs * idm_domain_reset_token_key wasn't working, added a test and fixed it (we weren't testing it) * idm_domain_set_ldap_basedn - adding tests * adding testing for idm_account_credential_update_cancel_mfareg * warning of deprecation
2938 lines
109 KiB
Rust
2938 lines
109 KiB
Rust
//! [`Schema`] is one of the foundational concepts of the server. It provides a
|
|
//! set of rules to enforce that [`Entries`] ava's must be compliant to, to be
|
|
//! considered valid for commit to the database. This allows us to provide
|
|
//! requirements and structure as to what an [`Entry`] must have and may contain
|
|
//! which enables many other parts to function.
|
|
//!
|
|
//! To define this structure we define [`Attributes`] that provide rules for how
|
|
//! and ava should be structured. We also define [`Classes`] that define
|
|
//! the rules of which [`Attributes`] may or must exist on an [`Entry`] for it
|
|
//! to be considered valid. An [`Entry`] must have at least 1 to infinite
|
|
//! [`Classes`]. [`Classes'] are additive.
|
|
//!
|
|
//! [`Schema`]: struct.Schema.html
|
|
//! [`Entries`]: ../entry/index.html
|
|
//! [`Entry`]: ../entry/index.html
|
|
//! [`Attributes`]: struct.SchemaAttribute.html
|
|
//! [`Classes`]: struct.SchemaClass.html
|
|
|
|
use std::collections::BTreeSet;
|
|
|
|
use concread::cowcell::*;
|
|
use hashbrown::{HashMap, HashSet};
|
|
use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
|
|
use tracing::trace;
|
|
use uuid::Uuid;
|
|
|
|
use crate::be::IdxKey;
|
|
use crate::prelude::*;
|
|
use crate::valueset::ValueSet;
|
|
|
|
// representations of schema that confines object types, classes
|
|
// and attributes. This ties in deeply with "Entry".
|
|
//
|
|
// In the future this will parse/read it's schema from the db
|
|
// but we have to bootstrap with some core types.
|
|
|
|
/// Schema stores the set of [`Classes`] and [`Attributes`] that the server will
|
|
/// use to validate [`Entries`], [`Filters`] and [`Modifications`]. Additionally the
|
|
/// schema stores an extracted copy of the current attribute indexing metadata that
|
|
/// is used by the backend during queries.
|
|
///
|
|
/// [`Filters`]: ../filter/index.html
|
|
/// [`Modifications`]: ../modify/index.html
|
|
/// [`Entries`]: ../entry/index.html
|
|
/// [`Attributes`]: struct.SchemaAttribute.html
|
|
/// [`Classes`]: struct.SchemaClass.html
|
|
pub struct Schema {
|
|
classes: CowCell<HashMap<AttrString, SchemaClass>>,
|
|
attributes: CowCell<HashMap<AttrString, SchemaAttribute>>,
|
|
unique_cache: CowCell<Vec<AttrString>>,
|
|
ref_cache: CowCell<HashMap<AttrString, SchemaAttribute>>,
|
|
}
|
|
|
|
/// A writable transaction of the working schema set. You should not change this directly,
|
|
/// the writability is for the server internally to allow reloading of the schema. Changes
|
|
/// you make will be lost when the server re-reads the schema from disk.
|
|
pub struct SchemaWriteTransaction<'a> {
|
|
classes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaClass>>,
|
|
attributes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaAttribute>>,
|
|
|
|
unique_cache: CowCellWriteTxn<'a, Vec<AttrString>>,
|
|
ref_cache: CowCellWriteTxn<'a, HashMap<AttrString, SchemaAttribute>>,
|
|
}
|
|
|
|
/// A readonly transaction of the working schema set.
|
|
pub struct SchemaReadTransaction {
|
|
classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
|
|
attributes: CowCellReadTxn<HashMap<AttrString, SchemaAttribute>>,
|
|
|
|
unique_cache: CowCellReadTxn<Vec<AttrString>>,
|
|
ref_cache: CowCellReadTxn<HashMap<AttrString, SchemaAttribute>>,
|
|
}
|
|
|
|
/// An item representing an attribute and the rules that enforce it. These rules enforce if an
|
|
/// attribute on an [`Entry`] may be single or multi value, must be unique amongst all other types
|
|
/// of this attribute, if the attribute should be [`indexed`], and what type of data [`syntax`] it may hold.
|
|
///
|
|
/// [`Entry`]: ../entry/index.html
|
|
/// [`indexed`]: ../value/enum.IndexType.html
|
|
/// [`syntax`]: ../value/enum.SyntaxType.html
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct SchemaAttribute {
|
|
pub name: AttrString,
|
|
pub uuid: Uuid,
|
|
// Perhaps later add aliases?
|
|
pub description: String,
|
|
pub multivalue: bool,
|
|
pub unique: bool,
|
|
pub phantom: bool,
|
|
pub sync_allowed: bool,
|
|
pub replicated: bool,
|
|
pub index: Vec<IndexType>,
|
|
pub syntax: SyntaxType,
|
|
}
|
|
|
|
impl SchemaAttribute {
|
|
pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
|
|
// Convert entry to a schema attribute.
|
|
|
|
// uuid
|
|
let uuid = value.get_uuid();
|
|
|
|
// class
|
|
if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) {
|
|
admin_error!(
|
|
"class {} not present - {:?}",
|
|
EntryClass::AttributeType,
|
|
uuid
|
|
);
|
|
return Err(OperationError::InvalidSchemaState(format!(
|
|
"missing {}",
|
|
EntryClass::AttributeType
|
|
)));
|
|
}
|
|
|
|
// name
|
|
let name = value
|
|
.get_ava_single_iutf8(Attribute::AttributeName)
|
|
.map(|s| s.into())
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {:?}", Attribute::AttributeName.as_ref(), uuid);
|
|
OperationError::InvalidSchemaState("missing attributename".to_string())
|
|
})?;
|
|
// description
|
|
let description = value
|
|
.get_ava_single_utf8(Attribute::Description)
|
|
.map(|s| s.to_string())
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {}", Attribute::Description, name);
|
|
OperationError::InvalidSchemaState("missing description".to_string())
|
|
})?;
|
|
|
|
// multivalue
|
|
let multivalue = value
|
|
.get_ava_single_bool(Attribute::MultiValue)
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {}", Attribute::MultiValue, name);
|
|
OperationError::InvalidSchemaState("missing multivalue".to_string())
|
|
})?;
|
|
let unique = value
|
|
.get_ava_single_bool(Attribute::Unique)
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {}", Attribute::Unique, name);
|
|
OperationError::InvalidSchemaState("missing unique".to_string())
|
|
})?;
|
|
|
|
let phantom = value
|
|
.get_ava_single_bool(Attribute::Phantom)
|
|
.unwrap_or(false);
|
|
|
|
let sync_allowed = value
|
|
.get_ava_single_bool(Attribute::SyncAllowed)
|
|
.unwrap_or(false);
|
|
|
|
// Default, all attributes are replicated unless you opt in for them to NOT be.
|
|
// Generally this is internal to the server only, so we don't advertise it.
|
|
let replicated = value
|
|
.get_ava_single_bool(Attribute::Replicated)
|
|
.unwrap_or(true);
|
|
|
|
// index vec
|
|
// even if empty, it SHOULD be present ... (is that valid to put an empty set?)
|
|
// The get_ava_opt_index handles the optional case for us :)
|
|
let index = value.get_ava_opt_index(Attribute::Index).ok_or_else(|| {
|
|
admin_error!("invalid {} - {}", Attribute::Index, name);
|
|
OperationError::InvalidSchemaState(format!("invalid {}", Attribute::Index))
|
|
})?;
|
|
// syntax type
|
|
let syntax = value
|
|
.get_ava_single_syntax(Attribute::Syntax)
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {}", Attribute::Syntax, name);
|
|
OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax))
|
|
})?;
|
|
|
|
Ok(SchemaAttribute {
|
|
name,
|
|
uuid,
|
|
description,
|
|
multivalue,
|
|
unique,
|
|
phantom,
|
|
sync_allowed,
|
|
replicated,
|
|
index,
|
|
syntax,
|
|
})
|
|
}
|
|
|
|
// There may be a difference between a value and a filter value on complex
|
|
// types - IE a complex type may have multiple parts that are secret, but a filter
|
|
// on that may only use a single tagged attribute for example.
|
|
pub fn validate_partialvalue(&self, a: &str, v: &PartialValue) -> Result<(), SchemaError> {
|
|
let r = match self.syntax {
|
|
SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
|
|
SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
|
|
SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
|
|
SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
|
|
SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
|
|
SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
|
|
SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
|
|
SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
|
|
SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
|
|
SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
|
|
SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
|
|
SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
|
|
SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
|
|
SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
|
|
SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
|
|
SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
|
|
SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
|
|
SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
|
|
SyntaxType::Url => matches!(v, PartialValue::Url(_)),
|
|
SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
|
|
SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
|
|
SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
|
|
SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
|
|
SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
|
|
SyntaxType::DeviceKey => matches!(v, PartialValue::DeviceKey(_)),
|
|
// Allow refer types.
|
|
SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
|
|
SyntaxType::ApiToken => matches!(v, PartialValue::Refer(_)),
|
|
SyntaxType::Oauth2Session => matches!(v, PartialValue::Refer(_)),
|
|
// These are just insensitive string lookups on the hex-ified kid.
|
|
SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
|
|
SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
|
|
SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)),
|
|
SyntaxType::EcKeyPrivate => matches!(v, PartialValue::SecretValue),
|
|
// Comparing on the label.
|
|
SyntaxType::TotpSecret => matches!(v, PartialValue::Utf8(_)),
|
|
SyntaxType::AuditLogString => matches!(v, PartialValue::Utf8(_)),
|
|
SyntaxType::Image => matches!(v, PartialValue::Utf8(_)),
|
|
};
|
|
if r {
|
|
Ok(())
|
|
} else {
|
|
trace!(
|
|
?a,
|
|
?self,
|
|
?v,
|
|
"validate_partialvalue InvalidAttributeSyntax"
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
|
|
pub fn validate_value(&self, a: &str, v: &Value) -> Result<(), SchemaError> {
|
|
let r = v.validate()
|
|
&& match self.syntax {
|
|
SyntaxType::Boolean => matches!(v, Value::Bool(_)),
|
|
SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
|
|
SyntaxType::IndexId => matches!(v, Value::Index(_)),
|
|
SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
|
|
SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
|
|
SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
|
|
SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
|
|
SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
|
|
SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
|
|
SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
|
|
SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
|
|
SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
|
|
SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
|
|
SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
|
|
SyntaxType::Cid => matches!(v, Value::Cid(_)),
|
|
SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
|
|
SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
|
|
SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
|
|
SyntaxType::Url => matches!(v, Value::Url(_)),
|
|
SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
|
|
SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
|
|
SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
|
|
SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
|
|
SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
|
|
SyntaxType::DeviceKey => matches!(v, Value::DeviceKey(_, _, _)),
|
|
SyntaxType::Session => matches!(v, Value::Session(_, _)),
|
|
SyntaxType::ApiToken => matches!(v, Value::ApiToken(_, _)),
|
|
SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)),
|
|
SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
|
|
SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
|
|
SyntaxType::UiHint => matches!(v, Value::UiHint(_)),
|
|
SyntaxType::TotpSecret => matches!(v, Value::TotpSecret(_, _)),
|
|
SyntaxType::AuditLogString => matches!(v, Value::Utf8(_)),
|
|
SyntaxType::EcKeyPrivate => matches!(v, Value::EcKeyPrivate(_)),
|
|
SyntaxType::Image => matches!(v, Value::Image(_)),
|
|
};
|
|
if r {
|
|
Ok(())
|
|
} else {
|
|
trace!(
|
|
?a,
|
|
?self,
|
|
?v,
|
|
"validate_value failure - InvalidAttributeSyntax"
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
|
|
pub fn validate_ava(&self, a: &str, ava: &ValueSet) -> Result<(), SchemaError> {
|
|
trace!("Checking for valid {:?} -> {:?}", self.name, ava);
|
|
// Check multivalue
|
|
if !self.multivalue && ava.len() > 1 {
|
|
// lrequest_error!("Ava len > 1 on single value attribute!");
|
|
admin_error!("Ava len > 1 on single value attribute!");
|
|
return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
|
|
};
|
|
// If syntax, check the type is correct
|
|
let valid = self.syntax == ava.syntax();
|
|
if valid && ava.validate(self) {
|
|
Ok(())
|
|
} else {
|
|
admin_error!(
|
|
?a,
|
|
"validate_ava - InvalidAttributeSyntax for {:?}",
|
|
self.syntax
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SchemaAttribute> for EntryInitNew {
|
|
fn from(value: SchemaAttribute) -> Self {
|
|
let mut entry = EntryInitNew::new();
|
|
|
|
entry.set_ava(
|
|
Attribute::AttributeName,
|
|
vec![Value::new_iutf8(&value.name)],
|
|
);
|
|
entry.add_ava(Attribute::MultiValue, Value::Bool(value.multivalue));
|
|
// syntax
|
|
entry.set_ava(Attribute::Syntax, vec![Value::Syntax(value.syntax)]);
|
|
entry.set_ava(Attribute::Unique, vec![Value::Bool(value.unique)]);
|
|
// index
|
|
entry.set_ava(Attribute::Index, value.index.into_iter().map(Value::Index));
|
|
|
|
// class
|
|
entry.set_ava(
|
|
Attribute::Class,
|
|
vec![
|
|
EntryClass::Object.to_value(),
|
|
EntryClass::System.into(),
|
|
EntryClass::AttributeType.to_value(),
|
|
],
|
|
);
|
|
// description
|
|
entry.set_ava(
|
|
Attribute::Description,
|
|
vec![Value::new_utf8s(&value.description)],
|
|
);
|
|
// unique
|
|
// multivalue
|
|
|
|
// sync_allowed
|
|
entry.set_ava(
|
|
Attribute::SyncAllowed,
|
|
vec![Value::Bool(value.sync_allowed)],
|
|
);
|
|
|
|
// uid
|
|
entry.set_ava(Attribute::Uuid, vec![Value::Uuid(value.uuid)]);
|
|
|
|
entry
|
|
}
|
|
}
|
|
|
|
/// An item representing a class and the rules for that class. These rules enforce that an
|
|
/// [`Entry`]'s avas conform to a set of requirements, giving structure to an entry about
|
|
/// what avas must or may exist. The kanidm project provides attributes in `systemmust` and
|
|
/// `systemmay`, which can not be altered. An administrator may extend these in the `must`
|
|
/// and `may` attributes.
|
|
///
|
|
/// Classes are additive, meaning that if there are two classes, the `may` rules of both union,
|
|
/// and that if an attribute is `must` on one class, and `may` in another, the `must` rule
|
|
/// takes precedence. It is not possible to combine classes in an incompatible way due to these
|
|
/// rules.
|
|
///
|
|
/// That in mind, an entry that has one of every possible class would probably be nonsensical,
|
|
/// but the addition rules make it easy to construct and understand with concepts like [`access`]
|
|
/// controls or accounts and posix extensions.
|
|
///
|
|
/// [`Entry`]: ../entry/index.html
|
|
/// [`access`]: ../access/index.html
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct SchemaClass {
|
|
// Is this used?
|
|
// class: Vec<String>,
|
|
pub name: AttrString,
|
|
pub uuid: Uuid,
|
|
pub description: String,
|
|
pub sync_allowed: bool,
|
|
/// This allows modification of system types to be extended in custom ways
|
|
pub systemmay: Vec<AttrString>,
|
|
pub may: Vec<AttrString>,
|
|
pub systemmust: Vec<AttrString>,
|
|
pub must: Vec<AttrString>,
|
|
/// A list of classes that this extends. These are an "or", as at least one
|
|
/// of the supplementing classes must also be present. Think of this as
|
|
/// "inherits toward" or "provides". This is just as "strict" as requires but
|
|
/// operates in the opposite direction allowing a tree structure.
|
|
pub systemsupplements: Vec<AttrString>,
|
|
pub supplements: Vec<AttrString>,
|
|
/// A list of classes that can not co-exist with this item at the same time.
|
|
pub systemexcludes: Vec<AttrString>,
|
|
pub excludes: Vec<AttrString>,
|
|
}
|
|
|
|
impl SchemaClass {
|
|
pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
|
|
trace!("Converting {}", value);
|
|
// uuid
|
|
let uuid = value.get_uuid();
|
|
// Convert entry to a schema class.
|
|
if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
|
|
admin_error!("class classtype not present - {:?}", uuid);
|
|
return Err(OperationError::InvalidSchemaState(
|
|
"missing classtype".to_string(),
|
|
));
|
|
}
|
|
|
|
// name
|
|
let name = value
|
|
.get_ava_single_iutf8(Attribute::ClassName)
|
|
.map(AttrString::from)
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {:?}", Attribute::ClassName, uuid);
|
|
OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName))
|
|
})?;
|
|
// description
|
|
let description = value
|
|
.get_ava_single_utf8(Attribute::Description)
|
|
.map(String::from)
|
|
.ok_or_else(|| {
|
|
admin_error!("missing {} - {}", Attribute::Description, name);
|
|
OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description))
|
|
})?;
|
|
|
|
let sync_allowed = value
|
|
.get_ava_single_bool(Attribute::SyncAllowed)
|
|
.unwrap_or(false);
|
|
|
|
// These are all "optional" lists of strings.
|
|
let systemmay = value
|
|
.get_ava_iter_iutf8(Attribute::SystemMay)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let systemmust = value
|
|
.get_ava_iter_iutf8(Attribute::SystemMust)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let may = value
|
|
.get_ava_iter_iutf8(Attribute::May)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let must = value
|
|
.get_ava_iter_iutf8(Attribute::Must)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
|
|
let systemsupplements = value
|
|
.get_ava_iter_iutf8(Attribute::SystemSupplements)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let supplements = value
|
|
.get_ava_iter_iutf8(Attribute::Supplements)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let systemexcludes = value
|
|
.get_ava_iter_iutf8(Attribute::SystemExcludes)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
let excludes = value
|
|
.get_ava_iter_iutf8(Attribute::Excludes)
|
|
.map(|i| i.map(|v| v.into()).collect())
|
|
.unwrap_or_default();
|
|
|
|
Ok(SchemaClass {
|
|
name,
|
|
uuid,
|
|
description,
|
|
sync_allowed,
|
|
systemmay,
|
|
may,
|
|
systemmust,
|
|
must,
|
|
systemsupplements,
|
|
supplements,
|
|
systemexcludes,
|
|
excludes,
|
|
})
|
|
}
|
|
|
|
/// An iterator over the full set of attrs that may or must exist
|
|
/// on this class.
|
|
pub fn may_iter(&self) -> impl Iterator<Item = &AttrString> {
|
|
self.systemmay
|
|
.iter()
|
|
.chain(self.may.iter())
|
|
.chain(self.systemmust.iter())
|
|
.chain(self.must.iter())
|
|
}
|
|
}
|
|
|
|
impl From<SchemaClass> for EntryInitNew {
|
|
fn from(value: SchemaClass) -> Self {
|
|
let mut entry = EntryInitNew::new();
|
|
|
|
entry.set_ava(Attribute::ClassName, vec![Value::new_iutf8(&value.name)]);
|
|
|
|
// class
|
|
entry.set_ava(
|
|
Attribute::Class,
|
|
vec![
|
|
EntryClass::Object.to_value(),
|
|
EntryClass::System.into(),
|
|
EntryClass::ClassType.into(),
|
|
],
|
|
);
|
|
|
|
// description
|
|
entry.set_ava(
|
|
Attribute::Description,
|
|
vec![Value::new_utf8s(&value.description)],
|
|
);
|
|
|
|
// sync_allowed
|
|
entry.set_ava(
|
|
Attribute::SyncAllowed,
|
|
vec![Value::Bool(value.sync_allowed)],
|
|
);
|
|
|
|
// uid
|
|
entry.set_ava(Attribute::Uuid, vec![Value::Uuid(value.uuid)]);
|
|
|
|
// systemmay
|
|
if !value.systemmay.is_empty() {
|
|
entry.set_ava(
|
|
Attribute::SystemMay,
|
|
value.systemmay.iter().map(|s| Value::new_iutf8(s)),
|
|
);
|
|
}
|
|
// systemexcludes
|
|
if !value.systemexcludes.is_empty() {
|
|
entry.set_ava(
|
|
Attribute::SystemExcludes,
|
|
value.systemexcludes.iter().map(|s| Value::new_iutf8(s)),
|
|
);
|
|
}
|
|
// systemmust
|
|
if !value.systemmust.is_empty() {
|
|
entry.set_ava(
|
|
Attribute::SystemMust,
|
|
value.systemmust.iter().map(|s| Value::new_iutf8(s)),
|
|
);
|
|
}
|
|
// systemsupplements
|
|
if !value.systemsupplements.is_empty() {
|
|
entry.set_ava(
|
|
Attribute::SystemSupplements,
|
|
value.systemsupplements.iter().map(|s| Value::new_iutf8(s)),
|
|
);
|
|
}
|
|
|
|
entry
|
|
}
|
|
}
|
|
|
|
pub trait SchemaTransaction {
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
|
|
fn get_attributes(&self) -> &HashMap<AttrString, SchemaAttribute>;
|
|
|
|
fn get_attributes_unique(&self) -> &Vec<AttrString>;
|
|
fn get_reference_types(&self) -> &HashMap<AttrString, SchemaAttribute>;
|
|
|
|
fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
|
|
let mut res = Vec::new();
|
|
|
|
let class_snapshot = self.get_classes();
|
|
let attribute_snapshot = self.get_attributes();
|
|
|
|
// We need to check that every uuid is unique because during tests we aren't doing
|
|
// a disk reload, which means we were missing this and causing potential migration
|
|
// failures on upgrade.
|
|
|
|
let mut unique_uuid_set = HashSet::new();
|
|
class_snapshot
|
|
.values()
|
|
.map(|class| &class.uuid)
|
|
.chain(attribute_snapshot.values().map(|attr| &attr.uuid))
|
|
.for_each(|uuid| {
|
|
// If the set did not have this value present, true is returned.
|
|
if !unique_uuid_set.insert(uuid) {
|
|
res.push(Err(ConsistencyError::SchemaUuidNotUnique(*uuid)))
|
|
}
|
|
});
|
|
|
|
class_snapshot.values().for_each(|class| {
|
|
// report the class we are checking
|
|
class
|
|
.systemmay
|
|
.iter()
|
|
.chain(class.may.iter())
|
|
.chain(class.systemmust.iter())
|
|
.chain(class.must.iter())
|
|
.for_each(|a| {
|
|
match attribute_snapshot.get(a) {
|
|
Some(attr) => {
|
|
// We have the attribute, ensure it's not a phantom.
|
|
if attr.phantom {
|
|
res.push(Err(ConsistencyError::SchemaClassPhantomAttribute(
|
|
class.name.to_string(),
|
|
a.to_string(),
|
|
)))
|
|
}
|
|
}
|
|
None => {
|
|
// No such attr, something is missing!
|
|
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
|
|
class.name.to_string(),
|
|
a.to_string(),
|
|
)))
|
|
}
|
|
}
|
|
})
|
|
}); // end for
|
|
res
|
|
}
|
|
|
|
fn is_replicated(&self, attr: &str) -> bool {
|
|
match self.get_attributes().get(attr) {
|
|
Some(a_schema) => {
|
|
// We'll likely add more conditions here later.
|
|
// Allow items that are replicated and not phantoms
|
|
a_schema.replicated && !a_schema.phantom
|
|
}
|
|
None => {
|
|
warn!(
|
|
"Attribute {} was not found in schema during replication request",
|
|
attr
|
|
);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_multivalue(&self, attr: &str) -> Result<bool, SchemaError> {
|
|
match self.get_attributes().get(attr) {
|
|
Some(a_schema) => Ok(a_schema.multivalue),
|
|
None => {
|
|
// ladmin_error!("Attribute does not exist?!");
|
|
Err(SchemaError::InvalidAttribute(attr.to_string()))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn normalise_attr_name(&self, an: &str) -> AttrString {
|
|
// Will duplicate.
|
|
AttrString::from(an.to_lowercase())
|
|
}
|
|
|
|
fn normalise_attr_if_exists(&self, an: &str) -> Option<AttrString> {
|
|
if self.get_attributes().contains_key(an) {
|
|
Some(self.normalise_attr_name(an))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn query_attrs_difference(
|
|
&self,
|
|
prev_class: &BTreeSet<&str>,
|
|
new_class: &BTreeSet<&str>,
|
|
) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
|
|
let schema_classes = self.get_classes();
|
|
|
|
let mut invalid_classes = Vec::with_capacity(0);
|
|
|
|
let prev_attrs: BTreeSet<&str> = prev_class
|
|
.iter()
|
|
.filter_map(|cls| match schema_classes.get(*cls) {
|
|
Some(x) => Some(x.may_iter()),
|
|
None => {
|
|
admin_debug!("invalid class: {:?}", cls);
|
|
invalid_classes.push(cls.to_string());
|
|
None
|
|
}
|
|
})
|
|
// flatten all the inner iters.
|
|
.flatten()
|
|
.map(|s| s.as_str())
|
|
.collect();
|
|
|
|
if !invalid_classes.is_empty() {
|
|
return Err(SchemaError::InvalidClass(invalid_classes));
|
|
};
|
|
|
|
let new_attrs: BTreeSet<&str> = new_class
|
|
.iter()
|
|
.filter_map(|cls| match schema_classes.get(*cls) {
|
|
Some(x) => Some(x.may_iter()),
|
|
None => {
|
|
admin_debug!("invalid class: {:?}", cls);
|
|
invalid_classes.push(cls.to_string());
|
|
None
|
|
}
|
|
})
|
|
// flatten all the inner iters.
|
|
.flatten()
|
|
.map(|s| s.as_str())
|
|
.collect();
|
|
|
|
if !invalid_classes.is_empty() {
|
|
return Err(SchemaError::InvalidClass(invalid_classes));
|
|
};
|
|
|
|
let removed = prev_attrs.difference(&new_attrs).copied().collect();
|
|
let added = new_attrs.difference(&prev_attrs).copied().collect();
|
|
|
|
Ok((added, removed))
|
|
}
|
|
}
|
|
|
|
impl<'a> SchemaWriteTransaction<'a> {
|
|
// Schema probably needs to be part of the backend, so that commits are wholly atomic
|
|
// but in the current design, we need to open be first, then schema, but we have to commit be
|
|
// first, then schema to ensure that the be content matches our schema. Saying this, if your
|
|
// schema commit fails we need to roll back still .... How great are transactions.
|
|
// At the least, this is what validation is for!
|
|
pub fn commit(self) -> Result<(), OperationError> {
|
|
let SchemaWriteTransaction {
|
|
classes,
|
|
attributes,
|
|
unique_cache,
|
|
ref_cache,
|
|
} = self;
|
|
|
|
unique_cache.commit();
|
|
ref_cache.commit();
|
|
classes.commit();
|
|
attributes.commit();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn update_attributes(
|
|
&mut self,
|
|
attributetypes: Vec<SchemaAttribute>,
|
|
) -> Result<(), OperationError> {
|
|
// purge all old attributes.
|
|
self.attributes.clear();
|
|
|
|
self.unique_cache.clear();
|
|
self.ref_cache.clear();
|
|
// Update with new ones.
|
|
// Do we need to check for dups?
|
|
// No, they'll over-write each other ... but we do need name uniqueness.
|
|
attributetypes.into_iter().for_each(|a| {
|
|
// Update the unique and ref caches.
|
|
if a.syntax == SyntaxType::ReferenceUuid ||
|
|
a.syntax == SyntaxType::OauthScopeMap ||
|
|
// So that when an rs is removed we trigger removal of the sessions.
|
|
a.syntax == SyntaxType::Oauth2Session
|
|
// May not need to be a ref type since it doesn't have external links/impact?
|
|
// || a.syntax == SyntaxType::Session
|
|
{
|
|
self.ref_cache.insert(a.name.clone(), a.clone());
|
|
}
|
|
if a.unique {
|
|
self.unique_cache.push(a.name.clone());
|
|
}
|
|
// Finally insert.
|
|
self.attributes.insert(a.name.clone(), a);
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn update_classes(&mut self, classtypes: Vec<SchemaClass>) -> Result<(), OperationError> {
|
|
// purge all old attributes.
|
|
self.classes.clear();
|
|
// Update with new ones.
|
|
// Do we need to check for dups?
|
|
// No, they'll over-write each other ... but we do need name uniqueness.
|
|
classtypes.into_iter().for_each(|a| {
|
|
self.classes.insert(a.name.clone(), a);
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
pub fn to_entries(&self) -> Vec<Entry<EntryInit, EntryNew>> {
|
|
let r: Vec<_> = self
|
|
.attributes
|
|
.values()
|
|
.map(Entry::<EntryInit, EntryNew>::from)
|
|
.chain(
|
|
self.classes
|
|
.values()
|
|
.map(Entry::<EntryInit, EntryNew>::from),
|
|
)
|
|
.collect();
|
|
r
|
|
}
|
|
|
|
pub fn reload_idxmeta(&self) -> Vec<IdxKey> {
|
|
self.get_attributes()
|
|
.values()
|
|
.flat_map(|a| {
|
|
a.index.iter().map(move |itype: &IndexType| IdxKey {
|
|
attr: a.name.clone(),
|
|
itype: *itype,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "schema::generate_in_memory", skip_all)]
|
|
pub fn generate_in_memory(&mut self) -> Result<(), OperationError> {
|
|
//
|
|
self.classes.clear();
|
|
self.attributes.clear();
|
|
// Bootstrap in definitions of our own schema types
|
|
// First, add all the needed core attributes for schema parsing
|
|
self.attributes.insert(
|
|
EntryClass::Class.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Class.into(),
|
|
uuid: UUID_SCHEMA_ATTR_CLASS,
|
|
description: String::from("The set of classes defining an object"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::Presence],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Uuid.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Uuid.into(),
|
|
uuid: UUID_SCHEMA_ATTR_UUID,
|
|
description: String::from("The universal unique id of the object"),
|
|
multivalue: false,
|
|
// Uniqueness is handled by base.rs, not attrunique here due to
|
|
// needing to check recycled objects too.
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::Presence],
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SourceUuid.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SourceUuid.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SOURCE_UUID,
|
|
description: String::from(
|
|
"The universal unique id of the source object(s) which conflicted with this entry",
|
|
),
|
|
multivalue: true,
|
|
// Uniqueness is handled by base.rs, not attrunique here due to
|
|
// needing to check recycled objects too.
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::Presence],
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LastModifiedCid.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::LastModifiedCid.into(),
|
|
uuid: UUID_SCHEMA_ATTR_LAST_MOD_CID,
|
|
description: String::from("The cid of the last change to this object"),
|
|
multivalue: false,
|
|
// Uniqueness is handled by base.rs, not attrunique here due to
|
|
// needing to check recycled objects too.
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Cid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Name.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Name.into(),
|
|
uuid: UUID_SCHEMA_ATTR_NAME,
|
|
description: String::from("The shortform name of an object"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::Presence],
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Spn.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Spn.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SPN,
|
|
description: String::from(
|
|
"The Security Principal Name of an object, unique across all domain trusts",
|
|
),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::SecurityPrincipalName,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AttributeName.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AttributeName.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ATTRIBUTENAME,
|
|
description: String::from("The name of a schema attribute"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::ClassName.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::ClassName.into(),
|
|
uuid: UUID_SCHEMA_ATTR_CLASSNAME,
|
|
description: String::from("The name of a schema class"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Description.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Description.into(),
|
|
uuid: UUID_SCHEMA_ATTR_DESCRIPTION,
|
|
description: String::from("A description of an attribute, object or class"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
self.attributes.insert(Attribute::MultiValue.into(), SchemaAttribute {
|
|
name: Attribute::MultiValue.into(),
|
|
uuid: UUID_SCHEMA_ATTR_MULTIVALUE,
|
|
description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::Phantom.into(), SchemaAttribute {
|
|
name: Attribute::Phantom.into(),
|
|
uuid: UUID_SCHEMA_ATTR_PHANTOM,
|
|
description: String::from("If true, this attribute must NOT be present in any may/must sets of a class as. This represents generated attributes."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::SyncAllowed.into(), SchemaAttribute {
|
|
name: Attribute::SyncAllowed.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYNC_ALLOWED,
|
|
description: String::from("If true, this attribute or class can by synchronised by an external scim import"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::Replicated.into(), SchemaAttribute {
|
|
name: Attribute::Replicated.into(),
|
|
uuid: UUID_SCHEMA_ATTR_REPLICATED,
|
|
description: String::from("If true, this attribute or class can by replicated between nodes in the topology"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(
|
|
Attribute::Unique.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Unique.into(),
|
|
uuid: UUID_SCHEMA_ATTR_UNIQUE,
|
|
description: String::from(
|
|
"If true, this attribute must store a unique value through out the database.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Index.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Index.into(),
|
|
uuid: UUID_SCHEMA_ATTR_INDEX,
|
|
description: String::from(
|
|
"Describe the indexes to apply to instances of this attribute.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::IndexId,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Syntax.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Syntax.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYNTAX,
|
|
description: String::from(
|
|
"Describe the syntax of this attribute. This affects indexing and sorting.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::SyntaxId,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemMay.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SystemMay.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYSTEMMAY,
|
|
description: String::from(
|
|
"A list of system provided optional attributes this class can store.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::May.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::May.into(),
|
|
uuid: UUID_SCHEMA_ATTR_MAY,
|
|
description: String::from(
|
|
"A user modifiable list of optional attributes this class can store.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemMust.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SystemMust.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYSTEMMUST,
|
|
description: String::from(
|
|
"A list of system provided required attributes this class must store.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Must.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Must.into(),
|
|
uuid: UUID_SCHEMA_ATTR_MUST,
|
|
description: String::from(
|
|
"A user modifiable list of required attributes this class must store.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemSupplements.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SystemSupplements.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS,
|
|
description: String::from(
|
|
"A set of classes that this type supplements too, where this class can't exist without their presence.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Supplements.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Supplements.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SUPPLEMENTS,
|
|
description: String::from(
|
|
"A set of user modifiable classes, where this determines that at least one other type must supplement this type",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemExcludes.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SystemExcludes.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYSTEMEXCLUDES,
|
|
description: String::from(
|
|
"A set of classes that are denied presence in connection to this class",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Excludes.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Excludes.into(),
|
|
uuid: UUID_SCHEMA_ATTR_EXCLUDES,
|
|
description: String::from(
|
|
"A set of user modifiable classes that are denied presence in connection to this class",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
// SYSINFO attrs
|
|
// ACP attributes.
|
|
self.attributes.insert(
|
|
Attribute::AcpEnable.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpEnable.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_ENABLE,
|
|
description: String::from("A flag to determine if this ACP is active for application. True is enabled, and enforce. False is checked but not enforced."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpReceiver.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpReceiver.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER,
|
|
description: String::from(
|
|
"Who the ACP applies to, constraining or allowing operations.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::SubString],
|
|
syntax: SyntaxType::JsonFilter,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpReceiverGroup.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpReceiverGroup.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER_GROUP,
|
|
description: String::from(
|
|
"The group that receives this access control to allow access",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpTargetScope.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpTargetScope.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_TARGETSCOPE,
|
|
description: String::from(
|
|
"The effective targets of the ACP, IE what will be acted upon.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality, IndexType::SubString],
|
|
syntax: SyntaxType::JsonFilter,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpSearchAttr.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpSearchAttr.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR,
|
|
description: String::from(
|
|
"The attributes that may be viewed or searched by the receiver on targetscope.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpCreateClass.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpCreateClass.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_CREATE_CLASS,
|
|
description: String::from("The set of classes that can be created on a new entry."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpCreateAttr.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpCreateAttr.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_CREATE_ATTR,
|
|
description: String::from(
|
|
"The set of attribute types that can be created on an entry.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyRemovedAttr.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyRemovedAttr.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR,
|
|
description: String::from(
|
|
"The set of attribute types that could be removed or purged in a modification.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyPresentAttr.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyPresentAttr.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR,
|
|
description: String::from(
|
|
"The set of attribute types that could be added or asserted in a modification.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyClass.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyClass.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS,
|
|
description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::present operations on class."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
// MO/Member
|
|
self.attributes.insert(
|
|
Attribute::MemberOf.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::MemberOf.into(),
|
|
uuid: UUID_SCHEMA_ATTR_MEMBEROF,
|
|
description: String::from("reverse group membership of the object"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::DirectMemberOf.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::DirectMemberOf.into(),
|
|
uuid: UUID_SCHEMA_ATTR_DIRECTMEMBEROF,
|
|
description: String::from("reverse direct group membership of the object"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Member.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Member.into(),
|
|
uuid: UUID_SCHEMA_ATTR_MEMBER,
|
|
description: String::from("List of members of the group"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::DynMember.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::DynMember.into(),
|
|
uuid: UUID_SCHEMA_ATTR_DYNMEMBER,
|
|
description: String::from("List of dynamic members of the group"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: false,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
// Migration related
|
|
self.attributes.insert(
|
|
Attribute::Version.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Version.into(),
|
|
uuid: UUID_SCHEMA_ATTR_VERSION,
|
|
description: String::from(
|
|
"The systems internal migration version for provided objects",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Uint32,
|
|
},
|
|
);
|
|
// Domain for sysinfo
|
|
self.attributes.insert(
|
|
Attribute::Domain.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Domain.into(),
|
|
uuid: UUID_SCHEMA_ATTR_DOMAIN,
|
|
description: String::from("A DNS Domain name entry."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Claim.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Claim.into(),
|
|
uuid: UUID_SCHEMA_ATTR_CLAIM,
|
|
description: String::from(
|
|
"The string identifier of an extracted claim that can be filtered",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Scope.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Scope.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SCOPE,
|
|
description: String::from(
|
|
"The string identifier of a permission scope in a session",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
// External Scim Sync
|
|
self.attributes.insert(
|
|
Attribute::SyncExternalId.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SyncExternalId.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYNC_EXTERNAL_ID,
|
|
description: String::from(
|
|
"An external string ID of an entry imported from a sync agreement",
|
|
),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SyncParentUuid.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SyncParentUuid.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYNC_PARENT_UUID,
|
|
description: String::from(
|
|
"The UUID of the parent sync agreement that created this entry.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SyncClass.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::SyncClass.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SYNC_CLASS,
|
|
description: String::from("The set of classes requested by the sync client."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::PasswordImport.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::PasswordImport.into(),
|
|
uuid: UUID_SCHEMA_ATTR_PASSWORD_IMPORT,
|
|
description: String::from("An imported password hash from an external system."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: true,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::TotpImport.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::TotpImport.into(),
|
|
uuid: UUID_SCHEMA_ATTR_TOTP_IMPORT,
|
|
description: String::from("An imported totp secret from an external system."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: true,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::TotpSecret,
|
|
},
|
|
);
|
|
|
|
// LDAP Masking Phantoms
|
|
self.attributes.insert(
|
|
Attribute::Dn.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Dn.into(),
|
|
uuid: UUID_SCHEMA_ATTR_DN,
|
|
description: String::from("An LDAP Compatible DN"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EntryDn.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::EntryDn.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ENTRYDN,
|
|
description: String::from("An LDAP Compatible EntryDN"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EntryUuid.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::EntryUuid.into(),
|
|
uuid: UUID_SCHEMA_ATTR_ENTRYUUID,
|
|
description: String::from("An LDAP Compatible entryUUID"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::ObjectClass.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::ObjectClass.into(),
|
|
uuid: UUID_SCHEMA_ATTR_OBJECTCLASS,
|
|
description: String::from("An LDAP Compatible objectClass"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Cn.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Cn.into(),
|
|
uuid: UUID_SCHEMA_ATTR_CN,
|
|
description: String::from("An LDAP Compatible objectClass"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapKeys.into(), // keys
|
|
SchemaAttribute {
|
|
name: Attribute::LdapKeys.into(), // keys
|
|
uuid: UUID_SCHEMA_ATTR_KEYS,
|
|
description: String::from("An LDAP Compatible keys (ssh)"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::SshKey,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapSshPublicKey.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::LdapSshPublicKey.into(),
|
|
uuid: UUID_SCHEMA_ATTR_SSHPUBLICKEY,
|
|
description: String::from("An LDAP Compatible sshPublicKey"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::SshKey,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Email.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Email.into(),
|
|
uuid: UUID_SCHEMA_ATTR_EMAIL,
|
|
description: String::from("An LDAP Compatible email"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EmailPrimary.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::EmailPrimary.into(),
|
|
uuid: UUID_SCHEMA_ATTR_EMAILPRIMARY,
|
|
description: String::from("An LDAP Compatible primary email"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EmailAlternative.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::EmailAlternative.into(),
|
|
uuid: UUID_SCHEMA_ATTR_EMAILALTERNATIVE,
|
|
description: String::from("An LDAP Compatible alternative email"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapEmailAddress.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::LdapEmailAddress.into(),
|
|
uuid: UUID_SCHEMA_ATTR_EMAILADDRESS,
|
|
description: String::from("An LDAP Compatible emailAddress"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::UidNumber.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::UidNumber.into(),
|
|
uuid: UUID_SCHEMA_ATTR_UIDNUMBER,
|
|
description: String::from("An LDAP Compatible uidNumber."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: false,
|
|
index: vec![],
|
|
syntax: SyntaxType::Uint32,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Image.into(),
|
|
SchemaAttribute {
|
|
name: Attribute::Image.into(),
|
|
uuid: UUID_SCHEMA_ATTR_IMAGE,
|
|
description: String::from("An image for display to end users."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: true,
|
|
index: vec![],
|
|
syntax: SyntaxType::Image,
|
|
},
|
|
);
|
|
// end LDAP masking phantoms
|
|
|
|
self.classes.insert(
|
|
EntryClass::AttributeType.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AttributeType.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ATTRIBUTETYPE,
|
|
description: String::from("Definition of a schema attribute"),
|
|
systemmay: vec![
|
|
Attribute::Replicated.into(),
|
|
Attribute::Phantom.into(),
|
|
Attribute::SyncAllowed.into(),
|
|
Attribute::Index.into(),
|
|
],
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::AttributeName.into(),
|
|
Attribute::MultiValue.into(),
|
|
Attribute::Unique.into(),
|
|
Attribute::Syntax.into(),
|
|
Attribute::Description.into(),
|
|
],
|
|
systemexcludes: vec![EntryClass::ClassType.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::ClassType.into(),
|
|
SchemaClass {
|
|
name: EntryClass::ClassType.into(),
|
|
uuid: UUID_SCHEMA_CLASS_CLASSTYPE,
|
|
description: String::from("Definition of a schema classtype"),
|
|
systemmay: vec![
|
|
Attribute::SyncAllowed.into(),
|
|
Attribute::SystemMay.into(),
|
|
Attribute::May.into(),
|
|
Attribute::SystemMust.into(),
|
|
Attribute::Must.into(),
|
|
Attribute::SystemSupplements.into(),
|
|
Attribute::Supplements.into(),
|
|
Attribute::SystemExcludes.into(),
|
|
Attribute::Excludes.into(),
|
|
],
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::ClassName.into(),
|
|
Attribute::Description.into(),
|
|
],
|
|
systemexcludes: vec![Attribute::AttributeType.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::Object.into(),
|
|
SchemaClass {
|
|
name: EntryClass::Object.into(),
|
|
uuid: UUID_SCHEMA_CLASS_OBJECT,
|
|
description: String::from("A system created class that all objects must contain"),
|
|
systemmay: vec![Attribute::Description.into()],
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::Uuid.into(),
|
|
Attribute::LastModifiedCid.into(),
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::MemberOf.into(),
|
|
SchemaClass {
|
|
name: EntryClass::MemberOf.into(),
|
|
uuid: UUID_SCHEMA_CLASS_MEMBEROF,
|
|
description: String::from(
|
|
"Class that is dynamically added to recipients of memberof or directmemberof",
|
|
),
|
|
systemmay: vec![Attribute::MemberOf.into(), Attribute::DirectMemberOf.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::ExtensibleObject.into(),
|
|
SchemaClass {
|
|
name: EntryClass::ExtensibleObject.into(),
|
|
uuid: UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT,
|
|
description: String::from(
|
|
"A class type that has green hair and turns off all rules ...",
|
|
),
|
|
..Default::default()
|
|
},
|
|
);
|
|
/* These two classes are core to the entry lifecycle for recycling and tombstoning */
|
|
self.classes.insert(
|
|
EntryClass::Recycled.into(),
|
|
SchemaClass {
|
|
name: EntryClass::Recycled.into(),
|
|
uuid: UUID_SCHEMA_CLASS_RECYCLED,
|
|
description: String::from("An object that has been deleted, but still recoverable via the revive operation. Recycled objects are not modifiable, only revivable."),
|
|
.. Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::Tombstone.into(),
|
|
SchemaClass {
|
|
name: EntryClass::Tombstone.into(),
|
|
uuid: UUID_SCHEMA_CLASS_TOMBSTONE,
|
|
description: String::from("An object that is purged from the recycle bin. This is a system internal state. Tombstones have no attributes beside UUID."),
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::Uuid.into(),
|
|
],
|
|
.. Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::Conflict.into(),
|
|
SchemaClass {
|
|
name: EntryClass::Conflict.into(),
|
|
uuid: UUID_SCHEMA_CLASS_CONFLICT,
|
|
description: String::from(
|
|
"An entry representing conflicts that occurred during replication",
|
|
),
|
|
systemmust: vec![Attribute::SourceUuid.into()],
|
|
systemsupplements: vec![EntryClass::Recycled.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
// sysinfo
|
|
self.classes.insert(
|
|
EntryClass::SystemInfo.into(),
|
|
SchemaClass {
|
|
name: EntryClass::SystemInfo.into(),
|
|
uuid: UUID_SCHEMA_CLASS_SYSTEM_INFO,
|
|
description: String::from("System metadata object class"),
|
|
systemmust: vec![Attribute::Version.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
// ACP
|
|
self.classes.insert(
|
|
EntryClass::AccessControlProfile.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlProfile.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
|
|
description: String::from("System Access Control Profile Class"),
|
|
systemmay: vec![
|
|
Attribute::AcpEnable.into(),
|
|
Attribute::Description.into(),
|
|
Attribute::AcpReceiver.into(),
|
|
],
|
|
systemmust: vec![
|
|
Attribute::AcpReceiverGroup.into(),
|
|
Attribute::AcpTargetScope.into(),
|
|
Attribute::Name.into(),
|
|
],
|
|
systemsupplements: vec![
|
|
EntryClass::AccessControlSearch.into(),
|
|
EntryClass::AccessControlDelete.into(),
|
|
EntryClass::AccessControlModify.into(),
|
|
EntryClass::AccessControlCreate.into(),
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlSearch.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlSearch.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH,
|
|
description: String::from("System Access Control Search Class"),
|
|
systemmust: vec![Attribute::AcpSearchAttr.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlDelete.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlDelete.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE,
|
|
description: String::from("System Access Control DELETE Class"),
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlModify.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlModify.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY,
|
|
description: String::from("System Access Control Modify Class"),
|
|
systemmay: vec![
|
|
Attribute::AcpModifyRemovedAttr.into(),
|
|
Attribute::AcpModifyPresentAttr.into(),
|
|
Attribute::AcpModifyClass.into(),
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlCreate.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlCreate.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE,
|
|
description: String::from("System Access Control Create Class"),
|
|
systemmay: vec![
|
|
Attribute::AcpCreateClass.into(),
|
|
Attribute::AcpCreateAttr.into(),
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::System.into(),
|
|
SchemaClass {
|
|
name: EntryClass::System.into(),
|
|
uuid: UUID_SCHEMA_CLASS_SYSTEM,
|
|
description: String::from("A class denoting that a type is system generated and protected. It has special internal behaviour."),
|
|
.. Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::SyncObject.into(),
|
|
SchemaClass {
|
|
name: EntryClass::SyncObject.into(),
|
|
uuid: UUID_SCHEMA_CLASS_SYNC_OBJECT,
|
|
description: String::from("A class denoting that an entry is synchronised from an external source. This entry may not be modifiable."),
|
|
systemmust: vec![
|
|
Attribute::SyncParentUuid.into()
|
|
],
|
|
systemmay: vec![
|
|
Attribute::SyncExternalId.into(),
|
|
Attribute::SyncClass.into(),
|
|
],
|
|
.. Default::default()
|
|
},
|
|
);
|
|
|
|
let r = self.validate();
|
|
if r.is_empty() {
|
|
admin_debug!("schema validate -> passed");
|
|
Ok(())
|
|
} else {
|
|
admin_error!(err = ?r, "schema validate -> errors");
|
|
Err(OperationError::ConsistencyError(r))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> SchemaTransaction for SchemaWriteTransaction<'a> {
|
|
fn get_attributes_unique(&self) -> &Vec<AttrString> {
|
|
&self.unique_cache
|
|
}
|
|
|
|
fn get_reference_types(&self) -> &HashMap<AttrString, SchemaAttribute> {
|
|
&self.ref_cache
|
|
}
|
|
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
|
|
&self.classes
|
|
}
|
|
|
|
fn get_attributes(&self) -> &HashMap<AttrString, SchemaAttribute> {
|
|
&self.attributes
|
|
}
|
|
}
|
|
|
|
impl SchemaTransaction for SchemaReadTransaction {
|
|
fn get_attributes_unique(&self) -> &Vec<AttrString> {
|
|
&self.unique_cache
|
|
}
|
|
|
|
fn get_reference_types(&self) -> &HashMap<AttrString, SchemaAttribute> {
|
|
&self.ref_cache
|
|
}
|
|
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
|
|
&self.classes
|
|
}
|
|
|
|
fn get_attributes(&self) -> &HashMap<AttrString, SchemaAttribute> {
|
|
&self.attributes
|
|
}
|
|
}
|
|
|
|
impl Schema {
|
|
pub fn new() -> Result<Self, OperationError> {
|
|
let s = Schema {
|
|
classes: CowCell::new(HashMap::with_capacity(128)),
|
|
attributes: CowCell::new(HashMap::with_capacity(128)),
|
|
unique_cache: CowCell::new(Vec::new()),
|
|
ref_cache: CowCell::new(HashMap::with_capacity(64)),
|
|
};
|
|
// let mut sw = task::block_on(s.write());
|
|
let mut sw = s.write();
|
|
let r1 = sw.generate_in_memory();
|
|
debug_assert!(r1.is_ok());
|
|
r1?;
|
|
let r2 = sw.commit().map(|_| s);
|
|
debug_assert!(r2.is_ok());
|
|
r2
|
|
}
|
|
|
|
pub fn read(&self) -> SchemaReadTransaction {
|
|
SchemaReadTransaction {
|
|
classes: self.classes.read(),
|
|
attributes: self.attributes.read(),
|
|
unique_cache: self.unique_cache.read(),
|
|
ref_cache: self.ref_cache.read(),
|
|
}
|
|
}
|
|
|
|
pub fn write(&self) -> SchemaWriteTransaction<'_> {
|
|
SchemaWriteTransaction {
|
|
classes: self.classes.write(),
|
|
attributes: self.attributes.write(),
|
|
unique_cache: self.unique_cache.write(),
|
|
ref_cache: self.ref_cache.write(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
|
|
self.write()
|
|
}
|
|
|
|
/*
|
|
pub async fn write<'a>(&'a self) -> SchemaWriteTransaction<'a> {
|
|
SchemaWriteTransaction {
|
|
classes: self.classes.write().await,
|
|
attributes: self.attributes.write().await,
|
|
unique_cache: self.unique_cache.write().await,
|
|
ref_cache: self.ref_cache.write().await,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn write_blocking<'a>(&'a self) -> SchemaWriteTransaction<'a> {
|
|
task::block_on(self.write())
|
|
}
|
|
*/
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use kanidm_proto::v1::{ConsistencyError, SchemaError};
|
|
use uuid::Uuid;
|
|
|
|
use crate::prelude::*;
|
|
use crate::schema::{
|
|
IndexType, Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType,
|
|
};
|
|
|
|
// use crate::proto_v1::Filter as ProtoFilter;
|
|
|
|
macro_rules! validate_schema {
|
|
($sch:ident) => {{
|
|
// Turns into a result type
|
|
let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
|
|
assert!(r.is_ok());
|
|
}};
|
|
}
|
|
|
|
macro_rules! sch_from_entry_ok {
|
|
(
|
|
$e:expr,
|
|
$type:ty
|
|
) => {{
|
|
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
|
|
let ev1 = e1.into_sealed_committed();
|
|
|
|
let r1 = <$type>::try_from(&ev1);
|
|
assert!(r1.is_ok());
|
|
}};
|
|
}
|
|
|
|
macro_rules! sch_from_entry_err {
|
|
(
|
|
$e:expr,
|
|
$type:ty
|
|
) => {{
|
|
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
|
|
let ev1 = e1.into_sealed_committed();
|
|
|
|
let r1 = <$type>::try_from(&ev1);
|
|
assert!(r1.is_err());
|
|
}};
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_attribute_from_entry() {
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"unique": ["false"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"multivalue": ["false"],
|
|
"unique": ["false"],
|
|
"index": ["EQUALITY"],
|
|
"syntax": ["UTF8STRING"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"description": ["Test attr parsing"],
|
|
"multivalue": ["htouaoeu"],
|
|
"unique": ["false"],
|
|
"index": ["EQUALITY"],
|
|
"syntax": ["UTF8STRING"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"description": ["Test attr parsing"],
|
|
"multivalue": ["false"],
|
|
"unique": ["false"],
|
|
"index": ["NTEHNOU"],
|
|
"syntax": ["UTF8STRING"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"description": ["Test attr parsing"],
|
|
"multivalue": ["false"],
|
|
"unique": ["false"],
|
|
"index": ["EQUALITY"],
|
|
"syntax": ["TNEOUNTUH"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
// Index is allowed to be empty
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"description": ["Test attr parsing"],
|
|
"multivalue": ["false"],
|
|
"unique": ["false"],
|
|
"syntax": ["UTF8STRING"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
|
|
// Index present
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "attributetype"],
|
|
"attributename": ["schema_attr_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"description": ["Test attr parsing"],
|
|
"multivalue": ["false"],
|
|
"unique": ["false"],
|
|
"index": ["EQUALITY"],
|
|
"syntax": ["UTF8STRING"]
|
|
}
|
|
}"#,
|
|
SchemaAttribute
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_class_from_entry() {
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
// Classes can be valid with no attributes provided.
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
// Classes with various may/must
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"systemmust": ["d"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"systemmay": ["c"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"may": ["a"],
|
|
"must": ["b"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["object", "classtype"],
|
|
"classname": ["schema_class_test"],
|
|
"description": ["class test"],
|
|
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
|
|
"may": ["a"],
|
|
"must": ["b"],
|
|
"systemmay": ["c"],
|
|
"systemmust": ["d"]
|
|
}
|
|
}"#,
|
|
SchemaClass
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_attribute_simple() {
|
|
// Test schemaAttribute validation of types.
|
|
|
|
// Test single value string
|
|
let single_value_string = SchemaAttribute {
|
|
name: AttrString::from("single_value"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
..Default::default()
|
|
};
|
|
|
|
let r1 = single_value_string.validate_ava("single_value", &(vs_iutf8!["test"] as _));
|
|
assert_eq!(r1, Ok(()));
|
|
|
|
let rvs = vs_iutf8!["test1", "test2"] as _;
|
|
let r2 = single_value_string.validate_ava("single_value", &rvs);
|
|
assert_eq!(
|
|
r2,
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"single_value".to_string()
|
|
))
|
|
);
|
|
|
|
// test multivalue string, boolean
|
|
|
|
let multi_value_string = SchemaAttribute {
|
|
name: AttrString::from("mv_string"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
multivalue: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Utf8String,
|
|
..Default::default()
|
|
};
|
|
|
|
let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
|
|
let r5 = multi_value_string.validate_ava("mv_string", &rvs);
|
|
assert_eq!(r5, Ok(()));
|
|
|
|
let multi_value_boolean = SchemaAttribute {
|
|
name: AttrString::from("mv_bool"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
multivalue: true,
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::Boolean,
|
|
..Default::default()
|
|
};
|
|
|
|
// Since valueset now disallows such shenangians at a type level, this can't occur
|
|
/*
|
|
let rvs = unsafe {
|
|
valueset![
|
|
Value::new_bool(true),
|
|
Value::new_iutf8("test1"),
|
|
Value::new_iutf8("test2")
|
|
]
|
|
};
|
|
let r3 = multi_value_boolean.validate_ava("mv_bool", &rvs);
|
|
assert_eq!(
|
|
r3,
|
|
Err(SchemaError::InvalidAttributeSyntax("mv_bool".to_string()))
|
|
);
|
|
*/
|
|
|
|
let rvs = vs_bool![true, false];
|
|
let r4 = multi_value_boolean.validate_ava("mv_bool", &(rvs as _));
|
|
assert_eq!(r4, Ok(()));
|
|
|
|
// syntax_id and index_type values
|
|
let single_value_syntax = SchemaAttribute {
|
|
name: AttrString::from("sv_syntax"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::SyntaxId,
|
|
..Default::default()
|
|
};
|
|
|
|
let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
|
|
let r6 = single_value_syntax.validate_ava("sv_syntax", &rvs);
|
|
assert_eq!(r6, Ok(()));
|
|
|
|
let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
|
|
let r7 = single_value_syntax.validate_ava("sv_syntax", &rvs);
|
|
assert_eq!(
|
|
r7,
|
|
Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
|
|
);
|
|
|
|
let single_value_index = SchemaAttribute {
|
|
name: AttrString::from("sv_index"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
index: vec![IndexType::Equality],
|
|
syntax: SyntaxType::IndexId,
|
|
..Default::default()
|
|
};
|
|
//
|
|
let rvs = vs_index![IndexType::try_from("EQUALITY").unwrap()] as _;
|
|
let r8 = single_value_index.validate_ava("sv_index", &rvs);
|
|
assert_eq!(r8, Ok(()));
|
|
|
|
let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
|
|
let r9 = single_value_index.validate_ava("sv_index", &rvs);
|
|
assert_eq!(
|
|
r9,
|
|
Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_simple() {
|
|
let schema = Schema::new().expect("failed to create schema");
|
|
let schema_ro = schema.read();
|
|
validate_schema!(schema_ro);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_entries() {
|
|
sketching::test_init();
|
|
// Given an entry, assert it's schema is valid
|
|
// We do
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let schema = schema_outer.read();
|
|
|
|
let e_no_uuid = entry_init!().into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_no_uuid.validate(&schema),
|
|
Err(SchemaError::MissingMustAttribute(vec![
|
|
Attribute::Uuid.to_string()
|
|
]))
|
|
);
|
|
|
|
let e_no_class = entry_init!((
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
))
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));
|
|
|
|
let e_bad_class = entry_init!(
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::Class, Value::new_class("zzzzzz"))
|
|
)
|
|
.into_invalid_new();
|
|
assert_eq!(
|
|
e_bad_class.validate(&schema),
|
|
Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
|
|
);
|
|
|
|
let e_attr_invalid = entry_init!(
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value())
|
|
)
|
|
.into_invalid_new();
|
|
let res = e_attr_invalid.validate(&schema);
|
|
assert!(match res {
|
|
Err(SchemaError::MissingMustAttribute(_)) => true,
|
|
_ => false,
|
|
});
|
|
|
|
let e_attr_invalid_may = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(Attribute::AttributeName, Value::new_iutf8("testattr")),
|
|
(Attribute::Description, Value::Utf8("testattr".to_string())),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_attr_invalid_may.validate(&schema),
|
|
Err(SchemaError::AttributeNotValidForClass(
|
|
Attribute::TestAttr.to_string()
|
|
))
|
|
);
|
|
|
|
let e_attr_invalid_syn = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(Attribute::AttributeName, Value::new_iutf8("testattr")),
|
|
(Attribute::Description, Value::Utf8("testattr".to_string())),
|
|
(Attribute::MultiValue, Value::Utf8("false".to_string())),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
)
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_attr_invalid_syn.validate(&schema),
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"multivalue".to_string()
|
|
))
|
|
);
|
|
|
|
// You may not have the phantom.
|
|
let e_phantom = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(Attribute::AttributeName, Value::new_iutf8("testattr")),
|
|
(Attribute::Description, Value::Utf8("testattr".to_string())),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(
|
|
Attribute::PasswordImport,
|
|
Value::Utf8("password".to_string())
|
|
)
|
|
)
|
|
.into_invalid_new();
|
|
assert!(e_phantom.validate(&schema).is_err());
|
|
|
|
let e_ok = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(Attribute::AttributeName, Value::new_iutf8("testattr")),
|
|
(Attribute::Description, Value::Utf8("testattr".to_string())),
|
|
(Attribute::MultiValue, Value::Bool(true)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
)
|
|
)
|
|
.into_invalid_new();
|
|
assert!(e_ok.validate(&schema).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_entry_validate() {
|
|
// Check that entries can be normalised and validated sanely
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let schema = schema_outer.write_blocking();
|
|
|
|
// Check syntax to upper
|
|
// check index to upper
|
|
// insense to lower
|
|
// attr name to lower
|
|
let e_test: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["extensibleobject"],
|
|
"attributename": ["TestPerson"],
|
|
"syntax": ["utf8string"],
|
|
"UUID": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
|
|
"InDeX": ["equality"]
|
|
}
|
|
}"#,
|
|
)
|
|
.into_invalid_new();
|
|
|
|
let e_expect: Entry<EntryValid, EntryNew> = Entry::unsafe_from_entry_str(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["extensibleobject"],
|
|
"attributename": ["testperson"],
|
|
"syntax": ["UTF8STRING"],
|
|
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
|
|
"index": ["EQUALITY"]
|
|
}
|
|
}"#,
|
|
)
|
|
.into_valid_new();
|
|
|
|
let e_valid = e_test.validate(&schema).expect("validation failure");
|
|
|
|
assert_eq!(e_expect, e_valid);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_extensible() {
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let schema = schema_outer.read();
|
|
// Just because you are extensible, doesn't mean you can be lazy
|
|
|
|
let e_extensible_bad: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["extensibleobject"],
|
|
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
|
|
"multivalue": ["zzzz"]
|
|
}
|
|
}"#,
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_extensible_bad.validate(&schema),
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"multivalue".to_string()
|
|
))
|
|
);
|
|
|
|
// Extensible doesn't mean you can have the phantoms
|
|
let e_extensible_phantom: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["extensibleobject"],
|
|
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
|
|
"password_import": ["zzzz"]
|
|
}
|
|
}"#,
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_extensible_phantom.validate(&schema),
|
|
Err(SchemaError::PhantomAttribute(
|
|
Attribute::PasswordImport.to_string()
|
|
))
|
|
);
|
|
|
|
let e_extensible: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
|
|
r#"{
|
|
"attrs": {
|
|
"class": ["extensibleobject"],
|
|
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
|
|
"multivalue": ["true"]
|
|
}
|
|
}"#,
|
|
)
|
|
.into_invalid_new();
|
|
|
|
/* Is okay because extensible! */
|
|
assert!(e_extensible.validate(&schema).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_filter_validation() {
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let schema = schema_outer.read();
|
|
|
|
// test syntax of bool
|
|
let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
|
|
assert_eq!(
|
|
f_bool.validate(&schema),
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"multivalue".to_string()
|
|
))
|
|
);
|
|
// test insensitive values
|
|
let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
|
|
assert_eq!(
|
|
f_insense.validate(&schema),
|
|
Ok(filter_valid!(f_eq(
|
|
Attribute::Class,
|
|
EntryClass::AttributeType.into()
|
|
)))
|
|
);
|
|
// Test the recursive structures validate
|
|
let f_or_empty = filter_all!(f_or!([]));
|
|
assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
|
|
let f_or = filter_all!(f_or!([f_eq(
|
|
Attribute::MultiValue,
|
|
PartialValue::new_iutf8("zzzz")
|
|
)]));
|
|
assert_eq!(
|
|
f_or.validate(&schema),
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"multivalue".to_string()
|
|
))
|
|
);
|
|
let f_or_mult = filter_all!(f_and!([
|
|
f_eq(Attribute::Class, EntryClass::AttributeType.into()),
|
|
f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
|
|
]));
|
|
assert_eq!(
|
|
f_or_mult.validate(&schema),
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"multivalue".to_string()
|
|
))
|
|
);
|
|
// Test mixed case attr name - this is a pass, due to normalisation
|
|
let f_or_ok = filter_all!(f_andnot(f_and!([
|
|
f_eq(Attribute::Class, EntryClass::AttributeType.into()),
|
|
f_sub(Attribute::Class, EntryClass::ClassType.into()),
|
|
f_pres(Attribute::Class)
|
|
])));
|
|
assert_eq!(
|
|
f_or_ok.validate(&schema),
|
|
Ok(filter_valid!(f_andnot(f_and!([
|
|
f_eq(Attribute::Class, EntryClass::AttributeType.into()),
|
|
f_sub(Attribute::Class, EntryClass::ClassType.into()),
|
|
f_pres(Attribute::Class)
|
|
]))))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_class_phantom_reject() {
|
|
// Check that entries can be normalised and validated sanely
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let mut schema = schema_outer.write_blocking();
|
|
|
|
assert!(schema.validate().is_empty());
|
|
|
|
// Attempt to create a class with a phantom attribute, should be refused.
|
|
let class = SchemaClass {
|
|
name: AttrString::from("testobject"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from("test object"),
|
|
systemmay: vec![Attribute::Claim.into()],
|
|
..Default::default()
|
|
};
|
|
|
|
assert!(schema.update_classes(vec![class]).is_ok());
|
|
|
|
assert!(schema.validate().len() == 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_class_exclusion_requires() {
|
|
sketching::test_init();
|
|
|
|
let schema_outer = Schema::new().expect("failed to create schema");
|
|
let mut schema = schema_outer.write_blocking();
|
|
|
|
assert!(schema.validate().is_empty());
|
|
|
|
// We setup some classes that have requires and excludes and check that they
|
|
// are enforced correctly.
|
|
let class_account = SchemaClass {
|
|
name: Attribute::Account.into(),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from("account object"),
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::Uuid.into(),
|
|
Attribute::LastModifiedCid.into(),
|
|
],
|
|
systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
|
|
..Default::default()
|
|
};
|
|
|
|
let class_person = SchemaClass {
|
|
name: EntryClass::Person.into(),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from("person object"),
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::Uuid.into(),
|
|
Attribute::LastModifiedCid.into(),
|
|
],
|
|
..Default::default()
|
|
};
|
|
|
|
let class_service = SchemaClass {
|
|
name: EntryClass::Service.into(),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from("service object"),
|
|
systemmust: vec![
|
|
Attribute::Class.into(),
|
|
Attribute::Uuid.into(),
|
|
Attribute::LastModifiedCid.into(),
|
|
],
|
|
excludes: vec![EntryClass::Person.into()],
|
|
..Default::default()
|
|
};
|
|
|
|
assert!(schema
|
|
.update_classes(vec![class_account, class_person, class_service])
|
|
.is_ok());
|
|
|
|
// Missing person or service account.
|
|
let e_account = entry_init!(
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_account.validate(&schema),
|
|
Err(SchemaError::SupplementsNotSatisfied(vec![
|
|
EntryClass::Service.into(),
|
|
EntryClass::Person.into(),
|
|
]))
|
|
);
|
|
|
|
// Service account missing account
|
|
/*
|
|
let e_service = unsafe { entry_init!(
|
|
(Attribute::Class, EntryClass::Service.to_value()),
|
|
(Attribute::Uuid, Value::new_uuid(Uuid::new_v4()))
|
|
).into_invalid_new() };
|
|
|
|
assert_eq!(
|
|
e_service.validate(&schema),
|
|
Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()]))
|
|
);
|
|
*/
|
|
|
|
// Service can't have person
|
|
let e_service_person = entry_init!(
|
|
(Attribute::Class, EntryClass::Service.to_value()),
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_service_person.validate(&schema),
|
|
Err(SchemaError::ExcludesNotSatisfied(
|
|
vec![EntryClass::Person.to_string()]
|
|
))
|
|
);
|
|
|
|
// These are valid configurations.
|
|
let e_service_valid = entry_init!(
|
|
(Attribute::Class, EntryClass::Service.to_value()),
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert!(e_service_valid.validate(&schema).is_ok());
|
|
|
|
let e_person_valid = entry_init!(
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert!(e_person_valid.validate(&schema).is_ok());
|
|
|
|
let e_person_valid = entry_init!(
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert!(e_person_valid.validate(&schema).is_ok());
|
|
}
|
|
}
|