Skip to content

Commit

Permalink
Merge pull request #445 from xmtp/cv/permission-creation-options
Browse files Browse the repository at this point in the history
Adds custom permissions options on group creation
  • Loading branch information
cameronvoell authored Jul 24, 2024
2 parents d9563cd + 7e46566 commit 52b1540
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 63 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.14.9"
implementation "org.xmtp:android:0.14.10"
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.facebook.react:react-native:0.71.3'
implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,25 @@ class XMTPModule : Module() {
}
}

AsyncFunction("createGroupCustomPermissions") Coroutine { inboxId: String, peerAddresses: List<String>, permissionPolicySetJson: String, groupOptionsJson: String ->
withContext(Dispatchers.IO) {
logV("createGroup")
val client = clients[inboxId] ?: throw XMTPException("No client")
val createGroupParams =
CreateGroupParamsWrapper.createGroupParamsFromJson(groupOptionsJson)
val permissionPolicySet = PermissionPolicySetWrapper.createPermissionPolicySetFromJson(permissionPolicySetJson)
val group = client.conversations.newGroupCustomPermissions(
peerAddresses,
permissionPolicySet,
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.groupPinnedFrameUrl
)
GroupWrapper.encode(client, group)
}
}


AsyncFunction("listMemberInboxIds") Coroutine { inboxId: String, groupId: String ->
withContext(Dispatchers.IO) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionOption
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionPolicySet

Expand All @@ -16,6 +17,16 @@ class PermissionPolicySetWrapper {
PermissionOption.Unknown -> "unknown"
}
}

fun createPermissionOptionFromString(permissionOptionString: String): PermissionOption {
return when (permissionOptionString) {
"allow" -> PermissionOption.Allow
"deny" -> PermissionOption.Deny
"admin" -> PermissionOption.Admin
"superAdmin" -> PermissionOption.SuperAdmin
else -> PermissionOption.Unknown
}
}
fun encodeToObj(policySet: PermissionPolicySet): Map<String, Any> {
return mapOf(
"addMemberPolicy" to fromPermissionOption(policySet.addMemberPolicy),
Expand All @@ -29,6 +40,20 @@ class PermissionPolicySetWrapper {
)
}

fun createPermissionPolicySetFromJson(permissionPolicySetJson: String): PermissionPolicySet {
val jsonObj = JsonParser.parseString(permissionPolicySetJson).asJsonObject
return PermissionPolicySet(
addMemberPolicy = createPermissionOptionFromString(jsonObj.get("addMemberPolicy").asString),
removeMemberPolicy = createPermissionOptionFromString(jsonObj.get("removeMemberPolicy").asString),
addAdminPolicy = createPermissionOptionFromString(jsonObj.get("addAdminPolicy").asString),
removeAdminPolicy = createPermissionOptionFromString(jsonObj.get("removeAdminPolicy").asString),
updateGroupNamePolicy = createPermissionOptionFromString(jsonObj.get("updateGroupNamePolicy").asString),
updateGroupDescriptionPolicy = createPermissionOptionFromString(jsonObj.get("updateGroupDescriptionPolicy").asString),
updateGroupImagePolicy = createPermissionOptionFromString(jsonObj.get("updateGroupImagePolicy").asString),
updateGroupPinnedFrameUrlPolicy = createPermissionOptionFromString(jsonObj.get("updateGroupPinnedFrameUrlPolicy").asString)
)
}

fun encodeToJsonString(policySet: PermissionPolicySet): String {
val gson = GsonBuilder().create()
val obj = encodeToObj(policySet)
Expand Down
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ PODS:
- hermes-engine/Pre-built (= 0.71.14)
- hermes-engine/Pre-built (0.71.14)
- libevent (2.1.12)
- LibXMTP (0.5.6-beta0)
- LibXMTP (0.5.6-beta1)
- Logging (1.0.0)
- MessagePacker (0.4.7)
- MMKV (1.3.7):
Expand Down Expand Up @@ -449,16 +449,16 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.13.8):
- XMTP (0.13.10):
- Connect-Swift (= 0.12.0)
- GzipSwift
- LibXMTP (= 0.5.6-beta0)
- LibXMTP (= 0.5.6-beta1)
- web3.swift
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- secp256k1.swift
- XMTP (= 0.13.8)
- XMTP (= 0.13.10)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -711,7 +711,7 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: e7682dedb10e18343c011280d494a8e4a43d9eb7
LibXMTP: 2205108c6c3a2bcdc405e42d4c718ad87c31a7c2
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: 36a22a9ec84c9bb960613a089ddf6f48be9312b0
Expand Down Expand Up @@ -763,8 +763,8 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 7d0a3f3b22916acfbb0ae67f1ca6bbd3f5956138
XMTPReactNative: 51e5b1b8669dab2ad5e2d74b518146388f5f425e
XMTP: 19f9c073262c44fbe98489208cda7a44d079064d
XMTPReactNative: 296aaa356ea5c67c98779665bcb5e1cad140d135
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2
Expand Down
145 changes: 137 additions & 8 deletions example/src/tests/groupPermissionsTests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PermissionPolicySet } from 'xmtp-react-native-sdk/lib/types/PermissionPolicySet'

import { Test, assert, createClients } from './test-utils'

export const groupPermissionsTests: Test[] = []
Expand Down Expand Up @@ -57,9 +59,7 @@ test('super admin can add a new admin', async () => {
const boGroup = (await bo.conversations.listGroups())[0]
try {
await boGroup.addAdmin(caro.inboxId)
throw new Error(
'Expected exception when non-super admin attempts to add an admin.'
)
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -139,6 +139,7 @@ test('in admin only group, members can update group name once they are an admin'
const boGroup = (await bo.conversations.listGroups())[0]
try {
await boGroup.updateGroupName("bo's group")
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -214,6 +215,7 @@ test('in admin only group, members can not update group name after admin status
// Bo can no longer update the group name
try {
await boGroup.updateGroupName('new name 2')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected error
Expand Down Expand Up @@ -256,6 +258,7 @@ test('can not remove a super admin from a group', async () => {
// Bo should not be able to remove alix from the group
try {
await boGroup.removeMembersByInboxId([alix.inboxId])
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand All @@ -282,6 +285,7 @@ test('can not remove a super admin from a group', async () => {
// Verify bo can not remove alix bc alix is a super admin
try {
await boGroup.removeMembersByInboxId([alix.inboxId])
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -333,6 +337,7 @@ test('can commit after invalid permissions commit', async () => {
)
try {
await alixGroup.addAdmin(alix.inboxId)
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -374,7 +379,7 @@ test('group with All Members policy has remove function that is admin only', asy
// Verify that Alix cannot remove a member
try {
await alixGroup.removeMembers([caro.address])
assert(false, 'Alix should not be able to remove a member')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -427,8 +432,8 @@ test('can update group permissions', async () => {
await alix.conversations.syncGroups()
const alixGroup = (await alix.conversations.listGroups())[0]
try {
await alixGroup.updateGroupDescription('new description')
assert(false, 'Alix should not be able to update the group description')
await alixGroup.updateGroupDescription('new description 2')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand All @@ -437,7 +442,7 @@ test('can update group permissions', async () => {
// Verify that alix can not update permissions
try {
await alixGroup.updateGroupDescriptionPermission('allow')
assert(false, 'Alix should not be able to update the group name permission')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -478,7 +483,7 @@ test('can update group pinned frame', async () => {
const alixGroup = (await alix.conversations.listGroups())[0]
try {
await alixGroup.updateGroupPinnedFrameUrl('new pinned frame')
assert(false, 'Alix should not be able to update the group pinned frame')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
Expand Down Expand Up @@ -512,3 +517,127 @@ test('can update group pinned frame', async () => {

return true
})

test('can create a group with custom permissions', async () => {
// Create clients
const [alix, bo, caro] = await createClients(3)

const customPermissionsPolicySet: PermissionPolicySet = {
addMemberPolicy: 'allow',
removeMemberPolicy: 'deny',
addAdminPolicy: 'admin',
removeAdminPolicy: 'superAdmin',
updateGroupNamePolicy: 'admin',
updateGroupDescriptionPolicy: 'allow',
updateGroupImagePolicy: 'admin',
updateGroupPinnedFrameUrlPolicy: 'deny',
}

// Bo creates a group with Alix and Caro with custom permissions
const boGroup = await bo.conversations.newGroupCustomPermissions(
[alix.address, caro.address],
customPermissionsPolicySet
)

// Verify that bo can read the correct permissions
await alix.conversations.syncGroups()
const alixGroup = (await alix.conversations.listGroups())[0]
const permissions = await alixGroup.permissionPolicySet()
assert(
permissions.addMemberPolicy === customPermissionsPolicySet.addMemberPolicy,
`permissions.addMemberPolicy should be ${customPermissionsPolicySet.addMemberPolicy} but was ${permissions.addMemberPolicy}`
)
assert(
permissions.removeMemberPolicy ===
customPermissionsPolicySet.removeMemberPolicy,
`permissions.removeMemberPolicy should be ${customPermissionsPolicySet.removeMemberPolicy} but was ${permissions.removeMemberPolicy}`
)
assert(
permissions.addAdminPolicy === customPermissionsPolicySet.addAdminPolicy,
`permissions.addAdminPolicy should be ${customPermissionsPolicySet.addAdminPolicy} but was ${permissions.addAdminPolicy}`
)
assert(
permissions.removeAdminPolicy ===
customPermissionsPolicySet.removeAdminPolicy,
`permissions.removeAdminPolicy should be ${customPermissionsPolicySet.removeAdminPolicy} but was ${permissions.removeAdminPolicy}`
)
assert(
permissions.updateGroupNamePolicy ===
customPermissionsPolicySet.updateGroupNamePolicy,
`permissions.updateGroupNamePolicy should be ${customPermissionsPolicySet.updateGroupNamePolicy} but was ${permissions.updateGroupNamePolicy}`
)
assert(
permissions.updateGroupDescriptionPolicy ===
customPermissionsPolicySet.updateGroupDescriptionPolicy,
`permissions.updateGroupDescriptionPolicy should be ${customPermissionsPolicySet.updateGroupDescriptionPolicy} but was ${permissions.updateGroupDescriptionPolicy}`
)
assert(
permissions.updateGroupImagePolicy ===
customPermissionsPolicySet.updateGroupImagePolicy,
`permissions.updateGroupImagePolicy should be ${customPermissionsPolicySet.updateGroupImagePolicy} but was ${permissions.updateGroupImagePolicy}`
)
assert(
permissions.updateGroupPinnedFrameUrlPolicy ===
customPermissionsPolicySet.updateGroupPinnedFrameUrlPolicy,
`permissions.updateGroupPinnedFrameUrlPolicy should be ${customPermissionsPolicySet.updateGroupPinnedFrameUrlPolicy} but was ${permissions.updateGroupPinnedFrameUrlPolicy}`
)

// Verify that bo can not update the pinned frame even though they are a super admin
try {
await boGroup.updateGroupPinnedFrameUrl('new pinned frame')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
}

// Verify that alix can update the group description
await alixGroup.updateGroupDescription('new description')
await alixGroup.sync()
assert(
(await alixGroup.groupDescription()) === 'new description',
`alixGroup.groupDescription should be "new description" but was ${alixGroup.groupDescription}`
)

// Verify that alix can not update the group name
try {
await alixGroup.updateGroupName('new name')
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
}

return true
})

test('creating a group with invalid permissions should fail', async () => {
// Create clients
const [alix, bo, caro] = await createClients(3)

// Add/Remove admin can not be set to allow
const customPermissionsPolicySet: PermissionPolicySet = {
addMemberPolicy: 'allow',
removeMemberPolicy: 'deny',
addAdminPolicy: 'allow',
removeAdminPolicy: 'superAdmin',
updateGroupNamePolicy: 'admin',
updateGroupDescriptionPolicy: 'allow',
updateGroupImagePolicy: 'admin',
updateGroupPinnedFrameUrlPolicy: 'deny',
}

// Bo creates a group with Alix and Caro
try {
await bo.conversations.newGroupCustomPermissions(
[alix.address, caro.address],
customPermissionsPolicySet
)
return false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
console.log('error', error)
return true
}
})
2 changes: 1 addition & 1 deletion example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('can make a MLS V3 client', async () => {
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145,
])
const client = await Client.createRandom({
await Client.createRandom({
env: 'local',
appVersion: 'Testing/0.0.0',
enableV3: true,
Expand Down
7 changes: 3 additions & 4 deletions example/src/tests/tests.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { sha256 } from '@noble/hashes/sha256'
import { FramesClient } from '@xmtp/frames-client'
import { content, invitation, signature as signatureProto } from '@xmtp/proto'
import { content, invitation } from '@xmtp/proto'
import { createHmac } from 'crypto'
import ReactNativeBlobUtil from 'react-native-blob-util'
import Config from 'react-native-config'
import { TextEncoder, TextDecoder } from 'text-encoding'
import { createWalletClient, custom, PrivateKeyAccount, toHex } from 'viem'
import { PrivateKeyAccount } from 'viem'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { DecodedMessage } from 'xmtp-react-native-sdk/lib/DecodedMessage'

import { Test, assert, createClients, delayToPropogate } from './test-utils'
import { Test, assert, delayToPropogate } from './test-utils'
import {
Query,
JSContentCodec,
Expand Down
Loading

0 comments on commit 52b1540

Please sign in to comment.