kanidmd_lib/
schema.rs

1//! [`Schema`] is one of the foundational concepts of the server. It provides a
2//! set of rules to enforce that [`Entries`] ava's must be compliant to, to be
3//! considered valid for commit to the database. This allows us to provide
4//! requirements and structure as to what an [`Entry`] must have and may contain
5//! which enables many other parts to function.
6//!
7//! To define this structure we define [`Attributes`] that provide rules for how
8//! and ava should be structured. We also define [`Classes`] that define
9//! the rules of which [`Attributes`] may or must exist on an [`Entry`] for it
10//! to be considered valid. An [`Entry`] must have at least 1 to infinite
11//! [`Classes`]. [`Classes'] are additive.
12//!
13//! [`Schema`]: struct.Schema.html
14//! [`Entries`]: ../entry/index.html
15//! [`Entry`]: ../entry/index.html
16//! [`Attributes`]: struct.SchemaAttribute.html
17//! [`Classes`]: struct.SchemaClass.html
18
19use crate::be::IdxKey;
20use crate::prelude::*;
21use crate::valueset::ValueSet;
22use concread::cowcell::*;
23use hashbrown::{HashMap, HashSet};
24use std::collections::BTreeSet;
25use tracing::trace;
26use uuid::Uuid;
27
28// representations of schema that confines object types, classes
29// and attributes. This ties in deeply with "Entry".
30//
31// In the future this will parse/read it's schema from the db
32// but we have to bootstrap with some core types.
33
34/// Schema stores the set of [`Classes`] and [`Attributes`] that the server will
35/// use to validate [`Entries`], [`Filters`] and [`Modifications`]. Additionally the
36/// schema stores an extracted copy of the current attribute indexing metadata that
37/// is used by the backend during queries.
38///
39/// [`Filters`]: ../filter/index.html
40/// [`Modifications`]: ../modify/index.html
41/// [`Entries`]: ../entry/index.html
42/// [`Attributes`]: struct.SchemaAttribute.html
43/// [`Classes`]: struct.SchemaClass.html
44pub struct Schema {
45    classes: CowCell<HashMap<AttrString, SchemaClass>>,
46    attributes: CowCell<HashMap<Attribute, SchemaAttribute>>,
47    unique_cache: CowCell<Vec<Attribute>>,
48    ref_cache: CowCell<HashMap<Attribute, SchemaAttribute>>,
49}
50
51/// A writable transaction of the working schema set. You should not change this directly,
52/// the writability is for the server internally to allow reloading of the schema. Changes
53/// you make will be lost when the server re-reads the schema from disk.
54pub struct SchemaWriteTransaction<'a> {
55    classes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaClass>>,
56    attributes: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
57
58    unique_cache: CowCellWriteTxn<'a, Vec<Attribute>>,
59    ref_cache: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
60}
61
62/// A readonly transaction of the working schema set.
63pub struct SchemaReadTransaction {
64    classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
65    attributes: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
66
67    unique_cache: CowCellReadTxn<Vec<Attribute>>,
68    ref_cache: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
69}
70
71#[derive(Debug, Clone, Copy, Default)]
72pub enum Replicated {
73    #[default]
74    True,
75    False,
76}
77
78impl From<Replicated> for bool {
79    fn from(value: Replicated) -> bool {
80        match value {
81            Replicated::True => true,
82            Replicated::False => false,
83        }
84    }
85}
86
87impl From<bool> for Replicated {
88    fn from(value: bool) -> Self {
89        match value {
90            true => Replicated::True,
91            false => Replicated::False,
92        }
93    }
94}
95
96/// An item representing an attribute and the rules that enforce it. These rules enforce if an
97/// attribute on an [`Entry`] may be single or multi value, must be unique amongst all other types
98/// of this attribute, if the attribute should be [`indexed`], and what type of data [`syntax`] it may hold.
99///
100/// [`Entry`]: ../entry/index.html
101/// [`indexed`]: ../value/enum.IndexType.html
102/// [`syntax`]: ../value/enum.SyntaxType.html
103#[derive(Debug, Clone, Default)]
104pub struct SchemaAttribute {
105    pub name: Attribute,
106    pub uuid: Uuid,
107    pub description: String,
108    /// Defines if the attribute may have one or multiple values associated to it.
109    pub multivalue: bool,
110    /// If this flag is set, all instances of this attribute must be a unique value in the database.
111    pub unique: bool,
112    /// This defines that the value is a phantom - it is "not real", can never "be real". It
113    /// is synthesised in memory, and will never be written to the database. This can exist for
114    /// placeholders like cn/uid in ldap.
115    pub phantom: bool,
116    /// This boolean defines if this attribute may be altered by an external IDP sync
117    /// agreement.
118    pub sync_allowed: bool,
119
120    /// If set the value of this attribute get replicated to other servers
121    pub replicated: Replicated,
122    /// Define if this attribute is indexed or not according to its syntax type rule
123    pub indexed: bool,
124    /// THe type of data that this attribute may hold.
125    pub syntax: SyntaxType,
126}
127
128impl SchemaAttribute {
129    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
130        // Convert entry to a schema attribute.
131
132        // uuid
133        let uuid = value.get_uuid();
134
135        // class
136        if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) {
137            admin_error!(
138                "class {} not present - {:?}",
139                EntryClass::AttributeType,
140                uuid
141            );
142            return Err(OperationError::InvalidSchemaState(format!(
143                "missing {}",
144                EntryClass::AttributeType
145            )));
146        }
147
148        // name
149        let name = value
150            .get_ava_single_iutf8(Attribute::AttributeName)
151            .map(|s| s.into())
152            .ok_or_else(|| {
153                admin_error!("missing {} - {:?}", Attribute::AttributeName, uuid);
154                OperationError::InvalidSchemaState("missing attributename".to_string())
155            })?;
156        // description
157        let description = value
158            .get_ava_single_utf8(Attribute::Description)
159            .map(|s| s.to_string())
160            .ok_or_else(|| {
161                admin_error!("missing {} - {}", Attribute::Description, name);
162                OperationError::InvalidSchemaState("missing description".to_string())
163            })?;
164
165        // multivalue
166        let multivalue = value
167            .get_ava_single_bool(Attribute::MultiValue)
168            .ok_or_else(|| {
169                admin_error!("missing {} - {}", Attribute::MultiValue, name);
170                OperationError::InvalidSchemaState("missing multivalue".to_string())
171            })?;
172
173        let unique = value
174            .get_ava_single_bool(Attribute::Unique)
175            .ok_or_else(|| {
176                admin_error!("missing {} - {}", Attribute::Unique, name);
177                OperationError::InvalidSchemaState("missing unique".to_string())
178            })?;
179
180        let phantom = value
181            .get_ava_single_bool(Attribute::Phantom)
182            .unwrap_or_default();
183
184        let sync_allowed = value
185            .get_ava_single_bool(Attribute::SyncAllowed)
186            .unwrap_or_default();
187
188        // Default, all attributes are replicated unless you opt in for them to NOT be.
189        // Generally this is internal to the server only, so we don't advertise it.
190        let replicated = value
191            .get_ava_single_bool(Attribute::Replicated)
192            .map(Replicated::from)
193            .unwrap_or_default();
194
195        let indexed = value
196            .get_ava_single_bool(Attribute::Indexed)
197            .unwrap_or_default();
198
199        // syntax type
200        let syntax = value
201            .get_ava_single_syntax(Attribute::Syntax)
202            .ok_or_else(|| {
203                admin_error!("missing {} - {}", Attribute::Syntax, name);
204                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax))
205            })?;
206
207        trace!(?name, ?indexed);
208
209        Ok(SchemaAttribute {
210            name,
211            uuid,
212            description,
213            multivalue,
214            unique,
215            phantom,
216            sync_allowed,
217            replicated,
218            indexed,
219            syntax,
220        })
221    }
222
223    // There may be a difference between a value and a filter value on complex
224    // types - IE a complex type may have multiple parts that are secret, but a filter
225    // on that may only use a single tagged attribute for example.
226    pub fn validate_partialvalue(
227        &self,
228        a: &Attribute,
229        v: &PartialValue,
230    ) -> Result<(), SchemaError> {
231        let r = match self.syntax {
232            SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
233            SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
234            SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
235            SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
236            SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
237            SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
238            SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
239            SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
240            SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
241            SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
242            SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
243            SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
244            SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
245            SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
246            SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
247            SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
248            SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
249            SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
250            SyntaxType::Url => matches!(v, PartialValue::Url(_)),
251            SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
252            SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
253            SyntaxType::OauthClaimMap => {
254                matches!(v, PartialValue::Iutf8(_))
255                    || matches!(v, PartialValue::Refer(_))
256                    || matches!(v, PartialValue::OauthClaimValue(_, _, _))
257                    || matches!(v, PartialValue::OauthClaim(_, _))
258            }
259            SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
260            SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
261            SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
262            SyntaxType::AttestedPasskey => matches!(v, PartialValue::AttestedPasskey(_)),
263            // Allow refer types.
264            SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
265            SyntaxType::ApiToken => matches!(v, PartialValue::Refer(_)),
266            SyntaxType::Oauth2Session => matches!(v, PartialValue::Refer(_)),
267            // These are just insensitive string lookups on the hex-ified kid.
268            SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
269            SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
270            SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)),
271            SyntaxType::EcKeyPrivate => matches!(v, PartialValue::SecretValue),
272            // Comparing on the label.
273            SyntaxType::TotpSecret => matches!(v, PartialValue::Utf8(_)),
274            SyntaxType::AuditLogString => matches!(v, PartialValue::Utf8(_)),
275            SyntaxType::Image => matches!(v, PartialValue::Utf8(_)),
276            SyntaxType::CredentialType => matches!(v, PartialValue::CredentialType(_)),
277
278            SyntaxType::HexString | SyntaxType::Certificate | SyntaxType::KeyInternal => {
279                matches!(v, PartialValue::HexString(_))
280            }
281
282            SyntaxType::WebauthnAttestationCaList => false,
283            SyntaxType::ApplicationPassword => {
284                matches!(v, PartialValue::Uuid(_)) || matches!(v, PartialValue::Refer(_))
285            }
286            // SyntaxType::Json => matches!(v, PartialValue::Json),
287            // Should not be queried
288            SyntaxType::Json | SyntaxType::Message => false,
289        };
290        if r {
291            Ok(())
292        } else {
293            error!(
294                ?a,
295                ?self,
296                ?v,
297                "validate_partialvalue InvalidAttributeSyntax"
298            );
299            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
300        }
301    }
302
303    pub fn validate_value(&self, a: &Attribute, v: &Value) -> Result<(), SchemaError> {
304        let r = v.validate()
305            && match self.syntax {
306                SyntaxType::Boolean => matches!(v, Value::Bool(_)),
307                SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
308                SyntaxType::IndexId => matches!(v, Value::Index(_)),
309                SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
310                SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
311                SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
312                SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
313                SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
314                SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
315                SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
316                SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
317                SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
318                SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
319                SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
320                SyntaxType::Cid => matches!(v, Value::Cid(_)),
321                SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
322                SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
323                SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
324                SyntaxType::Url => matches!(v, Value::Url(_)),
325                SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
326                SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
327                SyntaxType::OauthClaimMap => {
328                    matches!(v, Value::OauthClaimValue(_, _, _))
329                        || matches!(v, Value::OauthClaimMap(_, _))
330                }
331                SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
332                SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
333                SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
334                SyntaxType::AttestedPasskey => matches!(v, Value::AttestedPasskey(_, _, _)),
335                SyntaxType::Session => matches!(v, Value::Session(_, _)),
336                SyntaxType::ApiToken => matches!(v, Value::ApiToken(_, _)),
337                SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)),
338                SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
339                SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
340                SyntaxType::UiHint => matches!(v, Value::UiHint(_)),
341                SyntaxType::TotpSecret => matches!(v, Value::TotpSecret(_, _)),
342                SyntaxType::AuditLogString => matches!(v, Value::Utf8(_)),
343                SyntaxType::EcKeyPrivate => matches!(v, Value::EcKeyPrivate(_)),
344                SyntaxType::Image => matches!(v, Value::Image(_)),
345                SyntaxType::CredentialType => matches!(v, Value::CredentialType(_)),
346                SyntaxType::WebauthnAttestationCaList => {
347                    matches!(v, Value::WebauthnAttestationCaList(_))
348                }
349                SyntaxType::KeyInternal => matches!(v, Value::KeyInternal { .. }),
350                SyntaxType::HexString => matches!(v, Value::HexString(_)),
351                SyntaxType::Certificate => matches!(v, Value::Certificate(_)),
352                SyntaxType::ApplicationPassword => matches!(v, Value::ApplicationPassword(..)),
353                SyntaxType::Json => matches!(v, Value::Json(_)),
354                SyntaxType::Message => false,
355            };
356        if r {
357            Ok(())
358        } else {
359            error!(
360                ?a,
361                ?self,
362                ?v,
363                "validate_value failure - InvalidAttributeSyntax"
364            );
365            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
366        }
367    }
368
369    pub fn validate_ava(&self, a: &Attribute, ava: &ValueSet) -> Result<(), SchemaError> {
370        trace!("Checking for valid {:?} -> {:?}", self.name, ava);
371        // Check multivalue
372        if !self.multivalue && ava.len() > 1 {
373            // lrequest_error!("Ava len > 1 on single value attribute!");
374            admin_error!("Ava len > 1 on single value attribute!");
375            return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
376        };
377        // If syntax, check the type is correct
378        let valid = self.syntax == ava.syntax();
379        if valid && ava.validate(self) {
380            Ok(())
381        } else {
382            error!(
383                ?a,
384                "validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
385            );
386            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
387        }
388    }
389}
390
391/// An item representing a class and the rules for that class. These rules enforce that an
392/// [`Entry`]'s avas conform to a set of requirements, giving structure to an entry about
393/// what avas must or may exist. The kanidm project provides attributes in `systemmust` and
394/// `systemmay`, which can not be altered. An administrator may extend these in the `must`
395/// and `may` attributes.
396///
397/// Classes are additive, meaning that if there are two classes, the `may` rules of both union,
398/// and that if an attribute is `must` on one class, and `may` in another, the `must` rule
399/// takes precedence. It is not possible to combine classes in an incompatible way due to these
400/// rules.
401///
402/// That in mind, an entry that has one of every possible class would probably be nonsensical,
403/// but the addition rules make it easy to construct and understand with concepts like [`access`]
404/// controls or accounts and posix extensions.
405///
406/// [`Entry`]: ../entry/index.html
407/// [`access`]: ../access/index.html
408#[derive(Debug, Clone, Default)]
409pub struct SchemaClass {
410    pub name: AttrString,
411    pub uuid: Uuid,
412    pub description: String,
413    pub sync_allowed: bool,
414    /// This allows modification of system types to be extended in custom ways
415    pub systemmay: Vec<Attribute>,
416    pub may: Vec<Attribute>,
417    pub systemmust: Vec<Attribute>,
418    pub must: Vec<Attribute>,
419    /// A list of classes that this extends. These are an "or", as at least one
420    /// of the supplementing classes must also be present. Think of this as
421    /// "inherits toward" or "provides". This is just as "strict" as requires but
422    /// operates in the opposite direction allowing a tree structure.
423    pub systemsupplements: Vec<AttrString>,
424    pub supplements: Vec<AttrString>,
425    /// A list of classes that can not co-exist with this item at the same time.
426    pub systemexcludes: Vec<AttrString>,
427    pub excludes: Vec<AttrString>,
428}
429
430impl SchemaClass {
431    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
432        // uuid
433        let uuid = value.get_uuid();
434        // Convert entry to a schema class.
435        if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
436            error!("class classtype not present - {:?}", uuid);
437            return Err(OperationError::InvalidSchemaState(
438                "missing classtype".to_string(),
439            ));
440        }
441
442        // name
443        let name = value
444            .get_ava_single_iutf8(Attribute::ClassName)
445            .map(AttrString::from)
446            .ok_or_else(|| {
447                error!("missing {} - {:?}", Attribute::ClassName, uuid);
448                OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName))
449            })?;
450
451        // description
452        let description = value
453            .get_ava_single_utf8(Attribute::Description)
454            .map(String::from)
455            .ok_or_else(|| {
456                error!("missing {} - {}", Attribute::Description, name);
457                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description))
458            })?;
459
460        let sync_allowed = value
461            .get_ava_single_bool(Attribute::SyncAllowed)
462            .unwrap_or(false);
463
464        // These are all "optional" lists of strings.
465        let systemmay = value
466            .get_ava_iter_iutf8(Attribute::SystemMay)
467            .into_iter()
468            .flat_map(|iter| iter.map(Attribute::from))
469            .collect();
470        let systemmust = value
471            .get_ava_iter_iutf8(Attribute::SystemMust)
472            .into_iter()
473            .flat_map(|iter| iter.map(Attribute::from))
474            .collect();
475        let may = value
476            .get_ava_iter_iutf8(Attribute::May)
477            .into_iter()
478            .flat_map(|iter| iter.map(Attribute::from))
479            .collect();
480        let must = value
481            .get_ava_iter_iutf8(Attribute::Must)
482            .into_iter()
483            .flat_map(|iter| iter.map(Attribute::from))
484            .collect();
485
486        let systemsupplements = value
487            .get_ava_iter_iutf8(Attribute::SystemSupplements)
488            .map(|i| i.map(|v| v.into()).collect())
489            .unwrap_or_default();
490        let supplements = value
491            .get_ava_iter_iutf8(Attribute::Supplements)
492            .map(|i| i.map(|v| v.into()).collect())
493            .unwrap_or_default();
494        let systemexcludes = value
495            .get_ava_iter_iutf8(Attribute::SystemExcludes)
496            .map(|i| i.map(|v| v.into()).collect())
497            .unwrap_or_default();
498        let excludes = value
499            .get_ava_iter_iutf8(Attribute::Excludes)
500            .map(|i| i.map(|v| v.into()).collect())
501            .unwrap_or_default();
502
503        Ok(SchemaClass {
504            name,
505            uuid,
506            description,
507            sync_allowed,
508            systemmay,
509            may,
510            systemmust,
511            must,
512            systemsupplements,
513            supplements,
514            systemexcludes,
515            excludes,
516        })
517    }
518
519    /// An iterator over the full set of attrs that may or must exist
520    /// on this class.
521    pub fn may_iter(&self) -> impl Iterator<Item = &Attribute> {
522        self.systemmay
523            .iter()
524            .chain(self.may.iter())
525            .chain(self.systemmust.iter())
526            .chain(self.must.iter())
527    }
528}
529
530pub trait SchemaTransaction {
531    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
532    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute>;
533
534    fn get_attributes_unique(&self) -> &Vec<Attribute>;
535    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute>;
536
537    fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
538        let mut res = Vec::with_capacity(0);
539
540        let class_snapshot = self.get_classes();
541        let attribute_snapshot = self.get_attributes();
542
543        // We need to check that every uuid is unique because during tests we aren't doing
544        // a disk reload, which means we were missing this and causing potential migration
545        // failures on upgrade.
546
547        let mut unique_uuid_set = HashSet::new();
548        class_snapshot
549            .values()
550            .map(|class| &class.uuid)
551            .chain(attribute_snapshot.values().map(|attr| &attr.uuid))
552            .for_each(|uuid| {
553                // If the set did not have this value present, true is returned.
554                if !unique_uuid_set.insert(uuid) {
555                    res.push(Err(ConsistencyError::SchemaUuidNotUnique(*uuid)))
556                }
557            });
558
559        class_snapshot.values().for_each(|class| {
560            // report the class we are checking
561            class
562                .systemmay
563                .iter()
564                .chain(class.may.iter())
565                .chain(class.systemmust.iter())
566                .chain(class.must.iter())
567                .for_each(|a| {
568                    match attribute_snapshot.get(a) {
569                        Some(attr) => {
570                            // We have the attribute, ensure it's not a phantom.
571                            if attr.phantom {
572                                res.push(Err(ConsistencyError::SchemaClassPhantomAttribute(
573                                    class.name.to_string(),
574                                    a.to_string(),
575                                )))
576                            }
577                        }
578                        None => {
579                            // No such attr, something is missing!
580                            res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
581                                class.name.to_string(),
582                                a.to_string(),
583                            )))
584                        }
585                    }
586                })
587        }); // end for
588        res
589    }
590
591    fn is_replicated(&self, attr: &Attribute) -> bool {
592        match self.get_attributes().get(attr) {
593            Some(a_schema) => {
594                // We'll likely add more conditions here later.
595                // Allow items that are replicated and not phantoms
596                a_schema.replicated.into() && !a_schema.phantom
597            }
598            None => {
599                warn!(
600                    "Attribute {} was not found in schema during replication request",
601                    attr
602                );
603                false
604            }
605        }
606    }
607
608    fn is_multivalue(&self, attr: &Attribute) -> Result<bool, SchemaError> {
609        match self.get_attributes().get(attr) {
610            Some(a_schema) => Ok(a_schema.multivalue),
611            None => {
612                // ladmin_error!("Attribute does not exist?!");
613                Err(SchemaError::InvalidAttribute(attr.to_string()))
614            }
615        }
616    }
617
618    fn normalise_attr_if_exists(&self, an: &str) -> Option<Attribute> {
619        let attr = Attribute::from(an);
620        if self.get_attributes().contains_key(&attr) {
621            Some(attr)
622        } else {
623            None
624        }
625    }
626
627    fn query_attrs_difference(
628        &self,
629        prev_class: &BTreeSet<&str>,
630        new_iutf8: &BTreeSet<&str>,
631    ) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
632        let schema_classes = self.get_classes();
633
634        let mut invalid_classes = Vec::with_capacity(0);
635
636        let prev_attrs: BTreeSet<&str> = prev_class
637            .iter()
638            .filter_map(|cls| match schema_classes.get(*cls) {
639                Some(x) => Some(x.may_iter()),
640                None => {
641                    admin_debug!("invalid class: {:?}", cls);
642                    invalid_classes.push(cls.to_string());
643                    None
644                }
645            })
646            // flatten all the inner iters.
647            .flatten()
648            .map(|s| s.as_str())
649            .collect();
650
651        if !invalid_classes.is_empty() {
652            return Err(SchemaError::InvalidClass(invalid_classes));
653        };
654
655        let new_attrs: BTreeSet<&str> = new_iutf8
656            .iter()
657            .filter_map(|cls| match schema_classes.get(*cls) {
658                Some(x) => Some(x.may_iter()),
659                None => {
660                    admin_debug!("invalid class: {:?}", cls);
661                    invalid_classes.push(cls.to_string());
662                    None
663                }
664            })
665            // flatten all the inner iters.
666            .flatten()
667            .map(|s| s.as_str())
668            .collect();
669
670        if !invalid_classes.is_empty() {
671            return Err(SchemaError::InvalidClass(invalid_classes));
672        };
673
674        let removed = prev_attrs.difference(&new_attrs).copied().collect();
675        let added = new_attrs.difference(&prev_attrs).copied().collect();
676
677        Ok((added, removed))
678    }
679}
680
681impl SchemaWriteTransaction<'_> {
682    // Schema probably needs to be part of the backend, so that commits are wholly atomic
683    // but in the current design, we need to open be first, then schema, but we have to commit be
684    // first, then schema to ensure that the be content matches our schema. Saying this, if your
685    // schema commit fails we need to roll back still .... How great are transactions.
686    // At the least, this is what validation is for!
687    pub fn commit(self) -> Result<(), OperationError> {
688        let SchemaWriteTransaction {
689            classes,
690            attributes,
691            unique_cache,
692            ref_cache,
693        } = self;
694
695        unique_cache.commit();
696        ref_cache.commit();
697        classes.commit();
698        attributes.commit();
699        Ok(())
700    }
701
702    pub fn update_attributes(
703        &mut self,
704        attributetypes: Vec<SchemaAttribute>,
705    ) -> Result<(), OperationError> {
706        // purge all old attributes.
707        self.attributes.clear();
708
709        self.unique_cache.clear();
710        self.ref_cache.clear();
711        // Update with new ones.
712        // Do we need to check for dups?
713        // No, they'll over-write each other ... but we do need name uniqueness.
714        attributetypes.into_iter().for_each(|a| {
715            // Update the unique and ref caches.
716            if a.syntax == SyntaxType::ReferenceUuid ||
717                a.syntax == SyntaxType::OauthScopeMap ||
718                a.syntax == SyntaxType::OauthClaimMap ||
719                // So that when an rs is removed we trigger removal of the sessions.
720                a.syntax == SyntaxType::Oauth2Session ||
721                // When an application is removed we trigger removal of passwords
722                a.syntax == SyntaxType::ApplicationPassword
723            // May not need to be a ref type since it doesn't have external links/impact?
724            // || a.syntax == SyntaxType::Session
725            {
726                self.ref_cache.insert(a.name.clone(), a.clone());
727            }
728            if a.unique {
729                self.unique_cache.push(a.name.clone());
730            }
731            // Finally insert.
732            self.attributes.insert(a.name.clone(), a);
733        });
734
735        Ok(())
736    }
737
738    pub fn update_classes(&mut self, classtypes: Vec<SchemaClass>) -> Result<(), OperationError> {
739        // purge all old attributes.
740        self.classes.clear();
741        // Update with new ones.
742        // Do we need to check for dups?
743        // No, they'll over-write each other ... but we do need name uniqueness.
744        classtypes.into_iter().for_each(|a| {
745            self.classes.insert(a.name.clone(), a);
746        });
747        Ok(())
748    }
749
750    pub fn to_entries(&self) -> Vec<Entry<EntryInit, EntryNew>> {
751        let r: Vec<_> = self
752            .attributes
753            .values()
754            .map(Entry::<EntryInit, EntryNew>::from)
755            .chain(
756                self.classes
757                    .values()
758                    .map(Entry::<EntryInit, EntryNew>::from),
759            )
760            .collect();
761        r
762    }
763
764    pub fn reload_idxmeta(&self) -> Vec<IdxKey> {
765        self.get_attributes()
766            .values()
767            .flat_map(|a| {
768                // Unique values must be indexed
769                if a.indexed || a.unique {
770                    a.syntax.index_types()
771                } else {
772                    &[]
773                }
774                .iter()
775                .map(move |itype: &IndexType| IdxKey {
776                    attr: a.name.clone(),
777                    itype: *itype,
778                })
779            })
780            .collect()
781    }
782
783    /// Generate the minimal in memory schema needed to begin the server bootstrap
784    /// process. This should contain the most critical schema definitions that the
785    /// server requires to be able to read in other schema objects and persist them
786    /// into our database.
787    ///
788    /// THIS IS FOR SYSTEM CRITICAL INTERNAL SCHEMA ONLY
789    ///
790    /// Schema should otherwise be in our migration data - not here.
791    #[instrument(level = "debug", name = "schema::generate_in_memory", skip_all)]
792    pub fn generate_in_memory(&mut self) -> Result<(), OperationError> {
793        self.classes.clear();
794        self.attributes.clear();
795        // Bootstrap in definitions of our own schema types
796        // First, add all the needed core attributes for schema parsing
797        self.attributes.insert(
798            Attribute::Class,
799            SchemaAttribute {
800                name: Attribute::Class,
801                uuid: UUID_SCHEMA_ATTR_CLASS,
802                description: String::from("The set of classes defining an object"),
803                multivalue: true,
804                unique: false,
805                phantom: false,
806                sync_allowed: false,
807                replicated: Replicated::True,
808                indexed: true,
809                syntax: SyntaxType::Utf8StringInsensitive,
810            },
811        );
812        self.attributes.insert(
813            Attribute::Uuid,
814            SchemaAttribute {
815                name: Attribute::Uuid,
816                uuid: UUID_SCHEMA_ATTR_UUID,
817                description: String::from("The universal unique id of the object"),
818                multivalue: false,
819                // Uniqueness is handled by base.rs, not attrunique here due to
820                // needing to check recycled objects too.
821                unique: false,
822                phantom: false,
823                sync_allowed: false,
824                replicated: Replicated::True,
825                indexed: true,
826                syntax: SyntaxType::Uuid,
827            },
828        );
829        self.attributes.insert(
830            Attribute::SourceUuid,
831            SchemaAttribute {
832                name: Attribute::SourceUuid,
833                uuid: UUID_SCHEMA_ATTR_SOURCE_UUID,
834                description: String::from(
835                    "The universal unique id of the source object(s) which conflicted with this entry",
836                ),
837                multivalue: true,
838                // Uniqueness is handled by base.rs, not attrunique here due to
839                // needing to check recycled objects too.
840                unique: false,
841                phantom: false,
842                sync_allowed: false,
843                replicated: Replicated::True,
844                indexed: true,
845                syntax: SyntaxType::Uuid,
846            },
847        );
848        self.attributes.insert(
849            Attribute::CreatedAtCid,
850            SchemaAttribute {
851                name: Attribute::CreatedAtCid,
852                uuid: UUID_SCHEMA_ATTR_CREATED_AT_CID,
853                description: String::from("The cid when this entry was created"),
854                multivalue: false,
855                // Uniqueness is handled by base.rs, not attrunique here due to
856                // needing to check recycled objects too.
857                unique: false,
858                phantom: false,
859                sync_allowed: false,
860                replicated: Replicated::False,
861                indexed: false,
862                syntax: SyntaxType::Cid,
863            },
864        );
865        self.attributes.insert(
866            Attribute::LastModifiedCid,
867            SchemaAttribute {
868                name: Attribute::LastModifiedCid,
869                uuid: UUID_SCHEMA_ATTR_LAST_MOD_CID,
870                description: String::from("The cid of the last change to this object"),
871                multivalue: false,
872                // Uniqueness is handled by base.rs, not attrunique here due to
873                // needing to check recycled objects too.
874                unique: false,
875                phantom: false,
876                sync_allowed: false,
877                replicated: Replicated::False,
878                indexed: false,
879                syntax: SyntaxType::Cid,
880            },
881        );
882        self.attributes.insert(
883            Attribute::Name,
884            SchemaAttribute {
885                name: Attribute::Name,
886                uuid: UUID_SCHEMA_ATTR_NAME,
887                description: String::from("The shortform name of an object"),
888                multivalue: false,
889                unique: true,
890                phantom: false,
891                sync_allowed: true,
892                replicated: Replicated::True,
893                indexed: true,
894                syntax: SyntaxType::Utf8StringIname,
895            },
896        );
897        self.attributes.insert(
898            Attribute::Spn,
899            SchemaAttribute {
900                name: Attribute::Spn,
901                uuid: UUID_SCHEMA_ATTR_SPN,
902                description: String::from(
903                    "The Security Principal Name of an object, unique across all domain trusts",
904                ),
905                multivalue: false,
906                unique: true,
907                phantom: false,
908                sync_allowed: false,
909                replicated: Replicated::True,
910                indexed: true,
911                syntax: SyntaxType::SecurityPrincipalName,
912            },
913        );
914        self.attributes.insert(
915            Attribute::AttributeName,
916            SchemaAttribute {
917                name: Attribute::AttributeName,
918                uuid: UUID_SCHEMA_ATTR_ATTRIBUTENAME,
919                description: String::from("The name of a schema attribute"),
920                multivalue: false,
921                unique: true,
922                phantom: false,
923                sync_allowed: false,
924                replicated: Replicated::True,
925                indexed: true,
926                syntax: SyntaxType::Utf8StringInsensitive,
927            },
928        );
929        self.attributes.insert(
930            Attribute::ClassName,
931            SchemaAttribute {
932                name: Attribute::ClassName,
933                uuid: UUID_SCHEMA_ATTR_CLASSNAME,
934                description: String::from("The name of a schema class"),
935                multivalue: false,
936                unique: true,
937                phantom: false,
938                sync_allowed: false,
939                replicated: Replicated::True,
940                indexed: true,
941                syntax: SyntaxType::Utf8StringInsensitive,
942            },
943        );
944        self.attributes.insert(
945            Attribute::Description,
946            SchemaAttribute {
947                name: Attribute::Description,
948                uuid: UUID_SCHEMA_ATTR_DESCRIPTION,
949                description: String::from("A description of an attribute, object or class"),
950                multivalue: false,
951                unique: false,
952                phantom: false,
953                sync_allowed: true,
954                replicated: Replicated::True,
955                indexed: false,
956                syntax: SyntaxType::Utf8String,
957            },
958        );
959        self.attributes.insert(Attribute::MultiValue, SchemaAttribute {
960                name: Attribute::MultiValue,
961                uuid: UUID_SCHEMA_ATTR_MULTIVALUE,
962                description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
963                multivalue: false,
964                unique: false,
965                phantom: false,
966                sync_allowed: false,
967                replicated: Replicated::True,
968                indexed: false,
969                syntax: SyntaxType::Boolean,
970            });
971        self.attributes.insert(Attribute::Phantom, SchemaAttribute {
972                name: Attribute::Phantom,
973                uuid: UUID_SCHEMA_ATTR_PHANTOM,
974                description: String::from("If true, this attribute must NOT be present in any may/must sets of a class as. This represents generated attributes."),
975                multivalue: false,
976                unique: false,
977                phantom: false,
978                sync_allowed: false,
979                replicated: Replicated::True,
980                indexed: false,
981                syntax: SyntaxType::Boolean,
982            });
983        self.attributes.insert(Attribute::SyncAllowed, SchemaAttribute {
984                name: Attribute::SyncAllowed,
985                uuid: UUID_SCHEMA_ATTR_SYNC_ALLOWED,
986                description: String::from("If true, this attribute or class can by synchronised by an external scim import"),
987                multivalue: false,
988                unique: false,
989                phantom: false,
990                sync_allowed: false,
991                replicated: Replicated::True,
992                indexed: false,
993                syntax: SyntaxType::Boolean,
994            });
995        self.attributes.insert(Attribute::Replicated, SchemaAttribute {
996                name: Attribute::Replicated,
997                uuid: UUID_SCHEMA_ATTR_REPLICATED,
998                description: String::from("If true, this attribute or class can by replicated between nodes in the topology"),
999                multivalue: false,
1000                unique: false,
1001                phantom: false,
1002                sync_allowed: false,
1003                replicated: Replicated::True,
1004                indexed: false,
1005                syntax: SyntaxType::Boolean,
1006            });
1007        self.attributes.insert(
1008            Attribute::Unique,
1009            SchemaAttribute {
1010                name: Attribute::Unique,
1011                uuid: UUID_SCHEMA_ATTR_UNIQUE,
1012                description: String::from(
1013                    "If true, this attribute must store a unique value through out the database.",
1014                ),
1015                multivalue: false,
1016                unique: false,
1017                phantom: false,
1018                sync_allowed: false,
1019                replicated: Replicated::True,
1020                indexed: false,
1021                syntax: SyntaxType::Boolean,
1022            },
1023        );
1024        self.attributes.insert(
1025            Attribute::Index,
1026            SchemaAttribute {
1027                name: Attribute::Index,
1028                uuid: UUID_SCHEMA_ATTR_INDEX,
1029                description: String::from(
1030                    "Describe the indexes to apply to instances of this attribute.",
1031                ),
1032                multivalue: true,
1033                unique: false,
1034                phantom: false,
1035                sync_allowed: false,
1036                replicated: Replicated::True,
1037                indexed: false,
1038                syntax: SyntaxType::IndexId,
1039            },
1040        );
1041        self.attributes.insert(
1042            Attribute::Indexed,
1043            SchemaAttribute {
1044                name: Attribute::Indexed,
1045                uuid: UUID_SCHEMA_ATTR_INDEXED,
1046                description: String::from(
1047                    "A boolean stating if this attribute will be indexed according to its syntax rules."
1048                ),
1049                multivalue: false,
1050                unique: false,
1051                phantom: false,
1052                sync_allowed: false,
1053                replicated: Replicated::True,
1054                indexed: false,
1055                syntax: SyntaxType::Boolean,
1056            },
1057        );
1058        self.attributes.insert(
1059            Attribute::Syntax,
1060            SchemaAttribute {
1061                name: Attribute::Syntax,
1062                uuid: UUID_SCHEMA_ATTR_SYNTAX,
1063                description: String::from(
1064                    "Describe the syntax of this attribute. This affects indexing and sorting.",
1065                ),
1066                multivalue: false,
1067                unique: false,
1068                phantom: false,
1069                sync_allowed: false,
1070                replicated: Replicated::True,
1071                indexed: false,
1072                syntax: SyntaxType::SyntaxId,
1073            },
1074        );
1075        self.attributes.insert(
1076            Attribute::SystemMay,
1077            SchemaAttribute {
1078                name: Attribute::SystemMay,
1079                uuid: UUID_SCHEMA_ATTR_SYSTEMMAY,
1080                description: String::from(
1081                    "A list of system provided optional attributes this class can store.",
1082                ),
1083                multivalue: true,
1084                unique: false,
1085                phantom: false,
1086                sync_allowed: false,
1087                replicated: Replicated::True,
1088                indexed: false,
1089                syntax: SyntaxType::Utf8StringInsensitive,
1090            },
1091        );
1092        self.attributes.insert(
1093            Attribute::May,
1094            SchemaAttribute {
1095                name: Attribute::May,
1096                uuid: UUID_SCHEMA_ATTR_MAY,
1097                description: String::from(
1098                    "A user modifiable list of optional attributes this class can store.",
1099                ),
1100                multivalue: true,
1101                unique: false,
1102                phantom: false,
1103                sync_allowed: false,
1104                replicated: Replicated::True,
1105                indexed: false,
1106                syntax: SyntaxType::Utf8StringInsensitive,
1107            },
1108        );
1109        self.attributes.insert(
1110            Attribute::SystemMust,
1111            SchemaAttribute {
1112                name: Attribute::SystemMust,
1113                uuid: UUID_SCHEMA_ATTR_SYSTEMMUST,
1114                description: String::from(
1115                    "A list of system provided required attributes this class must store.",
1116                ),
1117                multivalue: true,
1118                unique: false,
1119                phantom: false,
1120                sync_allowed: false,
1121                replicated: Replicated::True,
1122                indexed: false,
1123                syntax: SyntaxType::Utf8StringInsensitive,
1124            },
1125        );
1126        self.attributes.insert(
1127            Attribute::Must,
1128            SchemaAttribute {
1129                name: Attribute::Must,
1130                uuid: UUID_SCHEMA_ATTR_MUST,
1131                description: String::from(
1132                    "A user modifiable list of required attributes this class must store.",
1133                ),
1134                multivalue: true,
1135                unique: false,
1136                phantom: false,
1137                sync_allowed: false,
1138                replicated: Replicated::True,
1139                indexed: false,
1140                syntax: SyntaxType::Utf8StringInsensitive,
1141            },
1142        );
1143        self.attributes.insert(
1144            Attribute::SystemSupplements,
1145            SchemaAttribute {
1146                name: Attribute::SystemSupplements,
1147                uuid: UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS,
1148                description: String::from(
1149                    "A set of classes that this type supplements, where this class can't exist without their presence.",
1150                ),
1151                multivalue: true,
1152                unique: false,
1153                phantom: false,
1154                sync_allowed: false,
1155                replicated: Replicated::True,
1156                indexed: false,
1157                syntax: SyntaxType::Utf8StringInsensitive,
1158            },
1159        );
1160        self.attributes.insert(
1161            Attribute::Supplements,
1162            SchemaAttribute {
1163                name: Attribute::Supplements,
1164                uuid: UUID_SCHEMA_ATTR_SUPPLEMENTS,
1165                description: String::from(
1166                    "A set of user modifiable classes, where this determines that at least one other type must supplement this type",
1167                ),
1168                multivalue: true,
1169                unique: false,
1170                phantom: false,
1171                sync_allowed: false,
1172                replicated: Replicated::True,
1173                indexed: false,
1174                syntax: SyntaxType::Utf8StringInsensitive,
1175            },
1176        );
1177        self.attributes.insert(
1178            Attribute::SystemExcludes,
1179            SchemaAttribute {
1180                name: Attribute::SystemExcludes,
1181                uuid: UUID_SCHEMA_ATTR_SYSTEMEXCLUDES,
1182                description: String::from(
1183                    "A set of classes that are denied presence in connection to this class",
1184                ),
1185                multivalue: true,
1186                unique: false,
1187                phantom: false,
1188                sync_allowed: false,
1189                replicated: Replicated::True,
1190                indexed: false,
1191                syntax: SyntaxType::Utf8StringInsensitive,
1192            },
1193        );
1194        self.attributes.insert(
1195            Attribute::Excludes,
1196            SchemaAttribute {
1197                name: Attribute::Excludes,
1198                uuid: UUID_SCHEMA_ATTR_EXCLUDES,
1199                description: String::from(
1200                    "A set of user modifiable classes that are denied presence in connection to this class",
1201                ),
1202                multivalue: true,
1203                unique: false,
1204                phantom: false,
1205                sync_allowed: false,
1206                replicated: Replicated::True,
1207                indexed: false,
1208                syntax: SyntaxType::Utf8StringInsensitive,
1209            },
1210        );
1211
1212        // SYSINFO attrs
1213        // ACP attributes.
1214        self.attributes.insert(
1215            Attribute::AcpEnable,
1216            SchemaAttribute {
1217                name: Attribute::AcpEnable,
1218                uuid: UUID_SCHEMA_ATTR_ACP_ENABLE,
1219                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."),
1220                multivalue: false,
1221                unique: false,
1222                phantom: false,
1223                sync_allowed: false,
1224                replicated: Replicated::True,
1225                indexed: true,
1226                syntax: SyntaxType::Boolean,
1227            },
1228        );
1229
1230        self.attributes.insert(
1231            Attribute::AcpReceiver,
1232            SchemaAttribute {
1233                name: Attribute::AcpReceiver,
1234                uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER,
1235                description: String::from(
1236                    "Who the ACP applies to, constraining or allowing operations.",
1237                ),
1238                multivalue: false,
1239                unique: false,
1240                phantom: false,
1241                sync_allowed: false,
1242                replicated: Replicated::True,
1243                indexed: true,
1244                syntax: SyntaxType::JsonFilter,
1245            },
1246        );
1247        self.attributes.insert(
1248            Attribute::AcpReceiverGroup,
1249            SchemaAttribute {
1250                name: Attribute::AcpReceiverGroup,
1251                uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER_GROUP,
1252                description: String::from(
1253                    "The group that receives this access control to allow access",
1254                ),
1255                multivalue: true,
1256                unique: false,
1257                phantom: false,
1258                sync_allowed: false,
1259                replicated: Replicated::True,
1260                indexed: true,
1261                syntax: SyntaxType::ReferenceUuid,
1262            },
1263        );
1264
1265        self.attributes.insert(
1266            Attribute::AcpTargetScope,
1267            SchemaAttribute {
1268                name: Attribute::AcpTargetScope,
1269                uuid: UUID_SCHEMA_ATTR_ACP_TARGETSCOPE,
1270                description: String::from(
1271                    "The effective targets of the ACP, e.g. what will be acted upon.",
1272                ),
1273                multivalue: false,
1274                unique: false,
1275                phantom: false,
1276                sync_allowed: false,
1277                replicated: Replicated::True,
1278                indexed: true,
1279                syntax: SyntaxType::JsonFilter,
1280            },
1281        );
1282        self.attributes.insert(
1283            Attribute::AcpSearchAttr,
1284            SchemaAttribute {
1285                name: Attribute::AcpSearchAttr,
1286                uuid: UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR,
1287                description: String::from(
1288                    "The attributes that may be viewed or searched by the receiver on targetscope.",
1289                ),
1290                multivalue: true,
1291                unique: false,
1292                phantom: false,
1293                sync_allowed: false,
1294                replicated: Replicated::True,
1295                indexed: true,
1296                syntax: SyntaxType::Utf8StringInsensitive,
1297            },
1298        );
1299        self.attributes.insert(
1300            Attribute::AcpCreateClass,
1301            SchemaAttribute {
1302                name: Attribute::AcpCreateClass,
1303                uuid: UUID_SCHEMA_ATTR_ACP_CREATE_CLASS,
1304                description: String::from("The set of classes that can be created on a new entry."),
1305                multivalue: true,
1306                unique: false,
1307                phantom: false,
1308                sync_allowed: false,
1309                replicated: Replicated::True,
1310                indexed: true,
1311                syntax: SyntaxType::Utf8StringInsensitive,
1312            },
1313        );
1314        self.attributes.insert(
1315            Attribute::AcpCreateAttr,
1316            SchemaAttribute {
1317                name: Attribute::AcpCreateAttr,
1318                uuid: UUID_SCHEMA_ATTR_ACP_CREATE_ATTR,
1319                description: String::from(
1320                    "The set of attribute types that can be created on an entry.",
1321                ),
1322                multivalue: true,
1323                unique: false,
1324                phantom: false,
1325                sync_allowed: false,
1326                replicated: Replicated::True,
1327                indexed: true,
1328                syntax: SyntaxType::Utf8StringInsensitive,
1329            },
1330        );
1331
1332        self.attributes.insert(
1333            Attribute::AcpModifyRemovedAttr,
1334            SchemaAttribute {
1335                name: Attribute::AcpModifyRemovedAttr,
1336                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR,
1337                description: String::from(
1338                    "The set of attribute types that could be removed or purged in a modification.",
1339                ),
1340                multivalue: true,
1341                unique: false,
1342                phantom: false,
1343                sync_allowed: false,
1344                replicated: Replicated::True,
1345                indexed: true,
1346                syntax: SyntaxType::Utf8StringInsensitive,
1347            },
1348        );
1349        self.attributes.insert(
1350            Attribute::AcpModifyPresentAttr,
1351            SchemaAttribute {
1352                name: Attribute::AcpModifyPresentAttr,
1353                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR,
1354                description: String::from(
1355                    "The set of attribute types that could be added or asserted in a modification.",
1356                ),
1357                multivalue: true,
1358                unique: false,
1359                phantom: false,
1360                sync_allowed: false,
1361                replicated: Replicated::True,
1362                indexed: true,
1363                syntax: SyntaxType::Utf8StringInsensitive,
1364            },
1365        );
1366        self.attributes.insert(
1367            Attribute::AcpModifyClass,
1368            SchemaAttribute {
1369                name: Attribute::AcpModifyClass,
1370                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS,
1371                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."),
1372                multivalue: true,
1373                unique: false,
1374                phantom: false,
1375                sync_allowed: false,
1376                replicated: Replicated::True,
1377                indexed: true,
1378                syntax: SyntaxType::Utf8StringInsensitive,
1379            },
1380        );
1381        self.attributes.insert(
1382                Attribute::AcpModifyPresentClass,
1383                SchemaAttribute {
1384                    name: Attribute::AcpModifyPresentClass,
1385                    uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENT_CLASS,
1386                    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."),
1387                    multivalue: true,
1388                    unique: false,
1389                    phantom: false,
1390                    sync_allowed: false,
1391                    replicated: Replicated::True,
1392                    indexed: false,
1393                    syntax: SyntaxType::Utf8StringInsensitive,
1394                },
1395            );
1396        self.attributes.insert(
1397                Attribute::AcpModifyRemoveClass,
1398                SchemaAttribute {
1399                    name: Attribute::AcpModifyRemoveClass,
1400                    uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVE_CLASS,
1401                    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."),
1402                    multivalue: true,
1403                    unique: false,
1404                    phantom: false,
1405                    sync_allowed: false,
1406                    replicated: Replicated::True,
1407                    indexed: false,
1408                    syntax: SyntaxType::Utf8StringInsensitive,
1409                },
1410            );
1411        self.attributes.insert(
1412            Attribute::EntryManagedBy,
1413            SchemaAttribute {
1414                name: Attribute::EntryManagedBy,
1415                uuid: UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY,
1416                description: String::from(
1417                    "A reference to a group that has access to manage the content of this entry.",
1418                ),
1419                multivalue: false,
1420                unique: false,
1421                phantom: false,
1422                sync_allowed: false,
1423                replicated: Replicated::True,
1424                indexed: true,
1425                syntax: SyntaxType::ReferenceUuid,
1426            },
1427        );
1428        // MO/Member
1429        self.attributes.insert(
1430            Attribute::MemberOf,
1431            SchemaAttribute {
1432                name: Attribute::MemberOf,
1433                uuid: UUID_SCHEMA_ATTR_MEMBEROF,
1434                description: String::from("reverse group membership of the object"),
1435                multivalue: true,
1436                unique: false,
1437                phantom: false,
1438                sync_allowed: false,
1439                replicated: Replicated::False,
1440                indexed: true,
1441                syntax: SyntaxType::ReferenceUuid,
1442            },
1443        );
1444        self.attributes.insert(
1445            Attribute::DirectMemberOf,
1446            SchemaAttribute {
1447                name: Attribute::DirectMemberOf,
1448                uuid: UUID_SCHEMA_ATTR_DIRECTMEMBEROF,
1449                description: String::from("reverse direct group membership of the object"),
1450                multivalue: true,
1451                unique: false,
1452                phantom: false,
1453                sync_allowed: false,
1454                replicated: Replicated::False,
1455                indexed: true,
1456                syntax: SyntaxType::ReferenceUuid,
1457            },
1458        );
1459        self.attributes.insert(
1460            Attribute::RecycledDirectMemberOf,
1461            SchemaAttribute {
1462                name: Attribute::RecycledDirectMemberOf,
1463                uuid: UUID_SCHEMA_ATTR_RECYCLEDDIRECTMEMBEROF,
1464                description: String::from("recycled reverse direct group membership of the object to assist in revive operations."),
1465                multivalue: true,
1466                unique: false,
1467                phantom: false,
1468                sync_allowed: false,
1469                // Unlike DMO this must be replicated so that on a recycle event, these groups
1470                //  "at delete" are replicated to partners. This avoids us having to replicate
1471                // DMO which is very costly, while still retaining our ability to revive entries
1472                // and their group memberships as a best effort.
1473                replicated: Replicated::True,
1474                indexed: true,
1475                syntax: SyntaxType::ReferenceUuid,
1476            },
1477        );
1478        self.attributes.insert(
1479            Attribute::Member,
1480            SchemaAttribute {
1481                name: Attribute::Member,
1482                uuid: UUID_SCHEMA_ATTR_MEMBER,
1483                description: String::from("List of members of the group"),
1484                multivalue: true,
1485                unique: false,
1486                phantom: false,
1487                sync_allowed: true,
1488                replicated: Replicated::True,
1489                indexed: true,
1490                syntax: SyntaxType::ReferenceUuid,
1491            },
1492        );
1493        self.attributes.insert(
1494            Attribute::DynMember,
1495            SchemaAttribute {
1496                name: Attribute::DynMember,
1497                uuid: UUID_SCHEMA_ATTR_DYNMEMBER,
1498                description: String::from("List of dynamic members of the group"),
1499                multivalue: true,
1500                unique: false,
1501                phantom: false,
1502                sync_allowed: true,
1503                replicated: Replicated::False,
1504                indexed: true,
1505                syntax: SyntaxType::ReferenceUuid,
1506            },
1507        );
1508
1509        self.attributes.insert(
1510            Attribute::Refers,
1511            SchemaAttribute {
1512                name: Attribute::Refers,
1513                uuid: UUID_SCHEMA_ATTR_REFERS,
1514                description: String::from("A reference to another object"),
1515                multivalue: false,
1516                unique: false,
1517                phantom: false,
1518                sync_allowed: false,
1519                replicated: Replicated::True,
1520                indexed: true,
1521                syntax: SyntaxType::ReferenceUuid,
1522            },
1523        );
1524
1525        self.attributes.insert(
1526            Attribute::CascadeDeleted,
1527            SchemaAttribute {
1528                name: Attribute::CascadeDeleted,
1529                uuid: UUID_SCHEMA_ATTR_CASCADE_DELETED,
1530                description: String::from("A marker attribute denoting that this entry was deleted by cascade when this UUID was deleted."),
1531                multivalue: false,
1532                unique: false,
1533                phantom: false,
1534                sync_allowed: false,
1535                replicated: Replicated::True,
1536                indexed: true,
1537                // NOTE: This has to be Uuid so that referential integrity doesn't consider
1538                // this value in its operation.
1539                syntax: SyntaxType::Uuid,
1540            },
1541        );
1542
1543        // Migration related
1544        self.attributes.insert(
1545            Attribute::Version,
1546            SchemaAttribute {
1547                name: Attribute::Version,
1548                uuid: UUID_SCHEMA_ATTR_VERSION,
1549                description: String::from(
1550                    "The systems internal migration version for provided objects",
1551                ),
1552                multivalue: false,
1553                unique: false,
1554                phantom: false,
1555                sync_allowed: false,
1556                replicated: Replicated::True,
1557                indexed: false,
1558                syntax: SyntaxType::Uint32,
1559            },
1560        );
1561        // Domain for sysinfo
1562        self.attributes.insert(
1563            Attribute::Domain,
1564            SchemaAttribute {
1565                name: Attribute::Domain,
1566                uuid: UUID_SCHEMA_ATTR_DOMAIN,
1567                description: String::from("A DNS Domain name entry."),
1568                multivalue: true,
1569                unique: false,
1570                phantom: false,
1571                sync_allowed: false,
1572                replicated: Replicated::True,
1573                indexed: true,
1574                syntax: SyntaxType::Utf8StringIname,
1575            },
1576        );
1577        self.attributes.insert(
1578            Attribute::Claim,
1579            SchemaAttribute {
1580                name: Attribute::Claim,
1581                uuid: UUID_SCHEMA_ATTR_CLAIM,
1582                description: String::from(
1583                    "The string identifier of an extracted claim that can be filtered",
1584                ),
1585                multivalue: true,
1586                unique: false,
1587                phantom: true,
1588                sync_allowed: false,
1589                replicated: Replicated::True,
1590                indexed: false,
1591                syntax: SyntaxType::Utf8StringInsensitive,
1592            },
1593        );
1594        self.attributes.insert(
1595            Attribute::Scope,
1596            SchemaAttribute {
1597                name: Attribute::Scope,
1598                uuid: UUID_SCHEMA_ATTR_SCOPE,
1599                description: String::from(
1600                    "The string identifier of a permission scope in a session",
1601                ),
1602                multivalue: true,
1603                unique: false,
1604                phantom: true,
1605                sync_allowed: false,
1606                replicated: Replicated::True,
1607                indexed: false,
1608                syntax: SyntaxType::Utf8StringInsensitive,
1609            },
1610        );
1611
1612        // External Scim Sync
1613        self.attributes.insert(
1614            Attribute::SyncExternalId,
1615            SchemaAttribute {
1616                name: Attribute::SyncExternalId,
1617                uuid: UUID_SCHEMA_ATTR_SYNC_EXTERNAL_ID,
1618                description: String::from(
1619                    "An external string ID of an entry imported from a sync agreement",
1620                ),
1621                multivalue: false,
1622                unique: true,
1623                phantom: false,
1624                sync_allowed: false,
1625                replicated: Replicated::True,
1626                indexed: true,
1627                syntax: SyntaxType::Utf8StringInsensitive,
1628            },
1629        );
1630        self.attributes.insert(
1631            Attribute::SyncParentUuid,
1632            SchemaAttribute {
1633                name: Attribute::SyncParentUuid,
1634                uuid: UUID_SCHEMA_ATTR_SYNC_PARENT_UUID,
1635                description: String::from(
1636                    "The UUID of the parent sync agreement that created this entry.",
1637                ),
1638                multivalue: false,
1639                unique: false,
1640                phantom: false,
1641                sync_allowed: false,
1642                replicated: Replicated::True,
1643                indexed: true,
1644                syntax: SyntaxType::ReferenceUuid,
1645            },
1646        );
1647        self.attributes.insert(
1648            Attribute::SyncClass,
1649            SchemaAttribute {
1650                name: Attribute::SyncClass,
1651                uuid: UUID_SCHEMA_ATTR_SYNC_CLASS,
1652                description: String::from("The set of classes requested by the sync client."),
1653                multivalue: true,
1654                unique: false,
1655                phantom: false,
1656                sync_allowed: false,
1657                replicated: Replicated::True,
1658                indexed: false,
1659                syntax: SyntaxType::Utf8StringInsensitive,
1660            },
1661        );
1662
1663        self.attributes.insert(
1664            Attribute::PasswordImport,
1665            SchemaAttribute {
1666                name: Attribute::PasswordImport,
1667                uuid: UUID_SCHEMA_ATTR_PASSWORD_IMPORT,
1668                description: String::from("An imported password hash from an external system."),
1669                multivalue: false,
1670                unique: false,
1671                phantom: true,
1672                sync_allowed: true,
1673                replicated: Replicated::False,
1674                indexed: false,
1675                syntax: SyntaxType::Utf8String,
1676            },
1677        );
1678
1679        self.attributes.insert(
1680            Attribute::UnixPasswordImport,
1681            SchemaAttribute {
1682                name: Attribute::UnixPasswordImport,
1683                uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD_IMPORT,
1684                description: String::from(
1685                    "An imported unix password hash from an external system.",
1686                ),
1687                multivalue: false,
1688                unique: false,
1689                phantom: true,
1690                sync_allowed: true,
1691                replicated: Replicated::False,
1692                indexed: false,
1693                syntax: SyntaxType::Utf8String,
1694            },
1695        );
1696
1697        self.attributes.insert(
1698            Attribute::TotpImport,
1699            SchemaAttribute {
1700                name: Attribute::TotpImport,
1701                uuid: UUID_SCHEMA_ATTR_TOTP_IMPORT,
1702                description: String::from("An imported totp secret from an external system."),
1703                multivalue: true,
1704                unique: false,
1705                phantom: true,
1706                sync_allowed: true,
1707                replicated: Replicated::False,
1708                indexed: false,
1709                syntax: SyntaxType::TotpSecret,
1710            },
1711        );
1712
1713        // LDAP Masking Phantoms
1714        self.attributes.insert(
1715            Attribute::Dn,
1716            SchemaAttribute {
1717                name: Attribute::Dn,
1718                uuid: UUID_SCHEMA_ATTR_DN,
1719                description: String::from("An LDAP Compatible DN"),
1720                multivalue: false,
1721                unique: false,
1722                phantom: true,
1723                sync_allowed: false,
1724                replicated: Replicated::False,
1725                indexed: false,
1726                syntax: SyntaxType::Utf8StringInsensitive,
1727            },
1728        );
1729        self.attributes.insert(
1730            Attribute::EntryDn,
1731            SchemaAttribute {
1732                name: Attribute::EntryDn,
1733                uuid: UUID_SCHEMA_ATTR_ENTRYDN,
1734                description: String::from("An LDAP Compatible EntryDN"),
1735                multivalue: false,
1736                unique: false,
1737                phantom: true,
1738                sync_allowed: false,
1739                replicated: Replicated::False,
1740                indexed: false,
1741                syntax: SyntaxType::Utf8StringInsensitive,
1742            },
1743        );
1744        self.attributes.insert(
1745            Attribute::EntryUuid,
1746            SchemaAttribute {
1747                name: Attribute::EntryUuid,
1748                uuid: UUID_SCHEMA_ATTR_ENTRYUUID,
1749                description: String::from("An LDAP Compatible entryUUID"),
1750                multivalue: false,
1751                unique: false,
1752                phantom: true,
1753                sync_allowed: false,
1754                replicated: Replicated::False,
1755                indexed: false,
1756                syntax: SyntaxType::Uuid,
1757            },
1758        );
1759        self.attributes.insert(
1760            Attribute::ObjectClass,
1761            SchemaAttribute {
1762                name: Attribute::ObjectClass,
1763                uuid: UUID_SCHEMA_ATTR_OBJECTCLASS,
1764                description: String::from("An LDAP Compatible objectClass"),
1765                multivalue: true,
1766                unique: false,
1767                phantom: true,
1768                sync_allowed: false,
1769                replicated: Replicated::False,
1770                indexed: false,
1771                syntax: SyntaxType::Utf8StringInsensitive,
1772            },
1773        );
1774        self.attributes.insert(
1775            Attribute::Cn,
1776            SchemaAttribute {
1777                name: Attribute::Cn,
1778                uuid: UUID_SCHEMA_ATTR_CN,
1779                description: String::from("An LDAP Compatible objectClass"),
1780                multivalue: false,
1781                unique: false,
1782                phantom: true,
1783                sync_allowed: false,
1784                replicated: Replicated::False,
1785                indexed: false,
1786                syntax: SyntaxType::Utf8StringIname,
1787            },
1788        );
1789        self.attributes.insert(
1790            Attribute::LdapKeys, // keys
1791            SchemaAttribute {
1792                name: Attribute::LdapKeys, // keys
1793                uuid: UUID_SCHEMA_ATTR_KEYS,
1794                description: String::from("An LDAP Compatible keys (ssh)"),
1795                multivalue: true,
1796                unique: false,
1797                phantom: true,
1798                sync_allowed: false,
1799                replicated: Replicated::False,
1800                indexed: false,
1801                syntax: SyntaxType::SshKey,
1802            },
1803        );
1804        self.attributes.insert(
1805            Attribute::LdapSshPublicKey,
1806            SchemaAttribute {
1807                name: Attribute::LdapSshPublicKey,
1808                uuid: UUID_SCHEMA_ATTR_SSHPUBLICKEY,
1809                description: String::from("An LDAP Compatible sshPublicKey"),
1810                multivalue: true,
1811                unique: false,
1812                phantom: true,
1813                sync_allowed: false,
1814                replicated: Replicated::False,
1815                indexed: false,
1816                syntax: SyntaxType::SshKey,
1817            },
1818        );
1819        self.attributes.insert(
1820            Attribute::Email,
1821            SchemaAttribute {
1822                name: Attribute::Email,
1823                uuid: UUID_SCHEMA_ATTR_EMAIL,
1824                description: String::from("An LDAP Compatible email"),
1825                multivalue: true,
1826                unique: false,
1827                phantom: true,
1828                sync_allowed: false,
1829                replicated: Replicated::False,
1830                indexed: false,
1831                syntax: SyntaxType::EmailAddress,
1832            },
1833        );
1834        self.attributes.insert(
1835            Attribute::EmailPrimary,
1836            SchemaAttribute {
1837                name: Attribute::EmailPrimary,
1838                uuid: UUID_SCHEMA_ATTR_EMAILPRIMARY,
1839                description: String::from("An LDAP Compatible primary email"),
1840                multivalue: false,
1841                unique: false,
1842                phantom: true,
1843                sync_allowed: false,
1844                replicated: Replicated::False,
1845                indexed: false,
1846                syntax: SyntaxType::EmailAddress,
1847            },
1848        );
1849        self.attributes.insert(
1850            Attribute::EmailAlternative,
1851            SchemaAttribute {
1852                name: Attribute::EmailAlternative,
1853                uuid: UUID_SCHEMA_ATTR_EMAILALTERNATIVE,
1854                description: String::from("An LDAP Compatible alternative email"),
1855                multivalue: false,
1856                unique: false,
1857                phantom: true,
1858                sync_allowed: false,
1859                replicated: Replicated::False,
1860                indexed: false,
1861                syntax: SyntaxType::EmailAddress,
1862            },
1863        );
1864        self.attributes.insert(
1865            Attribute::LdapEmailAddress,
1866            SchemaAttribute {
1867                name: Attribute::LdapEmailAddress,
1868                uuid: UUID_SCHEMA_ATTR_EMAILADDRESS,
1869                description: String::from("An LDAP Compatible emailAddress"),
1870                multivalue: true,
1871                unique: false,
1872                phantom: true,
1873                sync_allowed: false,
1874                replicated: Replicated::False,
1875                indexed: false,
1876                syntax: SyntaxType::EmailAddress,
1877            },
1878        );
1879        self.attributes.insert(
1880            Attribute::Gecos,
1881            SchemaAttribute {
1882                name: Attribute::Gecos,
1883                uuid: UUID_SCHEMA_ATTR_GECOS,
1884                description: String::from("An LDAP Compatible gecos."),
1885                multivalue: false,
1886                unique: false,
1887                phantom: true,
1888                sync_allowed: false,
1889                replicated: Replicated::False,
1890                indexed: false,
1891                syntax: SyntaxType::Utf8String,
1892            },
1893        );
1894        self.attributes.insert(
1895            Attribute::Uid,
1896            SchemaAttribute {
1897                name: Attribute::Uid,
1898                uuid: UUID_SCHEMA_ATTR_UID,
1899                description: String::from("An LDAP Compatible uid."),
1900                multivalue: false,
1901                unique: false,
1902                phantom: true,
1903                sync_allowed: false,
1904                replicated: Replicated::False,
1905                indexed: false,
1906                syntax: SyntaxType::Utf8String,
1907            },
1908        );
1909        self.attributes.insert(
1910            Attribute::UidNumber,
1911            SchemaAttribute {
1912                name: Attribute::UidNumber,
1913                uuid: UUID_SCHEMA_ATTR_UIDNUMBER,
1914                description: String::from("An LDAP Compatible uidNumber."),
1915                multivalue: false,
1916                unique: false,
1917                phantom: true,
1918                sync_allowed: false,
1919                replicated: Replicated::False,
1920                indexed: false,
1921                syntax: SyntaxType::Uint32,
1922            },
1923        );
1924        self.attributes.insert(
1925            Attribute::SudoHost,
1926            SchemaAttribute {
1927                name: Attribute::SudoHost,
1928                uuid: UUID_SCHEMA_ATTR_SUDOHOST,
1929                description: String::from("An LDAP Compatible sudohost."),
1930                multivalue: false,
1931                unique: false,
1932                phantom: true,
1933                sync_allowed: false,
1934                replicated: Replicated::False,
1935                indexed: false,
1936                syntax: SyntaxType::Utf8String,
1937            },
1938        );
1939        // end LDAP masking phantoms
1940
1941        // THIS IS FOR SYSTEM CRITICAL INTERNAL SCHEMA ONLY
1942
1943        // =================================================================
1944
1945        self.classes.insert(
1946            EntryClass::AttributeType.into(),
1947            SchemaClass {
1948                name: EntryClass::AttributeType.into(),
1949                uuid: UUID_SCHEMA_CLASS_ATTRIBUTETYPE,
1950                description: String::from("Definition of a schema attribute"),
1951                systemmay: vec![
1952                    Attribute::Replicated,
1953                    Attribute::Phantom,
1954                    Attribute::SyncAllowed,
1955                    Attribute::Index,
1956                    Attribute::Indexed,
1957                ],
1958                systemmust: vec![
1959                    Attribute::Class,
1960                    Attribute::AttributeName,
1961                    Attribute::MultiValue,
1962                    Attribute::Unique,
1963                    Attribute::Syntax,
1964                    Attribute::Description,
1965                ],
1966                systemexcludes: vec![EntryClass::ClassType.into()],
1967                ..Default::default()
1968            },
1969        );
1970        self.classes.insert(
1971            EntryClass::ClassType.into(),
1972            SchemaClass {
1973                name: EntryClass::ClassType.into(),
1974                uuid: UUID_SCHEMA_CLASS_CLASSTYPE,
1975                description: String::from("Definition of a schema classtype"),
1976                systemmay: vec![
1977                    Attribute::SyncAllowed,
1978                    Attribute::SystemMay,
1979                    Attribute::May,
1980                    Attribute::SystemMust,
1981                    Attribute::Must,
1982                    Attribute::SystemSupplements,
1983                    Attribute::Supplements,
1984                    Attribute::SystemExcludes,
1985                    Attribute::Excludes,
1986                ],
1987                systemmust: vec![
1988                    Attribute::Class,
1989                    Attribute::ClassName,
1990                    Attribute::Description,
1991                ],
1992                systemexcludes: vec![Attribute::AttributeType.into()],
1993                ..Default::default()
1994            },
1995        );
1996        self.classes.insert(
1997            EntryClass::Object.into(),
1998            SchemaClass {
1999                name: EntryClass::Object.into(),
2000                uuid: UUID_SCHEMA_CLASS_OBJECT,
2001                description: String::from("A system created class that all objects must contain"),
2002                systemmay: vec![Attribute::Description, Attribute::EntryManagedBy],
2003                systemmust: vec![
2004                    Attribute::Class,
2005                    Attribute::Uuid,
2006                    Attribute::LastModifiedCid,
2007                    Attribute::CreatedAtCid,
2008                ],
2009                ..Default::default()
2010            },
2011        );
2012        self.classes.insert(
2013            EntryClass::Builtin.into(),
2014            SchemaClass {
2015                name: EntryClass::Builtin.into(),
2016                uuid: UUID_SCHEMA_CLASS_BUILTIN,
2017                description: String::from("A marker class denoting builtin entries"),
2018                ..Default::default()
2019            },
2020        );
2021        self.classes.insert(
2022            EntryClass::MemberOf.into(),
2023            SchemaClass {
2024                name: EntryClass::MemberOf.into(),
2025                uuid: UUID_SCHEMA_CLASS_MEMBEROF,
2026                description: String::from(
2027                    "Class that is dynamically added to recipients of memberof or directmemberof",
2028                ),
2029                systemmay: vec![Attribute::MemberOf, Attribute::DirectMemberOf],
2030                ..Default::default()
2031            },
2032        );
2033        self.classes.insert(
2034            EntryClass::ExtensibleObject.into(),
2035            SchemaClass {
2036                name: EntryClass::ExtensibleObject.into(),
2037                uuid: UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT,
2038                description: String::from(
2039                    "A class type that has green hair and turns off all rules ...",
2040                ),
2041                ..Default::default()
2042            },
2043        );
2044        /* These two classes are core to the entry lifecycle for recycling and tombstoning */
2045        self.classes.insert(
2046                EntryClass::Recycled.into(),
2047                SchemaClass {
2048                    name: EntryClass::Recycled.into(),
2049                    uuid: UUID_SCHEMA_CLASS_RECYCLED,
2050                    description: String::from("An object that has been deleted, but still recoverable via the revive operation. Recycled objects are not modifiable, only revivable."),
2051                    systemmay: vec![Attribute::RecycledDirectMemberOf, Attribute::CascadeDeleted],
2052                    .. Default::default()
2053                },
2054            );
2055        self.classes.insert(
2056                EntryClass::Tombstone.into(),
2057                SchemaClass {
2058                    name: EntryClass::Tombstone.into(),
2059                    uuid: UUID_SCHEMA_CLASS_TOMBSTONE,
2060                    description: String::from("An object that is purged from the recycle bin. This is a system internal state. Tombstones have no attributes beside UUID."),
2061                    systemmust: vec![
2062                        Attribute::Class,
2063                        Attribute::Uuid,
2064                    ],
2065                    .. Default::default()
2066                },
2067            );
2068        self.classes.insert(
2069            EntryClass::Conflict.into(),
2070            SchemaClass {
2071                name: EntryClass::Conflict.into(),
2072                uuid: UUID_SCHEMA_CLASS_CONFLICT,
2073                description: String::from(
2074                    "An entry representing conflicts that occurred during replication",
2075                ),
2076                systemmust: vec![Attribute::SourceUuid],
2077                systemsupplements: vec![EntryClass::Recycled.into()],
2078                ..Default::default()
2079            },
2080        );
2081        // sysinfo
2082        self.classes.insert(
2083            EntryClass::SystemInfo.into(),
2084            SchemaClass {
2085                name: EntryClass::SystemInfo.into(),
2086                uuid: UUID_SCHEMA_CLASS_SYSTEM_INFO,
2087                description: String::from("System metadata object class"),
2088                systemmust: vec![Attribute::Version],
2089                ..Default::default()
2090            },
2091        );
2092        // ACP
2093        self.classes.insert(
2094            EntryClass::AccessControlSearch.into(),
2095            SchemaClass {
2096                name: EntryClass::AccessControlSearch.into(),
2097                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH,
2098                description: String::from("System Access Control Search Class"),
2099                systemmust: vec![Attribute::AcpSearchAttr],
2100                ..Default::default()
2101            },
2102        );
2103        self.classes.insert(
2104            EntryClass::AccessControlDelete.into(),
2105            SchemaClass {
2106                name: EntryClass::AccessControlDelete.into(),
2107                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE,
2108                description: String::from("System Access Control DELETE Class"),
2109                ..Default::default()
2110            },
2111        );
2112        self.classes.insert(
2113            EntryClass::AccessControlModify.into(),
2114            SchemaClass {
2115                name: EntryClass::AccessControlModify.into(),
2116                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY,
2117                description: String::from("System Access Control Modify Class"),
2118                systemmay: vec![
2119                    Attribute::AcpModifyRemovedAttr,
2120                    Attribute::AcpModifyPresentAttr,
2121                    Attribute::AcpModifyClass,
2122                    Attribute::AcpModifyPresentClass,
2123                    Attribute::AcpModifyRemoveClass,
2124                ],
2125                ..Default::default()
2126            },
2127        );
2128        self.classes.insert(
2129            EntryClass::AccessControlCreate.into(),
2130            SchemaClass {
2131                name: EntryClass::AccessControlCreate.into(),
2132                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE,
2133                description: String::from("System Access Control Create Class"),
2134                systemmay: vec![Attribute::AcpCreateClass, Attribute::AcpCreateAttr],
2135                ..Default::default()
2136            },
2137        );
2138        self.classes.insert(
2139            EntryClass::AccessControlProfile.into(),
2140            SchemaClass {
2141                name: EntryClass::AccessControlProfile.into(),
2142                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
2143                description: String::from("System Access Control Profile Class"),
2144                systemmay: vec![Attribute::AcpEnable, Attribute::Description],
2145                systemmust: vec![Attribute::Name],
2146                systemsupplements: vec![
2147                    EntryClass::AccessControlSearch.into(),
2148                    EntryClass::AccessControlDelete.into(),
2149                    EntryClass::AccessControlModify.into(),
2150                    EntryClass::AccessControlCreate.into(),
2151                ],
2152                ..Default::default()
2153            },
2154        );
2155        self.classes.insert(
2156            EntryClass::AccessControlReceiverEntryManager.into(),
2157            SchemaClass {
2158                name: EntryClass::AccessControlReceiverEntryManager.into(),
2159                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER,
2160                description: String::from("System Access Control Profile Receiver - Entry Manager"),
2161                systemexcludes: vec![EntryClass::AccessControlReceiverGroup.into()],
2162                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2163                ..Default::default()
2164            },
2165        );
2166        self.classes.insert(
2167            EntryClass::AccessControlReceiverGroup.into(),
2168            SchemaClass {
2169                name: EntryClass::AccessControlReceiverGroup.into(),
2170                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP,
2171                description: String::from("System Access Control Profile Receiver - Group"),
2172                systemmay: vec![Attribute::AcpReceiver],
2173                systemmust: vec![Attribute::AcpReceiverGroup],
2174                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2175                systemexcludes: vec![EntryClass::AccessControlReceiverEntryManager.into()],
2176                ..Default::default()
2177            },
2178        );
2179        self.classes.insert(
2180            EntryClass::AccessControlTargetScope.into(),
2181            SchemaClass {
2182                name: EntryClass::AccessControlTargetScope.into(),
2183                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE,
2184                description: String::from("System Access Control Profile Target - Scope"),
2185                systemmust: vec![Attribute::AcpTargetScope],
2186                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2187                ..Default::default()
2188            },
2189        );
2190
2191        // System attrs
2192        self.classes.insert(
2193            EntryClass::System.into(),
2194            SchemaClass {
2195                name: EntryClass::System.into(),
2196                uuid: UUID_SCHEMA_CLASS_SYSTEM,
2197                description: String::from("A class denoting that a type is system generated and protected. It has special internal behaviour."),
2198                .. Default::default()
2199            },
2200        );
2201        self.classes.insert(
2202            EntryClass::SyncObject.into(),
2203            SchemaClass {
2204                name: EntryClass::SyncObject.into(),
2205                uuid: UUID_SCHEMA_CLASS_SYNC_OBJECT,
2206                description: String::from("A class denoting that an entry is synchronised from an external source. This entry may not be modifiable."),
2207                systemmust: vec![
2208                    Attribute::SyncParentUuid
2209                ],
2210                systemmay: vec![
2211                    Attribute::SyncExternalId,
2212                    Attribute::SyncClass,
2213                ],
2214                .. Default::default()
2215            },
2216        );
2217
2218        let r = self.validate();
2219        if r.is_empty() {
2220            admin_debug!("schema validate -> passed");
2221            Ok(())
2222        } else {
2223            admin_error!(err = ?r, "schema validate -> errors");
2224            Err(OperationError::ConsistencyError(
2225                r.into_iter().filter_map(|v| v.err()).collect(),
2226            ))
2227        }
2228    }
2229}
2230
2231impl SchemaTransaction for SchemaWriteTransaction<'_> {
2232    fn get_attributes_unique(&self) -> &Vec<Attribute> {
2233        &self.unique_cache
2234    }
2235
2236    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
2237        &self.ref_cache
2238    }
2239
2240    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
2241        &self.classes
2242    }
2243
2244    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
2245        &self.attributes
2246    }
2247}
2248
2249impl SchemaTransaction for SchemaReadTransaction {
2250    fn get_attributes_unique(&self) -> &Vec<Attribute> {
2251        &self.unique_cache
2252    }
2253
2254    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
2255        &self.ref_cache
2256    }
2257
2258    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
2259        &self.classes
2260    }
2261
2262    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
2263        &self.attributes
2264    }
2265}
2266
2267impl Schema {
2268    pub fn new() -> Result<Self, OperationError> {
2269        let s = Schema {
2270            classes: CowCell::new(HashMap::with_capacity(128)),
2271            attributes: CowCell::new(HashMap::with_capacity(128)),
2272            unique_cache: CowCell::new(Vec::with_capacity(0)),
2273            ref_cache: CowCell::new(HashMap::with_capacity(64)),
2274        };
2275        // let mut sw = task::block_on(s.write());
2276        let mut sw = s.write();
2277        let r1 = sw.generate_in_memory();
2278        debug_assert!(r1.is_ok());
2279        r1?;
2280        let r2 = sw.commit().map(|_| s);
2281        debug_assert!(r2.is_ok());
2282        r2
2283    }
2284
2285    pub fn read(&self) -> SchemaReadTransaction {
2286        SchemaReadTransaction {
2287            classes: self.classes.read(),
2288            attributes: self.attributes.read(),
2289            unique_cache: self.unique_cache.read(),
2290            ref_cache: self.ref_cache.read(),
2291        }
2292    }
2293
2294    pub fn write(&self) -> SchemaWriteTransaction<'_> {
2295        SchemaWriteTransaction {
2296            classes: self.classes.write(),
2297            attributes: self.attributes.write(),
2298            unique_cache: self.unique_cache.write(),
2299            ref_cache: self.ref_cache.write(),
2300        }
2301    }
2302
2303    #[cfg(test)]
2304    pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
2305        self.write()
2306    }
2307}
2308
2309#[cfg(test)]
2310mod tests {
2311    use crate::prelude::*;
2312    use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
2313    use uuid::Uuid;
2314
2315    // use crate::proto_v1::Filter as ProtoFilter;
2316
2317    macro_rules! validate_schema {
2318        ($sch:ident) => {{
2319            // Turns into a result type
2320            let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
2321            assert!(r.is_ok());
2322        }};
2323    }
2324
2325    macro_rules! sch_from_entry_ok {
2326        (
2327            $e:expr,
2328            $type:ty
2329        ) => {{
2330            let ev1 = $e.into_sealed_committed();
2331
2332            let r1 = <$type>::try_from(&ev1);
2333            assert!(r1.is_ok());
2334        }};
2335    }
2336
2337    macro_rules! sch_from_entry_err {
2338        (
2339            $e:expr,
2340            $type:ty
2341        ) => {{
2342            let ev1 = $e.into_sealed_committed();
2343
2344            let r1 = <$type>::try_from(&ev1);
2345            assert!(r1.is_err());
2346        }};
2347    }
2348
2349    #[test]
2350    fn test_schema_attribute_from_entry() {
2351        sketching::test_init();
2352
2353        sch_from_entry_err!(
2354            entry_init!(
2355                (Attribute::Class, EntryClass::Object.to_value()),
2356                (Attribute::Class, EntryClass::AttributeType.to_value()),
2357                (
2358                    Attribute::AttributeName,
2359                    Value::new_iutf8("schema_attr_test")
2360                ),
2361                (
2362                    Attribute::Uuid,
2363                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2364                ),
2365                (Attribute::Unique, Value::Bool(false))
2366            ),
2367            SchemaAttribute
2368        );
2369
2370        sch_from_entry_err!(
2371            entry_init!(
2372                (Attribute::Class, EntryClass::Object.to_value()),
2373                (Attribute::Class, EntryClass::AttributeType.to_value()),
2374                (
2375                    Attribute::AttributeName,
2376                    Value::new_iutf8("schema_attr_test")
2377                ),
2378                (
2379                    Attribute::Uuid,
2380                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2381                ),
2382                (Attribute::MultiValue, Value::Bool(false)),
2383                (Attribute::Unique, Value::Bool(false)),
2384                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2385            ),
2386            SchemaAttribute
2387        );
2388
2389        sch_from_entry_err!(
2390            entry_init!(
2391                (Attribute::Class, EntryClass::Object.to_value()),
2392                (Attribute::Class, EntryClass::AttributeType.to_value()),
2393                (
2394                    Attribute::AttributeName,
2395                    Value::new_iutf8("schema_attr_test")
2396                ),
2397                (
2398                    Attribute::Uuid,
2399                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2400                ),
2401                (
2402                    Attribute::Description,
2403                    Value::Utf8("Test attr parsing".to_string())
2404                ),
2405                (Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
2406                (Attribute::Unique, Value::Bool(false)),
2407                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2408            ),
2409            SchemaAttribute
2410        );
2411
2412        sch_from_entry_err!(
2413            entry_init!(
2414                (Attribute::Class, EntryClass::Object.to_value()),
2415                (Attribute::Class, EntryClass::AttributeType.to_value()),
2416                (
2417                    Attribute::AttributeName,
2418                    Value::new_iutf8("schema_attr_test")
2419                ),
2420                (
2421                    Attribute::Uuid,
2422                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2423                ),
2424                (
2425                    Attribute::Description,
2426                    Value::Utf8("Test attr parsing".to_string())
2427                ),
2428                (Attribute::MultiValue, Value::Bool(false)),
2429                (Attribute::Unique, Value::Bool(false)),
2430                (Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
2431            ),
2432            SchemaAttribute
2433        );
2434
2435        // Index is allowed to be empty
2436        sch_from_entry_ok!(
2437            entry_init!(
2438                (Attribute::Class, EntryClass::Object.to_value()),
2439                (Attribute::Class, EntryClass::AttributeType.to_value()),
2440                (
2441                    Attribute::AttributeName,
2442                    Value::new_iutf8("schema_attr_test")
2443                ),
2444                (
2445                    Attribute::Uuid,
2446                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2447                ),
2448                (
2449                    Attribute::Description,
2450                    Value::Utf8("Test attr parsing".to_string())
2451                ),
2452                (Attribute::MultiValue, Value::Bool(false)),
2453                (Attribute::Unique, Value::Bool(false)),
2454                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2455            ),
2456            SchemaAttribute
2457        );
2458
2459        // Index present
2460        sch_from_entry_ok!(
2461            entry_init!(
2462                (Attribute::Class, EntryClass::Object.to_value()),
2463                (Attribute::Class, EntryClass::AttributeType.to_value()),
2464                (
2465                    Attribute::AttributeName,
2466                    Value::new_iutf8("schema_attr_test")
2467                ),
2468                (
2469                    Attribute::Uuid,
2470                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2471                ),
2472                (
2473                    Attribute::Description,
2474                    Value::Utf8("Test attr parsing".to_string())
2475                ),
2476                (Attribute::MultiValue, Value::Bool(false)),
2477                (Attribute::Unique, Value::Bool(false)),
2478                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2479                (Attribute::Index, Value::Bool(true))
2480            ),
2481            SchemaAttribute
2482        );
2483    }
2484
2485    #[test]
2486    fn test_schema_class_from_entry() {
2487        sch_from_entry_err!(
2488            entry_init!(
2489                (Attribute::Class, EntryClass::Object.to_value()),
2490                (Attribute::Class, EntryClass::ClassType.to_value()),
2491                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2492                (
2493                    Attribute::Uuid,
2494                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2495                )
2496            ),
2497            SchemaClass
2498        );
2499
2500        sch_from_entry_err!(
2501            entry_init!(
2502                (Attribute::Class, EntryClass::Object.to_value()),
2503                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2504                (
2505                    Attribute::Uuid,
2506                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2507                ),
2508                (
2509                    Attribute::Description,
2510                    Value::Utf8("class test".to_string())
2511                )
2512            ),
2513            SchemaClass
2514        );
2515
2516        // Classes can be valid with no attributes provided.
2517        sch_from_entry_ok!(
2518            entry_init!(
2519                (Attribute::Class, EntryClass::Object.to_value()),
2520                (Attribute::Class, EntryClass::ClassType.to_value()),
2521                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2522                (
2523                    Attribute::Uuid,
2524                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2525                ),
2526                (
2527                    Attribute::Description,
2528                    Value::Utf8("class test".to_string())
2529                )
2530            ),
2531            SchemaClass
2532        );
2533
2534        // Classes with various may/must
2535        sch_from_entry_ok!(
2536            entry_init!(
2537                (Attribute::Class, EntryClass::Object.to_value()),
2538                (Attribute::Class, EntryClass::ClassType.to_value()),
2539                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2540                (
2541                    Attribute::Uuid,
2542                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2543                ),
2544                (
2545                    Attribute::Description,
2546                    Value::Utf8("class test".to_string())
2547                ),
2548                (Attribute::SystemMust, Value::new_iutf8("a"))
2549            ),
2550            SchemaClass
2551        );
2552
2553        sch_from_entry_ok!(
2554            entry_init!(
2555                (Attribute::Class, EntryClass::Object.to_value()),
2556                (Attribute::Class, EntryClass::ClassType.to_value()),
2557                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2558                (
2559                    Attribute::Uuid,
2560                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2561                ),
2562                (
2563                    Attribute::Description,
2564                    Value::Utf8("class test".to_string())
2565                ),
2566                (Attribute::SystemMay, Value::new_iutf8("a"))
2567            ),
2568            SchemaClass
2569        );
2570
2571        sch_from_entry_ok!(
2572            entry_init!(
2573                (Attribute::Class, EntryClass::Object.to_value()),
2574                (Attribute::Class, EntryClass::ClassType.to_value()),
2575                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2576                (
2577                    Attribute::Uuid,
2578                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2579                ),
2580                (
2581                    Attribute::Description,
2582                    Value::Utf8("class test".to_string())
2583                ),
2584                (Attribute::May, Value::new_iutf8("a")),
2585                (Attribute::Must, Value::new_iutf8("b"))
2586            ),
2587            SchemaClass
2588        );
2589
2590        sch_from_entry_ok!(
2591            entry_init!(
2592                (Attribute::Class, EntryClass::Object.to_value()),
2593                (Attribute::Class, EntryClass::ClassType.to_value()),
2594                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2595                (
2596                    Attribute::Uuid,
2597                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2598                ),
2599                (
2600                    Attribute::Description,
2601                    Value::Utf8("class test".to_string())
2602                ),
2603                (Attribute::May, Value::new_iutf8("a")),
2604                (Attribute::Must, Value::new_iutf8("b")),
2605                (Attribute::SystemMay, Value::new_iutf8("c")),
2606                (Attribute::SystemMust, Value::new_iutf8("d"))
2607            ),
2608            SchemaClass
2609        );
2610    }
2611
2612    #[test]
2613    fn test_schema_attribute_simple() {
2614        // Test schemaAttribute validation of types.
2615
2616        // Test single value string
2617        let single_value_string = SchemaAttribute {
2618            name: Attribute::from("single_value"),
2619            uuid: Uuid::new_v4(),
2620            description: String::from(""),
2621            syntax: SyntaxType::Utf8StringInsensitive,
2622            ..Default::default()
2623        };
2624
2625        let r1 = single_value_string
2626            .validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
2627        assert_eq!(r1, Ok(()));
2628
2629        let rvs = vs_iutf8!["test1", "test2"] as _;
2630        let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
2631        assert_eq!(
2632            r2,
2633            Err(SchemaError::InvalidAttributeSyntax(
2634                "single_value".to_string()
2635            ))
2636        );
2637
2638        // test multivalue string, boolean
2639
2640        let multi_value_string = SchemaAttribute {
2641            name: Attribute::from("mv_string"),
2642            uuid: Uuid::new_v4(),
2643            description: String::from(""),
2644            multivalue: true,
2645            syntax: SyntaxType::Utf8String,
2646            ..Default::default()
2647        };
2648
2649        let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
2650        let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
2651        assert_eq!(r5, Ok(()));
2652
2653        let multi_value_boolean = SchemaAttribute {
2654            name: Attribute::from("mv_bool"),
2655            uuid: Uuid::new_v4(),
2656            description: String::from(""),
2657            multivalue: true,
2658            syntax: SyntaxType::Boolean,
2659            ..Default::default()
2660        };
2661
2662        // Since valueset now disallows such shenanigans at a type level, this can't occur
2663        /*
2664        let rvs = unsafe {
2665            valueset![
2666                Value::new_bool(true),
2667                Value::new_iutf8("test1"),
2668                Value::new_iutf8("test2")
2669            ]
2670        };
2671        let r3 = multi_value_boolean.validate_ava("mv_bool", &rvs);
2672        assert_eq!(
2673            r3,
2674            Err(SchemaError::InvalidAttributeSyntax("mv_bool".to_string()))
2675        );
2676        */
2677
2678        let rvs = vs_bool![true, false];
2679        let r4 = multi_value_boolean.validate_ava(&Attribute::from("mv_bool"), &(rvs as _));
2680        assert_eq!(r4, Ok(()));
2681
2682        // syntax_id and index_type values
2683        let single_value_syntax = SchemaAttribute {
2684            name: Attribute::from("sv_syntax"),
2685            uuid: Uuid::new_v4(),
2686            description: String::from(""),
2687            syntax: SyntaxType::SyntaxId,
2688            ..Default::default()
2689        };
2690
2691        let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
2692        let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
2693        assert_eq!(r6, Ok(()));
2694
2695        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
2696        let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
2697        assert_eq!(
2698            r7,
2699            Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
2700        );
2701
2702        let single_value_index = SchemaAttribute {
2703            name: Attribute::from("sv_index"),
2704            uuid: Uuid::new_v4(),
2705            description: String::from(""),
2706            syntax: SyntaxType::IndexId,
2707            ..Default::default()
2708        };
2709
2710        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
2711        let r9 = single_value_index.validate_ava(&Attribute::from("sv_index"), &rvs);
2712        assert_eq!(
2713            r9,
2714            Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
2715        );
2716    }
2717
2718    #[test]
2719    fn test_schema_simple() {
2720        let schema = Schema::new().expect("failed to create schema");
2721        let schema_ro = schema.read();
2722        validate_schema!(schema_ro);
2723    }
2724
2725    #[test]
2726    fn test_schema_entries() {
2727        sketching::test_init();
2728        // Given an entry, assert it's schema is valid
2729        // We do
2730        let schema_outer = Schema::new().expect("failed to create schema");
2731        let schema = schema_outer.read();
2732
2733        let e_no_uuid = entry_init!().into_invalid_new();
2734
2735        assert_eq!(
2736            e_no_uuid.validate(&schema),
2737            Err(SchemaError::MissingMustAttribute(vec![Attribute::Uuid]))
2738        );
2739
2740        let e_no_class = entry_init!((
2741            Attribute::Uuid,
2742            Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2743        ))
2744        .into_invalid_new();
2745
2746        assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));
2747
2748        let e_bad_class = entry_init!(
2749            (
2750                Attribute::Uuid,
2751                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2752            ),
2753            (Attribute::Class, Value::new_iutf8("zzzzzz"))
2754        )
2755        .into_invalid_new();
2756        assert_eq!(
2757            e_bad_class.validate(&schema),
2758            Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
2759        );
2760
2761        let e_attr_invalid = entry_init!(
2762            (
2763                Attribute::Uuid,
2764                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2765            ),
2766            (Attribute::Class, EntryClass::Object.to_value()),
2767            (Attribute::Class, EntryClass::AttributeType.to_value())
2768        )
2769        .into_invalid_new();
2770        let res = e_attr_invalid.validate(&schema);
2771        matches!(res, Err(SchemaError::MissingMustAttribute(_)));
2772
2773        let e_attr_invalid_may = entry_init!(
2774            (Attribute::Class, EntryClass::Object.to_value()),
2775            (Attribute::Class, EntryClass::AttributeType.to_value()),
2776            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2777            (Attribute::Description, Value::Utf8("testattr".to_string())),
2778            (Attribute::MultiValue, Value::Bool(false)),
2779            (Attribute::Unique, Value::Bool(false)),
2780            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2781            (
2782                Attribute::Uuid,
2783                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2784            ),
2785            (Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
2786        )
2787        .into_invalid_new();
2788
2789        assert_eq!(
2790            e_attr_invalid_may.validate(&schema),
2791            Err(SchemaError::AttributeNotValidForClass(
2792                Attribute::TestAttr.to_string()
2793            ))
2794        );
2795
2796        let e_attr_invalid_syn = entry_init!(
2797            (Attribute::Class, EntryClass::Object.to_value()),
2798            (Attribute::Class, EntryClass::AttributeType.to_value()),
2799            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2800            (Attribute::Description, Value::Utf8("testattr".to_string())),
2801            (Attribute::MultiValue, Value::Utf8("false".to_string())),
2802            (Attribute::Unique, Value::Bool(false)),
2803            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2804            (
2805                Attribute::Uuid,
2806                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2807            )
2808        )
2809        .into_invalid_new();
2810
2811        assert_eq!(
2812            e_attr_invalid_syn.validate(&schema),
2813            Err(SchemaError::InvalidAttributeSyntax(
2814                "multivalue".to_string()
2815            ))
2816        );
2817
2818        // You may not have the phantom.
2819        let e_phantom = entry_init!(
2820            (Attribute::Class, EntryClass::Object.to_value()),
2821            (Attribute::Class, EntryClass::AttributeType.to_value()),
2822            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2823            (Attribute::Description, Value::Utf8("testattr".to_string())),
2824            (Attribute::MultiValue, Value::Bool(false)),
2825            (Attribute::Unique, Value::Bool(false)),
2826            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2827            (
2828                Attribute::Uuid,
2829                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2830            ),
2831            (
2832                Attribute::PasswordImport,
2833                Value::Utf8("password".to_string())
2834            )
2835        )
2836        .into_invalid_new();
2837        assert!(e_phantom.validate(&schema).is_err());
2838
2839        let e_ok = entry_init!(
2840            (Attribute::Class, EntryClass::Object.to_value()),
2841            (Attribute::Class, EntryClass::AttributeType.to_value()),
2842            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2843            (Attribute::Description, Value::Utf8("testattr".to_string())),
2844            (Attribute::MultiValue, Value::Bool(true)),
2845            (Attribute::Unique, Value::Bool(false)),
2846            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2847            (
2848                Attribute::Uuid,
2849                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2850            )
2851        )
2852        .into_invalid_new();
2853        assert!(e_ok.validate(&schema).is_ok());
2854    }
2855
2856    #[test]
2857    fn test_schema_extensible() {
2858        let schema_outer = Schema::new().expect("failed to create schema");
2859        let schema = schema_outer.read();
2860        // Just because you are extensible, doesn't mean you can be lazy
2861        let e_extensible_bad = entry_init!(
2862            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2863            (
2864                Attribute::Uuid,
2865                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2866            ),
2867            (Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
2868        )
2869        .into_invalid_new();
2870
2871        assert_eq!(
2872            e_extensible_bad.validate(&schema),
2873            Err(SchemaError::InvalidAttributeSyntax(
2874                "multivalue".to_string()
2875            ))
2876        );
2877
2878        // Extensible doesn't mean you can have the phantoms
2879        let e_extensible_phantom = entry_init!(
2880            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2881            (
2882                Attribute::Uuid,
2883                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2884            ),
2885            (Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
2886        )
2887        .into_invalid_new();
2888
2889        assert_eq!(
2890            e_extensible_phantom.validate(&schema),
2891            Err(SchemaError::PhantomAttribute(
2892                Attribute::PasswordImport.to_string()
2893            ))
2894        );
2895
2896        let e_extensible = entry_init!(
2897            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2898            (
2899                Attribute::Uuid,
2900                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2901            ),
2902            (Attribute::MultiValue, Value::Bool(true))
2903        )
2904        .into_invalid_new();
2905
2906        /* Is okay because extensible! */
2907        assert!(e_extensible.validate(&schema).is_ok());
2908    }
2909
2910    #[test]
2911    fn test_schema_filter_validation() {
2912        let schema_outer = Schema::new().expect("failed to create schema");
2913        let schema = schema_outer.read();
2914
2915        // test syntax of bool
2916        let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
2917        assert_eq!(
2918            f_bool.validate(&schema),
2919            Err(SchemaError::InvalidAttributeSyntax(
2920                "multivalue".to_string()
2921            ))
2922        );
2923        // test insensitive values
2924        let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2925        assert_eq!(
2926            f_insense.validate(&schema),
2927            Ok(filter_valid!(f_eq(
2928                Attribute::Class,
2929                EntryClass::AttributeType.into()
2930            )))
2931        );
2932        // Test the recursive structures validate
2933        let f_or_empty = filter_all!(f_or!([]));
2934        assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
2935        let f_or = filter_all!(f_or!([f_eq(
2936            Attribute::MultiValue,
2937            PartialValue::new_iutf8("zzzz")
2938        )]));
2939        assert_eq!(
2940            f_or.validate(&schema),
2941            Err(SchemaError::InvalidAttributeSyntax(
2942                "multivalue".to_string()
2943            ))
2944        );
2945        let f_or_mult = filter_all!(f_and!([
2946            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2947            f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
2948        ]));
2949        assert_eq!(
2950            f_or_mult.validate(&schema),
2951            Err(SchemaError::InvalidAttributeSyntax(
2952                "multivalue".to_string()
2953            ))
2954        );
2955        // Test mixed case attr name - this is a pass, due to normalisation
2956        let f_or_ok = filter_all!(f_andnot(f_and!([
2957            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2958            f_sub(Attribute::Class, EntryClass::ClassType.into()),
2959            f_pres(Attribute::Class)
2960        ])));
2961        assert_eq!(
2962            f_or_ok.validate(&schema),
2963            Ok(filter_valid!(f_andnot(f_and!([
2964                f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2965                f_sub(Attribute::Class, EntryClass::ClassType.into()),
2966                f_pres(Attribute::Class)
2967            ]))))
2968        );
2969    }
2970
2971    #[test]
2972    fn test_schema_class_phantom_reject() {
2973        // Check that entries can be normalised and validated sanely
2974        let schema_outer = Schema::new().expect("failed to create schema");
2975        let mut schema = schema_outer.write_blocking();
2976
2977        assert!(schema.validate().is_empty());
2978
2979        // Attempt to create a class with a phantom attribute, should be refused.
2980        let class = SchemaClass {
2981            name: AttrString::from("testobject"),
2982            uuid: Uuid::new_v4(),
2983            description: String::from("test object"),
2984            systemmay: vec![Attribute::Claim],
2985            ..Default::default()
2986        };
2987
2988        assert!(schema.update_classes(vec![class]).is_ok());
2989
2990        assert_eq!(schema.validate().len(), 1);
2991    }
2992
2993    #[test]
2994    fn test_schema_class_exclusion_requires() {
2995        sketching::test_init();
2996
2997        let schema_outer = Schema::new().expect("failed to create schema");
2998        let mut schema = schema_outer.write_blocking();
2999
3000        assert!(schema.validate().is_empty());
3001
3002        // We setup some classes that have requires and excludes and check that they
3003        // are enforced correctly.
3004        let class_account = SchemaClass {
3005            name: Attribute::Account.into(),
3006            uuid: Uuid::new_v4(),
3007            description: String::from("account object"),
3008            systemmust: vec![
3009                Attribute::Class,
3010                Attribute::Uuid,
3011                Attribute::LastModifiedCid,
3012                Attribute::CreatedAtCid,
3013            ],
3014            systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
3015            ..Default::default()
3016        };
3017
3018        let class_person = SchemaClass {
3019            name: EntryClass::Person.into(),
3020            uuid: Uuid::new_v4(),
3021            description: String::from("person object"),
3022            systemmust: vec![
3023                Attribute::Class,
3024                Attribute::Uuid,
3025                Attribute::LastModifiedCid,
3026                Attribute::CreatedAtCid,
3027            ],
3028            ..Default::default()
3029        };
3030
3031        let class_service = SchemaClass {
3032            name: EntryClass::Service.into(),
3033            uuid: Uuid::new_v4(),
3034            description: String::from("service object"),
3035            systemmust: vec![
3036                Attribute::Class,
3037                Attribute::Uuid,
3038                Attribute::LastModifiedCid,
3039                Attribute::CreatedAtCid,
3040            ],
3041            excludes: vec![EntryClass::Person.into()],
3042            ..Default::default()
3043        };
3044
3045        assert!(schema
3046            .update_classes(vec![class_account, class_person, class_service])
3047            .is_ok());
3048
3049        // Missing person or service account.
3050        let e_account = entry_init!(
3051            (Attribute::Class, EntryClass::Account.to_value()),
3052            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3053        )
3054        .into_invalid_new();
3055
3056        assert_eq!(
3057            e_account.validate(&schema),
3058            Err(SchemaError::SupplementsNotSatisfied(vec![
3059                EntryClass::Service.into(),
3060                EntryClass::Person.into(),
3061            ]))
3062        );
3063
3064        // Service account missing account
3065        /*
3066        let e_service = unsafe { entry_init!(
3067            (Attribute::Class, EntryClass::Service.to_value()),
3068            (Attribute::Uuid, Value::new_uuid(Uuid::new_v4()))
3069        ).into_invalid_new() };
3070
3071        assert_eq!(
3072            e_service.validate(&schema),
3073            Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()]))
3074        );
3075        */
3076
3077        // Service can't have person
3078        let e_service_person = entry_init!(
3079            (Attribute::Class, EntryClass::Service.to_value()),
3080            (Attribute::Class, EntryClass::Account.to_value()),
3081            (Attribute::Class, EntryClass::Person.to_value()),
3082            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3083        )
3084        .into_invalid_new();
3085
3086        assert_eq!(
3087            e_service_person.validate(&schema),
3088            Err(SchemaError::ExcludesNotSatisfied(vec![
3089                EntryClass::Person.to_string()
3090            ]))
3091        );
3092
3093        // These are valid configurations.
3094        let e_service_valid = entry_init!(
3095            (Attribute::Class, EntryClass::Service.to_value()),
3096            (Attribute::Class, EntryClass::Account.to_value()),
3097            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3098        )
3099        .into_invalid_new();
3100
3101        assert!(e_service_valid.validate(&schema).is_ok());
3102
3103        let e_person_valid = entry_init!(
3104            (Attribute::Class, EntryClass::Person.to_value()),
3105            (Attribute::Class, EntryClass::Account.to_value()),
3106            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3107        )
3108        .into_invalid_new();
3109
3110        assert!(e_person_valid.validate(&schema).is_ok());
3111
3112        let e_person_valid = entry_init!(
3113            (Attribute::Class, EntryClass::Person.to_value()),
3114            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3115        )
3116        .into_invalid_new();
3117
3118        assert!(e_person_valid.validate(&schema).is_ok());
3119    }
3120}