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![
2019                    Attribute::Description,
2020                    Attribute::EntryManagedBy,
2021                    Attribute::MemberOf,
2022                    Attribute::DirectMemberOf,
2023                ],
2024                systemmust: vec![
2025                    Attribute::Class,
2026                    Attribute::Uuid,
2027                    Attribute::LastModifiedCid,
2028                    Attribute::CreatedAtCid,
2029                ],
2030                ..Default::default()
2031            },
2032        );
2033        self.classes.insert(
2034            EntryClass::Builtin.into(),
2035            SchemaClass {
2036                name: EntryClass::Builtin.into(),
2037                uuid: UUID_SCHEMA_CLASS_BUILTIN,
2038                description: String::from("A marker class denoting builtin entries"),
2039                ..Default::default()
2040            },
2041        );
2042        self.classes.insert(
2043            EntryClass::MemberOf.into(),
2044            SchemaClass {
2045                name: EntryClass::MemberOf.into(),
2046                uuid: UUID_SCHEMA_CLASS_MEMBEROF,
2047                description: String::from(
2048                    "Class that is dynamically added to recipients of memberof or directmemberof. TO BE REMOVED.",
2049                ),
2050                ..Default::default()
2051            },
2052        );
2053        self.classes.insert(
2054            EntryClass::ExtensibleObject.into(),
2055            SchemaClass {
2056                name: EntryClass::ExtensibleObject.into(),
2057                uuid: UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT,
2058                description: String::from(
2059                    "A class type that has green hair and turns off all rules ...",
2060                ),
2061                ..Default::default()
2062            },
2063        );
2064        /* These two classes are core to the entry lifecycle for recycling and tombstoning */
2065        self.classes.insert(
2066                EntryClass::Recycled.into(),
2067                SchemaClass {
2068                    name: EntryClass::Recycled.into(),
2069                    uuid: UUID_SCHEMA_CLASS_RECYCLED,
2070                    description: String::from("An object that has been deleted, but still recoverable via the revive operation. Recycled objects are not modifiable, only revivable."),
2071                    systemmay: vec![Attribute::RecycledDirectMemberOf, Attribute::CascadeDeleted],
2072                    .. Default::default()
2073                },
2074            );
2075        self.classes.insert(
2076                EntryClass::Tombstone.into(),
2077                SchemaClass {
2078                    name: EntryClass::Tombstone.into(),
2079                    uuid: UUID_SCHEMA_CLASS_TOMBSTONE,
2080                    description: String::from("An object that is purged from the recycle bin. This is a system internal state. Tombstones have no attributes beside UUID."),
2081                    systemmust: vec![
2082                        Attribute::Class,
2083                        Attribute::Uuid,
2084                    ],
2085                    .. Default::default()
2086                },
2087            );
2088        self.classes.insert(
2089            EntryClass::Conflict.into(),
2090            SchemaClass {
2091                name: EntryClass::Conflict.into(),
2092                uuid: UUID_SCHEMA_CLASS_CONFLICT,
2093                description: String::from(
2094                    "An entry representing conflicts that occurred during replication",
2095                ),
2096                systemmust: vec![Attribute::SourceUuid],
2097                systemsupplements: vec![EntryClass::Recycled.into()],
2098                ..Default::default()
2099            },
2100        );
2101        // sysinfo
2102        self.classes.insert(
2103            EntryClass::SystemInfo.into(),
2104            SchemaClass {
2105                name: EntryClass::SystemInfo.into(),
2106                uuid: UUID_SCHEMA_CLASS_SYSTEM_INFO,
2107                description: String::from("System metadata object class"),
2108                systemmust: vec![Attribute::Version],
2109                ..Default::default()
2110            },
2111        );
2112        // ACP
2113        self.classes.insert(
2114            EntryClass::AccessControlSearch.into(),
2115            SchemaClass {
2116                name: EntryClass::AccessControlSearch.into(),
2117                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH,
2118                description: String::from("System Access Control Search Class"),
2119                systemmust: vec![Attribute::AcpSearchAttr],
2120                ..Default::default()
2121            },
2122        );
2123        self.classes.insert(
2124            EntryClass::AccessControlDelete.into(),
2125            SchemaClass {
2126                name: EntryClass::AccessControlDelete.into(),
2127                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE,
2128                description: String::from("System Access Control DELETE Class"),
2129                ..Default::default()
2130            },
2131        );
2132        self.classes.insert(
2133            EntryClass::AccessControlModify.into(),
2134            SchemaClass {
2135                name: EntryClass::AccessControlModify.into(),
2136                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY,
2137                description: String::from("System Access Control Modify Class"),
2138                systemmay: vec![
2139                    Attribute::AcpModifyRemovedAttr,
2140                    Attribute::AcpModifyPresentAttr,
2141                    Attribute::AcpModifyClass,
2142                    Attribute::AcpModifyPresentClass,
2143                    Attribute::AcpModifyRemoveClass,
2144                ],
2145                ..Default::default()
2146            },
2147        );
2148        self.classes.insert(
2149            EntryClass::AccessControlCreate.into(),
2150            SchemaClass {
2151                name: EntryClass::AccessControlCreate.into(),
2152                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE,
2153                description: String::from("System Access Control Create Class"),
2154                systemmay: vec![Attribute::AcpCreateClass, Attribute::AcpCreateAttr],
2155                ..Default::default()
2156            },
2157        );
2158        self.classes.insert(
2159            EntryClass::AccessControlProfile.into(),
2160            SchemaClass {
2161                name: EntryClass::AccessControlProfile.into(),
2162                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
2163                description: String::from("System Access Control Profile Class"),
2164                systemmay: vec![Attribute::AcpEnable, Attribute::Description],
2165                systemmust: vec![Attribute::Name],
2166                systemsupplements: vec![
2167                    EntryClass::AccessControlSearch.into(),
2168                    EntryClass::AccessControlDelete.into(),
2169                    EntryClass::AccessControlModify.into(),
2170                    EntryClass::AccessControlCreate.into(),
2171                ],
2172                ..Default::default()
2173            },
2174        );
2175        self.classes.insert(
2176            EntryClass::AccessControlReceiverEntryManager.into(),
2177            SchemaClass {
2178                name: EntryClass::AccessControlReceiverEntryManager.into(),
2179                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER,
2180                description: String::from("System Access Control Profile Receiver - Entry Manager"),
2181                systemexcludes: vec![EntryClass::AccessControlReceiverGroup.into()],
2182                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2183                ..Default::default()
2184            },
2185        );
2186        self.classes.insert(
2187            EntryClass::AccessControlReceiverGroup.into(),
2188            SchemaClass {
2189                name: EntryClass::AccessControlReceiverGroup.into(),
2190                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP,
2191                description: String::from("System Access Control Profile Receiver - Group"),
2192                systemmay: vec![Attribute::AcpReceiver],
2193                systemmust: vec![Attribute::AcpReceiverGroup],
2194                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2195                systemexcludes: vec![EntryClass::AccessControlReceiverEntryManager.into()],
2196                ..Default::default()
2197            },
2198        );
2199        self.classes.insert(
2200            EntryClass::AccessControlTargetScope.into(),
2201            SchemaClass {
2202                name: EntryClass::AccessControlTargetScope.into(),
2203                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE,
2204                description: String::from("System Access Control Profile Target - Scope"),
2205                systemmust: vec![Attribute::AcpTargetScope],
2206                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
2207                ..Default::default()
2208            },
2209        );
2210
2211        // System attrs
2212        self.classes.insert(
2213            EntryClass::System.into(),
2214            SchemaClass {
2215                name: EntryClass::System.into(),
2216                uuid: UUID_SCHEMA_CLASS_SYSTEM,
2217                description: String::from("A class denoting that a type is system generated and protected. It has special internal behaviour."),
2218                .. Default::default()
2219            },
2220        );
2221        self.classes.insert(
2222            EntryClass::SyncObject.into(),
2223            SchemaClass {
2224                name: EntryClass::SyncObject.into(),
2225                uuid: UUID_SCHEMA_CLASS_SYNC_OBJECT,
2226                description: String::from("A class denoting that an entry is synchronised from an external source. This entry may not be modifiable."),
2227                systemmust: vec![
2228                    Attribute::SyncParentUuid
2229                ],
2230                systemmay: vec![
2231                    Attribute::SyncExternalId,
2232                    Attribute::SyncClass,
2233                ],
2234                .. Default::default()
2235            },
2236        );
2237
2238        let r = self.validate();
2239        if r.is_empty() {
2240            admin_debug!("schema validate -> passed");
2241            Ok(())
2242        } else {
2243            admin_error!(err = ?r, "schema validate -> errors");
2244            Err(OperationError::ConsistencyError(
2245                r.into_iter().filter_map(|v| v.err()).collect(),
2246            ))
2247        }
2248    }
2249}
2250
2251impl SchemaTransaction for SchemaWriteTransaction<'_> {
2252    fn get_attributes_unique(&self) -> &Vec<Attribute> {
2253        &self.unique_cache
2254    }
2255
2256    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
2257        &self.ref_cache
2258    }
2259
2260    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
2261        &self.classes
2262    }
2263
2264    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
2265        &self.attributes
2266    }
2267}
2268
2269impl SchemaTransaction for SchemaReadTransaction {
2270    fn get_attributes_unique(&self) -> &Vec<Attribute> {
2271        &self.unique_cache
2272    }
2273
2274    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
2275        &self.ref_cache
2276    }
2277
2278    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
2279        &self.classes
2280    }
2281
2282    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
2283        &self.attributes
2284    }
2285}
2286
2287impl Schema {
2288    pub fn new() -> Result<Self, OperationError> {
2289        let s = Schema {
2290            classes: CowCell::new(HashMap::with_capacity(128)),
2291            attributes: CowCell::new(HashMap::with_capacity(128)),
2292            unique_cache: CowCell::new(Vec::with_capacity(0)),
2293            ref_cache: CowCell::new(HashMap::with_capacity(64)),
2294        };
2295        let mut sw = s.write();
2296        let r1 = sw.generate_in_memory();
2297        debug_assert!(r1.is_ok());
2298        r1?;
2299        let r2 = sw.commit().map(|_| s);
2300        debug_assert!(r2.is_ok());
2301        r2
2302    }
2303
2304    pub fn read(&self) -> SchemaReadTransaction {
2305        SchemaReadTransaction {
2306            classes: self.classes.read(),
2307            attributes: self.attributes.read(),
2308            unique_cache: self.unique_cache.read(),
2309            ref_cache: self.ref_cache.read(),
2310        }
2311    }
2312
2313    pub fn write(&self) -> SchemaWriteTransaction<'_> {
2314        SchemaWriteTransaction {
2315            classes: self.classes.write(),
2316            attributes: self.attributes.write(),
2317            unique_cache: self.unique_cache.write(),
2318            ref_cache: self.ref_cache.write(),
2319        }
2320    }
2321
2322    #[cfg(test)]
2323    pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
2324        self.write()
2325    }
2326}
2327
2328#[cfg(test)]
2329mod tests {
2330    use crate::prelude::*;
2331    use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
2332    use uuid::Uuid;
2333
2334    // use crate::proto_v1::Filter as ProtoFilter;
2335
2336    macro_rules! validate_schema {
2337        ($sch:ident) => {{
2338            // Turns into a result type
2339            let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
2340            assert!(r.is_ok());
2341        }};
2342    }
2343
2344    macro_rules! sch_from_entry_ok {
2345        (
2346            $e:expr,
2347            $type:ty
2348        ) => {{
2349            let ev1 = $e.into_sealed_committed();
2350
2351            let r1 = <$type>::try_from(&ev1);
2352            assert!(r1.is_ok());
2353        }};
2354    }
2355
2356    macro_rules! sch_from_entry_err {
2357        (
2358            $e:expr,
2359            $type:ty
2360        ) => {{
2361            let ev1 = $e.into_sealed_committed();
2362
2363            let r1 = <$type>::try_from(&ev1);
2364            assert!(r1.is_err());
2365        }};
2366    }
2367
2368    #[test]
2369    fn test_schema_attribute_from_entry() {
2370        sketching::test_init();
2371
2372        sch_from_entry_err!(
2373            entry_init!(
2374                (Attribute::Class, EntryClass::Object.to_value()),
2375                (Attribute::Class, EntryClass::AttributeType.to_value()),
2376                (
2377                    Attribute::AttributeName,
2378                    Value::new_iutf8("schema_attr_test")
2379                ),
2380                (
2381                    Attribute::Uuid,
2382                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2383                ),
2384                (Attribute::Unique, Value::Bool(false))
2385            ),
2386            SchemaAttribute
2387        );
2388
2389        sch_from_entry_err!(
2390            entry_init!(
2391                (Attribute::Class, EntryClass::Object.to_value()),
2392                (Attribute::Class, EntryClass::AttributeType.to_value()),
2393                (
2394                    Attribute::AttributeName,
2395                    Value::new_iutf8("schema_attr_test")
2396                ),
2397                (
2398                    Attribute::Uuid,
2399                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2400                ),
2401                (Attribute::MultiValue, Value::Bool(false)),
2402                (Attribute::Unique, Value::Bool(false)),
2403                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2404            ),
2405            SchemaAttribute
2406        );
2407
2408        sch_from_entry_err!(
2409            entry_init!(
2410                (Attribute::Class, EntryClass::Object.to_value()),
2411                (Attribute::Class, EntryClass::AttributeType.to_value()),
2412                (
2413                    Attribute::AttributeName,
2414                    Value::new_iutf8("schema_attr_test")
2415                ),
2416                (
2417                    Attribute::Uuid,
2418                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2419                ),
2420                (
2421                    Attribute::Description,
2422                    Value::Utf8("Test attr parsing".to_string())
2423                ),
2424                (Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
2425                (Attribute::Unique, Value::Bool(false)),
2426                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2427            ),
2428            SchemaAttribute
2429        );
2430
2431        sch_from_entry_err!(
2432            entry_init!(
2433                (Attribute::Class, EntryClass::Object.to_value()),
2434                (Attribute::Class, EntryClass::AttributeType.to_value()),
2435                (
2436                    Attribute::AttributeName,
2437                    Value::new_iutf8("schema_attr_test")
2438                ),
2439                (
2440                    Attribute::Uuid,
2441                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2442                ),
2443                (
2444                    Attribute::Description,
2445                    Value::Utf8("Test attr parsing".to_string())
2446                ),
2447                (Attribute::MultiValue, Value::Bool(false)),
2448                (Attribute::Unique, Value::Bool(false)),
2449                (Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
2450            ),
2451            SchemaAttribute
2452        );
2453
2454        // Index is allowed to be empty
2455        sch_from_entry_ok!(
2456            entry_init!(
2457                (Attribute::Class, EntryClass::Object.to_value()),
2458                (Attribute::Class, EntryClass::AttributeType.to_value()),
2459                (
2460                    Attribute::AttributeName,
2461                    Value::new_iutf8("schema_attr_test")
2462                ),
2463                (
2464                    Attribute::Uuid,
2465                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2466                ),
2467                (
2468                    Attribute::Description,
2469                    Value::Utf8("Test attr parsing".to_string())
2470                ),
2471                (Attribute::MultiValue, Value::Bool(false)),
2472                (Attribute::Unique, Value::Bool(false)),
2473                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
2474            ),
2475            SchemaAttribute
2476        );
2477
2478        // Index present
2479        sch_from_entry_ok!(
2480            entry_init!(
2481                (Attribute::Class, EntryClass::Object.to_value()),
2482                (Attribute::Class, EntryClass::AttributeType.to_value()),
2483                (
2484                    Attribute::AttributeName,
2485                    Value::new_iutf8("schema_attr_test")
2486                ),
2487                (
2488                    Attribute::Uuid,
2489                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2490                ),
2491                (
2492                    Attribute::Description,
2493                    Value::Utf8("Test attr parsing".to_string())
2494                ),
2495                (Attribute::MultiValue, Value::Bool(false)),
2496                (Attribute::Unique, Value::Bool(false)),
2497                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2498                (Attribute::Index, Value::Bool(true))
2499            ),
2500            SchemaAttribute
2501        );
2502    }
2503
2504    #[test]
2505    fn test_schema_class_from_entry() {
2506        sch_from_entry_err!(
2507            entry_init!(
2508                (Attribute::Class, EntryClass::Object.to_value()),
2509                (Attribute::Class, EntryClass::ClassType.to_value()),
2510                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2511                (
2512                    Attribute::Uuid,
2513                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2514                )
2515            ),
2516            SchemaClass
2517        );
2518
2519        sch_from_entry_err!(
2520            entry_init!(
2521                (Attribute::Class, EntryClass::Object.to_value()),
2522                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2523                (
2524                    Attribute::Uuid,
2525                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2526                ),
2527                (
2528                    Attribute::Description,
2529                    Value::Utf8("class test".to_string())
2530                )
2531            ),
2532            SchemaClass
2533        );
2534
2535        // Classes can be valid with no attributes provided.
2536        sch_from_entry_ok!(
2537            entry_init!(
2538                (Attribute::Class, EntryClass::Object.to_value()),
2539                (Attribute::Class, EntryClass::ClassType.to_value()),
2540                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2541                (
2542                    Attribute::Uuid,
2543                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2544                ),
2545                (
2546                    Attribute::Description,
2547                    Value::Utf8("class test".to_string())
2548                )
2549            ),
2550            SchemaClass
2551        );
2552
2553        // Classes with various may/must
2554        sch_from_entry_ok!(
2555            entry_init!(
2556                (Attribute::Class, EntryClass::Object.to_value()),
2557                (Attribute::Class, EntryClass::ClassType.to_value()),
2558                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2559                (
2560                    Attribute::Uuid,
2561                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2562                ),
2563                (
2564                    Attribute::Description,
2565                    Value::Utf8("class test".to_string())
2566                ),
2567                (Attribute::SystemMust, Value::new_iutf8("a"))
2568            ),
2569            SchemaClass
2570        );
2571
2572        sch_from_entry_ok!(
2573            entry_init!(
2574                (Attribute::Class, EntryClass::Object.to_value()),
2575                (Attribute::Class, EntryClass::ClassType.to_value()),
2576                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2577                (
2578                    Attribute::Uuid,
2579                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2580                ),
2581                (
2582                    Attribute::Description,
2583                    Value::Utf8("class test".to_string())
2584                ),
2585                (Attribute::SystemMay, Value::new_iutf8("a"))
2586            ),
2587            SchemaClass
2588        );
2589
2590        sch_from_entry_ok!(
2591            entry_init!(
2592                (Attribute::Class, EntryClass::Object.to_value()),
2593                (Attribute::Class, EntryClass::ClassType.to_value()),
2594                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2595                (
2596                    Attribute::Uuid,
2597                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2598                ),
2599                (
2600                    Attribute::Description,
2601                    Value::Utf8("class test".to_string())
2602                ),
2603                (Attribute::May, Value::new_iutf8("a")),
2604                (Attribute::Must, Value::new_iutf8("b"))
2605            ),
2606            SchemaClass
2607        );
2608
2609        sch_from_entry_ok!(
2610            entry_init!(
2611                (Attribute::Class, EntryClass::Object.to_value()),
2612                (Attribute::Class, EntryClass::ClassType.to_value()),
2613                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
2614                (
2615                    Attribute::Uuid,
2616                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
2617                ),
2618                (
2619                    Attribute::Description,
2620                    Value::Utf8("class test".to_string())
2621                ),
2622                (Attribute::May, Value::new_iutf8("a")),
2623                (Attribute::Must, Value::new_iutf8("b")),
2624                (Attribute::SystemMay, Value::new_iutf8("c")),
2625                (Attribute::SystemMust, Value::new_iutf8("d"))
2626            ),
2627            SchemaClass
2628        );
2629    }
2630
2631    #[test]
2632    fn test_schema_attribute_simple() {
2633        // Test schemaAttribute validation of types.
2634
2635        // Test single value string
2636        let single_value_string = SchemaAttribute {
2637            name: Attribute::from("single_value"),
2638            uuid: Uuid::new_v4(),
2639            description: String::from(""),
2640            syntax: SyntaxType::Utf8StringInsensitive,
2641            ..Default::default()
2642        };
2643
2644        let r1 = single_value_string
2645            .validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
2646        assert_eq!(r1, Ok(()));
2647
2648        let rvs = vs_iutf8!["test1", "test2"] as _;
2649        let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
2650        assert_eq!(
2651            r2,
2652            Err(SchemaError::InvalidAttributeSyntax(
2653                "single_value".to_string()
2654            ))
2655        );
2656
2657        // test multivalue string, boolean
2658
2659        let multi_value_string = SchemaAttribute {
2660            name: Attribute::from("mv_string"),
2661            uuid: Uuid::new_v4(),
2662            description: String::from(""),
2663            multivalue: true,
2664            syntax: SyntaxType::Utf8String,
2665            ..Default::default()
2666        };
2667
2668        let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
2669        let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
2670        assert_eq!(r5, Ok(()));
2671
2672        let multi_value_boolean = SchemaAttribute {
2673            name: Attribute::from("mv_bool"),
2674            uuid: Uuid::new_v4(),
2675            description: String::from(""),
2676            multivalue: true,
2677            syntax: SyntaxType::Boolean,
2678            ..Default::default()
2679        };
2680
2681        // Since valueset now disallows such shenanigans at a type level, this can't occur
2682        /*
2683        let rvs = unsafe {
2684            valueset![
2685                Value::new_bool(true),
2686                Value::new_iutf8("test1"),
2687                Value::new_iutf8("test2")
2688            ]
2689        };
2690        let r3 = multi_value_boolean.validate_ava("mv_bool", &rvs);
2691        assert_eq!(
2692            r3,
2693            Err(SchemaError::InvalidAttributeSyntax("mv_bool".to_string()))
2694        );
2695        */
2696
2697        let rvs = vs_bool![true, false];
2698        let r4 = multi_value_boolean.validate_ava(&Attribute::from("mv_bool"), &(rvs as _));
2699        assert_eq!(r4, Ok(()));
2700
2701        // syntax_id and index_type values
2702        let single_value_syntax = SchemaAttribute {
2703            name: Attribute::from("sv_syntax"),
2704            uuid: Uuid::new_v4(),
2705            description: String::from(""),
2706            syntax: SyntaxType::SyntaxId,
2707            ..Default::default()
2708        };
2709
2710        let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
2711        let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
2712        assert_eq!(r6, Ok(()));
2713
2714        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
2715        let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
2716        assert_eq!(
2717            r7,
2718            Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
2719        );
2720
2721        let single_value_index = SchemaAttribute {
2722            name: Attribute::from("sv_index"),
2723            uuid: Uuid::new_v4(),
2724            description: String::from(""),
2725            syntax: SyntaxType::IndexId,
2726            ..Default::default()
2727        };
2728
2729        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
2730        let r9 = single_value_index.validate_ava(&Attribute::from("sv_index"), &rvs);
2731        assert_eq!(
2732            r9,
2733            Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
2734        );
2735    }
2736
2737    #[test]
2738    fn test_schema_simple() {
2739        let schema = Schema::new().expect("failed to create schema");
2740        let schema_ro = schema.read();
2741        validate_schema!(schema_ro);
2742    }
2743
2744    #[test]
2745    fn test_schema_entries() {
2746        sketching::test_init();
2747        // Given an entry, assert it's schema is valid
2748        // We do
2749        let schema_outer = Schema::new().expect("failed to create schema");
2750        let schema = schema_outer.read();
2751
2752        let e_no_uuid = entry_init!().into_invalid_new();
2753
2754        assert_eq!(
2755            e_no_uuid.validate(&schema),
2756            Err(SchemaError::MissingMustAttribute(vec![Attribute::Uuid]))
2757        );
2758
2759        let e_no_class = entry_init!((
2760            Attribute::Uuid,
2761            Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2762        ))
2763        .into_invalid_new();
2764
2765        assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));
2766
2767        let e_bad_class = entry_init!(
2768            (
2769                Attribute::Uuid,
2770                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2771            ),
2772            (Attribute::Class, Value::new_iutf8("zzzzzz"))
2773        )
2774        .into_invalid_new();
2775        assert_eq!(
2776            e_bad_class.validate(&schema),
2777            Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
2778        );
2779
2780        let e_attr_invalid = entry_init!(
2781            (
2782                Attribute::Uuid,
2783                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2784            ),
2785            (Attribute::Class, EntryClass::Object.to_value()),
2786            (Attribute::Class, EntryClass::AttributeType.to_value())
2787        )
2788        .into_invalid_new();
2789        let res = e_attr_invalid.validate(&schema);
2790        matches!(res, Err(SchemaError::MissingMustAttribute(_)));
2791
2792        let e_attr_invalid_may = entry_init!(
2793            (Attribute::Class, EntryClass::Object.to_value()),
2794            (Attribute::Class, EntryClass::AttributeType.to_value()),
2795            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2796            (Attribute::Description, Value::Utf8("testattr".to_string())),
2797            (Attribute::MultiValue, Value::Bool(false)),
2798            (Attribute::Unique, Value::Bool(false)),
2799            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2800            (
2801                Attribute::Uuid,
2802                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2803            ),
2804            (Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
2805        )
2806        .into_invalid_new();
2807
2808        assert_eq!(
2809            e_attr_invalid_may.validate(&schema),
2810            Err(SchemaError::AttributeNotValidForClass(
2811                Attribute::TestAttr.to_string()
2812            ))
2813        );
2814
2815        let e_attr_invalid_syn = entry_init!(
2816            (Attribute::Class, EntryClass::Object.to_value()),
2817            (Attribute::Class, EntryClass::AttributeType.to_value()),
2818            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2819            (Attribute::Description, Value::Utf8("testattr".to_string())),
2820            (Attribute::MultiValue, Value::Utf8("false".to_string())),
2821            (Attribute::Unique, Value::Bool(false)),
2822            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2823            (
2824                Attribute::Uuid,
2825                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2826            )
2827        )
2828        .into_invalid_new();
2829
2830        assert_eq!(
2831            e_attr_invalid_syn.validate(&schema),
2832            Err(SchemaError::InvalidAttributeSyntax(
2833                "multivalue".to_string()
2834            ))
2835        );
2836
2837        // You may not have the phantom.
2838        let e_phantom = entry_init!(
2839            (Attribute::Class, EntryClass::Object.to_value()),
2840            (Attribute::Class, EntryClass::AttributeType.to_value()),
2841            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2842            (Attribute::Description, Value::Utf8("testattr".to_string())),
2843            (Attribute::MultiValue, Value::Bool(false)),
2844            (Attribute::Unique, Value::Bool(false)),
2845            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2846            (
2847                Attribute::Uuid,
2848                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2849            ),
2850            (
2851                Attribute::PasswordImport,
2852                Value::Utf8("password".to_string())
2853            )
2854        )
2855        .into_invalid_new();
2856        assert!(e_phantom.validate(&schema).is_err());
2857
2858        let e_ok = entry_init!(
2859            (Attribute::Class, EntryClass::Object.to_value()),
2860            (Attribute::Class, EntryClass::AttributeType.to_value()),
2861            (Attribute::AttributeName, Value::new_iutf8("testattr")),
2862            (Attribute::Description, Value::Utf8("testattr".to_string())),
2863            (Attribute::MultiValue, Value::Bool(true)),
2864            (Attribute::Unique, Value::Bool(false)),
2865            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
2866            (
2867                Attribute::Uuid,
2868                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2869            )
2870        )
2871        .into_invalid_new();
2872        assert!(e_ok.validate(&schema).is_ok());
2873    }
2874
2875    #[test]
2876    fn test_schema_extensible() {
2877        let schema_outer = Schema::new().expect("failed to create schema");
2878        let schema = schema_outer.read();
2879        // Just because you are extensible, doesn't mean you can be lazy
2880        let e_extensible_bad = entry_init!(
2881            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2882            (
2883                Attribute::Uuid,
2884                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2885            ),
2886            (Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
2887        )
2888        .into_invalid_new();
2889
2890        assert_eq!(
2891            e_extensible_bad.validate(&schema),
2892            Err(SchemaError::InvalidAttributeSyntax(
2893                "multivalue".to_string()
2894            ))
2895        );
2896
2897        // Extensible doesn't mean you can have the phantoms
2898        let e_extensible_phantom = entry_init!(
2899            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2900            (
2901                Attribute::Uuid,
2902                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2903            ),
2904            (Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
2905        )
2906        .into_invalid_new();
2907
2908        assert_eq!(
2909            e_extensible_phantom.validate(&schema),
2910            Err(SchemaError::PhantomAttribute(
2911                Attribute::PasswordImport.to_string()
2912            ))
2913        );
2914
2915        let e_extensible = entry_init!(
2916            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2917            (
2918                Attribute::Uuid,
2919                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
2920            ),
2921            (Attribute::MultiValue, Value::Bool(true))
2922        )
2923        .into_invalid_new();
2924
2925        /* Is okay because extensible! */
2926        assert!(e_extensible.validate(&schema).is_ok());
2927    }
2928
2929    #[test]
2930    fn test_schema_filter_validation() {
2931        let schema_outer = Schema::new().expect("failed to create schema");
2932        let schema = schema_outer.read();
2933
2934        // test syntax of bool
2935        let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
2936        assert_eq!(
2937            f_bool.validate(&schema),
2938            Err(SchemaError::InvalidAttributeSyntax(
2939                "multivalue".to_string()
2940            ))
2941        );
2942        // test insensitive values
2943        let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2944        assert_eq!(
2945            f_insense.validate(&schema),
2946            Ok(filter_valid!(f_eq(
2947                Attribute::Class,
2948                EntryClass::AttributeType.into()
2949            )))
2950        );
2951        // Test the recursive structures validate
2952        let f_or_empty = filter_all!(f_or!([]));
2953        assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
2954        let f_or = filter_all!(f_or!([f_eq(
2955            Attribute::MultiValue,
2956            PartialValue::new_iutf8("zzzz")
2957        )]));
2958        assert_eq!(
2959            f_or.validate(&schema),
2960            Err(SchemaError::InvalidAttributeSyntax(
2961                "multivalue".to_string()
2962            ))
2963        );
2964        let f_or_mult = filter_all!(f_and!([
2965            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2966            f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
2967        ]));
2968        assert_eq!(
2969            f_or_mult.validate(&schema),
2970            Err(SchemaError::InvalidAttributeSyntax(
2971                "multivalue".to_string()
2972            ))
2973        );
2974        // Test mixed case attr name - this is a pass, due to normalisation
2975        let f_or_ok = filter_all!(f_andnot(f_and!([
2976            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2977            f_sub(Attribute::Class, EntryClass::ClassType.into()),
2978            f_pres(Attribute::Class)
2979        ])));
2980        assert_eq!(
2981            f_or_ok.validate(&schema),
2982            Ok(filter_valid!(f_andnot(f_and!([
2983                f_eq(Attribute::Class, EntryClass::AttributeType.into()),
2984                f_sub(Attribute::Class, EntryClass::ClassType.into()),
2985                f_pres(Attribute::Class)
2986            ]))))
2987        );
2988    }
2989
2990    #[test]
2991    fn test_schema_class_phantom_reject() {
2992        // Check that entries can be normalised and validated sanely
2993        let schema_outer = Schema::new().expect("failed to create schema");
2994        let mut schema = schema_outer.write_blocking();
2995
2996        assert!(schema.validate().is_empty());
2997
2998        // Attempt to create a class with a phantom attribute, should be refused.
2999        let class = SchemaClass {
3000            name: AttrString::from("testobject"),
3001            uuid: Uuid::new_v4(),
3002            description: String::from("test object"),
3003            systemmay: vec![Attribute::Claim],
3004            ..Default::default()
3005        };
3006
3007        assert!(schema.update_classes(vec![class]).is_ok());
3008
3009        assert_eq!(schema.validate().len(), 1);
3010    }
3011
3012    #[test]
3013    fn test_schema_class_exclusion_requires() {
3014        sketching::test_init();
3015
3016        let schema_outer = Schema::new().expect("failed to create schema");
3017        let mut schema = schema_outer.write_blocking();
3018
3019        assert!(schema.validate().is_empty());
3020
3021        // We setup some classes that have requires and excludes and check that they
3022        // are enforced correctly.
3023        let class_account = SchemaClass {
3024            name: Attribute::Account.into(),
3025            uuid: Uuid::new_v4(),
3026            description: String::from("account object"),
3027            systemmust: vec![
3028                Attribute::Class,
3029                Attribute::Uuid,
3030                Attribute::LastModifiedCid,
3031                Attribute::CreatedAtCid,
3032            ],
3033            systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
3034            ..Default::default()
3035        };
3036
3037        let class_person = SchemaClass {
3038            name: EntryClass::Person.into(),
3039            uuid: Uuid::new_v4(),
3040            description: String::from("person object"),
3041            systemmust: vec![
3042                Attribute::Class,
3043                Attribute::Uuid,
3044                Attribute::LastModifiedCid,
3045                Attribute::CreatedAtCid,
3046            ],
3047            ..Default::default()
3048        };
3049
3050        let class_service = SchemaClass {
3051            name: EntryClass::Service.into(),
3052            uuid: Uuid::new_v4(),
3053            description: String::from("service object"),
3054            systemmust: vec![
3055                Attribute::Class,
3056                Attribute::Uuid,
3057                Attribute::LastModifiedCid,
3058                Attribute::CreatedAtCid,
3059            ],
3060            excludes: vec![EntryClass::Person.into()],
3061            ..Default::default()
3062        };
3063
3064        assert!(schema
3065            .update_classes(vec![class_account, class_person, class_service])
3066            .is_ok());
3067
3068        // Missing person or service account.
3069        let e_account = entry_init!(
3070            (Attribute::Class, EntryClass::Account.to_value()),
3071            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3072        )
3073        .into_invalid_new();
3074
3075        assert_eq!(
3076            e_account.validate(&schema),
3077            Err(SchemaError::SupplementsNotSatisfied(vec![
3078                EntryClass::Service.into(),
3079                EntryClass::Person.into(),
3080            ]))
3081        );
3082
3083        // Service account missing account
3084        /*
3085        let e_service = unsafe { entry_init!(
3086            (Attribute::Class, EntryClass::Service.to_value()),
3087            (Attribute::Uuid, Value::new_uuid(Uuid::new_v4()))
3088        ).into_invalid_new() };
3089
3090        assert_eq!(
3091            e_service.validate(&schema),
3092            Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()]))
3093        );
3094        */
3095
3096        // Service can't have person
3097        let e_service_person = entry_init!(
3098            (Attribute::Class, EntryClass::Service.to_value()),
3099            (Attribute::Class, EntryClass::Account.to_value()),
3100            (Attribute::Class, EntryClass::Person.to_value()),
3101            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3102        )
3103        .into_invalid_new();
3104
3105        assert_eq!(
3106            e_service_person.validate(&schema),
3107            Err(SchemaError::ExcludesNotSatisfied(vec![
3108                EntryClass::Person.to_string()
3109            ]))
3110        );
3111
3112        // These are valid configurations.
3113        let e_service_valid = entry_init!(
3114            (Attribute::Class, EntryClass::Service.to_value()),
3115            (Attribute::Class, EntryClass::Account.to_value()),
3116            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3117        )
3118        .into_invalid_new();
3119
3120        assert!(e_service_valid.validate(&schema).is_ok());
3121
3122        let e_person_valid = entry_init!(
3123            (Attribute::Class, EntryClass::Person.to_value()),
3124            (Attribute::Class, EntryClass::Account.to_value()),
3125            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3126        )
3127        .into_invalid_new();
3128
3129        assert!(e_person_valid.validate(&schema).is_ok());
3130
3131        let e_person_valid = entry_init!(
3132            (Attribute::Class, EntryClass::Person.to_value()),
3133            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
3134        )
3135        .into_invalid_new();
3136
3137        assert!(e_person_valid.validate(&schema).is_ok());
3138    }
3139}