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