From eb05ef166892895639381a0ee194e769c21a6876 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Mon, 15 Jul 2024 15:31:22 -0500 Subject: [PATCH] Update node bindings group permissions (#903) --- bindings_node/src/conversations.rs | 19 ++- bindings_node/src/groups.rs | 49 +------ bindings_node/src/lib.rs | 1 + bindings_node/src/permissions.rs | 172 +++++++++++++++++++++++ bindings_node/test/Conversations.test.ts | 30 +++- 5 files changed, 210 insertions(+), 61 deletions(-) create mode 100644 bindings_node/src/permissions.rs diff --git a/bindings_node/src/conversations.rs b/bindings_node/src/conversations.rs index 0ba01618e..10240189d 100644 --- a/bindings_node/src/conversations.rs +++ b/bindings_node/src/conversations.rs @@ -6,14 +6,11 @@ use napi::bindgen_prelude::{Error, Result, Uint8Array}; use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}; use napi::JsFunction; use napi_derive::napi; -use xmtp_mls::groups::GroupMetadataOptions; +use xmtp_mls::groups::{GroupMetadataOptions, PreconfiguredPolicies}; use crate::messages::NapiMessage; -use crate::{ - groups::{GroupPermissions, NapiGroup}, - mls_client::RustXmtpClient, - streams::NapiStreamCloser, -}; +use crate::permissions::NapiGroupPermissionsOptions; +use crate::{groups::NapiGroup, mls_client::RustXmtpClient, streams::NapiStreamCloser}; #[napi(object)] pub struct NapiListConversationsOptions { @@ -24,7 +21,7 @@ pub struct NapiListConversationsOptions { #[napi(object)] pub struct NapiCreateGroupOptions { - pub permissions: Option, + pub permissions: Option, pub group_name: Option, pub group_image_url_square: Option, pub group_description: Option, @@ -70,9 +67,11 @@ impl NapiConversations { }, }; - let group_permissions = options - .permissions - .map(|group_permissions| group_permissions.into()); + let group_permissions = match options.permissions { + Some(NapiGroupPermissionsOptions::AllMembers) => Some(PreconfiguredPolicies::AllMembers), + Some(NapiGroupPermissionsOptions::AdminOnly) => Some(PreconfiguredPolicies::AdminsOnly), + _ => None, + }; let convo = self .inner_client diff --git a/bindings_node/src/groups.rs b/bindings_node/src/groups.rs index ab8862f28..e5c118d66 100644 --- a/bindings_node/src/groups.rs +++ b/bindings_node/src/groups.rs @@ -8,9 +8,8 @@ use napi::{ use xmtp_cryptography::signature::ed25519_public_key_to_address; use xmtp_mls::groups::{ group_metadata::{ConversationType, GroupMetadata}, - group_permissions::GroupMutablePermissions, members::PermissionLevel, - MlsGroup, PreconfiguredPolicies, UpdateAdminListType, + MlsGroup, UpdateAdminListType, }; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; @@ -18,6 +17,7 @@ use crate::{ encoded_content::NapiEncodedContent, messages::{NapiListMessagesOptions, NapiMessage}, mls_client::RustXmtpClient, + permissions::NapiGroupPermissions, streams::NapiStreamCloser, }; @@ -25,30 +25,6 @@ use prost::Message; use napi_derive::napi; -#[napi] -pub enum GroupPermissions { - EveryoneIsAdmin, - GroupCreatorIsAdmin, -} - -impl From for GroupPermissions { - fn from(policy: PreconfiguredPolicies) -> Self { - match policy { - PreconfiguredPolicies::AllMembers => GroupPermissions::EveryoneIsAdmin, - PreconfiguredPolicies::AdminsOnly => GroupPermissions::GroupCreatorIsAdmin, - } - } -} - -impl From for PreconfiguredPolicies { - fn from(permissions: GroupPermissions) -> Self { - match permissions { - GroupPermissions::EveryoneIsAdmin => PreconfiguredPolicies::AllMembers, - GroupPermissions::GroupCreatorIsAdmin => PreconfiguredPolicies::AdminsOnly, - } - } -} - #[napi] pub struct NapiGroupMetadata { inner: GroupMetadata, @@ -86,25 +62,6 @@ pub struct NapiGroupMember { pub permission_level: NapiPermissionLevel, } -#[napi] -pub struct NapiGroupPermissions { - inner: GroupMutablePermissions, -} - -#[napi] -impl NapiGroupPermissions { - #[napi] - pub fn policy_type(&self) -> Result { - Ok( - self - .inner - .preconfigured_policy() - .map_err(|e| Error::from_reason(format!("{}", e)))? - .into(), - ) - } -} - #[derive(Debug)] #[napi] pub struct NapiGroup { @@ -419,7 +376,7 @@ impl NapiGroup { .permissions() .map_err(|e| Error::from_reason(format!("{}", e)))?; - Ok(NapiGroupPermissions { inner: permissions }) + Ok(NapiGroupPermissions::new(permissions)) } #[napi] diff --git a/bindings_node/src/lib.rs b/bindings_node/src/lib.rs index 3084a43f8..f72879c40 100755 --- a/bindings_node/src/lib.rs +++ b/bindings_node/src/lib.rs @@ -4,4 +4,5 @@ pub mod encoded_content; mod groups; mod messages; pub mod mls_client; +mod permissions; mod streams; diff --git a/bindings_node/src/permissions.rs b/bindings_node/src/permissions.rs new file mode 100644 index 000000000..45c2f12d0 --- /dev/null +++ b/bindings_node/src/permissions.rs @@ -0,0 +1,172 @@ +use napi::bindgen_prelude::{Error, Result}; +use napi_derive::napi; +use xmtp_mls::groups::{ + group_mutable_metadata::MetadataField, + group_permissions::{ + BasePolicies, GroupMutablePermissions, MembershipPolicies, MetadataBasePolicies, + MetadataPolicies, PermissionsBasePolicies, PermissionsPolicies, + }, + intents::{PermissionPolicyOption, PermissionUpdateType}, + PreconfiguredPolicies, +}; + +#[napi] +pub enum NapiGroupPermissionsOptions { + AllMembers, + AdminOnly, + CustomPolicy, +} + +#[napi] +pub enum NapiPermissionUpdateType { + AddMember, + RemoveMember, + AddAdmin, + RemoveAdmin, + UpdateMetadata, +} + +impl From<&NapiPermissionUpdateType> for PermissionUpdateType { + fn from(update_type: &NapiPermissionUpdateType) -> Self { + match update_type { + NapiPermissionUpdateType::AddMember => PermissionUpdateType::AddMember, + NapiPermissionUpdateType::RemoveMember => PermissionUpdateType::RemoveMember, + NapiPermissionUpdateType::AddAdmin => PermissionUpdateType::AddAdmin, + NapiPermissionUpdateType::RemoveAdmin => PermissionUpdateType::RemoveAdmin, + NapiPermissionUpdateType::UpdateMetadata => PermissionUpdateType::UpdateMetadata, + } + } +} + +#[napi] +pub enum NapiPermissionPolicy { + Allow, + Deny, + Admin, + SuperAdmin, + DoesNotExist, + Other, +} + +impl TryInto for NapiPermissionPolicy { + type Error = Error; + + fn try_into(self) -> Result { + match self { + NapiPermissionPolicy::Allow => Ok(PermissionPolicyOption::Allow), + NapiPermissionPolicy::Deny => Ok(PermissionPolicyOption::Deny), + NapiPermissionPolicy::Admin => Ok(PermissionPolicyOption::AdminOnly), + NapiPermissionPolicy::SuperAdmin => Ok(PermissionPolicyOption::SuperAdminOnly), + _ => Err(Error::from_reason("InvalidPermissionPolicyOption")), + } + } +} + +impl From<&MembershipPolicies> for NapiPermissionPolicy { + fn from(policies: &MembershipPolicies) -> Self { + if let MembershipPolicies::Standard(base_policy) = policies { + match base_policy { + BasePolicies::Allow => NapiPermissionPolicy::Allow, + BasePolicies::Deny => NapiPermissionPolicy::Deny, + BasePolicies::AllowSameMember => NapiPermissionPolicy::Other, + BasePolicies::AllowIfAdminOrSuperAdmin => NapiPermissionPolicy::Admin, + BasePolicies::AllowIfSuperAdmin => NapiPermissionPolicy::SuperAdmin, + } + } else { + NapiPermissionPolicy::Other + } + } +} + +impl From<&MetadataPolicies> for NapiPermissionPolicy { + fn from(policies: &MetadataPolicies) -> Self { + if let MetadataPolicies::Standard(base_policy) = policies { + match base_policy { + MetadataBasePolicies::Allow => NapiPermissionPolicy::Allow, + MetadataBasePolicies::Deny => NapiPermissionPolicy::Deny, + MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin => NapiPermissionPolicy::Admin, + MetadataBasePolicies::AllowIfActorSuperAdmin => NapiPermissionPolicy::SuperAdmin, + } + } else { + NapiPermissionPolicy::Other + } + } +} + +impl From<&PermissionsPolicies> for NapiPermissionPolicy { + fn from(policies: &PermissionsPolicies) -> Self { + if let PermissionsPolicies::Standard(base_policy) = policies { + match base_policy { + PermissionsBasePolicies::Deny => NapiPermissionPolicy::Deny, + PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin => NapiPermissionPolicy::Admin, + PermissionsBasePolicies::AllowIfActorSuperAdmin => NapiPermissionPolicy::SuperAdmin, + } + } else { + NapiPermissionPolicy::Other + } + } +} + +#[napi(object)] +pub struct NapiPermissionPolicySet { + pub add_member_policy: NapiPermissionPolicy, + pub remove_member_policy: NapiPermissionPolicy, + pub add_admin_policy: NapiPermissionPolicy, + pub remove_admin_policy: NapiPermissionPolicy, + pub update_group_name_policy: NapiPermissionPolicy, + pub update_group_description_policy: NapiPermissionPolicy, + pub update_group_image_url_square_policy: NapiPermissionPolicy, + pub update_group_pinned_frame_url_policy: NapiPermissionPolicy, +} + +impl From for NapiGroupPermissionsOptions { + fn from(policy: PreconfiguredPolicies) -> Self { + match policy { + PreconfiguredPolicies::AllMembers => NapiGroupPermissionsOptions::AllMembers, + PreconfiguredPolicies::AdminsOnly => NapiGroupPermissionsOptions::AdminOnly, + } + } +} + +#[napi] +pub struct NapiGroupPermissions { + inner: GroupMutablePermissions, +} + +#[napi] +impl NapiGroupPermissions { + pub fn new(permissions: GroupMutablePermissions) -> Self { + Self { inner: permissions } + } + + #[napi] + pub fn policy_type(&self) -> Result { + if let Ok(preconfigured_policy) = self.inner.preconfigured_policy() { + Ok(preconfigured_policy.into()) + } else { + Ok(NapiGroupPermissionsOptions::CustomPolicy) + } + } + + #[napi] + pub fn policy_set(&self) -> Result { + let policy_set = &self.inner.policies; + let metadata_policy_map = &policy_set.update_metadata_policy; + let get_policy = |field: &str| { + metadata_policy_map + .get(field) + .map(NapiPermissionPolicy::from) + .unwrap_or(NapiPermissionPolicy::DoesNotExist) + }; + Ok(NapiPermissionPolicySet { + add_member_policy: NapiPermissionPolicy::from(&policy_set.add_member_policy), + remove_member_policy: NapiPermissionPolicy::from(&policy_set.remove_member_policy), + add_admin_policy: NapiPermissionPolicy::from(&policy_set.add_admin_policy), + remove_admin_policy: NapiPermissionPolicy::from(&policy_set.remove_admin_policy), + update_group_name_policy: get_policy(MetadataField::GroupName.as_str()), + update_group_description_policy: get_policy(MetadataField::Description.as_str()), + update_group_image_url_square_policy: get_policy(MetadataField::GroupImageUrlSquare.as_str()), + update_group_pinned_frame_url_policy: get_policy(MetadataField::GroupPinnedFrameUrl.as_str()), + }) + } +} diff --git a/bindings_node/test/Conversations.test.ts b/bindings_node/test/Conversations.test.ts index ac43bd36b..e9e3c5c1d 100644 --- a/bindings_node/test/Conversations.test.ts +++ b/bindings_node/test/Conversations.test.ts @@ -1,4 +1,3 @@ -import { encode } from 'punycode' import { describe, expect, it } from 'vitest' import { AsyncStream } from '@test/AsyncStream' import { @@ -6,7 +5,7 @@ import { createUser, encodeTextMessage, } from '@test/helpers' -import { GroupPermissions, NapiGroup, NapiMessage } from '../dist' +import { NapiGroup, NapiGroupPermissionsOptions, NapiMessage } from '../dist' describe('Conversations', () => { it('should not have initial conversations', async () => { @@ -30,8 +29,18 @@ describe('Conversations', () => { expect(group.isActive()).toBe(true) expect(group.groupName()).toBe('') expect(group.groupPermissions().policyType()).toBe( - GroupPermissions.EveryoneIsAdmin + NapiGroupPermissionsOptions.AllMembers ) + expect(group.groupPermissions().policySet()).toEqual({ + addMemberPolicy: 0, + removeMemberPolicy: 2, + addAdminPolicy: 3, + removeAdminPolicy: 3, + updateGroupNamePolicy: 0, + updateGroupDescriptionPolicy: 0, + updateGroupImageUrlSquarePolicy: 0, + updateGroupPinnedFrameUrlPolicy: 0, + }) expect(group.addedByInboxId()).toBe(client1.inboxId()) expect(group.findMessages().length).toBe(1) const members = group.listMembers() @@ -130,15 +139,26 @@ describe('Conversations', () => { const groupWithPermissions = await client1 .conversations() .createGroup([user4.account.address], { - permissions: GroupPermissions.GroupCreatorIsAdmin, + permissions: NapiGroupPermissionsOptions.AdminOnly, }) expect(groupWithPermissions).toBeDefined() expect(groupWithPermissions.groupName()).toBe('') expect(groupWithPermissions.groupImageUrlSquare()).toBe('') expect(groupWithPermissions.groupPermissions().policyType()).toBe( - GroupPermissions.GroupCreatorIsAdmin + NapiGroupPermissionsOptions.AdminOnly ) + expect(groupWithPermissions.groupPermissions().policySet()).toEqual({ + addMemberPolicy: 2, + removeMemberPolicy: 2, + addAdminPolicy: 3, + removeAdminPolicy: 3, + updateGroupNamePolicy: 2, + updateGroupDescriptionPolicy: 2, + updateGroupImageUrlSquarePolicy: 2, + updateGroupPinnedFrameUrlPolicy: 2, + }) + const groupWithDescription = await client1 .conversations() .createGroup([user2.account.address], {