249 2024 managed by syntax ()

Allows hierarchial entry management rules.
This commit is contained in:
Firstyear 2023-12-07 20:00:09 +10:00 committed by GitHub
parent 340d41482b
commit 854b696532
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1146 additions and 455 deletions

View file

@ -86,6 +86,7 @@ pub const ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
pub const ATTR_EMAIL_PRIMARY: &str = "emailprimary";
pub const ATTR_EMAIL: &str = "email";
pub const ATTR_ENTRYDN: &str = "entrydn";
pub const ATTR_ENTRY_MANAGED_BY: &str = "entry_managed_by";
pub const ATTR_ENTRYUUID: &str = "entryuuid";
pub const ATTR_LDAP_KEYS: &str = "keys";
pub const ATTR_EXCLUDES: &str = "excludes";

View file

@ -36,15 +36,36 @@ lazy_static! {
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
/// Who will receive the privileges of this ACP.
pub enum BuiltinAcpReceiver {
#[default]
None,
/// This functions as an "OR" condition, that membership of *at least one* of these UUIDs
/// is sufficient for you to receive the access control.
Group(Vec<Uuid>),
// ManagerOf,
}
#[derive(Clone, Debug, Default)]
/// Objects that are affected by the rules of this ACP.
pub enum BuiltinAcpTarget {
#[default]
None,
// Self,
Filter(ProtoFilter),
// MemberOf ( Uuid ),
}
#[derive(Clone, Debug, Default)]
/// Built-in Access Control Profile definitions
pub struct BuiltinAcp {
classes: Vec<EntryClass>,
pub name: &'static str,
uuid: Uuid,
description: &'static str,
receiver_group: Uuid,
target_scope: ProtoFilter,
receiver: BuiltinAcpReceiver,
target: BuiltinAcpTarget,
search_attrs: Vec<Attribute>,
modify_present_attrs: Vec<Attribute>,
modify_removed_attrs: Vec<Attribute>,
@ -53,25 +74,6 @@ pub struct BuiltinAcp {
create_attrs: Vec<Attribute>,
}
impl Default for BuiltinAcp {
fn default() -> Self {
Self {
classes: Default::default(),
name: Default::default(),
uuid: Default::default(),
description: Default::default(),
receiver_group: Default::default(),
search_attrs: Default::default(),
modify_present_attrs: Default::default(),
modify_removed_attrs: Default::default(),
modify_classes: Default::default(),
target_scope: DEFAULT_TARGET_SCOPE.clone(), // evals to matching nothing
create_classes: Default::default(),
create_attrs: Default::default(),
}
}
}
impl From<BuiltinAcp> for EntryInitNew {
fn from(value: BuiltinAcp) -> Self {
let mut entry = EntryInitNew::default();
@ -84,12 +86,8 @@ impl From<BuiltinAcp> for EntryInitNew {
if value.classes.is_empty() {
panic!("Builtin ACP has no classes! {:?}", value);
}
#[allow(clippy::panic)]
if DEFAULT_TARGET_SCOPE.clone() == value.target_scope {
panic!("Builtin ACP has an invalid target_scope! {:?}", value);
}
value.classes.into_iter().for_each(|class| {
value.classes.iter().for_each(|class| {
entry.add_ava(Attribute::Class, class.to_value());
});
@ -99,14 +97,39 @@ impl From<BuiltinAcp> for EntryInitNew {
Attribute::Description,
[Value::new_utf8s(value.description)],
);
entry.set_ava(
Attribute::AcpReceiverGroup,
[Value::Refer(value.receiver_group)],
);
entry.set_ava(
Attribute::AcpTargetScope,
[Value::JsonFilt(value.target_scope)],
);
match &value.receiver {
#[allow(clippy::panic)]
BuiltinAcpReceiver::None => {
panic!("Builtin ACP has no receiver! {:?}", &value);
}
BuiltinAcpReceiver::Group(list) => {
entry.add_ava(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value(),
);
for group in list {
entry.set_ava(Attribute::AcpReceiverGroup, [Value::Refer(*group)]);
}
}
};
match &value.target {
#[allow(clippy::panic)]
BuiltinAcpTarget::None => {
panic!("Builtin ACP has no target! {:?}", &value);
}
BuiltinAcpTarget::Filter(proto_filter) => {
entry.add_ava(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value(),
);
entry.set_ava(
Attribute::AcpTargetScope,
[Value::JsonFilt(proto_filter.clone())],
);
}
}
entry.set_ava(
Attribute::AcpSearchAttr,
@ -145,8 +168,11 @@ lazy_static! {
EntryClass::AccessControlProfile,
EntryClass::AccessControlSearch,
],
receiver_group: UUID_SYSTEM_ADMINS,
target_scope: ProtoFilter::Eq(Attribute::Class.to_string(), ATTR_RECYCLED.to_string()),
receiver: BuiltinAcpReceiver::Group(vec![UUID_SYSTEM_ADMINS]),
target: BuiltinAcpTarget::Filter(ProtoFilter::Eq(
Attribute::Class.to_string(),
ATTR_RECYCLED.to_string()
)),
search_attrs: vec![
Attribute::Class,
@ -168,8 +194,11 @@ lazy_static! {
EntryClass::AccessControlProfile,
EntryClass::AccessControlModify,
],
receiver_group: UUID_SYSTEM_ADMINS,
target_scope: ProtoFilter::Eq(Attribute::Class.to_string(), ATTR_RECYCLED.to_string()),
receiver: BuiltinAcpReceiver::Group(vec![UUID_SYSTEM_ADMINS]),
target: BuiltinAcpTarget::Filter(ProtoFilter::Eq(
Attribute::Class.to_string(),
ATTR_RECYCLED.to_string()
)),
modify_removed_attrs: vec![Attribute::Class],
modify_classes: vec![EntryClass::Recycled],
..Default::default()
@ -187,8 +216,8 @@ lazy_static! {
EntryClass::AccessControlProfile,
EntryClass::AccessControlSearch,
],
receiver_group: UUID_IDM_ALL_ACCOUNTS,
target_scope: ProtoFilter::SelfUuid,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ALL_ACCOUNTS] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::SelfUuid ),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -222,8 +251,9 @@ lazy_static! {
EntryClass::AccessControlModify,
],
description: "Builtin IDM Control for self write - required for people to update their own identities and credentials in line with best practices.",
receiver_group: UUID_IDM_ALL_PERSONS,
target_scope:
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ALL_PERSONS] ),
target:
BuiltinAcpTarget::Filter(
ProtoFilter::And(
vec![
match_class_filter!(EntryClass::Person),
@ -231,7 +261,7 @@ lazy_static! {
match_class_filter!(EntryClass::Account),
ProtoFilter::SelfUuid,
]
),
)),
modify_removed_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -267,8 +297,8 @@ lazy_static! {
EntryClass::AccessControlProfile,
EntryClass::AccessControlModify
],
receiver_group: UUID_IDM_ALL_ACCOUNTS,
target_scope: ProtoFilter::And(vec![ProtoFilter::Eq(Attribute::Class.to_string(), Attribute::Account.to_string()), ProtoFilter::SelfUuid]),
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ALL_ACCOUNTS] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![ProtoFilter::Eq(Attribute::Class.to_string(), Attribute::Account.to_string()), ProtoFilter::SelfUuid]) ),
modify_removed_attrs: vec![
Attribute::UserAuthTokenSession
],
@ -286,12 +316,12 @@ lazy_static! {
name: "idm_people_self_acp_write_mail",
uuid: UUID_IDM_PEOPLE_SELF_ACP_WRITE_MAIL_V1,
description: "Builtin IDM Control for self write of mail for people accounts.",
receiver_group: UUID_IDM_PEOPLE_SELF_WRITE_MAIL_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_SELF_WRITE_MAIL_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person).clone(),
match_class_filter!(EntryClass::Account).clone(),
ProtoFilter::SelfUuid,
]),
])),
modify_removed_attrs: vec![Attribute::Mail],
modify_present_attrs: vec![Attribute::Mail],
..Default::default()
@ -309,8 +339,8 @@ lazy_static! {
uuid: UUID_IDM_ALL_ACP_READ_V1,
description:
"Builtin IDM Control for all read - e.g. anonymous and all authenticated accounts.",
receiver_group: UUID_IDM_ALL_ACCOUNTS,
target_scope: ProtoFilter::And(
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ALL_ACCOUNTS] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::And(
vec![
ProtoFilter::Or(vec![
match_class_filter!(EntryClass::Account),
@ -318,7 +348,7 @@ lazy_static! {
]),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]
),
)),
// Value::new_json_filter_s(
// "{\"and\":
@ -364,11 +394,11 @@ lazy_static! {
name: "idm_acp_people_read_priv",
uuid: UUID_IDM_ACP_PEOPLE_READ_PRIV_V1,
description: "Builtin IDM Control for reading personal sensitive data.",
receiver_group: UUID_IDM_PEOPLE_READ_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_READ_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person).clone(),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -390,12 +420,12 @@ lazy_static! {
name: "idm_acp_people_write_priv",
uuid: UUID_IDM_ACP_PEOPLE_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing personal and sensitive data.",
receiver_group: UUID_IDM_PEOPLE_WRITE_PRIV,
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_WRITE_PRIV]),
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person).clone(),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
@ -424,12 +454,12 @@ lazy_static! {
name: "idm_acp_people_manage",
uuid: UUID_IDM_ACP_PEOPLE_MANAGE_PRIV_V1,
description: "Builtin IDM Control for creating person (user) accounts",
receiver_group: UUID_IDM_PEOPLE_MANAGE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_MANAGE_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person),
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
create_attrs: vec![
Attribute::Class,
@ -464,12 +494,12 @@ lazy_static! {
uuid: UUID_IDM_ACP_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
description:
"Builtin IDM Control for allowing imports of passwords to people+account types.",
receiver_group: UUID_IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person),
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_removed_attrs: vec![Attribute::PasswordImport],
modify_present_attrs: vec![Attribute::PasswordImport],
@ -487,11 +517,11 @@ lazy_static! {
name: "idm_acp_people_extend_priv",
uuid: UUID_IDM_ACP_PEOPLE_EXTEND_PRIV_V1,
description: "Builtin IDM Control for allowing person class extension",
receiver_group: UUID_IDM_PEOPLE_EXTEND_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_PEOPLE_EXTEND_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account).clone(),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -517,11 +547,11 @@ lazy_static! {
name: "idm_acp_hp_people_read_priv",
uuid: UUID_IDM_ACP_HP_PEOPLE_READ_PRIV_V1,
description: "Builtin IDM Control for reading high privilege personal sensitive data.",
receiver_group: UUID_IDM_HP_PEOPLE_READ_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_HP_PEOPLE_READ_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person).clone(),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -542,11 +572,11 @@ lazy_static! {
name: "idm_acp_account_mail_read_priv",
uuid: UUID_IDM_ACP_ACCOUNT_MAIL_READ_PRIV_V1,
description: "Builtin IDM Control for reading account mail attributes.",
receiver_group: UUID_IDM_ACCOUNT_MAIL_READ_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_ACCOUNT_MAIL_READ_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
search_attrs: vec![Attribute::Mail],
..Default::default()
@ -560,15 +590,15 @@ lazy_static! {
name: "idm_acp_hp_people_write_priv",
uuid: UUID_IDM_ACP_HP_PEOPLE_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing privilege personal and sensitive data.",
receiver_group: UUID_IDM_HP_PEOPLE_WRITE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_HP_PEOPLE_WRITE_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Person).clone(),
ProtoFilter::Eq(
Attribute::MemberOf.to_string(),
UUID_IDM_HIGH_PRIVILEGE.to_string()
),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -592,15 +622,15 @@ lazy_static! {
name: "idm_acp_hp_people_extend_priv",
uuid: UUID_IDM_ACP_HP_PEOPLE_EXTEND_PRIV_V1,
description: "Builtin IDM Control for allowing privilege person class extension",
receiver_group: UUID_IDM_HP_PEOPLE_EXTEND_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_HP_PEOPLE_EXTEND_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::Eq(
Attribute::MemberOf.to_string(),
UUID_IDM_HIGH_PRIVILEGE.to_string()
),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -632,13 +662,13 @@ lazy_static! {
name: "idm_acp_group_write_priv",
uuid: UUID_IDM_ACP_GROUP_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing groups",
receiver_group: UUID_IDM_GROUP_WRITE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_GROUP_WRITE_PRIV] ),
// group which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -674,12 +704,12 @@ lazy_static! {
name: "idm_acp_account_read_priv",
uuid: UUID_IDM_ACP_ACCOUNT_READ_PRIV_V1,
description: "Builtin IDM Control for reading accounts.",
receiver_group: UUID_IDM_ACCOUNT_READ_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ACCOUNT_READ_PRIV] ),
// Account which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::Class,
@ -713,12 +743,12 @@ lazy_static! {
name: "idm_acp_account_write_priv",
uuid: UUID_IDM_ACP_ACCOUNT_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing all accounts (both person and service).",
receiver_group: UUID_IDM_ACCOUNT_WRITE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ACCOUNT_WRITE_PRIV] ),
// Account which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
@ -762,12 +792,12 @@ lazy_static! {
name: "idm_acp_account_manage",
uuid: UUID_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1,
description: "Builtin IDM Control for creating and deleting (service) accounts",
receiver_group: UUID_IDM_ACCOUNT_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ACCOUNT_MANAGE_PRIV] ),
// Account which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
create_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -804,12 +834,12 @@ lazy_static! {
name: "idm_acp_radius_secret_read_priv",
uuid: UUID_IDM_ACP_RADIUS_SECRET_READ_PRIV_V1,
description: "Builtin IDM Control for reading user radius secrets.",
receiver_group: UUID_IDM_RADIUS_SECRET_READ_PRIV_V1,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_RADIUS_SECRET_READ_PRIV_V1] ),
// Account which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::RadiusSecret
],
@ -826,12 +856,12 @@ lazy_static! {
name: "idm_acp_radius_secret_write_priv",
uuid: UUID_IDM_ACP_RADIUS_SECRET_WRITE_PRIV_V1,
description: "Builtin IDM Control allowing writes to user radius secrets.",
receiver_group: UUID_IDM_RADIUS_SECRET_WRITE_PRIV_V1,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_RADIUS_SECRET_WRITE_PRIV_V1] ),
// Account which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
modify_present_attrs:vec![Attribute::RadiusSecret],
modify_removed_attrs: vec![Attribute::RadiusSecret],
..Default::default()
@ -849,12 +879,12 @@ lazy_static! {
name: "idm_acp_radius_servers",
uuid: UUID_IDM_ACP_RADIUS_SERVERS_V1,
description: "Builtin IDM Control for RADIUS servers to read credentials and other needed details.",
receiver_group: UUID_IDM_RADIUS_SERVERS,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_RADIUS_SERVERS] ),
// has a class, and isn't recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Pres(EntryClass::Class.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -876,13 +906,13 @@ lazy_static! {
name: "idm_acp_hp_account_read_priv",
uuid: UUID_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1,
description: "Builtin IDM Control for reading high privilege accounts.",
receiver_group: UUID_IDM_HP_ACCOUNT_READ_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_ACCOUNT_READ_PRIV] ),
// account, in hp, not recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_HP.clone(),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -910,13 +940,13 @@ lazy_static! {
name: "idm_acp_hp_account_write_priv",
uuid: UUID_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing high privilege accounts (both person and service).",
receiver_group: UUID_IDM_HP_ACCOUNT_WRITE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_ACCOUNT_WRITE_PRIV] ),
// account, in hp, not recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_HP.clone(),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
modify_removed_attrs: vec![
Attribute::Name,
Attribute::DisplayName,
@ -955,13 +985,13 @@ lazy_static! {
name: "idm_acp_hp_group_write_priv",
uuid: UUID_IDM_ACP_HP_GROUP_WRITE_PRIV_V1,
description: "Builtin IDM Control for managing high privilege groups",
receiver_group: UUID_IDM_HP_GROUP_WRITE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_GROUP_WRITE_PRIV] ),
// group, is HP, isn't recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
FILTER_HP.clone(),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
// Value::new_json_filter_s(
// "{\"and\":
// [{\"eq\": [\"class\",\"group\"]},
@ -1005,12 +1035,12 @@ lazy_static! {
name: "idm_acp_schema_write_attrs_priv",
uuid: UUID_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1,
description: "Builtin IDM Control for management of schema attributes.",
receiver_group: UUID_IDM_SCHEMA_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_SCHEMA_MANAGE_PRIV] ),
// has a class, and isn't recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(EntryClass::Class.to_string(),EntryClass::AttributeType.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"attributetype\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
@ -1071,12 +1101,12 @@ lazy_static! {
name: "idm_acp_acp_manage_priv",
uuid: UUID_IDM_ACP_ACP_MANAGE_PRIV_V1,
description: "Builtin IDM Control for access profiles management.",
receiver_group: UUID_IDM_ACP_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ACP_MANAGE_PRIV] ),
// has a class, and isn't recycled/tombstoned
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(EntryClass::Class.to_string(),EntryClass::AccessControlProfile.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// target_scope: Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
// )
@ -1165,11 +1195,11 @@ lazy_static! {
name: "idm_acp_schema_write_classes_priv",
uuid: UUID_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1,
description: "Builtin IDM Control for management of schema classes.",
receiver_group: UUID_IDM_SCHEMA_MANAGE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_SCHEMA_MANAGE_PRIV] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(EntryClass::Class.to_string(), EntryClass::ClassType.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"classtype\"]},
// {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
@ -1222,13 +1252,13 @@ lazy_static! {
name: "idm_acp_group_manage",
uuid: UUID_IDM_ACP_GROUP_MANAGE_PRIV_V1,
description: "Builtin IDM Control for creating and deleting groups in the directory",
receiver_group: UUID_IDM_GROUP_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_GROUP_MANAGE_PRIV] ),
// group which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
// target_scope: Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"group\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
// )
@ -1260,13 +1290,12 @@ lazy_static! {
// For now just target SA because we are going to rework this soon and I think
// there isn't a great reason to make more small priv groups that we plan to
// erase.
receiver_group: UUID_SYSTEM_ADMINS,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_SYSTEM_ADMINS] ),
// group which is not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -1316,12 +1345,12 @@ lazy_static! {
name: "idm_acp_hp_account_manage",
uuid: UUID_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1,
description: "Builtin IDM Control for creating and deleting hp and regular (service) accounts",
receiver_group: UUID_IDM_HP_ACCOUNT_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_ACCOUNT_MANAGE_PRIV] ),
// account that's not tombstoned?
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
create_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -1353,12 +1382,12 @@ lazy_static! {
name: "idm_acp_hp_group_manage",
uuid: UUID_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1,
description: "Builtin IDM Control for creating and deleting hp and regular groups in the directory",
receiver_group: UUID_IDM_HP_GROUP_MANAGE_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_GROUP_MANAGE_PRIV] ),
// account that's not tombstoned?
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
create_attrs: vec![
Attribute::Class,
Attribute::Name,
@ -1382,12 +1411,12 @@ lazy_static! {
name: "idm_acp_domain_admin_priv",
uuid: UUID_IDM_ACP_DOMAIN_ADMIN_PRIV_V1,
description: "Builtin IDM Control for granting domain info administration locally",
receiver_group: UUID_DOMAIN_ADMINS,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_DOMAIN_ADMINS] ),
// STR_UUID_DOMAIN_INFO
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(Attribute::Uuid.to_string(),STR_UUID_DOMAIN_INFO.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// target_scope: Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000025\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
// )
@ -1436,11 +1465,11 @@ lazy_static! {
name: "idm_acp_system_config_priv",
uuid: UUID_IDM_ACP_SYSTEM_CONFIG_PRIV_V1,
description: "Builtin IDM Control for granting system configuration rights",
receiver_group: UUID_SYSTEM_ADMINS,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group ( vec![UUID_SYSTEM_ADMINS] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(Attribute::Uuid.to_string(),STR_UUID_SYSTEM_CONFIG.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000027\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
// )
@ -1468,11 +1497,11 @@ lazy_static! {
name: "idm_acp_system_config_session_exp_priv",
uuid: UUID_IDM_ACP_SYSTEM_CONFIG_SESSION_EXP_PRIV_V1,
description: "Builtin IDM Control for granting session expiry configuration rights",
receiver_group: UUID_SYSTEM_ADMINS,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group ( vec![UUID_SYSTEM_ADMINS] ),
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
ProtoFilter::Eq(Attribute::Uuid.to_string(),STR_UUID_SYSTEM_CONFIG.to_string()),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000027\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
@ -1510,12 +1539,12 @@ lazy_static! {
name: "idm_acp_account_unix_extend_priv",
uuid: UUID_IDM_ACP_ACCOUNT_UNIX_EXTEND_PRIV_V1,
description: "Builtin IDM Control for managing and extending unix accounts",
receiver_group: UUID_IDM_ACCOUNT_UNIX_EXTEND_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_ACCOUNT_UNIX_EXTEND_PRIV] ),
// account not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [
@ -1561,12 +1590,12 @@ lazy_static! {
name: "idm_acp_group_unix_extend_priv",
uuid: UUID_IDM_ACP_GROUP_UNIX_EXTEND_PRIV_V1,
description: "Builtin IDM Control for managing and extending unix groups",
receiver_group: UUID_IDM_GROUP_UNIX_EXTEND_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_GROUP_UNIX_EXTEND_PRIV] ),
// group not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"group\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
@ -1604,13 +1633,13 @@ lazy_static! {
name: "idm_acp_hp_account_unix_extend_priv",
uuid: UUID_IDM_HP_ACP_ACCOUNT_UNIX_EXTEND_PRIV_V1,
description: "Builtin IDM Control for managing and extending unix accounts",
receiver_group: UUID_IDM_HP_ACCOUNT_UNIX_EXTEND_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_ACCOUNT_UNIX_EXTEND_PRIV] ),
// account not in HP, Recycled, Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_HP.clone(),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
// Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
@ -1655,13 +1684,13 @@ lazy_static! {
name: "idm_acp_hp_group_unix_extend_priv",
uuid: UUID_IDM_HP_ACP_GROUP_UNIX_EXTEND_PRIV_V1,
description: "Builtin IDM Control for managing and extending unix high privilege groups",
receiver_group: UUID_IDM_HP_GROUP_UNIX_EXTEND_PRIV,
receiver: BuiltinAcpReceiver::Group ( vec![UUID_IDM_HP_GROUP_UNIX_EXTEND_PRIV] ),
// HP group, not Recycled/Tombstone
target_scope: ProtoFilter::And(vec![
target: BuiltinAcpTarget::Filter( ProtoFilter::And(vec![
match_class_filter!(EntryClass::Group),
FILTER_HP.clone(),
ProtoFilter::AndNot(Box::new(FILTER_RECYCLED_OR_TOMBSTONE.clone())),
]),
])),
// target_scope: Value::new_json_filter_s(
// "{\"and\": [{\"eq\": [\"class\",\"group\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]},
@ -1707,11 +1736,11 @@ lazy_static! {
name: "idm_acp_hp_oauth2_manage_priv",
uuid: UUID_IDM_HP_ACP_OAUTH2_MANAGE_PRIV_V1,
description: "Builtin IDM Control for managing oauth2 resource server integrations.",
receiver_group: UUID_IDM_HP_OAUTH2_MANAGE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_HP_OAUTH2_MANAGE_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::OAuth2ResourceServer),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Description,
@ -1792,11 +1821,13 @@ lazy_static! {
name: "idm_hp_acp_service_account_into_person_migrate",
uuid: UUID_IDM_HP_ACP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_V1,
description: "Builtin IDM Control allowing service accounts to be migrated into persons",
receiver_group: UUID_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![
UUID_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV
]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
match_class_filter!(EntryClass::Account),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
]),
])),
search_attrs: vec![
Attribute::Class,
@ -1825,8 +1856,8 @@ lazy_static! {
name: "idm_acp_hp_sync_account_manage_priv",
uuid: UUID_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1,
description: "Builtin IDM Control for managing IDM synchronisation accounts / connections",
receiver_group: UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV,
target_scope: ProtoFilter::And(vec![
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
ProtoFilter::Eq(
Attribute::Class.to_string(),
EntryClass::SyncAccount.to_string()
@ -1841,7 +1872,7 @@ lazy_static! {
EntryClass::Tombstone.to_string()
),
]))),
]),
])),
search_attrs: vec![
Attribute::Class,
Attribute::Uuid,

View file

@ -83,6 +83,7 @@ pub enum Attribute {
EmailAlternative,
EmailPrimary,
EntryDn,
EntryManagedBy,
EntryUuid,
Es256PrivateKeyDer,
Excludes,
@ -266,6 +267,7 @@ impl TryFrom<String> for Attribute {
ATTR_EMAIL_ALTERNATIVE => Attribute::EmailAlternative,
ATTR_EMAIL_PRIMARY => Attribute::EmailPrimary,
ATTR_ENTRYDN => Attribute::EntryDn,
ATTR_ENTRY_MANAGED_BY => Attribute::EntryManagedBy,
ATTR_ENTRYUUID => Attribute::EntryUuid,
ATTR_ES256_PRIVATE_KEY_DER => Attribute::Es256PrivateKeyDer,
ATTR_EXCLUDES => Attribute::Excludes,
@ -425,6 +427,7 @@ impl From<Attribute> for &'static str {
Attribute::EmailAlternative => ATTR_EMAIL_ALTERNATIVE,
Attribute::EmailPrimary => ATTR_EMAIL_PRIMARY,
Attribute::EntryDn => ATTR_ENTRYDN,
Attribute::EntryManagedBy => ATTR_ENTRY_MANAGED_BY,
Attribute::EntryUuid => ATTR_ENTRYUUID,
Attribute::Es256PrivateKeyDer => ATTR_ES256_PRIVATE_KEY_DER,
Attribute::Excludes => ATTR_EXCLUDES,
@ -570,7 +573,10 @@ pub enum EntryClass {
AccessControlDelete,
AccessControlModify,
AccessControlProfile,
AccessControlReceiverEntryManager,
AccessControlReceiverGroup,
AccessControlSearch,
AccessControlTargetScope,
Account,
AccountPolicy,
AttributeType,
@ -611,7 +617,12 @@ impl From<EntryClass> for &'static str {
EntryClass::AccessControlDelete => "access_control_delete",
EntryClass::AccessControlModify => "access_control_modify",
EntryClass::AccessControlProfile => "access_control_profile",
EntryClass::AccessControlReceiverEntryManager => {
"access_control_receiver_entry_manager"
}
EntryClass::AccessControlReceiverGroup => "access_control_receiver_group",
EntryClass::AccessControlSearch => "access_control_search",
EntryClass::AccessControlTargetScope => "access_control_target_scope",
EntryClass::Account => "account",
EntryClass::AccountPolicy => "account_policy",
EntryClass::AttributeType => "attributetype",

View file

@ -254,6 +254,13 @@ pub const UUID_SCHEMA_ATTR_UID: Uuid = uuid!("00000000-0000-0000-0000-ffff000001
pub const UUID_SCHEMA_ATTR_GECOS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000151");
pub const UUID_SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000152");
pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000153");
pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000154");
pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000155");
pub const UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000156");
// System and domain infos
// I'd like to strongly criticise william of the past for making poor choices about these allocations.

View file

@ -270,6 +270,14 @@ mod tests {
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value()
),
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),

View file

@ -294,6 +294,14 @@ mod tests {
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value()
),
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),

View file

@ -1208,4 +1208,49 @@ mod tests {
assert!(server_txn.commit().is_ok());
}
#[qs_test]
async fn test_entry_managed_by_references(server: &QueryServer) {
let curtime = duration_from_epoch_now();
let mut server_txn = server.write(curtime).await;
let manages_uuid = Uuid::new_v4();
let e_manages: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("entry_manages")),
(Attribute::Uuid, Value::Uuid(manages_uuid))
);
let group_uuid = Uuid::new_v4();
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("entry_managed_by")),
(Attribute::Uuid, Value::Uuid(group_uuid)),
(Attribute::EntryManagedBy, Value::Refer(manages_uuid))
);
let ce = CreateEvent::new_internal(vec![e_manages, e_group]);
assert!(server_txn.create(&ce).is_ok());
let group = server_txn
.internal_search_uuid(group_uuid)
.expect("Failed to access group");
let entry_managed_by = group
.get_ava_single_refer(Attribute::EntryManagedBy)
.expect("No entry managed by");
assert_eq!(entry_managed_by, manages_uuid);
// It's valid to delete this, since entryManagedBy is may not must.
assert!(server_txn.internal_delete_uuid(manages_uuid).is_ok());
let group = server_txn
.internal_search_uuid(group_uuid)
.expect("Failed to access group");
assert!(group.get_ava_refer(Attribute::EntryManagedBy).is_none());
assert!(server_txn.commit().is_ok());
}
}

View file

@ -1248,7 +1248,7 @@ impl<'a> SchemaWriteTransaction<'a> {
description: String::from(
"The group that receives this access control to allow access",
),
multivalue: false,
multivalue: true,
unique: false,
phantom: false,
sync_allowed: false,
@ -1264,7 +1264,7 @@ impl<'a> SchemaWriteTransaction<'a> {
name: Attribute::AcpTargetScope.into(),
uuid: UUID_SCHEMA_ATTR_ACP_TARGETSCOPE,
description: String::from(
"The effective targets of the ACP, IE what will be acted upon.",
"The effective targets of the ACP, e.g. what will be acted upon.",
),
multivalue: false,
unique: false,
@ -1374,6 +1374,23 @@ impl<'a> SchemaWriteTransaction<'a> {
syntax: SyntaxType::Utf8StringInsensitive,
},
);
self.attributes.insert(
Attribute::EntryManagedBy.into(),
SchemaAttribute {
name: Attribute::EntryManagedBy.into(),
uuid: UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY,
description: String::from(
"A reference to a group that has access to manage the content of this entry.",
),
multivalue: false,
unique: false,
phantom: false,
sync_allowed: false,
replicated: true,
index: vec![IndexType::Equality],
syntax: SyntaxType::ReferenceUuid,
},
);
// MO/Member
self.attributes.insert(
Attribute::MemberOf.into(),
@ -1886,7 +1903,10 @@ impl<'a> SchemaWriteTransaction<'a> {
name: EntryClass::Object.into(),
uuid: UUID_SCHEMA_CLASS_OBJECT,
description: String::from("A system created class that all objects must contain"),
systemmay: vec![Attribute::Description.into()],
systemmay: vec![
Attribute::Description.into(),
Attribute::EntryManagedBy.into(),
],
systemmust: vec![
Attribute::Class.into(),
Attribute::Uuid.into(),
@ -1966,31 +1986,6 @@ impl<'a> SchemaWriteTransaction<'a> {
},
);
// ACP
self.classes.insert(
EntryClass::AccessControlProfile.into(),
SchemaClass {
name: EntryClass::AccessControlProfile.into(),
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
description: String::from("System Access Control Profile Class"),
systemmay: vec![
Attribute::AcpEnable.into(),
Attribute::Description.into(),
Attribute::AcpReceiver.into(),
],
systemmust: vec![
Attribute::AcpReceiverGroup.into(),
Attribute::AcpTargetScope.into(),
Attribute::Name.into(),
],
systemsupplements: vec![
EntryClass::AccessControlSearch.into(),
EntryClass::AccessControlDelete.into(),
EntryClass::AccessControlModify.into(),
EntryClass::AccessControlCreate.into(),
],
..Default::default()
},
);
self.classes.insert(
EntryClass::AccessControlSearch.into(),
SchemaClass {
@ -2037,6 +2032,60 @@ impl<'a> SchemaWriteTransaction<'a> {
..Default::default()
},
);
self.classes.insert(
EntryClass::AccessControlProfile.into(),
SchemaClass {
name: EntryClass::AccessControlProfile.into(),
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
description: String::from("System Access Control Profile Class"),
systemmay: vec![Attribute::AcpEnable.into(), Attribute::Description.into()],
systemmust: vec![Attribute::Name.into()],
systemsupplements: vec![
EntryClass::AccessControlSearch.into(),
EntryClass::AccessControlDelete.into(),
EntryClass::AccessControlModify.into(),
EntryClass::AccessControlCreate.into(),
],
..Default::default()
},
);
self.classes.insert(
EntryClass::AccessControlReceiverEntryManager.into(),
SchemaClass {
name: EntryClass::AccessControlReceiverEntryManager.into(),
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER,
description: String::from("System Access Control Profile Receiver - Entry Manager"),
systemexcludes: vec![EntryClass::AccessControlReceiverGroup.into()],
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
..Default::default()
},
);
self.classes.insert(
EntryClass::AccessControlReceiverGroup.into(),
SchemaClass {
name: EntryClass::AccessControlReceiverGroup.into(),
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP,
description: String::from("System Access Control Profile Receiver - Group"),
systemmay: vec![Attribute::AcpReceiver.into()],
systemmust: vec![Attribute::AcpReceiverGroup.into()],
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
systemexcludes: vec![EntryClass::AccessControlReceiverEntryManager.into()],
..Default::default()
},
);
self.classes.insert(
EntryClass::AccessControlTargetScope.into(),
SchemaClass {
name: EntryClass::AccessControlTargetScope.into(),
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE,
description: String::from("System Access Control Profile Target - Scope"),
systemmust: vec![Attribute::AcpTargetScope.into()],
systemsupplements: vec![EntryClass::AccessControlProfile.into()],
..Default::default()
},
);
// System attrs
self.classes.insert(
EntryClass::System.into(),
SchemaClass {

View file

@ -1,5 +1,6 @@
use super::profiles::AccessControlCreate;
use crate::filter::FilterValidResolved;
use super::profiles::{
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
};
use crate::prelude::*;
use std::collections::BTreeSet;
@ -16,7 +17,7 @@ enum IResult {
pub(super) fn apply_create_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlCreate, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlCreateResolved],
entry: &'a Entry<EntryInit, EntryNew>,
) -> CreateResult {
let mut denied = false;
@ -48,7 +49,7 @@ pub(super) fn apply_create_access<'a>(
fn create_filter_entry<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlCreate, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlCreateResolved],
entry: &'a Entry<EntryInit, EntryNew>,
) -> IResult {
match &ident.origin {
@ -106,37 +107,53 @@ fn create_filter_entry<'a>(
// 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: {:?} !⊆ allowed: {:?}", create_attrs, allowed_attrs);
let allow = related_acp.iter().any(|accr| {
// Assert that the receiver condition applies.
match &accr.receiver_condition {
AccessControlReceiverCondition::GroupChecked => {
// The groups were already checked during filter resolution. Trust
// that result, and continue.
}
AccessControlReceiverCondition::EntryManager => {
// Currently, this is unsatisfiable for creates.
return false;
}
if !create_classes.is_subset(&allowed_classes) {
security_error!("create_classes is not a subset of allowed");
security_error!(
"create: {:?} !⊆ allowed: {:?}",
create_classes,
allowed_classes
);
return false;
}
debug!("passed");
};
true
} else {
trace!(?entry, acs = %accr.acp.name, "entry DOES NOT match acs");
// Does not match, fail this rule.
match &accr.target_condition {
AccessControlTargetCondition::Scope(f_res) => {
if !entry.entry_match_no_index(f_res) {
trace!(?entry, acs = %accr.acp.acp.name, "entry DOES NOT match acs");
// Does not match, fail this rule.
return false;
}
}
};
// -- Conditions pass -- now verify the attributes.
security_access!(?entry, acs = ?accr.acp, "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.acp.attrs.iter().map(|s| s.as_str()).collect();
let allowed_classes: BTreeSet<&str> = accr.acp.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: {:?} !⊆ allowed: {:?}", create_attrs, allowed_attrs);
false
} else if !create_classes.is_subset(&allowed_classes) {
security_error!("create_classes is not a subset of allowed");
security_error!(
"create: {:?} !⊆ allowed: {:?}",
create_classes,
allowed_classes
);
false
} else {
// All attribute conditions are now met.
true
}
});

View file

@ -1,5 +1,6 @@
use super::profiles::AccessControlDelete;
use crate::filter::FilterValidResolved;
use super::profiles::{
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
};
use crate::prelude::*;
use std::sync::Arc;
@ -16,7 +17,7 @@ enum IResult {
pub(super) fn apply_delete_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlDelete, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlDeleteResolved],
entry: &'a Arc<EntrySealedCommitted>,
) -> DeleteResult {
let mut denied = false;
@ -47,7 +48,7 @@ pub(super) fn apply_delete_access<'a>(
fn delete_filter_entry<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlDelete, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlDeleteResolved],
entry: &'a Arc<EntrySealedCommitted>,
) -> IResult {
match &ident.origin {
@ -74,26 +75,68 @@ fn delete_filter_entry<'a>(
}
};
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!
debug!("passed");
true
} else {
trace!(
"entry {:?} DOES NOT match acs {}",
entry.get_uuid(),
acd.acp.name
);
// Does not match, fail.
false
} // else
let ident_memberof = ident.get_memberof();
let ident_uuid = ident.get_uuid();
let allow = related_acp.iter().any(|acd| {
// Assert that the receiver condition applies.
match &acd.receiver_condition {
AccessControlReceiverCondition::GroupChecked => {
// The groups were already checked during filter resolution. Trust
// that result, and continue.
}
AccessControlReceiverCondition::EntryManager => {
// This condition relies on the entry we are looking at to have a back-ref
// to our uuid or a group we are in as an entry manager.
// Note, while schema has this as single value, we currently
// fetch it as a multivalue btreeset for future incase we allow
// multiple entry manager by in future.
if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
let group_check = ident_memberof
// Have at least one group allowed.
.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) {
// Not the entry manager
return false;
}
} else {
// Can not satsify.
return false;
}
}
};
match &acd.target_condition {
AccessControlTargetCondition::Scope(f_res) => {
if !entry.entry_match_no_index(f_res) {
trace!(
"entry {:?} DOES NOT match acs {}",
entry.get_uuid(),
acd.acp.acp.name
);
// Does not match, fail.
return false;
}
}
};
security_access!(
entry_uuid = ?entry.get_uuid(),
acs = %acd.acp.acp.name,
"entry matches acs"
);
// It matches, so we can delete this!
trace!("passed");
true
}); // any related_acp
if allow {
IResult::Grant
} else {

View file

@ -32,7 +32,10 @@ use crate::modify::Modify;
use crate::prelude::*;
use self::profiles::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
AccessControlCreate, AccessControlCreateResolved, AccessControlDelete,
AccessControlDeleteResolved, AccessControlModify, AccessControlModifyResolved,
AccessControlReceiver, AccessControlReceiverCondition, AccessControlSearch,
AccessControlSearchResolved, AccessControlTarget, AccessControlTargetCondition,
};
use self::create::{apply_create_access, CreateResult};
@ -103,6 +106,52 @@ pub struct AccessControls {
ARCache<(IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>,
}
fn resolve_access_conditions(
ident: &Identity,
ident_memberof: Option<&BTreeSet<Uuid>>,
receiver: &AccessControlReceiver,
target: &AccessControlTarget,
acp_resolve_filter_cache: &mut ARCacheReadTxn<
'_,
(IdentityId, Filter<FilterValid>),
Filter<FilterValidResolved>,
(),
>,
) -> Option<(AccessControlReceiverCondition, AccessControlTargetCondition)> {
let receiver_condition = match receiver {
AccessControlReceiver::Group(groups) => {
let group_check = ident_memberof
// Have at least one group allowed.
.map(|imo| imo.intersection(groups).next().is_some())
.unwrap_or_default();
if group_check {
AccessControlReceiverCondition::GroupChecked
} else {
// AccessControlReceiverCondition::None
return None;
}
}
AccessControlReceiver::EntryManager => AccessControlReceiverCondition::EntryManager,
AccessControlReceiver::None => return None,
// AccessControlReceiverCondition::None,
};
let target_condition = match &target {
AccessControlTarget::Scope(filter) => filter
.resolve(ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(?e, "A internal filter/event was passed for resolution!?!?");
e
})
.ok()
.map(AccessControlTargetCondition::Scope)?,
AccessControlTarget::None => return None,
};
Some((receiver_condition, target_condition))
}
pub trait AccessControlsTransaction<'a> {
fn get_search(&self) -> &Vec<AccessControlSearch>;
fn get_create(&self) -> &Vec<AccessControlCreate>;
@ -116,10 +165,7 @@ pub trait AccessControlsTransaction<'a> {
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>;
#[instrument(level = "debug", name = "access::search_related_acp", skip_all)]
fn search_related_acp<'b>(
&'b self,
ident: &Identity,
) -> Vec<(&'b AccessControlSearch, Filter<FilterValidResolved>)> {
fn search_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlSearchResolved<'b>> {
let search_state = self.get_search();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
@ -149,7 +195,11 @@ pub trait AccessControlsTransaction<'a> {
} else {
*/
// else, we calculate this, and then stash/cache the uuids.
let related_acp: Vec<(&AccessControlSearch, Filter<FilterValidResolved>)> = search_state
let ident_memberof = ident.get_memberof();
// let related_acp: Vec<(&AccessControlSearch, Filter<FilterValidResolved>)> =
let related_acp: Vec<AccessControlSearchResolved<'b>> = search_state
.iter()
.filter_map(|acs| {
// Now resolve the receiver filter
@ -167,28 +217,19 @@ pub trait AccessControlsTransaction<'a> {
// A possible solution is to change the filter resolve function
// such that it takes an entry, rather than an event, but that
// would create issues in search.
if let Some(receiver) = acs.acp.receiver {
if ident.is_memberof(receiver) {
// Now, for each of the acp's that apply to our receiver, resolve their
// related target filters.
acs.acp
.targetscope
.resolve(ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(
?e,
"A internal filter/event was passed for resolution!?!?"
);
e
})
.ok()
.map(|f_res| (acs, f_res))
} else {
None
}
} else {
None
}
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlSearchResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
@ -210,7 +251,7 @@ pub trait AccessControlsTransaction<'a> {
let requested_attrs: BTreeSet<&str> = se.filter_orig.get_attr_set();
// First get the set of acps that apply to this receiver
let related_acp: Vec<(&AccessControlSearch, _)> = self.search_related_acp(&se.ident);
let related_acp = self.search_related_acp(&se.ident);
// For each entry.
let entries_is_empty = entries.is_empty();
@ -264,13 +305,13 @@ pub trait AccessControlsTransaction<'a> {
.map(|vs| vs.iter().map(|s| s.as_str()).collect());
// Get the relevant acps for this receiver.
let related_acp: Vec<(&AccessControlSearch, _)> = self.search_related_acp(&se.ident);
let related_acp: Vec<(&AccessControlSearch, _)> = if let Some(r_attrs) = se.attrs.as_ref() {
let related_acp = self.search_related_acp(&se.ident);
let related_acp: Vec<_> = if let Some(r_attrs) = se.attrs.as_ref() {
// If the acp doesn't overlap with our requested attrs, there is no point in
// testing it!
related_acp
.into_iter()
.filter(|(acs, _)| !acs.attrs.is_disjoint(r_attrs))
.filter(|acs| !acs.acp.attrs.is_disjoint(r_attrs))
.collect()
} else {
related_acp
@ -329,39 +370,31 @@ pub trait AccessControlsTransaction<'a> {
}
#[instrument(level = "debug", name = "access::modify_related_acp", skip_all)]
fn modify_related_acp<'b>(
&'b self,
ident: &Identity,
) -> Vec<(&'b AccessControlModify, Filter<FilterValidResolved>)> {
fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
// Some useful references we'll use for the remainder of the operation
let modify_state = self.get_modify();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ident.get_memberof();
// Find the acps that relate to the caller, and compile their related
// target filters.
let related_acp: Vec<(&AccessControlModify, _)> = modify_state
let related_acp: Vec<_> = modify_state
.iter()
.filter_map(|acs| {
if let Some(receiver) = acs.acp.receiver {
if ident.is_memberof(receiver) {
acs.acp
.targetscope
.resolve(ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(
"A internal filter/event was passed for resolution!?!? {:?}",
e
);
e
})
.ok()
.map(|f_res| (acs, f_res))
} else {
None
}
} else {
None
}
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlModifyResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
@ -387,7 +420,7 @@ pub trait AccessControlsTransaction<'a> {
// Find the acps that relate to the caller, and compile their related
// target filters.
let related_acp: Vec<(&AccessControlModify, _)> = self.modify_related_acp(&me.ident);
let related_acp: Vec<_> = self.modify_related_acp(&me.ident);
// build two sets of "requested pres" and "requested rem"
let requested_pres: BTreeSet<&str> = me
@ -503,7 +536,7 @@ pub trait AccessControlsTransaction<'a> {
) -> Result<bool, OperationError> {
// Find the acps that relate to the caller, and compile their related
// target filters.
let related_acp: Vec<(&AccessControlModify, _)> = self.modify_related_acp(&me.ident);
let related_acp = self.modify_related_acp(&me.ident);
let r = entries.iter().all(|e| {
// Due to how batch mod works, we have to check the modlist *per entry* rather
@ -634,30 +667,25 @@ pub trait AccessControlsTransaction<'a> {
let create_state = self.get_create();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ce.ident.get_memberof();
// Find the acps that relate to the caller.
let related_acp: Vec<(&AccessControlCreate, _)> = create_state
let related_acp: Vec<_> = create_state
.iter()
.filter_map(|acs| {
if let Some(receiver) = acs.acp.receiver {
if ce.ident.is_memberof(receiver) {
acs.acp
.targetscope
.resolve(&ce.ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(
"A internal filter/event was passed for resolution!?!? {:?}",
e
);
e
})
.ok()
.map(|f_res| (acs, f_res))
} else {
None
}
} else {
None
}
let (receiver_condition, target_condition) = resolve_access_conditions(
&ce.ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlCreateResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
@ -679,37 +707,29 @@ pub trait AccessControlsTransaction<'a> {
}
#[instrument(level = "debug", name = "access::delete_related_acp", skip_all)]
fn delete_related_acp<'b>(
&'b self,
ident: &Identity,
) -> Vec<(&'b AccessControlDelete, Filter<FilterValidResolved>)> {
fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
// Some useful references we'll use for the remainder of the operation
let delete_state = self.get_delete();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let related_acp: Vec<(&AccessControlDelete, _)> = delete_state
let ident_memberof = ident.get_memberof();
let related_acp: Vec<_> = delete_state
.iter()
.filter_map(|acs| {
if let Some(receiver) = acs.acp.receiver {
if ident.is_memberof(receiver) {
acs.acp
.targetscope
.resolve(ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(
"A internal filter/event was passed for resolution!?!? {:?}",
e
);
e
})
.ok()
.map(|f_res| (acs, f_res))
} else {
None
}
} else {
None
}
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlDeleteResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
@ -775,16 +795,17 @@ pub trait AccessControlsTransaction<'a> {
// == search ==
// Get the relevant acps for this receiver.
let search_related_acp: Vec<(&AccessControlSearch, _)> = self.search_related_acp(ident);
let search_related_acp: Vec<(&AccessControlSearch, _)> =
if let Some(r_attrs) = attrs.as_ref() {
search_related_acp
.into_iter()
.filter(|(acs, _)| !acs.attrs.is_disjoint(r_attrs))
.collect()
} else {
search_related_acp
};
let search_related_acp = self.search_related_acp(ident);
// Trim any search rule that doesn't provide attributes related to the request.
let search_related_acp = if let Some(r_attrs) = attrs.as_ref() {
search_related_acp
.into_iter()
.filter(|acs| !acs.acp.attrs.is_disjoint(r_attrs))
.collect()
} else {
// None here means all attrs requested.
search_related_acp
};
// == modify ==
@ -824,18 +845,12 @@ pub trait AccessControlsTransaction<'a> {
};
// == delete ==
let delete = delete_related_acp.iter().any(|(acd, f_res)| {
if e.entry_match_no_index(f_res) {
security_access!(
entry_uuid = ?e.get_uuid(),
acs = %acd.acp.name,
"entry matches acd"
);
true
} else {
false
}
});
let delete_status = apply_delete_access(ident, delete_related_acp.as_slice(), e);
let delete = match delete_status {
DeleteResult::Denied => false,
DeleteResult::Grant => true,
};
AccessEffectivePermission {
target: e.get_uuid(),
@ -1065,7 +1080,7 @@ mod tests {
use super::{
profiles::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
AccessControlSearch,
AccessControlSearch, AccessControlTarget,
},
Access, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
};
@ -1107,6 +1122,7 @@ mod tests {
let ev1 = e1.into_sealed_committed();
let r1 = <$type>::try_from($qs, &ev1);
error!(?r1);
assert!(r1.is_err());
}};
}
@ -1153,7 +1169,7 @@ mod tests {
&mut qs_write,
r#"{
"attrs": {
"class": ["object", "access_control_profile"],
"class": ["object", "access_control_profile", "access_control_receiver_g", "access_control_target_scope"],
"name": ["acp_invalid"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
}
@ -1165,7 +1181,7 @@ mod tests {
&mut qs_write,
r#"{
"attrs": {
"class": ["object", "access_control_profile"],
"class": ["object", "access_control_profile", "access_control_receiver_g", "access_control_target_scope"],
"name": ["acp_invalid"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"acp_receiver_group": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
@ -2915,4 +2931,200 @@ mod tests {
test_acp_search!(&se_b, vec![], r_set, ex_b);
}
#[test]
fn test_access_entry_managed_by_search() {
sketching::test_init();
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry.clone()];
let se_a = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let expect_a = vec![test_entry];
let se_b = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let expect_b = vec![];
let acp = AccessControlSearch::from_managed_by(
"test_acp",
Uuid::new_v4(),
// Allow admin to read only testperson1
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
// In that read, admin may only view the "name" attribute, or query on
// the name attribute. Any other query (should be) rejected.
Attribute::Name.as_ref(),
);
// Check where allowed
test_acp_search!(&se_a, vec![acp.clone()], data_set.clone(), expect_a);
// And where not
test_acp_search!(&se_b, vec![acp], data_set, expect_b);
}
#[test]
fn test_access_entry_managed_by_create() {
sketching::test_init();
let test_entry = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
);
let data_set = vec![test_entry];
let ce = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
vec![],
);
let acp = AccessControlCreate::from_managed_by(
"test_create",
Uuid::new_v4(),
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
// classes
EntryClass::Account.into(),
// attrs
"class name uuid",
);
// Test reject create (not allowed attr). This is because entry
// managed by is non-sensical with creates!
test_acp_create!(&ce, vec![acp.clone()], &data_set, false);
}
#[test]
fn test_access_entry_managed_by_modify() {
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry];
// Name present
let me_pres = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
// Name rem
let me_rem = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
);
// Name purge
let me_purge = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::Name)]),
);
let acp_allow = AccessControlModify::from_managed_by(
"test_modify_allow",
Uuid::new_v4(),
// To modify testperson
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
// Allow pres name and class
"name class",
// Allow rem name and class
"name class",
// And the class allowed is account
EntryClass::Account.into(),
);
// Test allowed pres
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &data_set, true);
// test allowed rem
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &data_set, true);
// test allowed purge
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &data_set, true);
}
#[test]
fn test_access_entry_managed_by_delete() {
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry];
let de_a = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let de_b = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_managed_by(
"test_delete",
Uuid::new_v4(),
// To delete testperson
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
);
// Test allowed to delete
test_acp_delete!(&de_a, vec![acp.clone()], &data_set, true);
// Test reject delete
test_acp_delete!(&de_b, vec![acp], &data_set, false);
}
}

View file

@ -2,9 +2,11 @@ use crate::prelude::*;
use hashbrown::HashMap;
use std::collections::BTreeSet;
use super::profiles::AccessControlModify;
use super::profiles::{
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
AccessControlTargetCondition,
};
use super::AccessResult;
use crate::filter::FilterValidResolved;
use std::sync::Arc;
pub(super) enum ModifyResult<'a> {
@ -19,7 +21,7 @@ pub(super) enum ModifyResult<'a> {
pub(super) fn apply_modify_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlModify, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlModifyResolved],
sync_agreements: &'a HashMap<Uuid, BTreeSet<String>>,
entry: &'a Arc<EntrySealedCommitted>,
) -> ModifyResult<'a> {
@ -32,6 +34,11 @@ pub(super) fn apply_modify_access<'a>(
let mut constrain_cls = BTreeSet::default();
let mut allow_cls = BTreeSet::default();
// Some useful references.
// - needed for checking entry manager conditions.
let ident_memberof = ident.get_memberof();
let ident_uuid = ident.get_uuid();
// run each module. These have to be broken down further due to modify
// kind of being three operations all in one.
@ -63,12 +70,51 @@ pub(super) fn apply_modify_access<'a>(
// 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
}
.filter_map(|acm| {
match &acm.receiver_condition {
AccessControlReceiverCondition::GroupChecked => {
// The groups were already checked during filter resolution. Trust
// that result, and continue.
}
AccessControlReceiverCondition::EntryManager => {
// This condition relies on the entry we are looking at to have a back-ref
// to our uuid or a group we are in as an entry manager.
// Note, while schema has this as single value, we currently
// fetch it as a multivalue btreeset for future incase we allow
// multiple entry manager by in future.
if let Some(entry_manager_uuids) =
entry.get_ava_refer(Attribute::EntryManagedBy)
{
let group_check = ident_memberof
// Have at least one group allowed.
.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) {
// Not the entry manager
return None;
}
} else {
// Can not satsify.
return None;
}
}
};
match &acm.target_condition {
AccessControlTargetCondition::Scope(f_res) => {
if !entry.entry_match_no_index(f_res) {
return None;
}
}
};
Some(acm.acp)
})
.collect();

View file

@ -1,7 +1,7 @@
use crate::prelude::*;
use std::collections::BTreeSet;
use crate::filter::{Filter, FilterValid};
use crate::filter::{Filter, FilterValid, FilterValidResolved};
use kanidm_proto::v1::Filter as ProtoFilter;
@ -9,6 +9,13 @@ use kanidm_proto::v1::Filter as ProtoFilter;
// PARSE ENTRY TO ACP, AND ACP MANAGEMENT
// =========================================================================
#[derive(Debug, Clone)]
pub struct AccessControlSearchResolved<'a> {
pub acp: &'a AccessControlSearch,
pub receiver_condition: AccessControlReceiverCondition,
pub target_condition: AccessControlTargetCondition,
}
#[derive(Debug, Clone)]
pub struct AccessControlSearch {
pub acp: AccessControlProfile,
@ -56,12 +63,39 @@ impl AccessControlSearch {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
target: AccessControlTarget::Scope(targetscope),
},
attrs: attrs.split_whitespace().map(AttrString::from).collect(),
}
}
/// ⚠️ - Manually create a search access profile from values.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub(super) fn from_managed_by(
name: &str,
uuid: Uuid,
target: AccessControlTarget,
attrs: &str,
) -> Self {
AccessControlSearch {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: AccessControlReceiver::EntryManager,
target,
},
attrs: attrs.split_whitespace().map(AttrString::from).collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlDeleteResolved<'a> {
pub acp: &'a AccessControlDelete,
pub receiver_condition: AccessControlReceiverCondition,
pub target_condition: AccessControlTargetCondition,
}
#[derive(Debug, Clone)]
@ -99,11 +133,32 @@ impl AccessControlDelete {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
target: AccessControlTarget::Scope(targetscope),
},
}
}
/// ⚠️ - Manually create a delete access profile from values.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub(super) fn from_managed_by(name: &str, uuid: Uuid, target: AccessControlTarget) -> Self {
AccessControlDelete {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: AccessControlReceiver::EntryManager,
target,
},
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlCreateResolved<'a> {
pub acp: &'a AccessControlCreate,
pub receiver_condition: AccessControlReceiverCondition,
pub target_condition: AccessControlTargetCondition,
}
#[derive(Debug, Clone)]
@ -158,13 +213,42 @@ impl AccessControlCreate {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
target: AccessControlTarget::Scope(targetscope),
},
classes: classes.split_whitespace().map(AttrString::from).collect(),
attrs: attrs.split_whitespace().map(AttrString::from).collect(),
}
}
/// ⚠️ - Manually create a create access profile from values.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub(super) fn from_managed_by(
name: &str,
uuid: Uuid,
target: AccessControlTarget,
classes: &str,
attrs: &str,
) -> Self {
AccessControlCreate {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: AccessControlReceiver::EntryManager,
target,
},
classes: classes.split_whitespace().map(AttrString::from).collect(),
attrs: attrs.split_whitespace().map(AttrString::from).collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct AccessControlModifyResolved<'a> {
pub acp: &'a AccessControlModify,
pub receiver_condition: AccessControlReceiverCondition,
pub target_condition: AccessControlTargetCondition,
}
#[derive(Debug, Clone)]
@ -226,8 +310,32 @@ impl AccessControlModify {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: Some(receiver),
targetscope,
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
target: AccessControlTarget::Scope(targetscope),
},
classes: classes.split_whitespace().map(AttrString::from).collect(),
presattrs: presattrs.split_whitespace().map(AttrString::from).collect(),
remattrs: remattrs.split_whitespace().map(AttrString::from).collect(),
}
}
/// ⚠️ - Manually create a modify access profile from values.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub(super) fn from_managed_by(
name: &str,
uuid: Uuid,
target: AccessControlTarget,
presattrs: &str,
remattrs: &str,
classes: &str,
) -> Self {
AccessControlModify {
acp: AccessControlProfile {
name: name.to_string(),
uuid,
receiver: AccessControlReceiver::EntryManager,
target,
},
classes: classes.split_whitespace().map(AttrString::from).collect(),
presattrs: presattrs.split_whitespace().map(AttrString::from).collect(),
@ -236,6 +344,48 @@ impl AccessControlModify {
}
}
#[derive(Debug, Clone)]
pub enum AccessControlReceiver {
None,
Group(BTreeSet<Uuid>),
EntryManager,
}
#[derive(Debug, Clone)]
pub enum AccessControlReceiverCondition {
// None,
GroupChecked,
EntryManager,
}
/*
impl AccessControlReceiverCondition {
pub(crate) fn is_none(&self) {
matches!(self, AccessControlReceiverCondition::None)
}
}
*/
#[derive(Debug, Clone)]
pub enum AccessControlTarget {
None,
Scope(Filter<FilterValid>),
}
#[derive(Debug, Clone)]
pub enum AccessControlTargetCondition {
// None,
Scope(Filter<FilterValidResolved>),
}
/*
impl AccessControlTargetCondition {
pub(crate) fn is_none(&self) {
matches!(&self, AccessControlTargetCondition::None)
}
}
*/
#[derive(Debug, Clone)]
pub struct AccessControlProfile {
pub name: String,
@ -243,23 +393,8 @@ pub struct AccessControlProfile {
// 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>,
pub receiver: AccessControlReceiver,
pub target: AccessControlTarget,
}
impl AccessControlProfile {
@ -269,7 +404,7 @@ impl AccessControlProfile {
) -> Result<Self, OperationError> {
// Assert we have class access_control_profile
if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) {
admin_error!("class access_control_profile not present.");
error!("class access_control_profile not present.");
return Err(OperationError::InvalidAcpState(
"Missing access_control_profile".to_string(),
));
@ -279,51 +414,85 @@ impl AccessControlProfile {
let name = value
.get_ava_single_iname(Attribute::Name)
.ok_or_else(|| {
admin_error!("Missing {}", Attribute::Name);
error!("Missing {}", Attribute::Name);
OperationError::InvalidAcpState(format!("Missing {}", Attribute::Name))
})?
.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(Attribute::AcpReceiverGroup);
/*
.ok_or_else(|| {
admin_error!("Missing acp_receiver_group");
OperationError::InvalidAcpState("Missing acp_receiver_group".to_string())
})?;
*/
let receiver = if value.attribute_equality(
Attribute::Class,
&EntryClass::AccessControlReceiverGroup.into(),
) {
value
.get_ava_refer(Attribute::AcpReceiverGroup)
.cloned()
.map(AccessControlReceiver::Group)
.ok_or_else(|| {
admin_error!("Missing {}", Attribute::AcpReceiverGroup);
OperationError::InvalidAcpState(format!(
"Missing {}",
Attribute::AcpReceiverGroup
))
})?
} else if value.attribute_equality(
Attribute::Class,
&EntryClass::AccessControlReceiverEntryManager.into(),
) {
AccessControlReceiver::EntryManager
} else {
warn!(
?name,
"access control has no defined receivers - this will do nothing!"
);
AccessControlReceiver::None
};
// targetscope, and turn to real filter
let targetscope_f: ProtoFilter = value
.get_ava_single_protofilter(Attribute::AcpTargetScope)
// .map(|pf| pf.clone())
.cloned()
.ok_or_else(|| {
admin_error!("Missing {}", Attribute::AcpTargetScope);
OperationError::InvalidAcpState(format!("Missing {}", Attribute::AcpTargetScope))
let target = if value.attribute_equality(
Attribute::Class,
&EntryClass::AccessControlTargetScope.into(),
) {
// targetscope, and turn to real filter
let targetscope_f: ProtoFilter = value
.get_ava_single_protofilter(Attribute::AcpTargetScope)
// .map(|pf| pf.clone())
.cloned()
.ok_or_else(|| {
admin_error!("Missing {}", Attribute::AcpTargetScope);
OperationError::InvalidAcpState(format!(
"Missing {}",
Attribute::AcpTargetScope
))
})?;
let ident = Identity::from_internal();
let targetscope_i = Filter::from_rw(&ident, &targetscope_f, qs).map_err(|e| {
admin_error!("{} validation failed {:?}", Attribute::AcpTargetScope, e);
e
})?;
let ident = Identity::from_internal();
let targetscope_i = Filter::from_rw(&ident, &targetscope_f, qs).map_err(|e| {
admin_error!("{} validation failed {:?}", Attribute::AcpTargetScope, e);
e
})?;
let targetscope = targetscope_i.validate(qs.get_schema()).map_err(|e| {
admin_error!("{} Schema Violation {:?}", Attribute::AcpTargetScope, e);
OperationError::SchemaViolation(e)
})?;
targetscope_i
.validate(qs.get_schema())
.map_err(|e| {
admin_error!("{} Schema Violation {:?}", Attribute::AcpTargetScope, e);
OperationError::SchemaViolation(e)
})
.map(AccessControlTarget::Scope)?
} else {
warn!(
?name,
"access control has no defined targets - this will do nothing!"
);
AccessControlTarget::None
};
Ok(AccessControlProfile {
name,
uuid,
receiver,
targetscope,
target,
})
}
}

View file

@ -1,9 +1,10 @@
use crate::prelude::*;
use std::collections::BTreeSet;
use super::profiles::AccessControlSearch;
use super::profiles::{
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
};
use super::AccessResult;
use crate::filter::FilterValidResolved;
use std::sync::Arc;
pub(super) enum SearchResult<'a> {
@ -14,7 +15,7 @@ pub(super) enum SearchResult<'a> {
pub(super) fn apply_search_access<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlSearch, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlSearchResolved],
entry: &'a Arc<EntrySealedCommitted>,
) -> SearchResult<'a> {
// This could be considered "slow" due to allocs each iter with the entry. We
@ -71,7 +72,7 @@ pub(super) fn apply_search_access<'a>(
fn search_filter_entry<'a>(
ident: &Identity,
related_acp: &'a [(&AccessControlSearch, Filter<FilterValidResolved>)],
related_acp: &'a [AccessControlSearchResolved],
entry: &'a Arc<EntrySealedCommitted>,
) -> AccessResult<'a> {
// If this is an internal search, return our working set.
@ -99,19 +100,62 @@ fn search_filter_entry<'a>(
}
};
// needed for checking entry manager conditions.
let ident_memberof = ident.get_memberof();
let ident_uuid = ident.get_uuid();
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_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.name, "acs applied to entry");
// add search_attrs to allowed.
Some(acs.attrs.iter().map(|s| s.as_str()))
} else {
// should this be `security_access`?
security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.name, "entry DOES NOT match acs");
None
}
.filter_map(|acs| {
// Assert that the receiver condition applies.
match &acs.receiver_condition {
AccessControlReceiverCondition::GroupChecked => {
// The groups were already checked during filter resolution. Trust
// that result, and continue.
}
AccessControlReceiverCondition::EntryManager => {
// This condition relies on the entry we are looking at to have a back-ref
// to our uuid or a group we are in as an entry manager.
// Note, while schema has this as single value, we currently
// fetch it as a multivalue btreeset for future incase we allow
// multiple entry manager by in future.
if let Some(entry_manager_uuids) = entry.get_ava_refer(Attribute::EntryManagedBy) {
let group_check = ident_memberof
// Have at least one group allowed.
.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) {
// Not the entry manager
return None
}
} else {
// Can not satsify.
return None
}
}
};
match &acs.target_condition {
AccessControlTargetCondition::Scope(f_res) => {
if !entry.entry_match_no_index(f_res) {
// should this be `security_access`?
security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "entry DOES NOT match acs");
return None
}
}
};
// -- Conditions pass -- release the attributes.
security_debug!(entry = ?entry.get_display_id(), acs = %acs.acp.acp.name, "acs applied to entry");
// add search_attrs to allowed.
Some(acs.acp.attrs.iter().map(|s| s.as_str()))
})
.flatten()
.collect();