Skip to content

Commit

Permalink
Set custom permissions on group creation
Browse files Browse the repository at this point in the history
  • Loading branch information
cameronvoell committed Jul 23, 2024
1 parent 9698552 commit 1b81852
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 40 deletions.
218 changes: 210 additions & 8 deletions bindings_ffi/src/mls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use xmtp_mls::groups::group_permissions::MetadataBasePolicies;
use xmtp_mls::groups::group_permissions::MetadataPolicies;
use xmtp_mls::groups::group_permissions::PermissionsBasePolicies;
use xmtp_mls::groups::group_permissions::PermissionsPolicies;
use xmtp_mls::groups::group_permissions::PolicySet;
use xmtp_mls::groups::intents::PermissionPolicyOption;
use xmtp_mls::groups::intents::PermissionUpdateType;
use xmtp_mls::groups::GroupMetadataOptions;
Expand Down Expand Up @@ -389,7 +390,7 @@ pub struct FfiConversations {
inner_client: Arc<RustXmtpClient>,
}

#[derive(uniffi::Enum, Debug)]
#[derive(uniffi::Enum, Clone, Debug)]
pub enum FfiGroupPermissionsOptions {
AllMembers,
AdminOnly,
Expand Down Expand Up @@ -417,7 +418,7 @@ impl From<&FfiPermissionUpdateType> for PermissionUpdateType {
}
}

#[derive(uniffi::Enum, Debug, PartialEq, Eq)]
#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
pub enum FfiPermissionPolicy {
Allow,
Deny,
Expand All @@ -441,6 +442,49 @@ impl TryInto<PermissionPolicyOption> for FfiPermissionPolicy {
}
}

impl TryInto<MembershipPolicies> for FfiPermissionPolicy {
type Error = GroupMutablePermissionsError;

fn try_into(self) -> Result<MembershipPolicies, Self::Error> {
match self {
FfiPermissionPolicy::Allow => Ok(MembershipPolicies::allow()),
FfiPermissionPolicy::Deny => Ok(MembershipPolicies::deny()),
FfiPermissionPolicy::Admin => Ok(MembershipPolicies::allow_if_actor_admin()),
FfiPermissionPolicy::SuperAdmin => Ok(MembershipPolicies::allow_if_actor_super_admin()),
_ => Err(GroupMutablePermissionsError::InvalidPermissionPolicyOption),
}
}
}

impl TryInto<MetadataPolicies> for FfiPermissionPolicy {
type Error = GroupMutablePermissionsError;

fn try_into(self) -> Result<MetadataPolicies, Self::Error> {
match self {
FfiPermissionPolicy::Allow => Ok(MetadataPolicies::allow()),
FfiPermissionPolicy::Deny => Ok(MetadataPolicies::deny()),
FfiPermissionPolicy::Admin => Ok(MetadataPolicies::allow_if_actor_admin()),
FfiPermissionPolicy::SuperAdmin => Ok(MetadataPolicies::allow_if_actor_super_admin()),
_ => Err(GroupMutablePermissionsError::InvalidPermissionPolicyOption),
}
}
}

impl TryInto<PermissionsPolicies> for FfiPermissionPolicy {
type Error = GroupMutablePermissionsError;

fn try_into(self) -> Result<PermissionsPolicies, Self::Error> {
match self {
FfiPermissionPolicy::Deny => Ok(PermissionsPolicies::deny()),
FfiPermissionPolicy::Admin => Ok(PermissionsPolicies::allow_if_actor_admin()),
FfiPermissionPolicy::SuperAdmin => {
Ok(PermissionsPolicies::allow_if_actor_super_admin())
}
_ => Err(GroupMutablePermissionsError::InvalidPermissionPolicyOption),
}
}
}

impl From<&MembershipPolicies> for FfiPermissionPolicy {
fn from(policies: &MembershipPolicies) -> Self {
if let MembershipPolicies::Standard(base_policy) = policies {
Expand Down Expand Up @@ -488,7 +532,7 @@ impl From<&PermissionsPolicies> for FfiPermissionPolicy {
}
}

#[derive(uniffi::Record, Debug, PartialEq, Eq)]
#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
pub struct FfiPermissionPolicySet {
pub add_member_policy: FfiPermissionPolicy,
pub remove_member_policy: FfiPermissionPolicy,
Expand All @@ -509,6 +553,38 @@ impl From<PreconfiguredPolicies> for FfiGroupPermissionsOptions {
}
}

impl TryFrom<FfiPermissionPolicySet> for PolicySet {
type Error = GroupMutablePermissionsError;
fn try_from(policy_set: FfiPermissionPolicySet) -> Result<Self, GroupMutablePermissionsError> {
let mut metadata_permissions_map: HashMap<String, MetadataPolicies> = HashMap::new();
metadata_permissions_map.insert(
MetadataField::GroupName.to_string(),
policy_set.update_group_name_policy.try_into()?,
);
metadata_permissions_map.insert(
MetadataField::Description.to_string(),
policy_set.update_group_description_policy.try_into()?,
);
metadata_permissions_map.insert(
MetadataField::GroupImageUrlSquare.to_string(),
policy_set.update_group_image_url_square_policy.try_into()?,
);
metadata_permissions_map.insert(
MetadataField::GroupPinnedFrameUrl.to_string(),
policy_set.update_group_pinned_frame_url_policy.try_into()?,
);

Ok(PolicySet {
add_member_policy: policy_set.add_member_policy.try_into()?,
remove_member_policy: policy_set.remove_member_policy.try_into()?,
add_admin_policy: policy_set.add_admin_policy.try_into()?,
remove_admin_policy: policy_set.remove_admin_policy.try_into()?,
update_metadata_policy: metadata_permissions_map,
update_permissions_policy: PermissionsPolicies::allow_if_actor_super_admin(),
})
}
}

#[derive(uniffi::Enum, Debug)]
pub enum FfiMetadataField {
GroupName,
Expand Down Expand Up @@ -540,19 +616,28 @@ impl FfiConversations {
account_addresses.join(", ")
);

let metadata_options = opts.clone().into_group_metadata_options();

let group_permissions = match opts.permissions {
Some(FfiGroupPermissionsOptions::AllMembers) => {
Some(xmtp_mls::groups::PreconfiguredPolicies::AllMembers)
Some(xmtp_mls::groups::PreconfiguredPolicies::AllMembers.to_policy_set())
}
Some(FfiGroupPermissionsOptions::AdminOnly) => {
Some(xmtp_mls::groups::PreconfiguredPolicies::AdminsOnly)
Some(xmtp_mls::groups::PreconfiguredPolicies::AdminsOnly.to_policy_set())
}
Some(FfiGroupPermissionsOptions::CustomPolicy) => {
if let Some(policy_set) = opts.custom_permission_policy_set {
Some(policy_set.try_into()?)
} else {
None
}
}
_ => None,
};

let convo = self
.inner_client
.create_group(group_permissions, opts.into_group_metadata_options())?;
.create_group(group_permissions, metadata_options)?;
if !account_addresses.is_empty() {
convo
.add_members(&self.inner_client, account_addresses)
Expand Down Expand Up @@ -672,13 +757,14 @@ pub struct FfiListMessagesOptions {
pub delivery_status: Option<FfiDeliveryStatus>,
}

#[derive(uniffi::Record, Default)]
#[derive(uniffi::Record, Clone, Default)]
pub struct FfiCreateGroupOptions {
pub permissions: Option<FfiGroupPermissionsOptions>,
pub group_name: Option<String>,
pub group_image_url_square: Option<String>,
pub group_description: Option<String>,
pub group_pinned_frame_url: Option<String>,
pub custom_permission_policy_set: Option<FfiPermissionPolicySet>,
}

impl FfiCreateGroupOptions {
Expand Down Expand Up @@ -1401,7 +1487,11 @@ mod tests {
use tokio::{sync::Notify, time::error::Elapsed};
use xmtp_cryptography::{signature::RecoverableSignature, utils::rng};
use xmtp_id::associations::generate_inbox_id;
use xmtp_mls::{storage::EncryptionKey, InboxOwner};
use xmtp_mls::{
groups::{group_permissions::PolicySet, PreconfiguredPolicies},
storage::EncryptionKey,
InboxOwner,
};

#[derive(Clone)]
pub struct LocalWalletInboxOwner {
Expand Down Expand Up @@ -1986,6 +2076,7 @@ mod tests {
group_image_url_square: Some("url".to_string()),
group_description: Some("group description".to_string()),
group_pinned_frame_url: Some("pinned frame".to_string()),
custom_permission_policy_set: None,
},
)
.await
Expand Down Expand Up @@ -2568,4 +2659,115 @@ mod tests {
);
assert_eq!(alix_group.group_name().unwrap(), "");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn test_group_creation_custom_permissions() {
let alix = new_test_client().await;
let bola = new_test_client().await;

let custom_permissions = FfiPermissionPolicySet {
add_admin_policy: FfiPermissionPolicy::Admin,
remove_admin_policy: FfiPermissionPolicy::Admin,
update_group_name_policy: FfiPermissionPolicy::Admin,
update_group_description_policy: FfiPermissionPolicy::Allow,
update_group_image_url_square_policy: FfiPermissionPolicy::Admin,
update_group_pinned_frame_url_policy: FfiPermissionPolicy::Admin,
add_member_policy: FfiPermissionPolicy::Allow,
remove_member_policy: FfiPermissionPolicy::Deny,
};

let create_group_options = FfiCreateGroupOptions {
permissions: Some(FfiGroupPermissionsOptions::CustomPolicy),
group_name: Some("Test Group".to_string()),
group_image_url_square: Some("https://example.com/image.png".to_string()),
group_description: Some("A test group".to_string()),
group_pinned_frame_url: Some("https://example.com/frame.png".to_string()),
custom_permission_policy_set: Some(custom_permissions),
};

let alix_group = alix
.conversations()
.create_group(vec![bola.account_address.clone()], create_group_options)
.await
.unwrap();

// Verify the group was created with the correct permissions
let group_permissions_policy_set = alix_group
.group_permissions()
.unwrap()
.policy_set()
.unwrap();
assert_eq!(
group_permissions_policy_set.add_admin_policy,
FfiPermissionPolicy::Admin
);
assert_eq!(
group_permissions_policy_set.remove_admin_policy,
FfiPermissionPolicy::Admin
);
assert_eq!(
group_permissions_policy_set.update_group_name_policy,
FfiPermissionPolicy::Admin
);
assert_eq!(
group_permissions_policy_set.update_group_description_policy,
FfiPermissionPolicy::Allow
);
assert_eq!(
group_permissions_policy_set.update_group_image_url_square_policy,
FfiPermissionPolicy::Admin
);
assert_eq!(
group_permissions_policy_set.update_group_pinned_frame_url_policy,
FfiPermissionPolicy::Admin
);
assert_eq!(
group_permissions_policy_set.add_member_policy,
FfiPermissionPolicy::Allow
);
assert_eq!(
group_permissions_policy_set.remove_member_policy,
FfiPermissionPolicy::Deny
);

// Verify that Bola can not update the group name
let bola_conversations = bola.conversations();
let _ = bola_conversations.sync().await;
let bola_groups = bola_conversations
.list(crate::FfiListConversationsOptions {
created_after_ns: None,
created_before_ns: None,
limit: None,
})
.await
.unwrap();

let bola_group = bola_groups.first().unwrap();
bola_group
.update_group_name("new_name".to_string())
.await
.unwrap_err();
let result = bola_group
.update_group_name("New Group Name".to_string())
.await;
assert!(result.is_err());

// Verify that Alix can update the group name
let result = alix_group
.update_group_name("New Group Name".to_string())
.await;
assert!(result.is_ok());

// Verify that Bola can update the group description
let result = bola_group
.update_group_description("New Description".to_string())
.await;
assert!(result.is_ok());

// Verify that Alix can not remove bola even though they are a super admin
let result = alix_group
.remove_members(vec![bola.account_address.clone()])
.await;
assert!(result.is_err());
}
}
8 changes: 6 additions & 2 deletions bindings_node/src/conversations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ impl NapiConversations {
};

let group_permissions = match options.permissions {
Some(NapiGroupPermissionsOptions::AllMembers) => Some(PreconfiguredPolicies::AllMembers),
Some(NapiGroupPermissionsOptions::AdminOnly) => Some(PreconfiguredPolicies::AdminsOnly),
Some(NapiGroupPermissionsOptions::AllMembers) => {
Some(PreconfiguredPolicies::AllMembers.to_policy_set())
}
Some(NapiGroupPermissionsOptions::AdminOnly) => {
Some(PreconfiguredPolicies::AdminsOnly.to_policy_set())
}
_ => None,
};

Expand Down
5 changes: 4 additions & 1 deletion examples/cli/cli-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,10 @@ async fn main() {
.unwrap();

let group = client
.create_group(Some(group_permissions), GroupMetadataOptions::default())
.create_group(
Some(group_permissions.to_policy_set()),
GroupMetadataOptions::default(),
)
.expect("failed to create group");
let group_id = hex::encode(group.group_id);
info!("Created group {}", group_id, { command_output: true, group_id: group_id})
Expand Down
8 changes: 4 additions & 4 deletions xmtp_mls/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use xmtp_proto::xmtp::mls::api::v1::{
use crate::{
api::ApiClientWrapper,
groups::{
validated_commit::CommitValidationError, GroupError, GroupMetadataOptions, IntentError,
MlsGroup, PreconfiguredPolicies,
group_permissions::PolicySet, validated_commit::CommitValidationError, GroupError,
GroupMetadataOptions, IntentError, MlsGroup,
},
identity::{parse_credential, Identity, IdentityError},
identity_updates::IdentityUpdateError,
Expand Down Expand Up @@ -331,15 +331,15 @@ where
/// Create a new group with the default settings
pub fn create_group(
&self,
permissions: Option<PreconfiguredPolicies>,
permissions_policy_set: Option<PolicySet>,
opts: GroupMetadataOptions,
) -> Result<MlsGroup, ClientError> {
log::info!("creating group");

let group = MlsGroup::create_and_insert(
self.context.clone(),
GroupMembershipState::Allowed,
permissions,
permissions_policy_set.unwrap_or_default(),
opts,
)
.map_err(Box::new)?;
Expand Down
5 changes: 5 additions & 0 deletions xmtp_mls/src/groups/group_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,11 @@ pub(crate) fn policy_admin_only() -> PolicySet {
PermissionsPolicies::allow_if_actor_super_admin(),
)
}
impl Default for PolicySet {
fn default() -> Self {
PreconfiguredPolicies::default().to_policy_set()
}
}

#[derive(Debug, Clone, PartialEq, Default)]
pub enum PreconfiguredPolicies {
Expand Down
Loading

0 comments on commit 1b81852

Please sign in to comment.