mirror of
https://github.com/kanidm/kanidm.git
synced 2025-04-21 09:45:39 +02:00
Removes the protected plugin into an access control module so that it's outputs can be properly represented in effective access checks.
3101 lines
117 KiB
Rust
3101 lines
117 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 crate::be::IdxKey;
|
|
use crate::prelude::*;
|
|
use crate::valueset::ValueSet;
|
|
use concread::cowcell::*;
|
|
use hashbrown::{HashMap, HashSet};
|
|
use std::collections::BTreeSet;
|
|
use tracing::trace;
|
|
use uuid::Uuid;
|
|
|
|
// 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<Attribute, SchemaAttribute>>,
|
|
unique_cache: CowCell<Vec<Attribute>>,
|
|
ref_cache: CowCell<HashMap<Attribute, 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<Attribute, SchemaAttribute>>,
|
|
|
|
unique_cache: CowCellWriteTxn<'a, Vec<Attribute>>,
|
|
ref_cache: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
|
|
}
|
|
|
|
/// A readonly transaction of the working schema set.
|
|
pub struct SchemaReadTransaction {
|
|
classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
|
|
attributes: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
|
|
|
|
unique_cache: CowCellReadTxn<Vec<Attribute>>,
|
|
ref_cache: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub enum Replicated {
|
|
#[default]
|
|
True,
|
|
False,
|
|
}
|
|
|
|
impl From<Replicated> for bool {
|
|
fn from(value: Replicated) -> bool {
|
|
match value {
|
|
Replicated::True => true,
|
|
Replicated::False => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Replicated {
|
|
fn from(value: bool) -> Self {
|
|
match value {
|
|
true => Replicated::True,
|
|
false => Replicated::False,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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: Attribute,
|
|
pub uuid: Uuid,
|
|
pub description: String,
|
|
/// Defines if the attribute may have one or multiple values associated to it.
|
|
pub multivalue: bool,
|
|
/// If this flag is set, all instances of this attribute must be a unique value in the database.
|
|
pub unique: bool,
|
|
/// This defines that the value is a phantom - it is "not real", can never "be real". It
|
|
/// is synthesised in memory, and will never be written to the database. This can exist for
|
|
/// placeholders like cn/uid in ldap.
|
|
pub phantom: bool,
|
|
/// This boolean defines if this attribute may be altered by an external IDP sync
|
|
/// agreement.
|
|
pub sync_allowed: bool,
|
|
|
|
/// If set the value of this attribute get replicated to other servers
|
|
pub replicated: Replicated,
|
|
/// Define if this attribute is indexed or not according to its syntax type rule
|
|
pub indexed: bool,
|
|
/// THe type of data that this attribute may hold.
|
|
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, 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_default();
|
|
|
|
let sync_allowed = value
|
|
.get_ava_single_bool(Attribute::SyncAllowed)
|
|
.unwrap_or_default();
|
|
|
|
// 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)
|
|
.map(Replicated::from)
|
|
.unwrap_or_default();
|
|
|
|
let indexed = value
|
|
.get_ava_single_bool(Attribute::Indexed)
|
|
.unwrap_or_default();
|
|
|
|
// 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))
|
|
})?;
|
|
|
|
trace!(?name, ?indexed);
|
|
|
|
Ok(SchemaAttribute {
|
|
name,
|
|
uuid,
|
|
description,
|
|
multivalue,
|
|
unique,
|
|
phantom,
|
|
sync_allowed,
|
|
replicated,
|
|
indexed,
|
|
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: &Attribute,
|
|
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::OauthClaimMap => {
|
|
matches!(v, PartialValue::Iutf8(_))
|
|
|| matches!(v, PartialValue::Refer(_))
|
|
|| matches!(v, PartialValue::OauthClaimValue(_, _, _))
|
|
|| matches!(v, PartialValue::OauthClaim(_, _))
|
|
}
|
|
SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
|
|
SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
|
|
SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
|
|
SyntaxType::AttestedPasskey => matches!(v, PartialValue::AttestedPasskey(_)),
|
|
// 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(_)),
|
|
SyntaxType::CredentialType => matches!(v, PartialValue::CredentialType(_)),
|
|
|
|
SyntaxType::HexString | SyntaxType::Certificate | SyntaxType::KeyInternal => {
|
|
matches!(v, PartialValue::HexString(_))
|
|
}
|
|
|
|
SyntaxType::WebauthnAttestationCaList => false,
|
|
SyntaxType::ApplicationPassword => {
|
|
matches!(v, PartialValue::Uuid(_)) || matches!(v, PartialValue::Refer(_))
|
|
}
|
|
};
|
|
if r {
|
|
Ok(())
|
|
} else {
|
|
error!(
|
|
?a,
|
|
?self,
|
|
?v,
|
|
"validate_partialvalue InvalidAttributeSyntax"
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
|
|
pub fn validate_value(&self, a: &Attribute, 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::OauthClaimMap => {
|
|
matches!(v, Value::OauthClaimValue(_, _, _))
|
|
|| matches!(v, Value::OauthClaimMap(_, _))
|
|
}
|
|
SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
|
|
SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
|
|
SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
|
|
SyntaxType::AttestedPasskey => matches!(v, Value::AttestedPasskey(_, _, _)),
|
|
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(_)),
|
|
SyntaxType::CredentialType => matches!(v, Value::CredentialType(_)),
|
|
SyntaxType::WebauthnAttestationCaList => {
|
|
matches!(v, Value::WebauthnAttestationCaList(_))
|
|
}
|
|
SyntaxType::KeyInternal => matches!(v, Value::KeyInternal { .. }),
|
|
SyntaxType::HexString => matches!(v, Value::HexString(_)),
|
|
SyntaxType::Certificate => matches!(v, Value::Certificate(_)),
|
|
SyntaxType::ApplicationPassword => matches!(v, Value::ApplicationPassword(..)),
|
|
};
|
|
if r {
|
|
Ok(())
|
|
} else {
|
|
error!(
|
|
?a,
|
|
?self,
|
|
?v,
|
|
"validate_value failure - InvalidAttributeSyntax"
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
|
|
pub fn validate_ava(&self, a: &Attribute, 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 {
|
|
error!(
|
|
?a,
|
|
"validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
|
|
);
|
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 {
|
|
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<Attribute>,
|
|
pub may: Vec<Attribute>,
|
|
pub systemmust: Vec<Attribute>,
|
|
pub must: Vec<Attribute>,
|
|
/// 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> {
|
|
// uuid
|
|
let uuid = value.get_uuid();
|
|
// Convert entry to a schema class.
|
|
if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
|
|
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(|| {
|
|
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(|| {
|
|
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)
|
|
.into_iter()
|
|
.flat_map(|iter| iter.map(Attribute::from))
|
|
.collect();
|
|
let systemmust = value
|
|
.get_ava_iter_iutf8(Attribute::SystemMust)
|
|
.into_iter()
|
|
.flat_map(|iter| iter.map(Attribute::from))
|
|
.collect();
|
|
let may = value
|
|
.get_ava_iter_iutf8(Attribute::May)
|
|
.into_iter()
|
|
.flat_map(|iter| iter.map(Attribute::from))
|
|
.collect();
|
|
let must = value
|
|
.get_ava_iter_iutf8(Attribute::Must)
|
|
.into_iter()
|
|
.flat_map(|iter| iter.map(Attribute::from))
|
|
.collect();
|
|
|
|
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 = &Attribute> {
|
|
self.systemmay
|
|
.iter()
|
|
.chain(self.may.iter())
|
|
.chain(self.systemmust.iter())
|
|
.chain(self.must.iter())
|
|
}
|
|
}
|
|
|
|
pub trait SchemaTransaction {
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
|
|
fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute>;
|
|
|
|
fn get_attributes_unique(&self) -> &Vec<Attribute>;
|
|
fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute>;
|
|
|
|
fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
|
|
let mut res = Vec::with_capacity(0);
|
|
|
|
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: &Attribute) -> 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.into() && !a_schema.phantom
|
|
}
|
|
None => {
|
|
warn!(
|
|
"Attribute {} was not found in schema during replication request",
|
|
attr
|
|
);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_multivalue(&self, attr: &Attribute) -> 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_if_exists(&self, an: &str) -> Option<Attribute> {
|
|
let attr = Attribute::from(an);
|
|
if self.get_attributes().contains_key(&attr) {
|
|
Some(attr)
|
|
} 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 SchemaWriteTransaction<'_> {
|
|
// 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 ||
|
|
a.syntax == SyntaxType::OauthClaimMap ||
|
|
// So that when an rs is removed we trigger removal of the sessions.
|
|
a.syntax == SyntaxType::Oauth2Session ||
|
|
// When an application is removed we trigger removal of passwords
|
|
a.syntax == SyntaxType::ApplicationPassword
|
|
// 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| {
|
|
// Unique values must be indexed
|
|
if a.indexed || a.unique {
|
|
a.syntax.index_types()
|
|
} else {
|
|
&[]
|
|
}
|
|
.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(
|
|
Attribute::Class,
|
|
SchemaAttribute {
|
|
name: Attribute::Class,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Uuid,
|
|
SchemaAttribute {
|
|
name: Attribute::Uuid,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SourceUuid,
|
|
SchemaAttribute {
|
|
name: Attribute::SourceUuid,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::CreatedAtCid,
|
|
SchemaAttribute {
|
|
name: Attribute::CreatedAtCid,
|
|
uuid: UUID_SCHEMA_ATTR_CREATED_AT_CID,
|
|
description: String::from("The cid when this entry was created"),
|
|
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: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Cid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LastModifiedCid,
|
|
SchemaAttribute {
|
|
name: Attribute::LastModifiedCid,
|
|
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: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Cid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Name,
|
|
SchemaAttribute {
|
|
name: Attribute::Name,
|
|
uuid: UUID_SCHEMA_ATTR_NAME,
|
|
description: String::from("The shortform name of an object"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Spn,
|
|
SchemaAttribute {
|
|
name: Attribute::Spn,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::SecurityPrincipalName,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AttributeName,
|
|
SchemaAttribute {
|
|
name: Attribute::AttributeName,
|
|
uuid: UUID_SCHEMA_ATTR_ATTRIBUTENAME,
|
|
description: String::from("The name of a schema attribute"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::ClassName,
|
|
SchemaAttribute {
|
|
name: Attribute::ClassName,
|
|
uuid: UUID_SCHEMA_ATTR_CLASSNAME,
|
|
description: String::from("The name of a schema class"),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Description,
|
|
SchemaAttribute {
|
|
name: Attribute::Description,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
self.attributes.insert(Attribute::MultiValue, SchemaAttribute {
|
|
name: Attribute::MultiValue,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::Phantom, SchemaAttribute {
|
|
name: Attribute::Phantom,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::SyncAllowed, SchemaAttribute {
|
|
name: Attribute::SyncAllowed,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(Attribute::Replicated, SchemaAttribute {
|
|
name: Attribute::Replicated,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
});
|
|
self.attributes.insert(
|
|
Attribute::Unique,
|
|
SchemaAttribute {
|
|
name: Attribute::Unique,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Index,
|
|
SchemaAttribute {
|
|
name: Attribute::Index,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::IndexId,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Indexed,
|
|
SchemaAttribute {
|
|
name: Attribute::Indexed,
|
|
uuid: UUID_SCHEMA_ATTR_INDEXED,
|
|
description: String::from(
|
|
"A boolean stating if this attribute will be indexed according to its syntax rules."
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Syntax,
|
|
SchemaAttribute {
|
|
name: Attribute::Syntax,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::SyntaxId,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemMay,
|
|
SchemaAttribute {
|
|
name: Attribute::SystemMay,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::May,
|
|
SchemaAttribute {
|
|
name: Attribute::May,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemMust,
|
|
SchemaAttribute {
|
|
name: Attribute::SystemMust,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Must,
|
|
SchemaAttribute {
|
|
name: Attribute::Must,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemSupplements,
|
|
SchemaAttribute {
|
|
name: Attribute::SystemSupplements,
|
|
uuid: UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS,
|
|
description: String::from(
|
|
"A set of classes that this type supplements, where this class can't exist without their presence.",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Supplements,
|
|
SchemaAttribute {
|
|
name: Attribute::Supplements,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SystemExcludes,
|
|
SchemaAttribute {
|
|
name: Attribute::SystemExcludes,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Excludes,
|
|
SchemaAttribute {
|
|
name: Attribute::Excludes,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
// SYSINFO attrs
|
|
// ACP attributes.
|
|
self.attributes.insert(
|
|
Attribute::AcpEnable,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpEnable,
|
|
uuid: UUID_SCHEMA_ATTR_ACP_ENABLE,
|
|
description: String::from("A flag to determine if this ACP is active for application. True is enabled, and enforced. False is checked but not enforced."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpReceiver,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpReceiver,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::JsonFilter,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpReceiverGroup,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpReceiverGroup,
|
|
uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER_GROUP,
|
|
description: String::from(
|
|
"The group that receives this access control to allow access",
|
|
),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpTargetScope,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpTargetScope,
|
|
uuid: UUID_SCHEMA_ATTR_ACP_TARGETSCOPE,
|
|
description: String::from(
|
|
"The effective targets of the ACP, e.g. what will be acted upon.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::JsonFilter,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpSearchAttr,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpSearchAttr,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpCreateClass,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpCreateClass,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpCreateAttr,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpCreateAttr,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyRemovedAttr,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyPresentAttr,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyPresentAttr,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyClass,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyClass,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyPresentClass,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyPresentClass,
|
|
uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENT_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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::AcpModifyRemoveClass,
|
|
SchemaAttribute {
|
|
name: Attribute::AcpModifyRemoveClass,
|
|
uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVE_CLASS,
|
|
description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::remove operations on class."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EntryManagedBy,
|
|
SchemaAttribute {
|
|
name: Attribute::EntryManagedBy,
|
|
uuid: UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY,
|
|
description: String::from(
|
|
"A reference to a group that has access to manage the content of this entry.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
// MO/Member
|
|
self.attributes.insert(
|
|
Attribute::MemberOf,
|
|
SchemaAttribute {
|
|
name: Attribute::MemberOf,
|
|
uuid: UUID_SCHEMA_ATTR_MEMBEROF,
|
|
description: String::from("reverse group membership of the object"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::DirectMemberOf,
|
|
SchemaAttribute {
|
|
name: Attribute::DirectMemberOf,
|
|
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: Replicated::False,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::RecycledDirectMemberOf,
|
|
SchemaAttribute {
|
|
name: Attribute::RecycledDirectMemberOf,
|
|
uuid: UUID_SCHEMA_ATTR_RECYCLEDDIRECTMEMBEROF,
|
|
description: String::from("recycled reverse direct group membership of the object to assist in revive operations."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
// Unlike DMO this must be replicated so that on a recycle event, these groups
|
|
// "at delete" are replicated to partners. This avoids us having to replicate
|
|
// DMO which is very costly, while still retaining our ability to revive entries
|
|
// and their group memberships as a best effort.
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Member,
|
|
SchemaAttribute {
|
|
name: Attribute::Member,
|
|
uuid: UUID_SCHEMA_ATTR_MEMBER,
|
|
description: String::from("List of members of the group"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: true,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::DynMember,
|
|
SchemaAttribute {
|
|
name: Attribute::DynMember,
|
|
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: Replicated::False,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
// Migration related
|
|
self.attributes.insert(
|
|
Attribute::Version,
|
|
SchemaAttribute {
|
|
name: Attribute::Version,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Uint32,
|
|
},
|
|
);
|
|
// Domain for sysinfo
|
|
self.attributes.insert(
|
|
Attribute::Domain,
|
|
SchemaAttribute {
|
|
name: Attribute::Domain,
|
|
uuid: UUID_SCHEMA_ATTR_DOMAIN,
|
|
description: String::from("A DNS Domain name entry."),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Claim,
|
|
SchemaAttribute {
|
|
name: Attribute::Claim,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Scope,
|
|
SchemaAttribute {
|
|
name: Attribute::Scope,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
// External Scim Sync
|
|
self.attributes.insert(
|
|
Attribute::SyncExternalId,
|
|
SchemaAttribute {
|
|
name: Attribute::SyncExternalId,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SyncParentUuid,
|
|
SchemaAttribute {
|
|
name: Attribute::SyncParentUuid,
|
|
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: Replicated::True,
|
|
indexed: true,
|
|
syntax: SyntaxType::ReferenceUuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SyncClass,
|
|
SchemaAttribute {
|
|
name: Attribute::SyncClass,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::PasswordImport,
|
|
SchemaAttribute {
|
|
name: Attribute::PasswordImport,
|
|
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: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::UnixPasswordImport,
|
|
SchemaAttribute {
|
|
name: Attribute::UnixPasswordImport,
|
|
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD_IMPORT,
|
|
description: String::from(
|
|
"An imported unix password hash from an external system.",
|
|
),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: true,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::TotpImport,
|
|
SchemaAttribute {
|
|
name: Attribute::TotpImport,
|
|
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: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::TotpSecret,
|
|
},
|
|
);
|
|
|
|
// LDAP Masking Phantoms
|
|
self.attributes.insert(
|
|
Attribute::Dn,
|
|
SchemaAttribute {
|
|
name: Attribute::Dn,
|
|
uuid: UUID_SCHEMA_ATTR_DN,
|
|
description: String::from("An LDAP Compatible DN"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EntryDn,
|
|
SchemaAttribute {
|
|
name: Attribute::EntryDn,
|
|
uuid: UUID_SCHEMA_ATTR_ENTRYDN,
|
|
description: String::from("An LDAP Compatible EntryDN"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EntryUuid,
|
|
SchemaAttribute {
|
|
name: Attribute::EntryUuid,
|
|
uuid: UUID_SCHEMA_ATTR_ENTRYUUID,
|
|
description: String::from("An LDAP Compatible entryUUID"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Uuid,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::ObjectClass,
|
|
SchemaAttribute {
|
|
name: Attribute::ObjectClass,
|
|
uuid: UUID_SCHEMA_ATTR_OBJECTCLASS,
|
|
description: String::from("An LDAP Compatible objectClass"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Cn,
|
|
SchemaAttribute {
|
|
name: Attribute::Cn,
|
|
uuid: UUID_SCHEMA_ATTR_CN,
|
|
description: String::from("An LDAP Compatible objectClass"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8StringIname,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapKeys, // keys
|
|
SchemaAttribute {
|
|
name: Attribute::LdapKeys, // keys
|
|
uuid: UUID_SCHEMA_ATTR_KEYS,
|
|
description: String::from("An LDAP Compatible keys (ssh)"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::SshKey,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapSshPublicKey,
|
|
SchemaAttribute {
|
|
name: Attribute::LdapSshPublicKey,
|
|
uuid: UUID_SCHEMA_ATTR_SSHPUBLICKEY,
|
|
description: String::from("An LDAP Compatible sshPublicKey"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::SshKey,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Email,
|
|
SchemaAttribute {
|
|
name: Attribute::Email,
|
|
uuid: UUID_SCHEMA_ATTR_EMAIL,
|
|
description: String::from("An LDAP Compatible email"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EmailPrimary,
|
|
SchemaAttribute {
|
|
name: Attribute::EmailPrimary,
|
|
uuid: UUID_SCHEMA_ATTR_EMAILPRIMARY,
|
|
description: String::from("An LDAP Compatible primary email"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::EmailAlternative,
|
|
SchemaAttribute {
|
|
name: Attribute::EmailAlternative,
|
|
uuid: UUID_SCHEMA_ATTR_EMAILALTERNATIVE,
|
|
description: String::from("An LDAP Compatible alternative email"),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::LdapEmailAddress,
|
|
SchemaAttribute {
|
|
name: Attribute::LdapEmailAddress,
|
|
uuid: UUID_SCHEMA_ATTR_EMAILADDRESS,
|
|
description: String::from("An LDAP Compatible emailAddress"),
|
|
multivalue: true,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::EmailAddress,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Gecos,
|
|
SchemaAttribute {
|
|
name: Attribute::Gecos,
|
|
uuid: UUID_SCHEMA_ATTR_GECOS,
|
|
description: String::from("An LDAP Compatible gecos."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::Uid,
|
|
SchemaAttribute {
|
|
name: Attribute::Uid,
|
|
uuid: UUID_SCHEMA_ATTR_UID,
|
|
description: String::from("An LDAP Compatible uid."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::UidNumber,
|
|
SchemaAttribute {
|
|
name: Attribute::UidNumber,
|
|
uuid: UUID_SCHEMA_ATTR_UIDNUMBER,
|
|
description: String::from("An LDAP Compatible uidNumber."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Uint32,
|
|
},
|
|
);
|
|
self.attributes.insert(
|
|
Attribute::SudoHost,
|
|
SchemaAttribute {
|
|
name: Attribute::SudoHost,
|
|
uuid: UUID_SCHEMA_ATTR_SUDOHOST,
|
|
description: String::from("An LDAP Compatible sudohost."),
|
|
multivalue: false,
|
|
unique: false,
|
|
phantom: true,
|
|
sync_allowed: false,
|
|
replicated: Replicated::False,
|
|
indexed: false,
|
|
syntax: SyntaxType::Utf8String,
|
|
},
|
|
);
|
|
// end LDAP masking phantoms
|
|
self.attributes.insert(
|
|
Attribute::Image,
|
|
SchemaAttribute {
|
|
name: Attribute::Image,
|
|
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: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Image,
|
|
},
|
|
);
|
|
|
|
self.attributes.insert(
|
|
Attribute::OAuth2DeviceFlowEnable,
|
|
SchemaAttribute {
|
|
name: Attribute::OAuth2DeviceFlowEnable,
|
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
|
|
description: String::from("Enable the OAuth2 Device Flow for this client."),
|
|
multivalue: false,
|
|
unique: true,
|
|
phantom: false,
|
|
sync_allowed: false,
|
|
replicated: Replicated::True,
|
|
indexed: false,
|
|
syntax: SyntaxType::Boolean,
|
|
},
|
|
);
|
|
|
|
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,
|
|
Attribute::Phantom,
|
|
Attribute::SyncAllowed,
|
|
Attribute::Index,
|
|
Attribute::Indexed,
|
|
],
|
|
systemmust: vec![
|
|
Attribute::Class,
|
|
Attribute::AttributeName,
|
|
Attribute::MultiValue,
|
|
Attribute::Unique,
|
|
Attribute::Syntax,
|
|
Attribute::Description,
|
|
],
|
|
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,
|
|
Attribute::SystemMay,
|
|
Attribute::May,
|
|
Attribute::SystemMust,
|
|
Attribute::Must,
|
|
Attribute::SystemSupplements,
|
|
Attribute::Supplements,
|
|
Attribute::SystemExcludes,
|
|
Attribute::Excludes,
|
|
],
|
|
systemmust: vec![
|
|
Attribute::Class,
|
|
Attribute::ClassName,
|
|
Attribute::Description,
|
|
],
|
|
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, Attribute::EntryManagedBy],
|
|
systemmust: vec![
|
|
Attribute::Class,
|
|
Attribute::Uuid,
|
|
Attribute::LastModifiedCid,
|
|
Attribute::CreatedAtCid,
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::Builtin.into(),
|
|
SchemaClass {
|
|
name: EntryClass::Builtin.into(),
|
|
uuid: UUID_SCHEMA_CLASS_BUILTIN,
|
|
description: String::from("A marker class denoting builtin entries"),
|
|
..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, Attribute::DirectMemberOf],
|
|
..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."),
|
|
systemmay: vec![Attribute::RecycledDirectMemberOf],
|
|
.. 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,
|
|
Attribute::Uuid,
|
|
],
|
|
.. 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],
|
|
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],
|
|
..Default::default()
|
|
},
|
|
);
|
|
// ACP
|
|
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],
|
|
..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,
|
|
Attribute::AcpModifyPresentAttr,
|
|
Attribute::AcpModifyClass,
|
|
Attribute::AcpModifyPresentClass,
|
|
Attribute::AcpModifyRemoveClass,
|
|
],
|
|
..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, Attribute::AcpCreateAttr],
|
|
..Default::default()
|
|
},
|
|
);
|
|
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, Attribute::Description],
|
|
systemmust: vec![Attribute::Name],
|
|
systemsupplements: vec![
|
|
EntryClass::AccessControlSearch.into(),
|
|
EntryClass::AccessControlDelete.into(),
|
|
EntryClass::AccessControlModify.into(),
|
|
EntryClass::AccessControlCreate.into(),
|
|
],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlReceiverEntryManager.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlReceiverEntryManager.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER,
|
|
description: String::from("System Access Control Profile Receiver - Entry Manager"),
|
|
systemexcludes: vec![EntryClass::AccessControlReceiverGroup.into()],
|
|
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlReceiverGroup.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlReceiverGroup.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP,
|
|
description: String::from("System Access Control Profile Receiver - Group"),
|
|
systemmay: vec![Attribute::AcpReceiver],
|
|
systemmust: vec![Attribute::AcpReceiverGroup],
|
|
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
|
|
systemexcludes: vec![EntryClass::AccessControlReceiverEntryManager.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
self.classes.insert(
|
|
EntryClass::AccessControlTargetScope.into(),
|
|
SchemaClass {
|
|
name: EntryClass::AccessControlTargetScope.into(),
|
|
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE,
|
|
description: String::from("System Access Control Profile Target - Scope"),
|
|
systemmust: vec![Attribute::AcpTargetScope],
|
|
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
|
|
..Default::default()
|
|
},
|
|
);
|
|
|
|
// System attrs
|
|
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
|
|
],
|
|
systemmay: vec![
|
|
Attribute::SyncExternalId,
|
|
Attribute::SyncClass,
|
|
],
|
|
.. 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.into_iter().filter_map(|v| v.err()).collect(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SchemaTransaction for SchemaWriteTransaction<'_> {
|
|
fn get_attributes_unique(&self) -> &Vec<Attribute> {
|
|
&self.unique_cache
|
|
}
|
|
|
|
fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
|
|
&self.ref_cache
|
|
}
|
|
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
|
|
&self.classes
|
|
}
|
|
|
|
fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
|
|
&self.attributes
|
|
}
|
|
}
|
|
|
|
impl SchemaTransaction for SchemaReadTransaction {
|
|
fn get_attributes_unique(&self) -> &Vec<Attribute> {
|
|
&self.unique_cache
|
|
}
|
|
|
|
fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
|
|
&self.ref_cache
|
|
}
|
|
|
|
fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
|
|
&self.classes
|
|
}
|
|
|
|
fn get_attributes(&self) -> &HashMap<Attribute, 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::with_capacity(0)),
|
|
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()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::prelude::*;
|
|
use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
|
|
use uuid::Uuid;
|
|
|
|
// 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 ev1 = $e.into_sealed_committed();
|
|
|
|
let r1 = <$type>::try_from(&ev1);
|
|
assert!(r1.is_ok());
|
|
}};
|
|
}
|
|
|
|
macro_rules! sch_from_entry_err {
|
|
(
|
|
$e:expr,
|
|
$type:ty
|
|
) => {{
|
|
let ev1 = $e.into_sealed_committed();
|
|
|
|
let r1 = <$type>::try_from(&ev1);
|
|
assert!(r1.is_err());
|
|
}};
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_attribute_from_entry() {
|
|
sketching::test_init();
|
|
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(Attribute::Unique, Value::Bool(false))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("Test attr parsing".to_string())
|
|
),
|
|
(Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("Test attr parsing".to_string())
|
|
),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
|
|
// Index is allowed to be empty
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("Test attr parsing".to_string())
|
|
),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
|
|
// Index present
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::AttributeType.to_value()),
|
|
(
|
|
Attribute::AttributeName,
|
|
Value::new_iutf8("schema_attr_test")
|
|
),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("Test attr parsing".to_string())
|
|
),
|
|
(Attribute::MultiValue, Value::Bool(false)),
|
|
(Attribute::Unique, Value::Bool(false)),
|
|
(Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
|
|
(Attribute::Index, Value::Bool(true))
|
|
),
|
|
SchemaAttribute
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_class_from_entry() {
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
)
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_err!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
)
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
// Classes can be valid with no attributes provided.
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
)
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
// Classes with various may/must
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
),
|
|
(Attribute::SystemMust, Value::new_iutf8("a"))
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
),
|
|
(Attribute::SystemMay, Value::new_iutf8("a"))
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
),
|
|
(Attribute::May, Value::new_iutf8("a")),
|
|
(Attribute::Must, Value::new_iutf8("b"))
|
|
),
|
|
SchemaClass
|
|
);
|
|
|
|
sch_from_entry_ok!(
|
|
entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("schema_class_test")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
),
|
|
(Attribute::May, Value::new_iutf8("a")),
|
|
(Attribute::Must, Value::new_iutf8("b")),
|
|
(Attribute::SystemMay, Value::new_iutf8("c")),
|
|
(Attribute::SystemMust, Value::new_iutf8("d"))
|
|
),
|
|
SchemaClass
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_attribute_simple() {
|
|
// Test schemaAttribute validation of types.
|
|
|
|
// Test single value string
|
|
let single_value_string = SchemaAttribute {
|
|
name: Attribute::from("single_value"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
syntax: SyntaxType::Utf8StringInsensitive,
|
|
..Default::default()
|
|
};
|
|
|
|
let r1 = single_value_string
|
|
.validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
|
|
assert_eq!(r1, Ok(()));
|
|
|
|
let rvs = vs_iutf8!["test1", "test2"] as _;
|
|
let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
|
|
assert_eq!(
|
|
r2,
|
|
Err(SchemaError::InvalidAttributeSyntax(
|
|
"single_value".to_string()
|
|
))
|
|
);
|
|
|
|
// test multivalue string, boolean
|
|
|
|
let multi_value_string = SchemaAttribute {
|
|
name: Attribute::from("mv_string"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
multivalue: true,
|
|
syntax: SyntaxType::Utf8String,
|
|
..Default::default()
|
|
};
|
|
|
|
let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
|
|
let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
|
|
assert_eq!(r5, Ok(()));
|
|
|
|
let multi_value_boolean = SchemaAttribute {
|
|
name: Attribute::from("mv_bool"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
multivalue: true,
|
|
syntax: SyntaxType::Boolean,
|
|
..Default::default()
|
|
};
|
|
|
|
// Since valueset now disallows such shenanigans 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(&Attribute::from("mv_bool"), &(rvs as _));
|
|
assert_eq!(r4, Ok(()));
|
|
|
|
// syntax_id and index_type values
|
|
let single_value_syntax = SchemaAttribute {
|
|
name: Attribute::from("sv_syntax"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
syntax: SyntaxType::SyntaxId,
|
|
..Default::default()
|
|
};
|
|
|
|
let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
|
|
let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
|
|
assert_eq!(r6, Ok(()));
|
|
|
|
let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
|
|
let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
|
|
assert_eq!(
|
|
r7,
|
|
Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
|
|
);
|
|
|
|
let single_value_index = SchemaAttribute {
|
|
name: Attribute::from("sv_index"),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from(""),
|
|
syntax: SyntaxType::IndexId,
|
|
..Default::default()
|
|
};
|
|
|
|
let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
|
|
let r9 = single_value_index.validate_ava(&Attribute::from("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]))
|
|
);
|
|
|
|
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);
|
|
matches!(res, Err(SchemaError::MissingMustAttribute(_)));
|
|
|
|
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_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_init!(
|
|
(Attribute::Class, EntryClass::ExtensibleObject.to_value()),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
|
|
)
|
|
.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_init!(
|
|
(Attribute::Class, EntryClass::ExtensibleObject.to_value()),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
|
|
)
|
|
.into_invalid_new();
|
|
|
|
assert_eq!(
|
|
e_extensible_phantom.validate(&schema),
|
|
Err(SchemaError::PhantomAttribute(
|
|
Attribute::PasswordImport.to_string()
|
|
))
|
|
);
|
|
|
|
let e_extensible = entry_init!(
|
|
(Attribute::Class, EntryClass::ExtensibleObject.to_value()),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
|
|
),
|
|
(Attribute::MultiValue, Value::Bool(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],
|
|
..Default::default()
|
|
};
|
|
|
|
assert!(schema.update_classes(vec![class]).is_ok());
|
|
|
|
assert_eq!(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,
|
|
Attribute::Uuid,
|
|
Attribute::LastModifiedCid,
|
|
Attribute::CreatedAtCid,
|
|
],
|
|
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,
|
|
Attribute::Uuid,
|
|
Attribute::LastModifiedCid,
|
|
Attribute::CreatedAtCid,
|
|
],
|
|
..Default::default()
|
|
};
|
|
|
|
let class_service = SchemaClass {
|
|
name: EntryClass::Service.into(),
|
|
uuid: Uuid::new_v4(),
|
|
description: String::from("service object"),
|
|
systemmust: vec![
|
|
Attribute::Class,
|
|
Attribute::Uuid,
|
|
Attribute::LastModifiedCid,
|
|
Attribute::CreatedAtCid,
|
|
],
|
|
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());
|
|
}
|
|
}
|