From afd674d34656a84337a1945c9bb8cd5cd99e42c9 Mon Sep 17 00:00:00 2001 From: Firstyear Date: Tue, 23 Apr 2024 17:08:28 +1000 Subject: [PATCH] Add mail support to groups (#2718) * Add mail support to groups * Update libs/client/src/group.rs --------- Co-authored-by: James Hodgkinson --- libs/client/src/group.rs | 18 ++++++++++++++++++ server/lib/src/constants/acp.rs | 27 +++++++++++++++++++++++++++ server/lib/src/constants/schema.rs | 18 ++++++++++++++++++ server/lib/src/server/migrations.rs | 2 ++ server/testkit/tests/proto_v1_test.rs | 26 ++++++++++++++++++++++++++ tools/cli/src/cli/group/mod.rs | 17 +++++++++++++++++ tools/cli/src/opt/kanidm.rs | 10 ++++++++++ 7 files changed, 118 insertions(+) diff --git a/libs/client/src/group.rs b/libs/client/src/group.rs index 916a8b734..f5d69389f 100644 --- a/libs/client/src/group.rs +++ b/libs/client/src/group.rs @@ -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( + &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>, ClientError> { + self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id)) + .await + } } diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index 7729f354f..bc52bf7bc 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -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() diff --git a/server/lib/src/constants/schema.rs b/server/lib/src/constants/schema.rs index 601f3f2ad..4d7b4875a 100644 --- a/server/lib/src/constants/schema.rs +++ b/server/lib/src/constants/schema.rs @@ -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(), diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index cecffd4b9..bb1e3cfc6 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -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. diff --git a/server/testkit/tests/proto_v1_test.rs b/server/testkit/tests/proto_v1_test.rs index 21ccdf88e..98cef04af 100644 --- a/server/testkit/tests/proto_v1_test.rs +++ b/server/testkit/tests/proto_v1_test.rs @@ -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(); diff --git a/tools/cli/src/cli/group/mod.rs b/tools/cli/src/cli/group/mod.rs index c3d9643b2..ba27947b2 100644 --- a/tools/cli/src/cli/group/mod.rs +++ b/tools/cli/src/cli/group/mod.rs @@ -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, diff --git a/tools/cli/src/opt/kanidm.rs b/tools/cli/src/opt/kanidm.rs index 4ba292b5f..cb0031d1a 100644 --- a/tools/cli/src/opt/kanidm.rs +++ b/tools/cli/src/opt/kanidm.rs @@ -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, + }, /// Set a new entry-managed-by for this group. #[clap(name = "set-entry-manager")] SetEntryManagedBy {