20230121 access improvement (#1345)

This commit is contained in:
Firstyear 2023-01-25 12:58:22 +10:00 committed by GitHub
parent 251feac7cb
commit 723c428e37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1171 additions and 864 deletions

View file

@ -885,7 +885,10 @@ fn config_security_checks(cfg_path: &Path) -> bool {
let cfg_meta = match metadata(&cfg_path) {
Ok(v) => v,
Err(e) => {
error!("Unable to read metadata for '{}' during security checks - {:?}", cfg_path_str, e);
error!(
"Unable to read metadata for '{}' during security checks - {:?}",
cfg_path_str, e
);
return false;
}
};

View file

@ -44,7 +44,6 @@ pub struct TlsConfiguration {
pub key: String,
}
#[derive(Debug, Deserialize)]
pub struct ServerConfig {
pub bindaddress: Option<String>,

View file

@ -20,6 +20,7 @@ use crate::credential::{BackupCodes, Credential};
use crate::idm::account::Account;
use crate::idm::server::{IdmServerCredUpdateTransaction, IdmServerProxyWriteTransaction};
use crate::prelude::*;
use crate::server::access::Access;
use crate::utils::{backup_code_from_random, readable_password_from_random, uuid_from_duration};
use crate::value::IntentTokenState;
@ -344,10 +345,25 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
return Err(OperationError::InvalidEntryState);
}
if !eperm.search.contains("primary_credential")
|| !eperm.modify_pres.contains("primary_credential")
|| !eperm.modify_rem.contains("primary_credential")
{
let eperm_search_primary_cred = match &eperm.search {
Access::Denied => false,
Access::Grant => true,
Access::Allow(attrs) => attrs.contains("primary_credential"),
};
let eperm_mod_primary_cred = match &eperm.modify_pres {
Access::Denied => false,
Access::Grant => true,
Access::Allow(attrs) => attrs.contains("primary_credential"),
};
let eperm_rem_primary_cred = match &eperm.modify_rem {
Access::Denied => false,
Access::Grant => true,
Access::Allow(attrs) => attrs.contains("primary_credential"),
};
if !eperm_search_primary_cred || !eperm_mod_primary_cred || !eperm_rem_primary_cred {
security_info!(
"Requestor {} does not have permission to update credentials of {}",
ident,

View file

@ -0,0 +1,138 @@
use super::profiles::AccessControlCreate;
use crate::filter::FilterValidResolved;
use crate::prelude::*;
use std::collections::BTreeSet;
pub(super) enum CreateResult {
Denied,
Grant,
}
enum IResult {
Denied,
Grant,
Ignore,
}
pub(super) fn apply_create_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlCreate, Filter<FilterValidResolved>)],
entry: &'a Entry<EntryInit, EntryNew>,
) -> CreateResult {
let mut denied = false;
let mut grant = false;
match create_filter_entry(ident, related_acp, entry) {
IResult::Denied => denied = true,
IResult::Grant => grant = true,
IResult::Ignore => {}
}
if denied {
// Something explicitly said no.
CreateResult::Denied
} else if grant {
// Something said yes
CreateResult::Grant
} else {
// Nothing said yes.
CreateResult::Denied
}
}
fn create_filter_entry<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlCreate, Filter<FilterValidResolved>)],
entry: &'a Entry<EntryInit, EntryNew>,
) -> 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(_) => {}
};
info!(event = %ident, "Access check for create event");
match ident.access_scope() {
AccessScope::IdentityOnly | AccessScope::ReadOnly | AccessScope::Synchronise => {
security_access!("denied ❌ - identity access scope is not permitted to create");
return IResult::Denied;
}
AccessScope::ReadWrite => {
// As you were
}
};
// Build the set of requested classes and attrs here.
let create_attrs: BTreeSet<&str> = entry.get_ava_names().collect();
// If this is empty, we make an empty set, which is fine because
// the empty class set despite matching is_subset, will have the
// following effect:
// * there is no class on entry, so schema will fail
// * plugin-base will add object to give a class, but excess
// attrs will cause fail (could this be a weakness?)
// * class is a "may", so this could be empty in the rules, so
// if the accr is empty this would not be a true subset,
// so this would "fail", but any content in the accr would
// have to be validated.
//
// I still think if this is None, we should just fail here ...
// because it shouldn't be possible to match.
let create_classes: BTreeSet<&str> = match entry.get_ava_iter_iutf8("class") {
Some(s) => s.collect(),
None => {
admin_error!("Class set failed to build - corrupted entry?");
return IResult::Denied;
}
};
// Find the set of related acps for this entry.
//
// For each "created" entry.
// If the created entry is 100% allowed by this acp
// IE: all attrs to be created AND classes match classes
// allow
// if no acp allows, fail operation.
let allow = related_acp.iter().any(|(accr, f_res)| {
// Check to see if allowed.
if entry.entry_match_no_index(f_res) {
security_access!(?entry, acs = ?accr, "entry matches acs");
// It matches, so now we have to check attrs and classes.
// Remember, we have to match ALL requested attrs
// and classes to pass!
let allowed_attrs: BTreeSet<&str> = accr.attrs.iter().map(|s| s.as_str()).collect();
let allowed_classes: BTreeSet<&str> = accr.classes.iter().map(|s| s.as_str()).collect();
if !create_attrs.is_subset(&allowed_attrs) {
security_access!("create_attrs is not a subset of allowed");
security_access!("{:?} !⊆ {:?}", create_attrs, allowed_attrs);
return false;
}
if !create_classes.is_subset(&allowed_classes) {
security_access!("create_classes is not a subset of allowed");
security_access!("{:?} !⊆ {:?}", create_classes, allowed_classes);
return false;
}
security_access!("passed");
true
} else {
trace!(?entry, acs = %accr.acp.name, "entry DOES NOT match acs");
// Does not match, fail this rule.
false
}
});
if allow {
IResult::Grant
} else {
IResult::Ignore
}
}

View file

@ -0,0 +1,97 @@
use super::profiles::AccessControlDelete;
use crate::filter::FilterValidResolved;
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 [(&AccessControlDelete, Filter<FilterValidResolved>)],
entry: &'a Arc<EntrySealedCommitted>,
) -> DeleteResult {
let mut denied = false;
let mut grant = false;
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 [(&AccessControlDelete, Filter<FilterValidResolved>)],
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(_) => {}
};
info!(event = %ident, "Access check for delete event");
match ident.access_scope() {
AccessScope::IdentityOnly | AccessScope::ReadOnly | AccessScope::Synchronise => {
security_access!("denied ❌ - identity access scope is not permitted to delete");
return IResult::Denied;
}
AccessScope::ReadWrite => {
// As you were
}
};
let allow = related_acp.iter().any(|(acd, f_res)| {
if entry.entry_match_no_index(f_res) {
security_access!(
entry_uuid = ?entry.get_uuid(),
acs = %acd.acp.name,
"entry matches acs"
);
// It matches, so we can delete this!
security_access!("passed");
true
} else {
trace!(
"entry {:?} DOES NOT match acs {}",
entry.get_uuid(),
acd.acp.name
);
// Does not match, fail.
false
} // else
}); // any related_acp
if allow {
IResult::Grant
} else {
IResult::Ignore
}
}

View file

@ -0,0 +1,169 @@
use crate::prelude::*;
use std::collections::BTreeSet;
use super::profiles::AccessControlModify;
use super::AccessResult;
use crate::filter::FilterValidResolved;
use std::sync::Arc;
pub(super) enum ModifyResult<'a> {
Denied,
Grant,
Allow {
pres: BTreeSet<&'a str>,
rem: BTreeSet<&'a str>,
cls: BTreeSet<&'a str>,
},
}
pub(super) fn apply_modify_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlModify, Filter<FilterValidResolved>)],
entry: &'a Arc<EntrySealedCommitted>,
) -> ModifyResult<'a> {
let mut denied = false;
let mut grant = false;
let mut constrain_pres = BTreeSet::default();
let mut allow_pres = BTreeSet::default();
let mut constrain_rem = BTreeSet::default();
let mut allow_rem = BTreeSet::default();
let mut constrain_cls = BTreeSet::default();
let mut allow_cls = BTreeSet::default();
// run each module. These have to be broken down further due to modify
// kind of being three operations all in one.
match modify_ident_test(ident) {
AccessResult::Denied => denied = true,
AccessResult::Grant => grant = true,
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
}
if !grant && !denied {
// Setup the acp's here
let scoped_acp: Vec<&AccessControlModify> = related_acp
.iter()
.filter_map(|(acm, f_res)| {
if entry.entry_match_no_index(f_res) {
Some(*acm)
} else {
None
}
})
.collect();
match modify_pres_test(scoped_acp.as_slice()) {
AccessResult::Denied => denied = true,
// Can never return a unilateral grant.
AccessResult::Grant => {}
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
}
match modify_rem_test(scoped_acp.as_slice()) {
AccessResult::Denied => denied = true,
// Can never return a unilateral grant.
AccessResult::Grant => {}
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain_rem.append(&mut set),
AccessResult::Allow(mut set) => allow_rem.append(&mut set),
}
match modify_cls_test(scoped_acp.as_slice()) {
AccessResult::Denied => denied = true,
// Can never return a unilateral grant.
AccessResult::Grant => {}
AccessResult::Ignore => {}
AccessResult::Constrain(mut set) => constrain_cls.append(&mut set),
AccessResult::Allow(mut set) => allow_cls.append(&mut set),
}
}
if denied {
ModifyResult::Denied
} else if grant {
ModifyResult::Grant
} else {
let allowed_pres = if !constrain_pres.is_empty() {
// bit_and
&constrain_pres & &allow_pres
} else {
allow_pres
};
let allowed_rem = if !constrain_rem.is_empty() {
// bit_and
&constrain_rem & &allow_rem
} else {
allow_rem
};
let allowed_cls = if !constrain_cls.is_empty() {
// bit_and
&constrain_cls & &allow_cls
} else {
allow_cls
};
ModifyResult::Allow {
pres: allowed_pres,
rem: allowed_rem,
cls: allowed_cls,
}
}
}
fn modify_ident_test<'a>(ident: &Identity) -> AccessResult<'a> {
match &ident.origin {
IdentType::Internal => {
trace!("Internal operation, bypassing access check");
// No need to check ACS
return AccessResult::Grant;
}
IdentType::Synch(_) => {
security_critical!("Blocking sync check");
return AccessResult::Denied;
}
IdentType::User(_) => {}
};
info!(event = %ident, "Access check for modify event");
match ident.access_scope() {
AccessScope::IdentityOnly | AccessScope::ReadOnly | AccessScope::Synchronise => {
security_access!("denied ❌ - identity access scope is not permitted to modify");
return AccessResult::Denied;
}
AccessScope::ReadWrite => {
// As you were
}
};
AccessResult::Ignore
}
fn modify_pres_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessResult<'a> {
let allowed_pres: BTreeSet<&str> = scoped_acp
.iter()
.flat_map(|acp| acp.presattrs.iter().map(|v| v.as_str()))
.collect();
AccessResult::Allow(allowed_pres)
}
fn modify_rem_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessResult<'a> {
let allowed_rem: BTreeSet<&str> = scoped_acp
.iter()
.flat_map(|acp| acp.remattrs.iter().map(|v| v.as_str()))
.collect();
AccessResult::Allow(allowed_rem)
}
fn modify_cls_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessResult<'a> {
let allowed_classes: BTreeSet<&str> = scoped_acp
.iter()
.flat_map(|acp| acp.classes.iter().map(|v| v.as_str()))
.collect();
AccessResult::Allow(allowed_classes)
}

View file

@ -0,0 +1,331 @@
use crate::prelude::*;
use std::collections::BTreeSet;
use crate::filter::{Filter, FilterValid};
use kanidm_proto::v1::Filter as ProtoFilter;
// =========================================================================
// PARSE ENTRY TO ACP, AND ACP MANAGEMENT
// =========================================================================
#[derive(Debug, Clone)]
pub struct AccessControlSearch {
pub acp: AccessControlProfile,
pub attrs: BTreeSet<AttrString>,
}
impl AccessControlSearch {
pub fn try_from(
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_equality("class", &PVCLASS_ACS) {
admin_error!("class access_control_search not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_search".to_string(),
));
}
let attrs = value
.get_ava_iter_iutf8("acp_search_attr")
.ok_or_else(|| {
admin_error!("Missing acp_search_attr");
OperationError::InvalidAcpState("Missing acp_search_attr".to_string())
})?
.map(AttrString::from)
.collect();
let acp = AccessControlProfile::try_from(qs, value)?;
Ok(AccessControlSearch { acp, attrs })
}
#[cfg(test)]
pub(super) unsafe fn from_raw(
name: &str,
uuid: Uuid,
receiver: Uuid,
targetscope: Filter<FilterValid>,
attrs: &str,
) -> Self {
AccessControlSearch {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
},
attrs: attrs
.split_whitespace()
.map(|s| AttrString::from(s))
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlDelete {
pub acp: AccessControlProfile,
}
impl AccessControlDelete {
pub fn try_from(
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_equality("class", &PVCLASS_ACD) {
admin_error!("class access_control_delete not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_delete".to_string(),
));
}
Ok(AccessControlDelete {
acp: AccessControlProfile::try_from(qs, value)?,
})
}
#[cfg(test)]
pub(super) unsafe fn from_raw(
name: &str,
uuid: Uuid,
receiver: Uuid,
targetscope: Filter<FilterValid>,
) -> Self {
AccessControlDelete {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
},
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlCreate {
pub acp: AccessControlProfile,
pub classes: Vec<AttrString>,
pub attrs: Vec<AttrString>,
}
impl AccessControlCreate {
pub fn try_from(
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_equality("class", &PVCLASS_ACC) {
admin_error!("class access_control_create not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_create".to_string(),
));
}
let attrs = value
.get_ava_iter_iutf8("acp_create_attr")
.map(|i| i.map(AttrString::from).collect())
.unwrap_or_else(Vec::new);
let classes = value
.get_ava_iter_iutf8("acp_create_class")
.map(|i| i.map(AttrString::from).collect())
.unwrap_or_else(Vec::new);
Ok(AccessControlCreate {
acp: AccessControlProfile::try_from(qs, value)?,
classes,
attrs,
})
}
#[cfg(test)]
pub(super) unsafe fn from_raw(
name: &str,
uuid: Uuid,
receiver: Uuid,
targetscope: Filter<FilterValid>,
classes: &str,
attrs: &str,
) -> Self {
AccessControlCreate {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
},
classes: classes.split_whitespace().map(AttrString::from).collect(),
attrs: attrs.split_whitespace().map(AttrString::from).collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlModify {
pub acp: AccessControlProfile,
pub classes: Vec<AttrString>,
pub presattrs: Vec<AttrString>,
pub remattrs: Vec<AttrString>,
}
impl AccessControlModify {
pub fn try_from(
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_equality("class", &PVCLASS_ACM) {
admin_error!("class access_control_modify not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_modify".to_string(),
));
}
let presattrs = value
.get_ava_iter_iutf8("acp_modify_presentattr")
.map(|i| i.map(AttrString::from).collect())
.unwrap_or_else(Vec::new);
let remattrs = value
.get_ava_iter_iutf8("acp_modify_removedattr")
.map(|i| i.map(AttrString::from).collect())
.unwrap_or_else(Vec::new);
let classes = value
.get_ava_iter_iutf8("acp_modify_class")
.map(|i| i.map(AttrString::from).collect())
.unwrap_or_else(Vec::new);
Ok(AccessControlModify {
acp: AccessControlProfile::try_from(qs, value)?,
classes,
presattrs,
remattrs,
})
}
#[cfg(test)]
pub(super) unsafe fn from_raw(
name: &str,
uuid: Uuid,
receiver: Uuid,
targetscope: Filter<FilterValid>,
presattrs: &str,
remattrs: &str,
classes: &str,
) -> Self {
AccessControlModify {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
},
classes: classes
.split_whitespace()
.map(|s| AttrString::from(s))
.collect(),
presattrs: presattrs
.split_whitespace()
.map(|s| AttrString::from(s))
.collect(),
remattrs: remattrs
.split_whitespace()
.map(|s| AttrString::from(s))
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlProfile {
pub name: String,
// Currently we retrieve this but don't use it. We could depending on how we change
// the acp update routine.
#[allow(dead_code)]
uuid: Uuid,
// Must be
// Group
// === ⚠️ WARNING!!! ⚠️ ===
// This is OPTION to allow migration from 10 -> 11. We have to do this because ACP is reloaded
// so early in the boot phase that we can't have migrated the content of the receiver yet! As a
// result we MUST be able to withstand some failure in the parse process. The INTENT is that
// during early boot this will be None, and will NEVER match. Once started, the migration
// will occur, and this will flip to Some. In a future version we can remove this!
pub receiver: Option<Uuid>,
// or
// Filter
// Group
// Self
// and
// exclude
// Group
pub targetscope: Filter<FilterValid>,
}
impl AccessControlProfile {
pub(super) fn try_from(
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
// Assert we have class access_control_profile
if !value.attribute_equality("class", &PVCLASS_ACP) {
admin_error!("class access_control_profile not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_profile".to_string(),
));
}
// copy name
let name = value
.get_ava_single_iname("name")
.ok_or_else(|| {
admin_error!("Missing name");
OperationError::InvalidAcpState("Missing name".to_string())
})?
.to_string();
// copy uuid
let uuid = value.get_uuid();
// receiver, and turn to real filter
// === ⚠️ WARNING!!! ⚠️ ===
// See struct ACP for details.
let receiver = value.get_ava_single_refer("acp_receiver_group");
/*
.ok_or_else(|| {
admin_error!("Missing acp_receiver_group");
OperationError::InvalidAcpState("Missing acp_receiver_group".to_string())
})?;
*/
// targetscope, and turn to real filter
let targetscope_f: ProtoFilter = value
.get_ava_single_protofilter("acp_targetscope")
// .map(|pf| pf.clone())
.cloned()
.ok_or_else(|| {
admin_error!("Missing acp_targetscope");
OperationError::InvalidAcpState("Missing acp_targetscope".to_string())
})?;
let ident = Identity::from_internal();
let targetscope_i = Filter::from_rw(&ident, &targetscope_f, qs).map_err(|e| {
admin_error!("Targetscope validation failed {:?}", e);
e
})?;
let targetscope = targetscope_i.validate(qs.get_schema()).map_err(|e| {
admin_error!("acp_targetscope Schema Violation {:?}", e);
OperationError::SchemaViolation(e)
})?;
Ok(AccessControlProfile {
name,
uuid,
receiver,
targetscope,
})
}
}

View file

@ -0,0 +1,104 @@
use crate::prelude::*;
use std::collections::BTreeSet;
use super::profiles::AccessControlSearch;
use super::AccessResult;
use crate::filter::FilterValidResolved;
use std::sync::Arc;
pub(super) enum SearchResult<'a> {
Denied,
Grant,
Allow(BTreeSet<&'a str>),
}
pub(super) fn apply_search_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlSearch, Filter<FilterValidResolved>)],
entry: &'a Arc<EntrySealedCommitted>,
) -> SearchResult<'a> {
// This could be considered "slow" due to allocs each iter with the entry. We
// could move these out of the loop and re-use, but there are likely risks to
// that.
let mut denied = false;
let mut grant = false;
let mut constrain = BTreeSet::default();
let mut allow = BTreeSet::default();
// The access control profile
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),
};
// We'll add more modules later.
// Now finalise the decision.
if denied {
SearchResult::Denied
} else if grant {
SearchResult::Grant
} else {
let allowed_attrs = if !constrain.is_empty() {
// bit_and
&constrain & &allow
} else {
allow
};
SearchResult::Allow(allowed_attrs)
}
}
fn search_filter_entry<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlSearch, Filter<FilterValidResolved>)],
entry: &'a Arc<EntrySealedCommitted>,
) -> AccessResult<'a> {
// If this is an internal search, return our working set.
match &ident.origin {
IdentType::Internal => {
trace!("Internal operation, bypassing access check");
// No need to check ACS
return AccessResult::Grant;
}
IdentType::Synch(_) => {
security_critical!("Blocking sync check");
return AccessResult::Denied;
}
IdentType::User(_) => {}
};
info!(event = %ident, "Access check for search (filter) event");
match ident.access_scope() {
AccessScope::IdentityOnly | AccessScope::Synchronise => {
security_access!("denied ❌ - identity access scope is not permitted to search");
return AccessResult::Denied;
}
AccessScope::ReadOnly | AccessScope::ReadWrite => {
// As you were
}
};
let allowed_attrs: BTreeSet<&str> = related_acp
.iter()
.filter_map(|(acs, f_res)| {
// if it applies
if entry.entry_match_no_index(f_res) {
security_access!(entry = ?entry.get_uuid(), acs = %acs.acp.name, "entry matches acs");
// add search_attrs to allowed.
Some(acs.attrs.iter().map(|s| s.as_str()))
} else {
// should this be `security_access`?
trace!(entry = ?entry.get_uuid(), acs = %acs.acp.name, "entry DOES NOT match acs");
None
}
})
.flatten()
.collect();
AccessResult::Allow(allowed_attrs)
}

View file

@ -14,7 +14,9 @@ use tokio::sync::{Semaphore, SemaphorePermit};
use tracing::trace;
use self::access::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
profiles::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
},
AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
AccessControlsWriteTransaction,
};