kanidmd_lib/server/access/
delete.rs

1use super::profiles::{
2    AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
3};
4use super::protected::PROTECTED_ENTRY_CLASSES;
5use crate::prelude::*;
6use std::sync::Arc;
7
8pub(super) enum DeleteResult {
9    Deny,
10    Grant,
11}
12
13enum IResult {
14    Deny,
15    Grant,
16    Ignore,
17}
18
19pub(super) fn apply_delete_access<'a>(
20    ident: &Identity,
21    related_acp: &'a [AccessControlDeleteResolved],
22    entry: &'a Arc<EntrySealedCommitted>,
23) -> DeleteResult {
24    let mut denied = false;
25    let mut grant = false;
26
27    match protected_filter_entry(ident, entry) {
28        IResult::Deny => denied = true,
29        IResult::Grant | IResult::Ignore => {}
30    }
31
32    match delete_filter_entry(ident, related_acp, entry) {
33        IResult::Deny => denied = true,
34        IResult::Grant => grant = true,
35        IResult::Ignore => {}
36    }
37
38    if denied {
39        // Something explicitly said no.
40        DeleteResult::Deny
41    } else if grant {
42        // Something said yes
43        DeleteResult::Grant
44    } else {
45        // Nothing said yes.
46        DeleteResult::Deny
47    }
48}
49
50fn delete_filter_entry<'a>(
51    ident: &Identity,
52    related_acp: &'a [AccessControlDeleteResolved],
53    entry: &'a Arc<EntrySealedCommitted>,
54) -> IResult {
55    match &ident.origin {
56        IdentType::Internal => {
57            trace!("Internal operation, bypassing access check");
58            // No need to check ACS
59            return IResult::Grant;
60        }
61        IdentType::Synch(_) => {
62            security_critical!("Blocking sync check");
63            return IResult::Deny;
64        }
65        IdentType::User(_) => {}
66    };
67    debug!(event = %ident, "Access check for delete event");
68
69    match ident.access_scope() {
70        AccessScope::ReadOnly | AccessScope::Synchronise => {
71            security_access!("denied ❌ - identity access scope is not permitted to delete");
72            return IResult::Deny;
73        }
74        AccessScope::ReadWrite => {
75            // As you were
76        }
77    };
78
79    let ident_memberof = ident.get_memberof();
80    let ident_uuid = ident.get_uuid();
81
82    let allow = related_acp.iter().any(|acd| {
83        // Assert that the receiver condition applies.
84        match &acd.receiver_condition {
85            AccessControlReceiverCondition::GroupChecked => {
86                // The groups were already checked during filter resolution. Trust
87                // that result, and continue.
88            }
89            AccessControlReceiverCondition::EntryManager => {
90                // This condition relies on the entry we are looking at to have a back-ref
91                // to our uuid or a group we are in as an entry manager.
92
93                // Note, while schema has this as single value, we currently
94                // fetch it as a multivalue btreeset for future incase we allow
95                // multiple entry manager by in future.
96                if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
97                    let group_check = ident_memberof
98                        // Have at least one group allowed.
99                        .map(|imo| imo.intersection(entry_manager_uuids).next().is_some())
100                        .unwrap_or_default();
101
102                    let user_check = ident_uuid
103                        .map(|u| entry_manager_uuids.contains(&u))
104                        .unwrap_or_default();
105
106                    if !(group_check || user_check) {
107                        // Not the entry manager
108                        return false;
109                    }
110                } else {
111                    // Can not satisfy.
112                    return false;
113                }
114            }
115        };
116
117        match &acd.target_condition {
118            AccessControlTargetCondition::Scope(f_res) => {
119                if !entry.entry_match_no_index(f_res) {
120                    trace!(
121                        "entry {:?} DOES NOT match acs {}",
122                        entry.get_uuid(),
123                        acd.acp.acp.name
124                    );
125                    // Does not match, fail.
126                    return false;
127                }
128            }
129        };
130
131        let entry_name = entry.get_display_id();
132        security_access!(
133            %entry_name,
134            acs = %acd.acp.acp.name,
135            "entry matches acs"
136        );
137
138        true
139    }); // any related_acp
140
141    if allow {
142        IResult::Grant
143    } else {
144        IResult::Ignore
145    }
146}
147
148fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> IResult {
149    match &ident.origin {
150        IdentType::Internal => {
151            trace!("Internal operation, protected rules do not apply.");
152            IResult::Ignore
153        }
154        IdentType::Synch(_) => {
155            security_access!("sync agreements may not directly delete entities");
156            IResult::Deny
157        }
158        IdentType::User(_) => {
159            // Prevent deletion of entries that exist in the system controlled entry range.
160            if entry.get_uuid() <= UUID_ANONYMOUS {
161                security_access!("attempt to delete system builtin entry");
162                return IResult::Deny;
163            }
164
165            // Prevent deleting some protected types.
166            if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
167                if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
168                    // It's different, go ahead
169                    IResult::Ignore
170                } else {
171                    // Block the mod, something is present
172                    security_access!("attempt to create with protected class type");
173                    IResult::Deny
174                }
175            } else {
176                // Nothing to check - this entry will fail to create anyway because it has
177                // no classes
178                IResult::Ignore
179            }
180        }
181    }
182}