kanidmd_lib/plugins/
domain.rs

1// Manage and generate domain uuid's and/or trust related domain
2// management.
3
4// The primary point of this is to generate a unique domain UUID on startup
5// which is importart for management of the replication topo and trust
6// relationships.
7use std::iter::once;
8use std::sync::Arc;
9
10use regex::Regex;
11use tracing::trace;
12
13use crate::event::{CreateEvent, ModifyEvent};
14use crate::plugins::Plugin;
15use crate::prelude::*;
16
17lazy_static! {
18    pub static ref DOMAIN_LDAP_BASEDN_RE: Regex = {
19        #[allow(clippy::expect_used)]
20        Regex::new(r"^(dc|o|ou)=[a-z][a-z0-9]*(,(dc|o|ou)=[a-z][a-z0-9]*)*$")
21            .expect("Invalid domain ldap basedn regex")
22    };
23}
24
25pub struct Domain {}
26
27impl Plugin for Domain {
28    fn id() -> &'static str {
29        "plugin_domain"
30    }
31
32    #[instrument(level = "debug", name = "domain_pre_create_transform", skip_all)]
33    fn pre_create_transform(
34        qs: &mut QueryServerWriteTransaction,
35        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
36        _ce: &CreateEvent,
37    ) -> Result<(), OperationError> {
38        Self::modify_inner(qs, cand)
39    }
40
41    #[instrument(level = "debug", name = "domain_pre_modify", skip_all)]
42    fn pre_modify(
43        qs: &mut QueryServerWriteTransaction,
44        _pre_cand: &[Arc<EntrySealedCommitted>],
45        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
46        _me: &ModifyEvent,
47    ) -> Result<(), OperationError> {
48        Self::modify_inner(qs, cand)
49    }
50
51    #[instrument(level = "debug", name = "domain_pre_batch_modify", skip_all)]
52    fn pre_batch_modify(
53        qs: &mut QueryServerWriteTransaction,
54        _pre_cand: &[Arc<EntrySealedCommitted>],
55        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
56        _me: &BatchModifyEvent,
57    ) -> Result<(), OperationError> {
58        Self::modify_inner(qs, cand)
59    }
60}
61
62impl Domain {
63    /// Generates the cookie key for the domain.
64    fn modify_inner<T: Clone + std::fmt::Debug>(
65        qs: &mut QueryServerWriteTransaction,
66        cand: &mut [Entry<EntryInvalid, T>],
67    ) -> Result<(), OperationError> {
68        cand.iter_mut().try_for_each(|e| {
69            if e.attribute_equality(Attribute::Class, &EntryClass::DomainInfo.into())
70                && e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)
71            {
72                // Validate the domain ldap basedn syntax.
73                if let Some(basedn) = e.get_ava_single_iutf8(Attribute::DomainLdapBasedn) {
74                    if !DOMAIN_LDAP_BASEDN_RE.is_match(basedn) {
75                        error!(
76                            "Invalid {} '{}'. Must pass regex \"{}\"",
77                            Attribute::DomainLdapBasedn,
78                            basedn,
79                            *DOMAIN_LDAP_BASEDN_RE
80                        );
81                        return Err(OperationError::InvalidState);
82                    }
83                }
84
85                // We always set this, because the DB uuid is authoritative.
86                let u = Value::Uuid(qs.get_domain_uuid());
87                e.set_ava(&Attribute::DomainUuid, once(u));
88                trace!("plugin_domain: Applying uuid transform");
89
90                // We only apply this if one isn't provided.
91                if !e.attribute_pres(Attribute::DomainName) {
92                    let n = Value::new_iname(qs.get_domain_name());
93                    e.set_ava(&Attribute::DomainName, once(n));
94                    trace!("plugin_domain: Applying domain_name transform");
95                }
96
97                // Setup the minimum functional level if one is not set already.
98                if !e.attribute_pres(Attribute::Version) {
99                    let n = Value::Uint32(DOMAIN_LEVEL_0);
100                    e.set_ava(&Attribute::Version, once(n));
101                    warn!("plugin_domain: Applying domain version transform");
102                } else {
103                    debug!("plugin_domain: NOT Applying domain version transform");
104                };
105
106                // create the domain_display_name if it's missing. This was the behaviour in versions
107                // prior to DL10. Rather than checking the domain version itself, the issue is we
108                // have to check the min remigration level. This is because during a server setup
109                // we start from the MIN remigration level and work up, and the domain version == 0.
110                //
111                // So effectively we only skip setting this value after we know that we are at DL12
112                // since we could never go back to anything lower than 10 at that point.
113                if DOMAIN_MIN_REMIGRATION_LEVEL < DOMAIN_LEVEL_10
114                    && !e.attribute_pres(Attribute::DomainDisplayName)
115                {
116                    let domain_display_name =
117                        Value::new_utf8(format!("Kanidm {}", qs.get_domain_name()));
118                    security_info!(
119                        "plugin_domain: setting default domain_display_name to {:?}",
120                        domain_display_name
121                    );
122
123                    e.set_ava(&Attribute::DomainDisplayName, once(domain_display_name));
124                }
125
126                Ok(())
127            } else {
128                Ok(())
129            }
130        })
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use crate::prelude::*;
137
138    // test we can create and generate the id
139    #[qs_test]
140    async fn test_domain_generate_uuid(server: &QueryServer) {
141        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
142        let e_dom = server_txn
143            .internal_search_uuid(UUID_DOMAIN_INFO)
144            .expect("must not fail");
145
146        let u_dom = server_txn.get_domain_uuid();
147
148        assert!(e_dom.attribute_equality(Attribute::DomainUuid, &PartialValue::Uuid(u_dom)));
149    }
150}