kanidmd_lib/server/access/
search.rsuse crate::prelude::*;
use std::collections::BTreeSet;
use super::profiles::{
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
};
use super::AccessResult;
use std::sync::Arc;
pub(super) enum SearchResult {
Denied,
Grant,
Allow(BTreeSet<Attribute>),
}
pub(super) fn apply_search_access(
ident: &Identity,
related_acp: &[AccessControlSearchResolved],
entry: &Arc<EntrySealedCommitted>,
) -> SearchResult {
let mut denied = false;
let mut grant = false;
let mut constrain = BTreeSet::default();
let mut allow = BTreeSet::default();
match search_filter_entry(ident, related_acp, entry) {
AccessResult::Denied => denied = true,
AccessResult::Grant => grant = true,
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain.append(&mut set),
AccessResult::Allow(mut set) => allow.append(&mut set),
};
match search_oauth2_filter_entry(ident, entry) {
AccessResult::Denied => denied = true,
AccessResult::Grant => grant = true,
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain.append(&mut set),
AccessResult::Allow(mut set) => allow.append(&mut set),
};
match search_sync_account_filter_entry(ident, entry) {
AccessResult::Denied => denied = true,
AccessResult::Grant => grant = true,
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain.append(&mut set),
AccessResult::Allow(mut set) => allow.append(&mut set),
};
if denied {
SearchResult::Denied
} else if grant {
SearchResult::Grant
} else {
let allowed_attrs = if !constrain.is_empty() {
&constrain & &allow
} else {
allow
};
SearchResult::Allow(allowed_attrs)
}
}
fn search_filter_entry(
ident: &Identity,
related_acp: &[AccessControlSearchResolved],
entry: &Arc<EntrySealedCommitted>,
) -> AccessResult {
match &ident.origin {
IdentType::Internal => {
trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
return AccessResult::Grant;
}
IdentType::Synch(_) => {
security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
return AccessResult::Denied;
}
IdentType::User(_) => {}
};
debug!(event = %ident, "Access check for search (filter) event");
match ident.access_scope() {
AccessScope::Synchronise => {
security_debug!(
"denied ❌ - identity access scope 'Synchronise' is not permitted to search"
);
return AccessResult::Denied;
}
AccessScope::ReadOnly | AccessScope::ReadWrite => {
}
};
let ident_memberof = ident.get_memberof();
let ident_uuid = ident.get_uuid();
let allowed_attrs: BTreeSet<Attribute> = related_acp
.iter()
.filter_map(|acs| {
match &acs.receiver_condition {
AccessControlReceiverCondition::GroupChecked => {
}
AccessControlReceiverCondition::EntryManager => {
if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
let group_check = ident_memberof
.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) {
return None
}
} else {
return None
}
}
};
match &acs.target_condition {
AccessControlTargetCondition::Scope(f_res) => {
if !entry.entry_match_no_index(f_res) {
security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "entry DOES NOT match acs");
return None
}
}
};
security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "acs applied to entry");
Some(acs.acp.attrs.iter().cloned())
})
.flatten()
.collect();
AccessResult::Allow(allowed_attrs)
}
fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> AccessResult {
match &ident.origin {
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
IdentType::User(iuser) => {
if iuser.entry.get_uuid() == UUID_ANONYMOUS {
debug!("Anonymous can't access OAuth2 entries, ignoring");
return AccessResult::Ignore;
}
let contains_o2_rs = entry
.get_ava_as_iutf8(Attribute::Class)
.map(|set| {
trace!(?set);
set.contains(&EntryClass::OAuth2ResourceServer.to_string())
})
.unwrap_or(false);
let contains_o2_scope_member = entry
.get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
.and_then(|maps| ident.get_memberof().map(|mo| (maps, mo)))
.map(|(maps, mo)| maps.keys().any(|k| mo.contains(k)))
.unwrap_or(false);
if contains_o2_rs && contains_o2_scope_member {
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
return AccessResult::Allow(btreeset!(
Attribute::Class,
Attribute::DisplayName,
Attribute::Uuid,
Attribute::Name,
Attribute::OAuth2RsOriginLanding,
Attribute::Image
));
}
AccessResult::Ignore
}
}
}
fn search_sync_account_filter_entry(
ident: &Identity,
entry: &Arc<EntrySealedCommitted>,
) -> AccessResult {
match &ident.origin {
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
IdentType::User(iuser) => {
let is_user_sync_account = iuser
.entry
.get_ava_as_iutf8(Attribute::Class)
.map(|set| {
trace!(?set);
set.contains(&EntryClass::SyncObject.to_string())
&& set.contains(EntryClass::Account.into())
})
.unwrap_or(false);
if is_user_sync_account {
let is_target_sync_account = entry
.get_ava_as_iutf8(Attribute::Class)
.map(|set| {
trace!(?set);
set.contains(&EntryClass::SyncAccount.to_string())
})
.unwrap_or(false);
if is_target_sync_account {
let sync_uuid = entry.get_uuid();
let sync_source_match = iuser
.entry
.get_ava_single_refer(Attribute::SyncParentUuid)
.map(|sync_parent_uuid| sync_parent_uuid == sync_uuid)
.unwrap_or(false);
if sync_source_match {
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
return AccessResult::Allow(btreeset!(
Attribute::Class,
Attribute::Uuid,
Attribute::SyncCredentialPortal
));
}
}
}
AccessResult::Ignore
}
}
}