1use super::profiles::{
2 AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
3};
4use super::protected::PROTECTED_ENTRY_CLASSES;
5use crate::prelude::*;
6use std::collections::BTreeSet;
78pub(super) enum CreateResult {
9 Deny,
10 Grant,
11}
1213enum IResult {
14 Deny,
15 Grant,
16 Ignore,
17}
1819pub(super) fn apply_create_access<'a>(
20 ident: &Identity,
21 related_acp: &'a [AccessControlCreateResolved],
22 entry: &'a Entry<EntryInit, EntryNew>,
23) -> CreateResult {
24let mut denied = false;
25let mut grant = false;
2627// This module can never yield a grant.
28match protected_filter_entry(ident, entry) {
29 IResult::Deny => denied = true,
30 IResult::Grant | IResult::Ignore => {}
31 }
3233match create_filter_entry(ident, related_acp, entry) {
34 IResult::Deny => denied = true,
35 IResult::Grant => grant = true,
36 IResult::Ignore => {}
37 }
3839if denied {
40// Something explicitly said no.
41CreateResult::Deny
42 } else if grant {
43// Something said yes
44CreateResult::Grant
45 } else {
46// Nothing said yes.
47CreateResult::Deny
48 }
49}
5051fn create_filter_entry<'a>(
52 ident: &Identity,
53 related_acp: &'a [AccessControlCreateResolved],
54 entry: &'a Entry<EntryInit, EntryNew>,
55) -> IResult {
56match &ident.origin {
57 IdentType::Internal => {
58trace!("Internal operation, bypassing access check");
59// No need to check ACS
60return IResult::Grant;
61 }
62 IdentType::Synch(_) => {
63security_critical!("Blocking sync check");
64return IResult::Deny;
65 }
66 IdentType::User(_) => {}
67 };
68debug!(event = %ident, "Access check for create event");
6970match ident.access_scope() {
71 AccessScope::ReadOnly | AccessScope::Synchronise => {
72security_access!("denied ❌ - identity access scope is not permitted to create");
73return IResult::Deny;
74 }
75 AccessScope::ReadWrite => {
76// As you were
77}
78 };
7980// Build the set of requested classes and attrs here.
81let 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.
9596let create_classes: BTreeSet<&str> = match entry.get_ava_iter_iutf8(Attribute::Class) {
97Some(s) => s.collect(),
98None => {
99admin_error!("Class set failed to build - corrupted entry?");
100return IResult::Deny;
101 }
102 };
103104// 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.
111let allow = related_acp.iter().any(|accr| {
112// Assert that the receiver condition applies.
113match &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.
120return false;
121 }
122 };
123124match &accr.target_condition {
125 AccessControlTargetCondition::Scope(f_res) => {
126if !entry.entry_match_no_index(f_res) {
127trace!(?entry, acs = %accr.acp.acp.name, "entry DOES NOT match acs");
128// Does not match, fail this rule.
129return false;
130 }
131 }
132 };
133134// -- Conditions pass -- now verify the attributes.
135136let entry_name = entry.get_display_id();
137security_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!
141let allowed_attrs: BTreeSet<&str> = accr.acp.attrs.iter().map(|s| s.as_str()).collect();
142let allowed_classes: BTreeSet<&str> = accr.acp.classes.iter().map(|s| s.as_str()).collect();
143144if !create_attrs.is_subset(&allowed_attrs) {
145security_error!("create_attrs is not a subset of allowed");
146security_error!("create: {:?} !⊆ allowed: {:?}", create_attrs, allowed_attrs);
147false
148} else if !create_classes.is_subset(&allowed_classes) {
149security_error!("create_classes is not a subset of allowed");
150security_error!(
151"create: {:?} !⊆ allowed: {:?}",
152 create_classes,
153 allowed_classes
154 );
155false
156} else {
157// All attribute conditions are now met.
158true
159}
160 });
161162if allow {
163 IResult::Grant
164 } else {
165 IResult::Ignore
166 }
167}
168169fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>) -> IResult {
170match &ident.origin {
171 IdentType::Internal => {
172trace!("Internal operation, protected rules do not apply.");
173 IResult::Ignore
174 }
175 IdentType::Synch(_) => {
176security_access!("sync agreements may not directly create entities");
177 IResult::Deny
178 }
179 IdentType::User(_) => {
180// Now check things ...
181if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
182if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
183// It's different, go ahead
184IResult::Ignore
185 } else {
186// Block the mod, something is present
187security_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
193IResult::Ignore
194 }
195 }
196 }
197}