Add mail support to groups (#2718)

* Add mail support to groups

* Update libs/client/src/group.rs

---------

Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
This commit is contained in:
Firstyear 2024-04-23 17:08:28 +10:00 committed by GitHub
parent 604adccdae
commit afd674d346
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 118 additions and 0 deletions

View file

@ -97,4 +97,22 @@ impl KanidmClient {
)
.await
}
pub async fn idm_group_purge_mail(&self, id: &str) -> Result<(), ClientError> {
self.idm_group_purge_attr(id, "mail").await
}
pub async fn idm_group_set_mail<T: serde::Serialize>(
&self,
id: &str,
values: &[T],
) -> Result<(), ClientError> {
self.perform_put_request(&format!("/v1/group/{}/_attr/mail", id), values)
.await
}
pub async fn idm_group_get_mail(&self, id: &str) -> Result<Option<Vec<String>>, ClientError> {
self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id))
.await
}
}

View file

@ -1330,6 +1330,29 @@ lazy_static! {
};
}
lazy_static! {
pub static ref IDM_ACP_ACCOUNT_MAIL_READ_DL6: BuiltinAcp = BuiltinAcp {
classes: vec![
EntryClass::Object,
EntryClass::AccessControlProfile,
EntryClass::AccessControlSearch
],
name: "idm_acp_account_mail_read",
uuid: UUID_IDM_ACP_ACCOUNT_MAIL_READ_V1,
description: "Builtin IDM Control for reading account and group mail attributes.",
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_ACCOUNT_MAIL_READ]),
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
ProtoFilter::Or(vec![
match_class_filter!(EntryClass::Account),
match_class_filter!(EntryClass::Group),
]),
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
])),
search_attrs: vec![Attribute::Mail],
..Default::default()
};
}
lazy_static! {
pub static ref IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1: BuiltinAcp = BuiltinAcp {
classes: vec![
@ -1499,6 +1522,7 @@ lazy_static! {
Attribute::Spn,
Attribute::Uuid,
Attribute::Description,
Attribute::Mail,
Attribute::Member,
Attribute::DynMember,
Attribute::EntryManagedBy,
@ -1508,6 +1532,7 @@ lazy_static! {
Attribute::Name,
Attribute::Uuid,
Attribute::Description,
Attribute::Mail,
Attribute::Member,
Attribute::EntryManagedBy,
],
@ -1518,11 +1543,13 @@ lazy_static! {
modify_present_attrs: vec![
Attribute::Name,
Attribute::Description,
Attribute::Mail,
Attribute::Member,
],
modify_removed_attrs: vec![
Attribute::Name,
Attribute::Description,
Attribute::Mail,
Attribute::Member,
],
..Default::default()

View file

@ -768,6 +768,24 @@ pub static ref SCHEMA_CLASS_GROUP: SchemaClass = SchemaClass {
..Default::default()
};
pub static ref SCHEMA_CLASS_GROUP_DL6: SchemaClass = SchemaClass {
uuid: UUID_SCHEMA_CLASS_GROUP,
name: EntryClass::Group.into(),
description: "Object representation of a group".to_string(),
sync_allowed: true,
systemmay: vec![
Attribute::Member.into(),
Attribute::GrantUiHint.into(),
Attribute::Description.into(),
Attribute::Mail.into(),
],
systemmust: vec![
Attribute::Name.into(),
Attribute::Spn.into()],
..Default::default()
};
pub static ref SCHEMA_CLASS_DYNGROUP: SchemaClass = SchemaClass {
uuid: UUID_SCHEMA_CLASS_DYNGROUP,
name: EntryClass::DynGroup.into(),

View file

@ -891,6 +891,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
SCHEMA_CLASS_DOMAIN_INFO_DL6.clone().into(),
SCHEMA_CLASS_SERVICE_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
@ -914,6 +915,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL6.clone().into(),
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_GROUP_MANAGE_DL6.clone().into(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
// Update anonymous with the correct entry manager,
BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into(),
// Add the internal key provider.

View file

@ -207,6 +207,32 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
let members = rsclient.idm_group_get_members("demo_group").await.unwrap();
assert!(members.is_none());
// Add a mail attribute.
rsclient
.idm_group_set_mail(
"demo_group",
&["primary@example.com", "secondary@example.com"],
)
.await
.unwrap();
let mail_addrs = rsclient.idm_group_get_mail("demo_group").await.unwrap();
assert_eq!(
mail_addrs,
Some(vec![
"primary@example.com".to_string(),
"secondary@example.com".to_string()
])
);
// Purge the mail addrs.
rsclient.idm_group_purge_mail("demo_group").await.unwrap();
let mail_addrs = rsclient.idm_group_get_mail("demo_group").await.unwrap();
assert_eq!(mail_addrs, None);
// Delete the group
rsclient.idm_group_delete("demo_group").await.unwrap();
let g_list_3 = rsclient.idm_group_list().await.unwrap();

View file

@ -16,6 +16,7 @@ impl GroupOpt {
GroupOpt::RemoveMembers(gcopt) => gcopt.copt.debug,
GroupOpt::SetMembers(gcopt) => gcopt.copt.debug,
GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug,
GroupOpt::SetMail { copt, .. } => copt.debug,
GroupOpt::Posix { commands } => match commands {
GroupPosix::Show(gcopt) => gcopt.copt.debug,
GroupPosix::Set(gcopt) => gcopt.copt.debug,
@ -145,6 +146,22 @@ impl GroupOpt {
Ok(_) => println!("Successfully set members for group {}", gcopt.name.as_str()),
}
}
GroupOpt::SetMail { copt, name, mail } => {
let client = copt.to_client(OpType::Write).await;
let result = if mail.is_empty() {
client.idm_group_purge_mail(name.as_str()).await
} else {
client
.idm_group_set_mail(name.as_str(), mail.as_slice())
.await
};
match result {
Err(e) => handle_client_error(e, copt.output_mode),
Ok(_) => println!("Successfully set mail for group {}", name.as_str()),
}
}
GroupOpt::SetEntryManagedBy {
name,
entry_managed_by,

View file

@ -256,6 +256,16 @@ pub enum GroupOpt {
/// set operation.
#[clap(name = "set-members")]
SetMembers(GroupNamedMembers),
/// Set the exact list of mail addresses that this group is associated with. The first
/// mail address in the list is the `primary` and the remainder are aliases. Setting
/// an empty list will clear the mail attribute.
#[clap(name = "set-mail")]
SetMail {
#[clap(flatten)]
copt: CommonOpt,
name: String,
mail: Vec<String>,
},
/// Set a new entry-managed-by for this group.
#[clap(name = "set-entry-manager")]
SetEntryManagedBy {