mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-22 17:03:55 +02:00
249 2024 managed by syntax (#2359)
Allows hierarchial entry management rules.
This commit is contained in:
parent
340d41482b
commit
854b696532
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue