Skip to main content

kanidmd_lib/
schema.rs

1//! [Schema] are one of the foundational concepts of the server. They provide a
2//! set of rules to enforce that an [Entry]'s values must be compliant to, to be
3//! considered valid for commit to the database. This allows us to provide
4//! requirements and structure as to what an [Entry] must have and may contain
5//! which enables many other parts to function.
6//!
7//! To define this structure we define [Attribute]s that provide rules for how
8//! an ava should be structured. We also define the [SchemaClass]es that define
9//! the rules of which [Attribute]s may or must exist on an [Entry] for it
10//! to be considered valid. An [Entry] must have at between 1 and infinite
11//! [SchemaClass]es. [SchemaClass] entries are additive.
12//!
13
14use crate::be::IdxKey;
15use crate::migration_data;
16use crate::prelude::*;
17use crate::valueset::ValueSet;
18use concread::cowcell::*;
19use hashbrown::{HashMap, HashSet};
20use std::collections::BTreeSet;
21use tracing::trace;
22use uuid::Uuid;
23
24// representations of schema that confines object types, classes
25// and attributes. This ties in deeply with "Entry".
26//
27// In the future this will parse/read it's schema from the db
28// but we have to bootstrap with some core types.
29
30/// Schema stores the set of [`Classes`] and [`Attributes`] that the server will
31/// use to validate [`Entries`], [`Filters`] and [`Modifications`]. Additionally the
32/// schema stores an extracted copy of the current attribute indexing metadata that
33/// is used by the backend during queries.
34///
35/// [`Filters`]: ../filter/index.html
36/// [`Modifications`]: ../modify/index.html
37/// [`Entries`]: ../entry/index.html
38/// [`Attributes`]: struct.SchemaAttribute.html
39/// [`Classes`]: struct.SchemaClass.html
40pub struct Schema {
41    classes: CowCell<HashMap<AttrString, SchemaClass>>,
42    attributes: CowCell<HashMap<Attribute, SchemaAttribute>>,
43    unique_cache: CowCell<Vec<Attribute>>,
44    ref_cache: CowCell<HashMap<Attribute, SchemaAttribute>>,
45}
46
47/// A writable transaction of the working schema set. You should not change this directly,
48/// the writability is for the server internally to allow reloading of the schema. Changes
49/// you make will be lost when the server re-reads the schema from disk.
50pub struct SchemaWriteTransaction<'a> {
51    classes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaClass>>,
52    attributes: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
53
54    unique_cache: CowCellWriteTxn<'a, Vec<Attribute>>,
55    ref_cache: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
56}
57
58/// A readonly transaction of the working schema set.
59pub struct SchemaReadTransaction {
60    classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
61    attributes: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
62
63    unique_cache: CowCellReadTxn<Vec<Attribute>>,
64    ref_cache: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
65}
66
67#[derive(Debug, Clone, Copy, Default)]
68pub enum Replicated {
69    #[default]
70    True,
71    False,
72}
73
74impl From<Replicated> for bool {
75    fn from(value: Replicated) -> bool {
76        match value {
77            Replicated::True => true,
78            Replicated::False => false,
79        }
80    }
81}
82
83impl From<bool> for Replicated {
84    fn from(value: bool) -> Self {
85        match value {
86            true => Replicated::True,
87            false => Replicated::False,
88        }
89    }
90}
91
92/// An item representing an attribute and the rules that enforce it. These rules enforce if an
93/// attribute on an [`Entry`] may be single or multi value, must be unique amongst all other types
94/// of this attribute, if the attribute should be [`indexed`], and what type of data [`syntax`] it may hold.
95///
96/// [`Entry`]: ../entry/index.html
97/// [`indexed`]: ../value/enum.IndexType.html
98/// [`syntax`]: ../value/enum.SyntaxType.html
99#[derive(Debug, Clone, Default)]
100pub struct SchemaAttribute {
101    pub name: Attribute,
102    pub uuid: Uuid,
103    pub description: String,
104    /// Defines if the attribute may have one or multiple values associated to it.
105    pub multivalue: bool,
106    /// If this flag is set, all instances of this attribute must be a unique value in the database.
107    pub unique: bool,
108    /// This defines that the value is a phantom - it is "not real", can never "be real". It
109    /// is synthesised in memory, and will never be written to the database. This can exist for
110    /// placeholders like cn/uid in ldap.
111    pub phantom: bool,
112    /// This boolean defines if this attribute may be altered by an external IDP sync
113    /// agreement.
114    pub sync_allowed: bool,
115
116    /// If set the value of this attribute get replicated to other servers
117    pub replicated: Replicated,
118    /// Define if this attribute is indexed or not according to its syntax type rule
119    pub indexed: bool,
120    /// THe type of data that this attribute may hold.
121    pub syntax: SyntaxType,
122}
123
124impl SchemaAttribute {
125    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
126        // Convert entry to a schema attribute.
127
128        // uuid
129        let uuid = value.get_uuid();
130
131        // class
132        if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) {
133            admin_error!(
134                "class {} not present - {:?}",
135                EntryClass::AttributeType,
136                uuid
137            );
138            return Err(OperationError::InvalidSchemaState(format!(
139                "missing {}",
140                EntryClass::AttributeType
141            )));
142        }
143
144        // name
145        let name = value
146            .get_ava_single_iutf8(Attribute::AttributeName)
147            .map(|s| s.into())
148            .ok_or_else(|| {
149                admin_error!("missing {} - {:?}", Attribute::AttributeName, uuid);
150                OperationError::InvalidSchemaState("missing attributename".to_string())
151            })?;
152        // description
153        let description = value
154            .get_ava_single_utf8(Attribute::Description)
155            .map(|s| s.to_string())
156            .ok_or_else(|| {
157                admin_error!("missing {} - {}", Attribute::Description, name);
158                OperationError::InvalidSchemaState("missing description".to_string())
159            })?;
160
161        // multivalue
162        let multivalue = value
163            .get_ava_single_bool(Attribute::MultiValue)
164            .ok_or_else(|| {
165                admin_error!("missing {} - {}", Attribute::MultiValue, name);
166                OperationError::InvalidSchemaState("missing multivalue".to_string())
167            })?;
168
169        let unique = value
170            .get_ava_single_bool(Attribute::Unique)
171            .ok_or_else(|| {
172                admin_error!("missing {} - {}", Attribute::Unique, name);
173                OperationError::InvalidSchemaState("missing unique".to_string())
174            })?;
175
176        let phantom = value
177            .get_ava_single_bool(Attribute::Phantom)
178            .unwrap_or_default();
179
180        let sync_allowed = value
181            .get_ava_single_bool(Attribute::SyncAllowed)
182            .unwrap_or_default();
183
184        // Default, all attributes are replicated unless you opt in for them to NOT be.
185        // Generally this is internal to the server only, so we don't advertise it.
186        let replicated = value
187            .get_ava_single_bool(Attribute::Replicated)
188            .map(Replicated::from)
189            .unwrap_or_default();
190
191        let indexed = value
192            .get_ava_single_bool(Attribute::Indexed)
193            .unwrap_or_default();
194
195        // syntax type
196        let syntax = value
197            .get_ava_single_syntax(Attribute::Syntax)
198            .ok_or_else(|| {
199                admin_error!("missing {} - {}", Attribute::Syntax, name);
200                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax))
201            })?;
202
203        trace!(?name, ?indexed);
204
205        Ok(SchemaAttribute {
206            name,
207            uuid,
208            description,
209            multivalue,
210            unique,
211            phantom,
212            sync_allowed,
213            replicated,
214            indexed,
215            syntax,
216        })
217    }
218
219    // There may be a difference between a value and a filter value on complex
220    // types - IE a complex type may have multiple parts that are secret, but a filter
221    // on that may only use a single tagged attribute for example.
222    pub fn validate_partialvalue(
223        &self,
224        a: &Attribute,
225        v: &PartialValue,
226    ) -> Result<(), SchemaError> {
227        let r = match self.syntax {
228            SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
229            SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
230            SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
231            SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
232            SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
233            SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
234            SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
235            SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
236            SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
237            SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
238            SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
239            SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
240            SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
241            SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
242            SyntaxType::Int64 => matches!(v, PartialValue::Int64(_)),
243            SyntaxType::Uint64 => matches!(v, PartialValue::Uint64(_)),
244            SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
245            SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
246            SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
247            SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
248            SyntaxType::Url => matches!(v, PartialValue::Url(_)),
249            SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
250            SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
251            SyntaxType::OauthClaimMap => {
252                matches!(v, PartialValue::Iutf8(_))
253                    || matches!(v, PartialValue::Refer(_))
254                    || matches!(v, PartialValue::OauthClaimValue(_, _, _))
255                    || matches!(v, PartialValue::OauthClaim(_, _))
256            }
257            SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
258            SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
259            SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
260            SyntaxType::AttestedPasskey => matches!(v, PartialValue::AttestedPasskey(_)),
261            // Allow refer types.
262            SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
263            SyntaxType::ApiToken => matches!(v, PartialValue::Refer(_)),
264            SyntaxType::Oauth2Session => matches!(v, PartialValue::Refer(_)),
265            // These are just insensitive string lookups on the hex-ified kid.
266            SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
267            SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
268            SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)),
269            SyntaxType::EcKeyPrivate => matches!(v, PartialValue::SecretValue),
270            // Comparing on the label.
271            SyntaxType::TotpSecret => matches!(v, PartialValue::Utf8(_)),
272            SyntaxType::AuditLogString => matches!(v, PartialValue::Utf8(_)),
273            SyntaxType::Image => matches!(v, PartialValue::Utf8(_)),
274            SyntaxType::CredentialType => matches!(v, PartialValue::CredentialType(_)),
275
276            SyntaxType::HexString | SyntaxType::Certificate | SyntaxType::KeyInternal => {
277                matches!(v, PartialValue::HexString(_))
278            }
279
280            SyntaxType::WebauthnAttestationCaList => false,
281            SyntaxType::ApplicationPassword => {
282                matches!(v, PartialValue::Uuid(_)) || matches!(v, PartialValue::Refer(_))
283            }
284            SyntaxType::Sha256 => matches!(v, PartialValue::Sha256(_)),
285            // SyntaxType::Json => matches!(v, PartialValue::Json),
286            // Should not be queried
287            SyntaxType::Json | SyntaxType::Message => false,
288        };
289        if r {
290            Ok(())
291        } else {
292            error!(
293                ?a,
294                ?self,
295                ?v,
296                "validate_partialvalue InvalidAttributeSyntax"
297            );
298            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
299        }
300    }
301
302    pub fn validate_value(&self, a: &Attribute, v: &Value) -> Result<(), SchemaError> {
303        let r = v.validate()
304            && match self.syntax {
305                SyntaxType::Boolean => matches!(v, Value::Bool(_)),
306                SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
307                SyntaxType::IndexId => matches!(v, Value::Index(_)),
308                SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
309                SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
310                SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
311                SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
312                SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
313                SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
314                SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
315                SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
316                SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
317                SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
318                SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
319                SyntaxType::Int64 => matches!(v, Value::Int64(_)),
320                SyntaxType::Uint64 => matches!(v, Value::Uint64(_)),
321                SyntaxType::Cid => matches!(v, Value::Cid(_)),
322                SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
323                SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
324                SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
325                SyntaxType::Url => matches!(v, Value::Url(_)),
326                SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
327                SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
328                SyntaxType::OauthClaimMap => {
329                    matches!(v, Value::OauthClaimValue(_, _, _))
330                        || matches!(v, Value::OauthClaimMap(_, _))
331                }
332                SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
333                SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
334                SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
335                SyntaxType::AttestedPasskey => matches!(v, Value::AttestedPasskey(_, _, _)),
336                SyntaxType::Session => matches!(v, Value::Session(_, _)),
337                SyntaxType::ApiToken => matches!(v, Value::ApiToken(_, _)),
338                SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)),
339                SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
340                SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
341                SyntaxType::UiHint => matches!(v, Value::UiHint(_)),
342                SyntaxType::TotpSecret => matches!(v, Value::TotpSecret(_, _)),
343                SyntaxType::AuditLogString => matches!(v, Value::Utf8(_)),
344                SyntaxType::Image => matches!(v, Value::Image(_)),
345                SyntaxType::CredentialType => matches!(v, Value::CredentialType(_)),
346                SyntaxType::WebauthnAttestationCaList => {
347                    matches!(v, Value::WebauthnAttestationCaList(_))
348                }
349                SyntaxType::KeyInternal => matches!(v, Value::KeyInternal { .. }),
350                SyntaxType::HexString => matches!(v, Value::HexString(_)),
351                SyntaxType::Certificate => matches!(v, Value::Certificate(_)),
352                SyntaxType::ApplicationPassword => matches!(v, Value::ApplicationPassword(..)),
353                SyntaxType::Json => matches!(v, Value::Json(_)),
354                SyntaxType::Sha256 => matches!(v, Value::Sha256(_)),
355                SyntaxType::EcKeyPrivate => matches!(v, Value::SecretValue(_)),
356                SyntaxType::Message => false,
357            };
358        if r {
359            Ok(())
360        } else {
361            error!(
362                ?a,
363                ?self,
364                ?v,
365                "validate_value failure - InvalidAttributeSyntax"
366            );
367            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
368        }
369    }
370
371    pub fn validate_ava(&self, a: &Attribute, ava: &ValueSet) -> Result<(), SchemaError> {
372        trace!("Checking for valid {:?} -> {:?}", self.name, ava);
373        // Check multivalue
374        if !self.multivalue && ava.len() > 1 {
375            // lrequest_error!("Ava len > 1 on single value attribute!");
376            admin_error!("Ava len > 1 on single value attribute!");
377            return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
378        };
379        // If syntax, check the type is correct
380        let valid = self.syntax == ava.syntax();
381        if valid && ava.validate(self) {
382            Ok(())
383        } else {
384            error!(
385                ?a,
386                "validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
387            );
388            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
389        }
390    }
391}
392
393/// An item representing a class and the rules for that class. These rules enforce that an
394/// [`Entry`]'s avas conform to a set of requirements, giving structure to an entry about
395/// what avas must or may exist. The kanidm project provides attributes in `systemmust` and
396/// `systemmay`, which can not be altered. An administrator may extend these in the `must`
397/// and `may` attributes.
398///
399/// Classes are additive, meaning that if there are two classes, the `may` rules of both union,
400/// and that if an attribute is `must` on one class, and `may` in another, the `must` rule
401/// takes precedence. It is not possible to combine classes in an incompatible way due to these
402/// rules.
403///
404/// That in mind, an entry that has one of every possible class would probably be nonsensical,
405/// but the addition rules make it easy to construct and understand with concepts like [`access`]
406/// controls or accounts and posix extensions.
407///
408/// [`Entry`]: ../entry/index.html
409/// [`access`]: ../access/index.html
410#[derive(Debug, Clone, Default)]
411pub struct SchemaClass {
412    pub name: AttrString,
413    pub uuid: Uuid,
414    pub description: String,
415    pub sync_allowed: bool,
416    /// This allows modification of system types to be extended in custom ways
417    pub systemmay: Vec<Attribute>,
418    pub may: Vec<Attribute>,
419    pub systemmust: Vec<Attribute>,
420    pub must: Vec<Attribute>,
421    /// A list of classes that this extends. These are an "or", as at least one
422    /// of the supplementing classes must also be present. Think of this as
423    /// "inherits toward" or "provides". This is just as "strict" as requires but
424    /// operates in the opposite direction allowing a tree structure.
425    pub systemsupplements: Vec<AttrString>,
426    pub supplements: Vec<AttrString>,
427    /// A list of classes that can not co-exist with this item at the same time.
428    pub systemexcludes: Vec<AttrString>,
429    pub excludes: Vec<AttrString>,
430}
431
432impl SchemaClass {
433    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
434        // uuid
435        let uuid = value.get_uuid();
436        // Convert entry to a schema class.
437        if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
438            error!("class classtype not present - {:?}", uuid);
439            return Err(OperationError::InvalidSchemaState(
440                "missing classtype".to_string(),
441            ));
442        }
443
444        // name
445        let name = value
446            .get_ava_single_iutf8(Attribute::ClassName)
447            .map(AttrString::from)
448            .ok_or_else(|| {
449                error!("missing {} - {:?}", Attribute::ClassName, uuid);
450                OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName))
451            })?;
452
453        // description
454        let description = value
455            .get_ava_single_utf8(Attribute::Description)
456            .map(String::from)
457            .ok_or_else(|| {
458                error!("missing {} - {}", Attribute::Description, name);
459                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description))
460            })?;
461
462        let sync_allowed = value
463            .get_ava_single_bool(Attribute::SyncAllowed)
464            .unwrap_or(false);
465
466        // These are all "optional" lists of strings.
467        let systemmay = value
468            .get_ava_iter_iutf8(Attribute::SystemMay)
469            .into_iter()
470            .flat_map(|iter| iter.map(Attribute::from))
471            .collect();
472        let systemmust = value
473            .get_ava_iter_iutf8(Attribute::SystemMust)
474            .into_iter()
475            .flat_map(|iter| iter.map(Attribute::from))
476            .collect();
477        let may = value
478            .get_ava_iter_iutf8(Attribute::May)
479            .into_iter()
480            .flat_map(|iter| iter.map(Attribute::from))
481            .collect();
482        let must = value
483            .get_ava_iter_iutf8(Attribute::Must)
484            .into_iter()
485            .flat_map(|iter| iter.map(Attribute::from))
486            .collect();
487
488        let systemsupplements = value
489            .get_ava_iter_iutf8(Attribute::SystemSupplements)
490            .map(|i| i.map(|v| v.into()).collect())
491            .unwrap_or_default();
492        let supplements = value
493            .get_ava_iter_iutf8(Attribute::Supplements)
494            .map(|i| i.map(|v| v.into()).collect())
495            .unwrap_or_default();
496        let systemexcludes = value
497            .get_ava_iter_iutf8(Attribute::SystemExcludes)
498            .map(|i| i.map(|v| v.into()).collect())
499            .unwrap_or_default();
500        let excludes = value
501            .get_ava_iter_iutf8(Attribute::Excludes)
502            .map(|i| i.map(|v| v.into()).collect())
503            .unwrap_or_default();
504
505        Ok(SchemaClass {
506            name,
507            uuid,
508            description,
509            sync_allowed,
510            systemmay,
511            may,
512            systemmust,
513            must,
514            systemsupplements,
515            supplements,
516            systemexcludes,
517            excludes,
518        })
519    }
520
521    /// An iterator over the full set of attrs that may or must exist
522    /// on this class.
523    pub fn may_iter(&self) -> impl Iterator<Item = &Attribute> {
524        self.systemmay
525            .iter()
526            .chain(self.may.iter())
527            .chain(self.systemmust.iter())
528            .chain(self.must.iter())
529    }
530}
531
532pub trait SchemaTransaction {
533    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
534    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute>;
535
536    fn get_attributes_unique(&self) -> &Vec<Attribute>;
537    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute>;
538
539    fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
540        let mut res = Vec::with_capacity(0);
541
542        let class_snapshot = self.get_classes();
543        let attribute_snapshot = self.get_attributes();
544
545        // We need to check that every uuid is unique because during tests we aren't doing
546        // a disk reload, which means we were missing this and causing potential migration
547        // failures on upgrade.
548
549        let mut unique_uuid_set = HashSet::new();
550        class_snapshot
551            .values()
552            .map(|class| &class.uuid)
553            .chain(attribute_snapshot.values().map(|attr| &attr.uuid))
554            .for_each(|uuid| {
555                // If the set did not have this value present, true is returned.
556                if !unique_uuid_set.insert(uuid) {
557                    res.push(Err(ConsistencyError::SchemaUuidNotUnique(*uuid)))
558                }
559            });
560
561        class_snapshot.values().for_each(|class| {
562            // report the class we are checking
563            class
564                .systemmay
565                .iter()
566                .chain(class.may.iter())
567                .chain(class.systemmust.iter())
568                .chain(class.must.iter())
569                .for_each(|a| {
570                    match attribute_snapshot.get(a) {
571                        Some(attr) => {
572                            // We have the attribute, ensure it's not a phantom.
573                            if attr.phantom {
574                                res.push(Err(ConsistencyError::SchemaClassPhantomAttribute(
575                                    class.name.to_string(),
576                                    a.to_string(),
577                                )))
578                            }
579                        }
580                        None => {
581                            // No such attr, something is missing!
582                            res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
583                                class.name.to_string(),
584                                a.to_string(),
585                            )))
586                        }
587                    }
588                })
589        }); // end for
590        res
591    }
592
593    fn is_replicated(&self, attr: &Attribute) -> bool {
594        match self.get_attributes().get(attr) {
595            Some(a_schema) => {
596                // We'll likely add more conditions here later.
597                // Allow items that are replicated and not phantoms
598                a_schema.replicated.into() && !a_schema.phantom
599            }
600            None => {
601                warn!(
602                    "Attribute {} was not found in schema during replication request",
603                    attr
604                );
605                false
606            }
607        }
608    }
609
610    fn is_multivalue(&self, attr: &Attribute) -> Result<bool, SchemaError> {
611        match self.get_attributes().get(attr) {
612            Some(a_schema) => Ok(a_schema.multivalue),
613            None => {
614                // ladmin_error!("Attribute does not exist?!");
615                Err(SchemaError::InvalidAttribute(attr.to_string()))
616            }
617        }
618    }
619
620    fn normalise_attr_if_exists(&self, an: &str) -> Option<Attribute> {
621        let attr = Attribute::from(an);
622        if self.get_attributes().contains_key(&attr) {
623            Some(attr)
624        } else {
625            None
626        }
627    }
628
629    fn query_attrs_difference(
630        &self,
631        prev_class: &BTreeSet<&str>,
632        new_iutf8: &BTreeSet<&str>,
633    ) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
634        let schema_classes = self.get_classes();
635
636        let mut invalid_classes = Vec::with_capacity(0);
637
638        let prev_attrs: BTreeSet<&str> = prev_class
639            .iter()
640            .filter_map(|cls| match schema_classes.get(*cls) {
641                Some(x) => Some(x.may_iter()),
642                None => {
643                    admin_debug!("invalid class: {:?}", cls);
644                    invalid_classes.push(cls.to_string());
645                    None
646                }
647            })
648            // flatten all the inner iters.
649            .flatten()
650            .map(|s| s.as_str())
651            .collect();
652
653        if !invalid_classes.is_empty() {
654            return Err(SchemaError::InvalidClass(invalid_classes));
655        };
656
657        let new_attrs: BTreeSet<&str> = new_iutf8
658            .iter()
659            .filter_map(|cls| match schema_classes.get(*cls) {
660                Some(x) => Some(x.may_iter()),
661                None => {
662                    admin_debug!("invalid class: {:?}", cls);
663                    invalid_classes.push(cls.to_string());
664                    None
665                }
666            })
667            // flatten all the inner iters.
668            .flatten()
669            .map(|s| s.as_str())
670            .collect();
671
672        if !invalid_classes.is_empty() {
673            return Err(SchemaError::InvalidClass(invalid_classes));
674        };
675
676        let removed = prev_attrs.difference(&new_attrs).copied().collect();
677        let added = new_attrs.difference(&prev_attrs).copied().collect();
678
679        Ok((added, removed))
680    }
681}
682
683impl SchemaWriteTransaction<'_> {
684    // Schema probably needs to be part of the backend, so that commits are wholly atomic
685    // but in the current design, we need to open be first, then schema, but we have to commit be
686    // first, then schema to ensure that the be content matches our schema. Saying this, if your
687    // schema commit fails we need to roll back still .... How great are transactions.
688    // At the least, this is what validation is for!
689    pub fn commit(self) -> Result<(), OperationError> {
690        let SchemaWriteTransaction {
691            classes,
692            attributes,
693            unique_cache,
694            ref_cache,
695        } = self;
696
697        unique_cache.commit();
698        ref_cache.commit();
699        classes.commit();
700        attributes.commit();
701        Ok(())
702    }
703
704    pub fn update_attributes<I: Iterator<Item = SchemaAttribute>>(
705        &mut self,
706        attributetypes: I,
707    ) -> Result<(), OperationError> {
708        // purge all old attributes.
709        self.attributes.clear();
710
711        self.unique_cache.clear();
712        self.ref_cache.clear();
713        // Update with new ones.
714        // Do we need to check for dups?
715        // No, they'll over-write each other ... but we do need name uniqueness.
716        attributetypes.for_each(|a| {
717            // Update the unique and ref caches.
718            if a.syntax == SyntaxType::ReferenceUuid ||
719                a.syntax == SyntaxType::OauthScopeMap ||
720                a.syntax == SyntaxType::OauthClaimMap ||
721                // So that when an rs is removed we trigger removal of the sessions.
722                a.syntax == SyntaxType::Oauth2Session ||
723                // When an application is removed we trigger removal of passwords
724                a.syntax == SyntaxType::ApplicationPassword
725            // May not need to be a ref type since it doesn't have external links/impact?
726            // || a.syntax == SyntaxType::Session
727            {
728                self.ref_cache.insert(a.name.clone(), a.clone());
729            }
730            if a.unique {
731                self.unique_cache.push(a.name.clone());
732            }
733            // Finally insert.
734            self.attributes.insert(a.name.clone(), a);
735        });
736
737        Ok(())
738    }
739
740    pub fn update_classes<I: Iterator<Item = SchemaClass>>(
741        &mut self,
742        classtypes: I,
743    ) -> Result<(), OperationError> {
744        // purge all old attributes.
745        self.classes.clear();
746        // Update with new ones.
747        // Do we need to check for dups?
748        // No, they'll over-write each other ... but we do need name uniqueness.
749        classtypes.into_iter().for_each(|a| {
750            self.classes.insert(a.name.clone(), a);
751        });
752        Ok(())
753    }
754
755    pub fn to_entries(&self) -> Vec<Entry<EntryInit, EntryNew>> {
756        let r: Vec<_> = self
757            .attributes
758            .values()
759            .map(Entry::<EntryInit, EntryNew>::from)
760            .chain(
761                self.classes
762                    .values()
763                    .map(Entry::<EntryInit, EntryNew>::from),
764            )
765            .collect();
766        r
767    }
768
769    pub fn reload_idxmeta(&self) -> Vec<IdxKey> {
770        self.get_attributes()
771            .values()
772            .flat_map(|a| {
773                // Unique values must be indexed
774                if a.indexed || a.unique {
775                    a.syntax.index_types()
776                } else {
777                    &[]
778                }
779                .iter()
780                .map(move |itype: &IndexType| IdxKey {
781                    attr: a.name.clone(),
782                    itype: *itype,
783                })
784            })
785            .collect()
786    }
787
788    /// Generate the minimal in memory schema needed to begin the server bootstrap
789    /// process. This should contain the most critical schema definitions that the
790    /// server requires to be able to read in other schema objects and persist them
791    /// into our database.
792    ///
793    /// THIS IS FOR SYSTEM CRITICAL INTERNAL SCHEMA ONLY
794    ///
795    /// Schema should otherwise be in our migration data - not here.
796    #[instrument(level = "debug", name = "schema::generate_in_memory", skip_all)]
797    pub fn generate_in_memory(&mut self) -> Result<(), OperationError> {
798        // Bootstrap in definitions of our own schema types
799        // First, add all the needed core attributes for schema parsing
800        self.update_attributes(migration_data::system::attributes().into_iter())?;
801        self.update_classes(migration_data::system::classes().into_iter())?;
802
803        let r = self.validate();
804        if r.is_empty() {
805            debug!("schema validate -> passed");
806            Ok(())
807        } else {
808            error!(err = ?r, "schema validate -> errors");
809            Err(OperationError::ConsistencyError(
810                r.into_iter().filter_map(|v| v.err()).collect(),
811            ))
812        }
813    }
814
815    #[instrument(level = "debug", name = "schema::extend_in_memory", skip_all)]
816    pub fn extend_in_memory(
817        &mut self,
818        extra_attrs: Vec<SchemaAttribute>,
819        extra_classes: Vec<SchemaClass>,
820    ) -> Result<(), OperationError> {
821        self.update_attributes(
822            migration_data::system::attributes()
823                .into_iter()
824                .chain(extra_attrs.into_iter()),
825        )?;
826        self.update_classes(
827            migration_data::system::classes()
828                .into_iter()
829                .chain(extra_classes.into_iter()),
830        )?;
831
832        let r = self.validate();
833        if r.is_empty() {
834            debug!("schema validate -> passed");
835            Ok(())
836        } else {
837            error!(err = ?r, "schema validate -> errors");
838            Err(OperationError::ConsistencyError(
839                r.into_iter().filter_map(|v| v.err()).collect(),
840            ))
841        }
842    }
843}
844
845impl SchemaTransaction for SchemaWriteTransaction<'_> {
846    fn get_attributes_unique(&self) -> &Vec<Attribute> {
847        &self.unique_cache
848    }
849
850    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
851        &self.ref_cache
852    }
853
854    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
855        &self.classes
856    }
857
858    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
859        &self.attributes
860    }
861}
862
863impl SchemaTransaction for SchemaReadTransaction {
864    fn get_attributes_unique(&self) -> &Vec<Attribute> {
865        &self.unique_cache
866    }
867
868    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
869        &self.ref_cache
870    }
871
872    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
873        &self.classes
874    }
875
876    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
877        &self.attributes
878    }
879}
880
881impl Schema {
882    pub fn new() -> Result<Self, OperationError> {
883        let s = Schema {
884            classes: CowCell::new(HashMap::with_capacity(128)),
885            attributes: CowCell::new(HashMap::with_capacity(128)),
886            unique_cache: CowCell::new(Vec::with_capacity(0)),
887            ref_cache: CowCell::new(HashMap::with_capacity(64)),
888        };
889        let mut sw = s.write();
890        let r1 = sw.generate_in_memory();
891        debug_assert!(r1.is_ok());
892        r1?;
893        let r2 = sw.commit().map(|_| s);
894        debug_assert!(r2.is_ok());
895        r2
896    }
897
898    pub fn read(&self) -> SchemaReadTransaction {
899        SchemaReadTransaction {
900            classes: self.classes.read(),
901            attributes: self.attributes.read(),
902            unique_cache: self.unique_cache.read(),
903            ref_cache: self.ref_cache.read(),
904        }
905    }
906
907    pub fn write(&self) -> SchemaWriteTransaction<'_> {
908        SchemaWriteTransaction {
909            classes: self.classes.write(),
910            attributes: self.attributes.write(),
911            unique_cache: self.unique_cache.write(),
912            ref_cache: self.ref_cache.write(),
913        }
914    }
915
916    #[cfg(test)]
917    pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
918        self.write()
919    }
920}
921
922#[cfg(test)]
923mod tests {
924    use crate::prelude::*;
925    use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
926    use uuid::Uuid;
927
928    // use crate::proto_v1::Filter as ProtoFilter;
929
930    macro_rules! validate_schema {
931        ($sch:ident) => {{
932            // Turns into a result type
933            let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
934            assert!(r.is_ok());
935        }};
936    }
937
938    macro_rules! sch_from_entry_ok {
939        (
940            $e:expr,
941            $type:ty
942        ) => {{
943            let ev1 = $e.into_sealed_committed();
944
945            let r1 = <$type>::try_from(&ev1);
946            assert!(r1.is_ok());
947        }};
948    }
949
950    macro_rules! sch_from_entry_err {
951        (
952            $e:expr,
953            $type:ty
954        ) => {{
955            let ev1 = $e.into_sealed_committed();
956
957            let r1 = <$type>::try_from(&ev1);
958            assert!(r1.is_err());
959        }};
960    }
961
962    #[test]
963    fn test_schema_attribute_from_entry() {
964        sketching::test_init();
965
966        sch_from_entry_err!(
967            entry_init!(
968                (Attribute::Class, EntryClass::Object.to_value()),
969                (Attribute::Class, EntryClass::AttributeType.to_value()),
970                (
971                    Attribute::AttributeName,
972                    Value::new_iutf8("schema_attr_test")
973                ),
974                (
975                    Attribute::Uuid,
976                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
977                ),
978                (Attribute::Unique, Value::Bool(false))
979            ),
980            SchemaAttribute
981        );
982
983        sch_from_entry_err!(
984            entry_init!(
985                (Attribute::Class, EntryClass::Object.to_value()),
986                (Attribute::Class, EntryClass::AttributeType.to_value()),
987                (
988                    Attribute::AttributeName,
989                    Value::new_iutf8("schema_attr_test")
990                ),
991                (
992                    Attribute::Uuid,
993                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
994                ),
995                (Attribute::MultiValue, Value::Bool(false)),
996                (Attribute::Unique, Value::Bool(false)),
997                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
998            ),
999            SchemaAttribute
1000        );
1001
1002        sch_from_entry_err!(
1003            entry_init!(
1004                (Attribute::Class, EntryClass::Object.to_value()),
1005                (Attribute::Class, EntryClass::AttributeType.to_value()),
1006                (
1007                    Attribute::AttributeName,
1008                    Value::new_iutf8("schema_attr_test")
1009                ),
1010                (
1011                    Attribute::Uuid,
1012                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1013                ),
1014                (
1015                    Attribute::Description,
1016                    Value::Utf8("Test attr parsing".to_string())
1017                ),
1018                (Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
1019                (Attribute::Unique, Value::Bool(false)),
1020                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
1021            ),
1022            SchemaAttribute
1023        );
1024
1025        sch_from_entry_err!(
1026            entry_init!(
1027                (Attribute::Class, EntryClass::Object.to_value()),
1028                (Attribute::Class, EntryClass::AttributeType.to_value()),
1029                (
1030                    Attribute::AttributeName,
1031                    Value::new_iutf8("schema_attr_test")
1032                ),
1033                (
1034                    Attribute::Uuid,
1035                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1036                ),
1037                (
1038                    Attribute::Description,
1039                    Value::Utf8("Test attr parsing".to_string())
1040                ),
1041                (Attribute::MultiValue, Value::Bool(false)),
1042                (Attribute::Unique, Value::Bool(false)),
1043                (Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
1044            ),
1045            SchemaAttribute
1046        );
1047
1048        // Index is allowed to be empty
1049        sch_from_entry_ok!(
1050            entry_init!(
1051                (Attribute::Class, EntryClass::Object.to_value()),
1052                (Attribute::Class, EntryClass::AttributeType.to_value()),
1053                (
1054                    Attribute::AttributeName,
1055                    Value::new_iutf8("schema_attr_test")
1056                ),
1057                (
1058                    Attribute::Uuid,
1059                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1060                ),
1061                (
1062                    Attribute::Description,
1063                    Value::Utf8("Test attr parsing".to_string())
1064                ),
1065                (Attribute::MultiValue, Value::Bool(false)),
1066                (Attribute::Unique, Value::Bool(false)),
1067                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
1068            ),
1069            SchemaAttribute
1070        );
1071
1072        // Index present
1073        sch_from_entry_ok!(
1074            entry_init!(
1075                (Attribute::Class, EntryClass::Object.to_value()),
1076                (Attribute::Class, EntryClass::AttributeType.to_value()),
1077                (
1078                    Attribute::AttributeName,
1079                    Value::new_iutf8("schema_attr_test")
1080                ),
1081                (
1082                    Attribute::Uuid,
1083                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1084                ),
1085                (
1086                    Attribute::Description,
1087                    Value::Utf8("Test attr parsing".to_string())
1088                ),
1089                (Attribute::MultiValue, Value::Bool(false)),
1090                (Attribute::Unique, Value::Bool(false)),
1091                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1092                (Attribute::Index, Value::Bool(true))
1093            ),
1094            SchemaAttribute
1095        );
1096    }
1097
1098    #[test]
1099    fn test_schema_class_from_entry() {
1100        sch_from_entry_err!(
1101            entry_init!(
1102                (Attribute::Class, EntryClass::Object.to_value()),
1103                (Attribute::Class, EntryClass::ClassType.to_value()),
1104                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1105                (
1106                    Attribute::Uuid,
1107                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1108                )
1109            ),
1110            SchemaClass
1111        );
1112
1113        sch_from_entry_err!(
1114            entry_init!(
1115                (Attribute::Class, EntryClass::Object.to_value()),
1116                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1117                (
1118                    Attribute::Uuid,
1119                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1120                ),
1121                (
1122                    Attribute::Description,
1123                    Value::Utf8("class test".to_string())
1124                )
1125            ),
1126            SchemaClass
1127        );
1128
1129        // Classes can be valid with no attributes provided.
1130        sch_from_entry_ok!(
1131            entry_init!(
1132                (Attribute::Class, EntryClass::Object.to_value()),
1133                (Attribute::Class, EntryClass::ClassType.to_value()),
1134                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1135                (
1136                    Attribute::Uuid,
1137                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1138                ),
1139                (
1140                    Attribute::Description,
1141                    Value::Utf8("class test".to_string())
1142                )
1143            ),
1144            SchemaClass
1145        );
1146
1147        // Classes with various may/must
1148        sch_from_entry_ok!(
1149            entry_init!(
1150                (Attribute::Class, EntryClass::Object.to_value()),
1151                (Attribute::Class, EntryClass::ClassType.to_value()),
1152                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1153                (
1154                    Attribute::Uuid,
1155                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1156                ),
1157                (
1158                    Attribute::Description,
1159                    Value::Utf8("class test".to_string())
1160                ),
1161                (Attribute::SystemMust, Value::new_iutf8("a"))
1162            ),
1163            SchemaClass
1164        );
1165
1166        sch_from_entry_ok!(
1167            entry_init!(
1168                (Attribute::Class, EntryClass::Object.to_value()),
1169                (Attribute::Class, EntryClass::ClassType.to_value()),
1170                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1171                (
1172                    Attribute::Uuid,
1173                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1174                ),
1175                (
1176                    Attribute::Description,
1177                    Value::Utf8("class test".to_string())
1178                ),
1179                (Attribute::SystemMay, Value::new_iutf8("a"))
1180            ),
1181            SchemaClass
1182        );
1183
1184        sch_from_entry_ok!(
1185            entry_init!(
1186                (Attribute::Class, EntryClass::Object.to_value()),
1187                (Attribute::Class, EntryClass::ClassType.to_value()),
1188                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1189                (
1190                    Attribute::Uuid,
1191                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1192                ),
1193                (
1194                    Attribute::Description,
1195                    Value::Utf8("class test".to_string())
1196                ),
1197                (Attribute::May, Value::new_iutf8("a")),
1198                (Attribute::Must, Value::new_iutf8("b"))
1199            ),
1200            SchemaClass
1201        );
1202
1203        sch_from_entry_ok!(
1204            entry_init!(
1205                (Attribute::Class, EntryClass::Object.to_value()),
1206                (Attribute::Class, EntryClass::ClassType.to_value()),
1207                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1208                (
1209                    Attribute::Uuid,
1210                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1211                ),
1212                (
1213                    Attribute::Description,
1214                    Value::Utf8("class test".to_string())
1215                ),
1216                (Attribute::May, Value::new_iutf8("a")),
1217                (Attribute::Must, Value::new_iutf8("b")),
1218                (Attribute::SystemMay, Value::new_iutf8("c")),
1219                (Attribute::SystemMust, Value::new_iutf8("d"))
1220            ),
1221            SchemaClass
1222        );
1223    }
1224
1225    #[test]
1226    fn test_schema_attribute_simple() {
1227        // Test schemaAttribute validation of types.
1228
1229        // Test single value string
1230        let single_value_string = SchemaAttribute {
1231            name: Attribute::from("single_value"),
1232            uuid: Uuid::new_v4(),
1233            description: String::from(""),
1234            syntax: SyntaxType::Utf8StringInsensitive,
1235            ..Default::default()
1236        };
1237
1238        let r1 = single_value_string
1239            .validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
1240        assert_eq!(r1, Ok(()));
1241
1242        let rvs = vs_iutf8!["test1", "test2"] as _;
1243        let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
1244        assert_eq!(
1245            r2,
1246            Err(SchemaError::InvalidAttributeSyntax(
1247                "single_value".to_string()
1248            ))
1249        );
1250
1251        // test multivalue string, boolean
1252
1253        let multi_value_string = SchemaAttribute {
1254            name: Attribute::from("mv_string"),
1255            uuid: Uuid::new_v4(),
1256            description: String::from(""),
1257            multivalue: true,
1258            syntax: SyntaxType::Utf8String,
1259            ..Default::default()
1260        };
1261
1262        let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
1263        let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
1264        assert_eq!(r5, Ok(()));
1265
1266        let multi_value_boolean = SchemaAttribute {
1267            name: Attribute::from("mv_bool"),
1268            uuid: Uuid::new_v4(),
1269            description: String::from(""),
1270            multivalue: true,
1271            syntax: SyntaxType::Boolean,
1272            ..Default::default()
1273        };
1274
1275        // Since valueset now disallows such shenanigans at a type level, this can't occur
1276        /*
1277        let rvs = unsafe {
1278            valueset![
1279                Value::new_bool(true),
1280                Value::new_iutf8("test1"),
1281                Value::new_iutf8("test2")
1282            ]
1283        };
1284        let r3 = multi_value_boolean.validate_ava("mv_bool", &rvs);
1285        assert_eq!(
1286            r3,
1287            Err(SchemaError::InvalidAttributeSyntax("mv_bool".to_string()))
1288        );
1289        */
1290
1291        let rvs = vs_bool![true, false];
1292        let r4 = multi_value_boolean.validate_ava(&Attribute::from("mv_bool"), &(rvs as _));
1293        assert_eq!(r4, Ok(()));
1294
1295        // syntax_id and index_type values
1296        let single_value_syntax = SchemaAttribute {
1297            name: Attribute::from("sv_syntax"),
1298            uuid: Uuid::new_v4(),
1299            description: String::from(""),
1300            syntax: SyntaxType::SyntaxId,
1301            ..Default::default()
1302        };
1303
1304        let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
1305        let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
1306        assert_eq!(r6, Ok(()));
1307
1308        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
1309        let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
1310        assert_eq!(
1311            r7,
1312            Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
1313        );
1314
1315        let single_value_index = SchemaAttribute {
1316            name: Attribute::from("sv_index"),
1317            uuid: Uuid::new_v4(),
1318            description: String::from(""),
1319            syntax: SyntaxType::IndexId,
1320            ..Default::default()
1321        };
1322
1323        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
1324        let r9 = single_value_index.validate_ava(&Attribute::from("sv_index"), &rvs);
1325        assert_eq!(
1326            r9,
1327            Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
1328        );
1329    }
1330
1331    #[test]
1332    fn test_schema_simple() {
1333        let schema = Schema::new().expect("failed to create schema");
1334        let schema_ro = schema.read();
1335        validate_schema!(schema_ro);
1336    }
1337
1338    #[test]
1339    fn test_schema_entries() {
1340        sketching::test_init();
1341        // Given an entry, assert it's schema is valid
1342        // We do
1343        let schema_outer = Schema::new().expect("failed to create schema");
1344        let schema = schema_outer.read();
1345
1346        let e_no_uuid = entry_init!().into_invalid_new();
1347
1348        assert_eq!(
1349            e_no_uuid.validate(&schema),
1350            Err(SchemaError::MissingMustAttribute(vec![Attribute::Uuid]))
1351        );
1352
1353        let e_no_class = entry_init!((
1354            Attribute::Uuid,
1355            Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1356        ))
1357        .into_invalid_new();
1358
1359        assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));
1360
1361        let e_bad_class = entry_init!(
1362            (
1363                Attribute::Uuid,
1364                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1365            ),
1366            (Attribute::Class, Value::new_iutf8("zzzzzz"))
1367        )
1368        .into_invalid_new();
1369        assert_eq!(
1370            e_bad_class.validate(&schema),
1371            Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
1372        );
1373
1374        let e_attr_invalid = entry_init!(
1375            (
1376                Attribute::Uuid,
1377                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1378            ),
1379            (Attribute::Class, EntryClass::Object.to_value()),
1380            (Attribute::Class, EntryClass::AttributeType.to_value())
1381        )
1382        .into_invalid_new();
1383        let res = e_attr_invalid.validate(&schema);
1384        matches!(res, Err(SchemaError::MissingMustAttribute(_)));
1385
1386        let e_attr_invalid_may = entry_init!(
1387            (Attribute::Class, EntryClass::Object.to_value()),
1388            (Attribute::Class, EntryClass::AttributeType.to_value()),
1389            (Attribute::AttributeName, Value::new_iutf8("testattr")),
1390            (Attribute::Description, Value::Utf8("testattr".to_string())),
1391            (Attribute::MultiValue, Value::Bool(false)),
1392            (Attribute::Unique, Value::Bool(false)),
1393            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1394            (
1395                Attribute::Uuid,
1396                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1397            ),
1398            (Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
1399        )
1400        .into_invalid_new();
1401
1402        assert_eq!(
1403            e_attr_invalid_may.validate(&schema),
1404            Err(SchemaError::AttributeNotValidForClass(
1405                Attribute::TestAttr.to_string()
1406            ))
1407        );
1408
1409        let e_attr_invalid_syn = entry_init!(
1410            (Attribute::Class, EntryClass::Object.to_value()),
1411            (Attribute::Class, EntryClass::AttributeType.to_value()),
1412            (Attribute::AttributeName, Value::new_iutf8("testattr")),
1413            (Attribute::Description, Value::Utf8("testattr".to_string())),
1414            (Attribute::MultiValue, Value::Utf8("false".to_string())),
1415            (Attribute::Unique, Value::Bool(false)),
1416            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1417            (
1418                Attribute::Uuid,
1419                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1420            )
1421        )
1422        .into_invalid_new();
1423
1424        assert_eq!(
1425            e_attr_invalid_syn.validate(&schema),
1426            Err(SchemaError::InvalidAttributeSyntax(
1427                "multivalue".to_string()
1428            ))
1429        );
1430
1431        // You may not have the phantom.
1432        let e_phantom = entry_init!(
1433            (Attribute::Class, EntryClass::Object.to_value()),
1434            (Attribute::Class, EntryClass::AttributeType.to_value()),
1435            (Attribute::AttributeName, Value::new_iutf8("testattr")),
1436            (Attribute::Description, Value::Utf8("testattr".to_string())),
1437            (Attribute::MultiValue, Value::Bool(false)),
1438            (Attribute::Unique, Value::Bool(false)),
1439            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1440            (
1441                Attribute::Uuid,
1442                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1443            ),
1444            (
1445                Attribute::PasswordImport,
1446                Value::Utf8("password".to_string())
1447            )
1448        )
1449        .into_invalid_new();
1450        assert!(e_phantom.validate(&schema).is_err());
1451
1452        let e_ok = entry_init!(
1453            (Attribute::Class, EntryClass::Object.to_value()),
1454            (Attribute::Class, EntryClass::AttributeType.to_value()),
1455            (Attribute::AttributeName, Value::new_iutf8("testattr")),
1456            (Attribute::Description, Value::Utf8("testattr".to_string())),
1457            (Attribute::MultiValue, Value::Bool(true)),
1458            (Attribute::Unique, Value::Bool(false)),
1459            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1460            (
1461                Attribute::Uuid,
1462                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1463            )
1464        )
1465        .into_invalid_new();
1466        assert!(e_ok.validate(&schema).is_ok());
1467    }
1468
1469    #[test]
1470    fn test_schema_extensible() {
1471        let schema_outer = Schema::new().expect("failed to create schema");
1472        let schema = schema_outer.read();
1473        // Just because you are extensible, doesn't mean you can be lazy
1474        let e_extensible_bad = entry_init!(
1475            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1476            (
1477                Attribute::Uuid,
1478                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1479            ),
1480            (Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
1481        )
1482        .into_invalid_new();
1483
1484        assert_eq!(
1485            e_extensible_bad.validate(&schema),
1486            Err(SchemaError::InvalidAttributeSyntax(
1487                "multivalue".to_string()
1488            ))
1489        );
1490
1491        // Extensible doesn't mean you can have the phantoms
1492        let e_extensible_phantom = entry_init!(
1493            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1494            (
1495                Attribute::Uuid,
1496                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1497            ),
1498            (Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
1499        )
1500        .into_invalid_new();
1501
1502        assert_eq!(
1503            e_extensible_phantom.validate(&schema),
1504            Err(SchemaError::PhantomAttribute(
1505                Attribute::PasswordImport.to_string()
1506            ))
1507        );
1508
1509        let e_extensible = entry_init!(
1510            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1511            (
1512                Attribute::Uuid,
1513                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1514            ),
1515            (Attribute::MultiValue, Value::Bool(true))
1516        )
1517        .into_invalid_new();
1518
1519        /* Is okay because extensible! */
1520        assert!(e_extensible.validate(&schema).is_ok());
1521    }
1522
1523    #[test]
1524    fn test_schema_filter_validation() {
1525        let schema_outer = Schema::new().expect("failed to create schema");
1526        let schema = schema_outer.read();
1527
1528        // test syntax of bool
1529        let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
1530        assert_eq!(
1531            f_bool.validate(&schema),
1532            Err(SchemaError::InvalidAttributeSyntax(
1533                "multivalue".to_string()
1534            ))
1535        );
1536        // test insensitive values
1537        let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
1538        assert_eq!(
1539            f_insense.validate(&schema),
1540            Ok(filter_valid!(f_eq(
1541                Attribute::Class,
1542                EntryClass::AttributeType.into()
1543            )))
1544        );
1545        // Test the recursive structures validate
1546        let f_or_empty = filter_all!(f_or!([]));
1547        assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
1548        let f_or = filter_all!(f_or!([f_eq(
1549            Attribute::MultiValue,
1550            PartialValue::new_iutf8("zzzz")
1551        )]));
1552        assert_eq!(
1553            f_or.validate(&schema),
1554            Err(SchemaError::InvalidAttributeSyntax(
1555                "multivalue".to_string()
1556            ))
1557        );
1558        let f_or_mult = filter_all!(f_and!([
1559            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1560            f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
1561        ]));
1562        assert_eq!(
1563            f_or_mult.validate(&schema),
1564            Err(SchemaError::InvalidAttributeSyntax(
1565                "multivalue".to_string()
1566            ))
1567        );
1568        // Test mixed case attr name - this is a pass, due to normalisation
1569        let f_or_ok = filter_all!(f_andnot(f_and!([
1570            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1571            f_sub(Attribute::Class, EntryClass::ClassType.into()),
1572            f_pres(Attribute::Class)
1573        ])));
1574        assert_eq!(
1575            f_or_ok.validate(&schema),
1576            Ok(filter_valid!(f_andnot(f_and!([
1577                f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1578                f_sub(Attribute::Class, EntryClass::ClassType.into()),
1579                f_pres(Attribute::Class)
1580            ]))))
1581        );
1582    }
1583
1584    #[test]
1585    fn test_schema_class_phantom_reject() {
1586        // Check that entries can be normalised and validated sanely
1587        let schema_outer = Schema::new().expect("failed to create schema");
1588        let mut schema = schema_outer.write_blocking();
1589
1590        assert!(schema.validate().is_empty());
1591
1592        // Attempt to create a class with a phantom attribute, should be refused.
1593        let class = SchemaClass {
1594            name: AttrString::from("testobject"),
1595            uuid: Uuid::new_v4(),
1596            description: String::from("test object"),
1597            systemmay: vec![Attribute::Claim],
1598            ..Default::default()
1599        };
1600
1601        assert!(schema.update_classes(std::iter::once(class)).is_ok());
1602
1603        assert_eq!(schema.validate().len(), 1);
1604    }
1605
1606    #[test]
1607    fn test_schema_class_exclusion_requires() {
1608        sketching::test_init();
1609
1610        let schema_outer = Schema::new().expect("failed to create schema");
1611        let mut schema = schema_outer.write_blocking();
1612
1613        assert!(schema.validate().is_empty());
1614
1615        // We setup some classes that have requires and excludes and check that they
1616        // are enforced correctly.
1617        let class_account = SchemaClass {
1618            name: Attribute::Account.into(),
1619            uuid: Uuid::new_v4(),
1620            description: String::from("account object"),
1621            systemmust: vec![
1622                Attribute::Class,
1623                Attribute::Uuid,
1624                Attribute::LastModifiedCid,
1625                Attribute::CreatedAtCid,
1626            ],
1627            systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
1628            ..Default::default()
1629        };
1630
1631        let class_person = SchemaClass {
1632            name: EntryClass::Person.into(),
1633            uuid: Uuid::new_v4(),
1634            description: String::from("person object"),
1635            systemmust: vec![
1636                Attribute::Class,
1637                Attribute::Uuid,
1638                Attribute::LastModifiedCid,
1639                Attribute::CreatedAtCid,
1640            ],
1641            ..Default::default()
1642        };
1643
1644        let class_service = SchemaClass {
1645            name: EntryClass::Service.into(),
1646            uuid: Uuid::new_v4(),
1647            description: String::from("service object"),
1648            systemmust: vec![
1649                Attribute::Class,
1650                Attribute::Uuid,
1651                Attribute::LastModifiedCid,
1652                Attribute::CreatedAtCid,
1653            ],
1654            excludes: vec![EntryClass::Person.into()],
1655            ..Default::default()
1656        };
1657
1658        assert!(schema
1659            .update_classes([class_account, class_person, class_service].into_iter())
1660            .is_ok());
1661
1662        // Missing person or service account.
1663        let e_account = entry_init!(
1664            (Attribute::Class, EntryClass::Account.to_value()),
1665            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1666        )
1667        .into_invalid_new();
1668
1669        assert_eq!(
1670            e_account.validate(&schema),
1671            Err(SchemaError::SupplementsNotSatisfied(vec![
1672                EntryClass::Service.into(),
1673                EntryClass::Person.into(),
1674            ]))
1675        );
1676
1677        // Service account missing account
1678        /*
1679        let e_service = unsafe { entry_init!(
1680            (Attribute::Class, EntryClass::Service.to_value()),
1681            (Attribute::Uuid, Value::new_uuid(Uuid::new_v4()))
1682        ).into_invalid_new() };
1683
1684        assert_eq!(
1685            e_service.validate(&schema),
1686            Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()]))
1687        );
1688        */
1689
1690        // Service can't have person
1691        let e_service_person = entry_init!(
1692            (Attribute::Class, EntryClass::Service.to_value()),
1693            (Attribute::Class, EntryClass::Account.to_value()),
1694            (Attribute::Class, EntryClass::Person.to_value()),
1695            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1696        )
1697        .into_invalid_new();
1698
1699        assert_eq!(
1700            e_service_person.validate(&schema),
1701            Err(SchemaError::ExcludesNotSatisfied(vec![
1702                EntryClass::Person.to_string()
1703            ]))
1704        );
1705
1706        // These are valid configurations.
1707        let e_service_valid = entry_init!(
1708            (Attribute::Class, EntryClass::Service.to_value()),
1709            (Attribute::Class, EntryClass::Account.to_value()),
1710            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1711        )
1712        .into_invalid_new();
1713
1714        assert!(e_service_valid.validate(&schema).is_ok());
1715
1716        let e_person_valid = entry_init!(
1717            (Attribute::Class, EntryClass::Person.to_value()),
1718            (Attribute::Class, EntryClass::Account.to_value()),
1719            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1720        )
1721        .into_invalid_new();
1722
1723        assert!(e_person_valid.validate(&schema).is_ok());
1724
1725        let e_person_valid = entry_init!(
1726            (Attribute::Class, EntryClass::Person.to_value()),
1727            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1728        )
1729        .into_invalid_new();
1730
1731        assert!(e_person_valid.validate(&schema).is_ok());
1732    }
1733}