kanidmd_lib/
schema.rs

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