kanidmd_lib/server/access/
delete.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use super::profiles::{
    AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
};
use crate::prelude::*;
use std::sync::Arc;

pub(super) enum DeleteResult {
    Denied,
    Grant,
}

enum IResult {
    Denied,
    Grant,
    Ignore,
}

pub(super) fn apply_delete_access<'a>(
    ident: &Identity,
    related_acp: &'a [AccessControlDeleteResolved],
    entry: &'a Arc<EntrySealedCommitted>,
) -> DeleteResult {
    let mut denied = false;
    let mut grant = false;

    match protected_filter_entry(ident, entry) {
        IResult::Denied => denied = true,
        IResult::Grant | IResult::Ignore => {}
    }

    match delete_filter_entry(ident, related_acp, entry) {
        IResult::Denied => denied = true,
        IResult::Grant => grant = true,
        IResult::Ignore => {}
    }

    if denied {
        // Something explicitly said no.
        DeleteResult::Denied
    } else if grant {
        // Something said yes
        DeleteResult::Grant
    } else {
        // Nothing said yes.
        DeleteResult::Denied
    }
}

fn delete_filter_entry<'a>(
    ident: &Identity,
    related_acp: &'a [AccessControlDeleteResolved],
    entry: &'a Arc<EntrySealedCommitted>,
) -> IResult {
    match &ident.origin {
        IdentType::Internal => {
            trace!("Internal operation, bypassing access check");
            // No need to check ACS
            return IResult::Grant;
        }
        IdentType::Synch(_) => {
            security_critical!("Blocking sync check");
            return IResult::Denied;
        }
        IdentType::User(_) => {}
    };
    debug!(event = %ident, "Access check for delete event");

    match ident.access_scope() {
        AccessScope::ReadOnly | AccessScope::Synchronise => {
            security_access!("denied ❌ - identity access scope is not permitted to delete");
            return IResult::Denied;
        }
        AccessScope::ReadWrite => {
            // As you were
        }
    };

    let ident_memberof = ident.get_memberof();
    let ident_uuid = ident.get_uuid();

    let allow = related_acp.iter().any(|acd| {
        // Assert that the receiver condition applies.
        match &acd.receiver_condition {
            AccessControlReceiverCondition::GroupChecked => {
                // The groups were already checked during filter resolution. Trust
                // that result, and continue.
            }
            AccessControlReceiverCondition::EntryManager => {
                // This condition relies on the entry we are looking at to have a back-ref
                // to our uuid or a group we are in as an entry manager.

                // Note, while schema has this as single value, we currently
                // fetch it as a multivalue btreeset for future incase we allow
                // multiple entry manager by in future.
                if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
                    let group_check = ident_memberof
                        // Have at least one group allowed.
                        .map(|imo| imo.intersection(entry_manager_uuids).next().is_some())
                        .unwrap_or_default();

                    let user_check = ident_uuid
                        .map(|u| entry_manager_uuids.contains(&u))
                        .unwrap_or_default();

                    if !(group_check || user_check) {
                        // Not the entry manager
                        return false;
                    }
                } else {
                    // Can not satisfy.
                    return false;
                }
            }
        };

        match &acd.target_condition {
            AccessControlTargetCondition::Scope(f_res) => {
                if !entry.entry_match_no_index(f_res) {
                    trace!(
                        "entry {:?} DOES NOT match acs {}",
                        entry.get_uuid(),
                        acd.acp.acp.name
                    );
                    // Does not match, fail.
                    return false;
                }
            }
        };

        let entry_name = entry.get_display_id();
        security_access!(
            %entry_name,
            acs = %acd.acp.acp.name,
            "entry matches acs"
        );

        true
    }); // any related_acp

    if allow {
        IResult::Grant
    } else {
        IResult::Ignore
    }
}

fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> IResult {
    match &ident.origin {
        IdentType::Internal => {
            trace!("Internal operation, protected rules do not apply.");
            IResult::Ignore
        }
        IdentType::Synch(_) => {
            security_access!("sync agreements may not directly delete entities");
            IResult::Denied
        }
        IdentType::User(_) => {
            // Now check things ...

            // For now we just block create on sync object
            if let Some(classes) = entry.get_ava_set(Attribute::Class) {
                if classes.contains(&EntryClass::SyncObject.into()) {
                    // Block the mod
                    security_access!("attempt to delete with protected class type");
                    return IResult::Denied;
                }
            };

            // Prevent deletion of entries that exist in the system controlled entry range.
            if entry.get_uuid() <= UUID_ANONYMOUS {
                security_access!("attempt to delete system builtin entry");
                return IResult::Denied;
            }

            // Checks exhausted, no more input from us
            IResult::Ignore
        }
    }
}