kanidmd_lib/server/access/
search.rs
1use crate::prelude::*;
2use std::collections::BTreeSet;
3
4use super::profiles::{
5 AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
6};
7use super::AccessSrchResult;
8use std::sync::Arc;
9
10pub(super) enum SearchResult {
11 Deny,
12 Grant,
13 Allow(BTreeSet<Attribute>),
14}
15
16pub(super) fn apply_search_access(
17 ident: &Identity,
18 related_acp: &[AccessControlSearchResolved],
19 entry: &Arc<EntrySealedCommitted>,
20) -> SearchResult {
21 let mut denied = false;
25 let mut grant = false;
26 let constrain = BTreeSet::default();
27 let mut allow = BTreeSet::default();
28
29 match search_filter_entry(ident, related_acp, entry) {
31 AccessSrchResult::Deny => denied = true,
32 AccessSrchResult::Grant => grant = true,
33 AccessSrchResult::Ignore => {}
34 AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
36 };
37
38 match search_oauth2_filter_entry(ident, entry) {
39 AccessSrchResult::Deny => denied = true,
40 AccessSrchResult::Grant => grant = true,
41 AccessSrchResult::Ignore => {}
42 AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
44 };
45
46 match search_sync_account_filter_entry(ident, entry) {
47 AccessSrchResult::Deny => denied = true,
48 AccessSrchResult::Grant => grant = true,
49 AccessSrchResult::Ignore => {}
50 AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
52 };
53
54 if denied {
59 SearchResult::Deny
60 } else if grant {
61 SearchResult::Grant
62 } else {
63 let allowed_attrs = if !constrain.is_empty() {
64 &constrain & &allow
66 } else {
67 allow
68 };
69 SearchResult::Allow(allowed_attrs)
70 }
71}
72
73fn search_filter_entry(
74 ident: &Identity,
75 related_acp: &[AccessControlSearchResolved],
76 entry: &Arc<EntrySealedCommitted>,
77) -> AccessSrchResult {
78 match &ident.origin {
80 IdentType::Internal => {
81 trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
82 return AccessSrchResult::Grant;
84 }
85 IdentType::Synch(_) => {
86 security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
87 return AccessSrchResult::Deny;
88 }
89 IdentType::User(_) => {}
90 };
91 debug!(event = %ident, "Access check for search (filter) event");
92
93 match ident.access_scope() {
94 AccessScope::Synchronise => {
95 security_debug!(
96 "denied ❌ - identity access scope 'Synchronise' is not permitted to search"
97 );
98 return AccessSrchResult::Deny;
99 }
100 AccessScope::ReadOnly | AccessScope::ReadWrite => {
101 }
103 };
104
105 let ident_memberof = ident.get_memberof();
107 let ident_uuid = ident.get_uuid();
108
109 let allowed_attrs: BTreeSet<Attribute> = related_acp
110 .iter()
111 .filter_map(|acs| {
112 match &acs.receiver_condition {
114 AccessControlReceiverCondition::GroupChecked => {
115 }
118 AccessControlReceiverCondition::EntryManager => {
119 if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
126 let group_check = ident_memberof
127 .map(|imo| imo.intersection(entry_manager_uuids).next().is_some())
129 .unwrap_or_default();
130
131 let user_check = ident_uuid
132 .map(|u| entry_manager_uuids.contains(&u))
133 .unwrap_or_default();
134
135 if !(group_check || user_check) {
136 return None
138 }
139 } else {
140 return None
142 }
143 }
144 };
145
146 match &acs.target_condition {
147 AccessControlTargetCondition::Scope(f_res) => {
148 if !entry.entry_match_no_index(f_res) {
149 security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "entry DOES NOT match acs");
150 return None
151 }
152 }
153 };
154
155 security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "acs applied to entry");
158 Some(acs.acp.attrs.iter().cloned())
160 })
161 .flatten()
162 .collect();
163
164 AccessSrchResult::Allow {
165 attr: allowed_attrs,
166 }
167}
168
169fn search_oauth2_filter_entry(
170 ident: &Identity,
171 entry: &Arc<EntrySealedCommitted>,
172) -> AccessSrchResult {
173 match &ident.origin {
174 IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
175 IdentType::User(iuser) => {
176 if iuser.entry.get_uuid() == UUID_ANONYMOUS {
177 debug!("Anonymous can't access OAuth2 entries, ignoring");
178 return AccessSrchResult::Ignore;
179 }
180
181 let contains_o2_rs = entry
182 .get_ava_as_iutf8(Attribute::Class)
183 .map(|set| {
184 trace!(?set);
185 set.contains(&EntryClass::OAuth2ResourceServer.to_string())
186 })
187 .unwrap_or(false);
188
189 let contains_o2_scope_member = entry
190 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
191 .and_then(|maps| ident.get_memberof().map(|mo| (maps, mo)))
192 .map(|(maps, mo)| maps.keys().any(|k| mo.contains(k)))
193 .unwrap_or(false);
194
195 if contains_o2_rs && contains_o2_scope_member {
196 security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
197
198 return AccessSrchResult::Allow {
199 attr: btreeset!(
200 Attribute::Class,
201 Attribute::DisplayName,
202 Attribute::Uuid,
203 Attribute::Name,
204 Attribute::OAuth2RsOriginLanding,
205 Attribute::Image
206 ),
207 };
208 }
209 AccessSrchResult::Ignore
210 }
211 }
212}
213
214fn search_sync_account_filter_entry(
215 ident: &Identity,
216 entry: &Arc<EntrySealedCommitted>,
217) -> AccessSrchResult {
218 match &ident.origin {
219 IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
220 IdentType::User(iuser) => {
221 let is_user_sync_account = iuser
223 .entry
224 .get_ava_as_iutf8(Attribute::Class)
225 .map(|set| {
226 trace!(?set);
227 set.contains(&EntryClass::SyncObject.to_string())
228 && set.contains(EntryClass::Account.into())
229 })
230 .unwrap_or(false);
231
232 if is_user_sync_account {
233 let is_target_sync_account = entry
234 .get_ava_as_iutf8(Attribute::Class)
235 .map(|set| {
236 trace!(?set);
237 set.contains(&EntryClass::SyncAccount.to_string())
238 })
239 .unwrap_or(false);
240
241 if is_target_sync_account {
242 let sync_uuid = entry.get_uuid();
244 let sync_source_match = iuser
245 .entry
246 .get_ava_single_refer(Attribute::SyncParentUuid)
247 .map(|sync_parent_uuid| sync_parent_uuid == sync_uuid)
248 .unwrap_or(false);
249
250 if sync_source_match {
251 security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
253
254 return AccessSrchResult::Allow {
255 attr: btreeset!(
256 Attribute::Class,
257 Attribute::Uuid,
258 Attribute::SyncCredentialPortal
259 ),
260 };
261 }
262 }
263 }
264 AccessSrchResult::Ignore
266 }
267 }
268}