kanidmd_lib/
schema.rs

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