kanidmd_lib/server/access/
create.rs

1use super::profiles::{
2    AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
3};
4use super::protected::PROTECTED_ENTRY_CLASSES;
5use crate::prelude::*;
6use std::collections::BTreeSet;
7
8pub(super) enum CreateResult {
9    Deny,
10    Grant,
11}
12
13enum IResult {
14    Deny,
15    Grant,
16    Ignore,
17}
18
19pub(super) fn apply_create_access<'a>(
20    ident: &Identity,
21    related_acp: &'a [AccessControlCreateResolved],
22    entry: &'a Entry<EntryInit, EntryNew>,
23) -> CreateResult {
24    let mut denied = false;
25    let mut grant = false;
26
27    // This module can never yield a grant.
28    match protected_filter_entry(ident, entry) {
29        IResult::Deny => denied = true,
30        IResult::Grant | IResult::Ignore => {}
31    }
32
33    match create_filter_entry(ident, related_acp, entry) {
34        IResult::Deny => denied = true,
35        IResult::Grant => grant = true,
36        IResult::Ignore => {}
37    }
38
39    if denied {
40        // Something explicitly said no.
41        CreateResult::Deny
42    } else if grant {
43        // Something said yes
44        CreateResult::Grant
45    } else {
46        // Nothing said yes.
47        CreateResult::Deny
48    }
49}
50
51fn create_filter_entry<'a>(
52    ident: &Identity,
53    related_acp: &'a [AccessControlCreateResolved],
54    entry: &'a Entry<EntryInit, EntryNew>,
55) -> IResult {
56    match &ident.origin {
57        IdentType::Internal => {
58            trace!("Internal operation, bypassing access check");
59            // No need to check ACS
60            return IResult::Grant;
61        }
62        IdentType::Synch(_) => {
63            security_critical!("Blocking sync check");
64            return IResult::Deny;
65        }
66        IdentType::User(_) => {}
67    };
68    debug!(event = %ident, "Access check for create event");
69
70    match ident.access_scope() {
71        AccessScope::ReadOnly | AccessScope::Synchronise => {
72            security_access!("denied ❌ - identity access scope is not permitted to create");
73            return IResult::Deny;
74        }
75        AccessScope::ReadWrite => {
76            // As you were
77        }
78    };
79
80    // Build the set of requested classes and attrs here.
81    let create_attrs: BTreeSet<&str> = entry.get_ava_names().collect();
82    // If this is empty, we make an empty set, which is fine because
83    // the empty class set despite matching is_subset, will have the
84    // following effect:
85    // * there is no class on entry, so schema will fail
86    // * plugin-base will add object to give a class, but excess
87    //   attrs will cause fail (could this be a weakness?)
88    // * class is a "may", so this could be empty in the rules, so
89    //   if the accr is empty this would not be a true subset,
90    //   so this would "fail", but any content in the accr would
91    //   have to be validated.
92    //
93    // I still think if this is None, we should just fail here ...
94    // because it shouldn't be possible to match.
95
96    let create_classes: BTreeSet<&str> = match entry.get_ava_iter_iutf8(Attribute::Class) {
97        Some(s) => s.collect(),
98        None => {
99            admin_error!("Class set failed to build - corrupted entry?");
100            return IResult::Deny;
101        }
102    };
103
104    //      Find the set of related acps for this entry.
105    //
106    //      For each "created" entry.
107    //          If the created entry is 100% allowed by this acp
108    //          IE: all attrs to be created AND classes match classes
109    //              allow
110    //          if no acp allows, fail operation.
111    let allow = related_acp.iter().any(|accr| {
112        // Assert that the receiver condition applies.
113        match &accr.receiver_condition {
114            AccessControlReceiverCondition::GroupChecked => {
115                // The groups were already checked during filter resolution. Trust
116                // that result, and continue.
117            }
118            AccessControlReceiverCondition::EntryManager => {
119                // Currently, this is unsatisfiable for creates.
120                return false;
121            }
122        };
123
124        match &accr.target_condition {
125            AccessControlTargetCondition::Scope(f_res) => {
126                if !entry.entry_match_no_index(f_res) {
127                    trace!(?entry, acs = %accr.acp.acp.name, "entry DOES NOT match acs");
128                    // Does not match, fail this rule.
129                    return false;
130                }
131            }
132        };
133
134        // -- Conditions pass -- now verify the attributes.
135
136        let entry_name = entry.get_display_id();
137        security_access!(%entry_name, acs = ?accr.acp.acp.name, "entry matches acs");
138        // It matches, so now we have to check attrs and classes.
139        // Remember, we have to match ALL requested attrs
140        // and classes to pass!
141        let allowed_attrs: BTreeSet<&str> = accr.acp.attrs.iter().map(|s| s.as_str()).collect();
142        let allowed_classes: BTreeSet<&str> = accr.acp.classes.iter().map(|s| s.as_str()).collect();
143
144        if !create_attrs.is_subset(&allowed_attrs) {
145            security_error!("create_attrs is not a subset of allowed");
146            security_error!("create: {:?} !⊆ allowed: {:?}", create_attrs, allowed_attrs);
147            false
148        } else if !create_classes.is_subset(&allowed_classes) {
149            security_error!("create_classes is not a subset of allowed");
150            security_error!(
151                "create: {:?} !⊆ allowed: {:?}",
152                create_classes,
153                allowed_classes
154            );
155            false
156        } else {
157            // All attribute conditions are now met.
158            true
159        }
160    });
161
162    if allow {
163        IResult::Grant
164    } else {
165        IResult::Ignore
166    }
167}
168
169fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>) -> IResult {
170    match &ident.origin {
171        IdentType::Internal => {
172            trace!("Internal operation, protected rules do not apply.");
173            IResult::Ignore
174        }
175        IdentType::Synch(_) => {
176            security_access!("sync agreements may not directly create entities");
177            IResult::Deny
178        }
179        IdentType::User(_) => {
180            // Now check things ...
181            if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
182                if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
183                    // It's different, go ahead
184                    IResult::Ignore
185                } else {
186                    // Block the mod, something is present
187                    security_access!("attempt to create with protected class type");
188                    IResult::Deny
189                }
190            } else {
191                // Nothing to check - this entry will fail to create anyway because it has
192                // no classes
193                IResult::Ignore
194            }
195        }
196    }
197}