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_applications_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 match search_sync_account_filter_entry(ident, entry) {
55 AccessSrchResult::Deny => denied = true,
56 AccessSrchResult::Grant => grant = true,
57 AccessSrchResult::Ignore => {}
58 AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
60 };
61
62 if denied {
67 SearchResult::Deny
68 } else if grant {
69 SearchResult::Grant
70 } else {
71 let allowed_attrs = if !constrain.is_empty() {
72 &constrain & &allow
74 } else {
75 allow
76 };
77 SearchResult::Allow(allowed_attrs)
78 }
79}
80
81fn search_filter_entry(
82 ident: &Identity,
83 related_acp: &[AccessControlSearchResolved],
84 entry: &Arc<EntrySealedCommitted>,
85) -> AccessSrchResult {
86 match &ident.origin {
88 IdentType::Internal => {
89 trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
90 return AccessSrchResult::Grant;
92 }
93 IdentType::Synch(_) => {
94 security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
95 return AccessSrchResult::Deny;
96 }
97 IdentType::User(_) => {}
98 };
99 debug!(event = %ident, "Access check for search (filter) event");
100
101 match ident.access_scope() {
102 AccessScope::Synchronise => {
103 security_debug!(
104 "denied ❌ - identity access scope 'Synchronise' is not permitted to search"
105 );
106 return AccessSrchResult::Deny;
107 }
108 AccessScope::ReadOnly | AccessScope::ReadWrite => {
109 }
111 };
112
113 let ident_memberof = ident.get_memberof();
115 let ident_uuid = ident.get_uuid();
116
117 let allowed_attrs: BTreeSet<Attribute> = related_acp
118 .iter()
119 .filter_map(|acs| {
120 match &acs.receiver_condition {
122 AccessControlReceiverCondition::GroupChecked => {
123 }
126 AccessControlReceiverCondition::EntryManager => {
127 if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
134 let group_check = ident_memberof
135 .map(|imo| imo.intersection(entry_manager_uuids).next().is_some())
137 .unwrap_or_default();
138
139 let user_check = ident_uuid
140 .map(|u| entry_manager_uuids.contains(&u))
141 .unwrap_or_default();
142
143 if !(group_check || user_check) {
144 return None
146 }
147 } else {
148 return None
150 }
151 }
152 };
153
154 match &acs.target_condition {
155 AccessControlTargetCondition::Scope(f_res) => {
156 if !entry.entry_match_no_index(f_res) {
157 security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "entry DOES NOT match acs");
158 return None
159 }
160 }
161 };
162
163 security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "acs applied to entry");
166 Some(acs.acp.attrs.iter().cloned())
168 })
169 .flatten()
170 .collect();
171
172 AccessSrchResult::Allow {
173 attr: allowed_attrs,
174 }
175}
176
177fn search_oauth2_filter_entry(
178 ident: &Identity,
179 entry: &Arc<EntrySealedCommitted>,
180) -> AccessSrchResult {
181 match &ident.origin {
182 IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
183 IdentType::User(iuser) => {
184 if iuser.entry.get_uuid() == UUID_ANONYMOUS {
185 debug!("Anonymous can't access OAuth2 entries, ignoring");
186 return AccessSrchResult::Ignore;
187 }
188
189 let contains_o2_rs = entry
190 .get_ava_as_iutf8(Attribute::Class)
191 .map(|set| {
192 trace!(?set);
193 set.contains(&EntryClass::OAuth2ResourceServer.to_string())
194 })
195 .unwrap_or(false);
196
197 let contains_o2_scope_member = entry
198 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
199 .and_then(|maps| ident.get_memberof().map(|mo| (maps, mo)))
200 .map(|(maps, mo)| maps.keys().any(|k| mo.contains(k)))
201 .unwrap_or(false);
202
203 if contains_o2_rs && contains_o2_scope_member {
204 security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
205
206 return AccessSrchResult::Allow {
207 attr: btreeset!(
208 Attribute::Class,
209 Attribute::DisplayName,
210 Attribute::Uuid,
211 Attribute::Name,
212 Attribute::OAuth2RsOriginLanding,
213 Attribute::Image
214 ),
215 };
216 }
217 AccessSrchResult::Ignore
218 }
219 }
220}
221
222fn search_applications_filter_entry(
223 ident: &Identity,
224 entry: &Arc<EntrySealedCommitted>,
225) -> AccessSrchResult {
226 match &ident.origin {
227 IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
228 IdentType::User(iuser) => {
229 if iuser.entry.get_uuid() == UUID_ANONYMOUS {
230 debug!("Anonymous can't access application entries, ignoring");
231 return AccessSrchResult::Ignore;
232 }
233
234 let contains_application = entry
235 .get_ava_as_iutf8(Attribute::Class)
236 .map(|set| {
237 trace!(?set);
238 set.contains(&EntryClass::Application.to_string())
239 })
240 .unwrap_or(false);
241
242 let contains_application_linked_group = entry
243 .get_ava_single_refer(Attribute::LinkedGroup)
244 .and_then(|group_uuid| ident.get_memberof().map(|mo| mo.contains(&group_uuid)))
245 .unwrap_or(false);
246
247 trace!(?entry);
248
249 if contains_application && contains_application_linked_group {
250 security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted application access for this entry");
251
252 return AccessSrchResult::Allow {
253 attr: btreeset!(
254 Attribute::Class,
255 Attribute::DisplayName,
256 Attribute::Uuid,
257 Attribute::Name,
258 Attribute::LinkedGroup
259 ),
260 };
261 }
262 AccessSrchResult::Ignore
263 }
264 }
265}
266
267fn search_sync_account_filter_entry(
268 ident: &Identity,
269 entry: &Arc<EntrySealedCommitted>,
270) -> AccessSrchResult {
271 match &ident.origin {
272 IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
273 IdentType::User(iuser) => {
274 let is_user_sync_account = iuser
276 .entry
277 .get_ava_as_iutf8(Attribute::Class)
278 .map(|set| {
279 trace!(?set);
280 set.contains(&EntryClass::SyncObject.to_string())
281 && set.contains(EntryClass::Account.into())
282 })
283 .unwrap_or(false);
284
285 if is_user_sync_account {
286 let is_target_sync_account = entry
287 .get_ava_as_iutf8(Attribute::Class)
288 .map(|set| {
289 trace!(?set);
290 set.contains(&EntryClass::SyncAccount.to_string())
291 })
292 .unwrap_or(false);
293
294 if is_target_sync_account {
295 let sync_uuid = entry.get_uuid();
297 let sync_source_match = iuser
298 .entry
299 .get_ava_single_refer(Attribute::SyncParentUuid)
300 .map(|sync_parent_uuid| sync_parent_uuid == sync_uuid)
301 .unwrap_or(false);
302
303 if sync_source_match {
304 security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
306
307 return AccessSrchResult::Allow {
308 attr: btreeset!(
309 Attribute::Class,
310 Attribute::Uuid,
311 Attribute::SyncCredentialPortal
312 ),
313 };
314 }
315 }
316 }
317 AccessSrchResult::Ignore
319 }
320 }
321}