From 77735dcba88a969f30dccf6a3332dfd0603adbee Mon Sep 17 00:00:00 2001 From: Cameron Voell <1103838+cameronvoell@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:53:25 -0700 Subject: [PATCH] Latest libxmtp - permissions and description field updates (#360) * update libxmtp-swift references * Updated for latest bindings, fixed tests * Add group description functions * Added funcitons for updating permissions and update permission test --------- Co-authored-by: cameronvoell --- Package.resolved | 6 +- Package.swift | 2 +- Sources/XMTPiOS/Conversations.swift | 10 ++- Sources/XMTPiOS/Group.swift | 41 ++++++++- Sources/XMTPiOS/Mls/PermissionPolicySet.swift | 90 +++++++++++++++++++ Tests/XMTPTests/GroupPermissionsTests.swift | 49 ++++++++-- Tests/XMTPTests/GroupTests.swift | 32 +++---- XMTP.podspec | 4 +- 8 files changed, 198 insertions(+), 36 deletions(-) create mode 100644 Sources/XMTPiOS/Mls/PermissionPolicySet.swift diff --git a/Package.resolved b/Package.resolved index 94c761de..dd6bc2bc 100644 --- a/Package.resolved +++ b/Package.resolved @@ -39,10 +39,10 @@ { "identity" : "libxmtp-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/xmtp/libxmtp-swift", + "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { - "revision" : "957945a1886bce6e290a7e763866d80ec6ba475b", - "version" : "0.5.3-beta2" + "revision" : "577a61e568cb5aaff10486172b1ef055faeff5b9", + "version" : "0.5.4-beta1" } }, { diff --git a/Package.swift b/Package.swift index 512524f8..29cb1aa9 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( .package(url: "https://github.com/1024jp/GzipSwift", from: "5.2.0"), .package(url: "https://github.com/bufbuild/connect-swift", exact: "0.12.0"), .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"), - .package(url: "https://github.com/xmtp/libxmtp-swift.git", branch: "main"), + .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "0.5.4-beta1"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 0d752bc6..79d66a5d 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -143,9 +143,10 @@ public actor Conversations { } public func newGroup(with addresses: [String], - permissions: GroupPermissions = .allMembers, + permissions: GroupPermissionPreconfiguration = .allMembers, name: String = "", - imageUrlSquare: String = "" + imageUrlSquare: String = "", + description: String = "" ) async throws -> Group { guard let v3Client = client.v3Client else { throw GroupError.alphaMLSNotEnabled @@ -175,9 +176,10 @@ public actor Conversations { throw GroupError.memberNotRegistered(erroredAddresses) } let group = try await v3Client.conversations().createGroup(accountAddresses: addresses, - opts: FfiCreateGroupOptions(permissions: permissions, + opts: FfiCreateGroupOptions(permissions: GroupPermissionPreconfiguration.toFfiGroupPermissionOptions(option: permissions), groupName: name, - groupImageUrlSquare: imageUrlSquare + groupImageUrlSquare: imageUrlSquare, + groupDescription: description )).fromFFI(client: client) try await client.contacts.allowGroups(groupIds: [group.id]) return group diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index 39f401e1..b6e182ef 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -99,8 +99,8 @@ public struct Group: Identifiable, Equatable, Hashable { try ffiGroup.superAdminList() } - public func permissionLevel() throws -> GroupPermissions { - return try permissions().policyType() + public func permissionPolicySet() throws -> PermissionPolicySet { + return PermissionPolicySet(ffiPermissionPolicySet: try permissions().policySet()) } public func creatorInboxId() throws -> String { @@ -157,6 +157,10 @@ public struct Group: Identifiable, Equatable, Hashable { return try ffiGroup.groupImageUrlSquare() } + public func groupDescription() throws -> String { + return try ffiGroup.groupDescription() + } + public func updateGroupName(groupName: String) async throws { try await ffiGroup.updateGroupName(groupName: groupName) } @@ -164,6 +168,39 @@ public struct Group: Identifiable, Equatable, Hashable { public func updateGroupImageUrlSquare(imageUrlSquare: String) async throws { try await ffiGroup.updateGroupImageUrlSquare(groupImageUrlSquare: imageUrlSquare) } + + public func updateGroupDescription(groupDescription: String) async throws { + try await ffiGroup.updateGroupDescription(groupDescription: groupDescription) + } + + public func updateAddMemberPermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.addMember, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: nil) + } + + public func updateRemoveMemberPermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.removeMember, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: nil) + } + + public func updateAddAdminPermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.addAdmin, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: nil) + } + + public func updateRemoveAdminPermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.removeAdmin, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: nil) + } + + public func updateGroupNamePermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.updateMetadata, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: FfiMetadataField.groupName) + } + + public func updateGroupDescriptionPermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.updateMetadata, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: FfiMetadataField.description) + } + + public func updateGroupImageUrlSquarePermission(newPermissionOption: PermissionOption) async throws { + try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.updateMetadata, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: FfiMetadataField.imageUrlSquare) + } + public func processMessage(envelopeBytes: Data) async throws -> DecodedMessage { let message = try await ffiGroup.processStreamedGroupMessage(envelopeBytes: envelopeBytes) diff --git a/Sources/XMTPiOS/Mls/PermissionPolicySet.swift b/Sources/XMTPiOS/Mls/PermissionPolicySet.swift new file mode 100644 index 00000000..4e19b68f --- /dev/null +++ b/Sources/XMTPiOS/Mls/PermissionPolicySet.swift @@ -0,0 +1,90 @@ +import Foundation +import LibXMTP + +public enum PermissionOption { + case allow + case deny + case admin + case superAdmin + case unknown + + static func toFfiPermissionPolicy(option: PermissionOption) -> FfiPermissionPolicy { + switch option { + case .allow: + return .allow + case .deny: + return .deny + case .admin: + return .admin + case .superAdmin: + return .superAdmin + case .unknown: + return .other + } + } + + static func fromFfiPermissionPolicy(ffiPolicy: FfiPermissionPolicy) -> PermissionOption { + switch ffiPolicy { + case .allow: + return .allow + case .deny: + return .deny + case .admin: + return .admin + case .superAdmin: + return .superAdmin + case .doesNotExist, .other: + return .unknown + } + } +} + +public enum GroupPermissionPreconfiguration { + case allMembers + case adminOnly + + static func toFfiGroupPermissionOptions(option: GroupPermissionPreconfiguration) -> FfiGroupPermissionsOptions { + switch option { + case .allMembers: + return .allMembers + case .adminOnly: + return .adminOnly + } + } +} + +public class PermissionPolicySet { + let ffiPermissionPolicySet: FfiPermissionPolicySet + + init(ffiPermissionPolicySet: FfiPermissionPolicySet) { + self.ffiPermissionPolicySet = ffiPermissionPolicySet + } + + var addMemberPolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.addMemberPolicy) + } + + var removeMemberPolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.removeMemberPolicy) + } + + var addAdminPolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.addAdminPolicy) + } + + var removeAdminPolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.removeAdminPolicy) + } + + var updateGroupNamePolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.updateGroupNamePolicy) + } + + var updateGroupDescriptionPolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.updateGroupDescriptionPolicy) + } + + var updateGroupImagePolicy: PermissionOption { + return PermissionOption.fromFfiPermissionPolicy(ffiPolicy: ffiPermissionPolicySet.updateGroupImageUrlSquarePolicy) + } +} diff --git a/Tests/XMTPTests/GroupPermissionsTests.swift b/Tests/XMTPTests/GroupPermissionsTests.swift index 2076d744..dce45887 100644 --- a/Tests/XMTPTests/GroupPermissionsTests.swift +++ b/Tests/XMTPTests/GroupPermissionsTests.swift @@ -72,7 +72,7 @@ class GroupPermissionTests: XCTestCase { try await fixtures.aliceClient.conversations.sync() let aliceGroup = try await fixtures.aliceClient.conversations.groups().first! - XCTAssertTrue(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssertFalse(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) XCTAssertTrue(try bobGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) XCTAssertFalse(try aliceGroup.isCreator()) XCTAssertFalse(try aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) @@ -81,8 +81,8 @@ class GroupPermissionTests: XCTestCase { let adminList = try bobGroup.listAdmins() let superAdminList = try bobGroup.listSuperAdmins() - XCTAssertEqual(adminList.count, 1) - XCTAssertTrue(adminList.contains(fixtures.bobClient.inboxID)) + XCTAssertEqual(adminList.count, 0) + XCTAssertFalse(adminList.contains(fixtures.bobClient.inboxID)) XCTAssertEqual(superAdminList.count, 1) XCTAssertTrue(superAdminList.contains(fixtures.bobClient.inboxID)) } @@ -93,7 +93,7 @@ class GroupPermissionTests: XCTestCase { try await fixtures.aliceClient.conversations.sync() let aliceGroup = try await fixtures.aliceClient.conversations.groups().first! - XCTAssertTrue(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssertFalse(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) XCTAssertTrue(try bobGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) XCTAssertFalse(try aliceGroup.isCreator()) XCTAssertFalse(try aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) @@ -101,8 +101,8 @@ class GroupPermissionTests: XCTestCase { var adminList = try bobGroup.listAdmins() var superAdminList = try bobGroup.listSuperAdmins() - XCTAssertEqual(adminList.count, 1) - XCTAssertTrue(adminList.contains(fixtures.bobClient.inboxID)) + XCTAssertEqual(adminList.count, 0) + XCTAssertFalse(adminList.contains(fixtures.bobClient.inboxID)) XCTAssertEqual(superAdminList.count, 1) XCTAssertTrue(superAdminList.contains(fixtures.bobClient.inboxID)) @@ -125,7 +125,7 @@ class GroupPermissionTests: XCTestCase { superAdminList = try bobGroup.listSuperAdmins() XCTAssertTrue(try aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) - XCTAssertEqual(adminList.count, 2) + XCTAssertEqual(adminList.count, 1) XCTAssertTrue(adminList.contains(fixtures.aliceClient.inboxID)) XCTAssertEqual(superAdminList.count, 1) @@ -144,7 +144,7 @@ class GroupPermissionTests: XCTestCase { superAdminList = try bobGroup.listSuperAdmins() XCTAssertFalse(try aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) - XCTAssertEqual(adminList.count, 1) + XCTAssertEqual(adminList.count, 0) XCTAssertFalse(adminList.contains(fixtures.aliceClient.inboxID)) XCTAssertEqual(superAdminList.count, 1) @@ -254,4 +254,37 @@ class GroupPermissionTests: XCTestCase { XCTAssertEqual(try bobGroup.groupName(), "Alice group name") XCTAssertEqual(try aliceGroup.groupName(), "Alice group name") } + + func testCanUpdatePermissions() async throws { + let fixtures = try await localFixtures() + let bobGroup = try await fixtures.bobClient.conversations.newGroup( + with: [fixtures.alice.walletAddress, fixtures.caro.walletAddress], + permissions: .adminOnly + ) + try await fixtures.aliceClient.conversations.sync() + let aliceGroup = try await fixtures.aliceClient.conversations.groups().first! + + // Verify that Alice cannot update group description + XCTAssertEqual(try bobGroup.groupDescription(), "") + await assertThrowsAsyncError( + try await aliceGroup.updateGroupDescription(groupDescription: "new group description") + ) + + try await aliceGroup.sync() + try await bobGroup.sync() + XCTAssertEqual(try bobGroup.permissionPolicySet().updateGroupDescriptionPolicy, .admin) + + // Update group description permissions so Alice can update + try await bobGroup.updateGroupDescriptionPermission(newPermissionOption: .allow) + try await bobGroup.sync() + try await aliceGroup.sync() + XCTAssertEqual(try bobGroup.permissionPolicySet().updateGroupDescriptionPolicy, .allow) + + // Verify that Alice can now update group description + try await aliceGroup.updateGroupDescription(groupDescription: "Alice group description") + try await aliceGroup.sync() + try await bobGroup.sync() + XCTAssertEqual(try bobGroup.groupDescription(), "Alice group description") + XCTAssertEqual(try aliceGroup.groupDescription(), "Alice group description") + } } diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index c4d2f315..924eda96 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -118,19 +118,19 @@ class GroupTests: XCTestCase { XCTAssertEqual(try aliceGroup.members.count, 3) XCTAssertEqual(try bobGroup.members.count, 3) - XCTAssertEqual(try bobGroup.permissionLevel(), .allMembers) - XCTAssertEqual(try aliceGroup.permissionLevel(), .allMembers) + XCTAssertEqual(try bobGroup.permissionPolicySet().addMemberPolicy, .allow) + XCTAssertEqual(try aliceGroup.permissionPolicySet().addMemberPolicy, .allow) - XCTAssert(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) - XCTAssert(try !bobGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) - XCTAssert(try aliceGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) - XCTAssert(try !aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) + XCTAssert(try bobGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssert(try !bobGroup.isSuperAdmin(inboxId: fixtures.aliceClient.inboxID)) + XCTAssert(try aliceGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssert(try !aliceGroup.isSuperAdmin(inboxId: fixtures.aliceClient.inboxID)) } func testCanCreateAGroupWithAdminPermissions() async throws { let fixtures = try await localFixtures() - let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address], permissions: GroupPermissions.adminOnly) + let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address], permissions: GroupPermissionPreconfiguration.adminOnly) try await fixtures.aliceClient.conversations.sync() let aliceGroup = try await fixtures.aliceClient.conversations.groups().first! XCTAssert(!bobGroup.id.isEmpty) @@ -170,12 +170,12 @@ class GroupTests: XCTestCase { XCTAssertEqual(try aliceGroup.members.count, 2) XCTAssertEqual(try bobGroup.members.count, 2) - XCTAssertEqual(try bobGroup.permissionLevel(), .adminOnly) - XCTAssertEqual(try aliceGroup.permissionLevel(), .adminOnly) - XCTAssert(try bobGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) - XCTAssert(try !bobGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) - XCTAssert(try aliceGroup.isAdmin(inboxId: fixtures.bobClient.inboxID)) - XCTAssert(try !aliceGroup.isAdmin(inboxId: fixtures.aliceClient.inboxID)) + XCTAssertEqual(try bobGroup.permissionPolicySet().addMemberPolicy, .admin) + XCTAssertEqual(try aliceGroup.permissionPolicySet().addMemberPolicy, .admin) + XCTAssert(try bobGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssert(try !bobGroup.isSuperAdmin(inboxId: fixtures.aliceClient.inboxID)) + XCTAssert(try aliceGroup.isSuperAdmin(inboxId: fixtures.bobClient.inboxID)) + XCTAssert(try !aliceGroup.isSuperAdmin(inboxId: fixtures.aliceClient.inboxID)) } func testCanListGroups() async throws { @@ -481,9 +481,9 @@ class GroupTests: XCTestCase { let bobGroup = try await fixtures.bobClient.conversations.groups()[0] try await bobGroup.sync() - var bobMessagesCount = try await bobGroup.messages().count - var bobMessagesUnpublishedCount = try await bobGroup.messages(deliveryStatus: .unpublished).count - var bobMessagesPublishedCount = try await bobGroup.messages(deliveryStatus: .published).count + let bobMessagesCount = try await bobGroup.messages().count + let bobMessagesUnpublishedCount = try await bobGroup.messages(deliveryStatus: .unpublished).count + let bobMessagesPublishedCount = try await bobGroup.messages(deliveryStatus: .published).count XCTAssertEqual(2, bobMessagesCount) XCTAssertEqual(0, bobMessagesUnpublishedCount) XCTAssertEqual(2, bobMessagesPublishedCount) diff --git a/XMTP.podspec b/XMTP.podspec index e7cb06b5..c8f69b47 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "XMTP" - spec.version = "0.13.0" + spec.version = "0.13.1" spec.summary = "XMTP SDK Cocoapod" # This description is used to generate tags and improve search results. @@ -44,5 +44,5 @@ Pod::Spec.new do |spec| spec.dependency "web3.swift" spec.dependency "GzipSwift" spec.dependency "Connect-Swift", "= 0.12.0" - spec.dependency 'LibXMTP', '= 0.5.4-beta0' + spec.dependency 'LibXMTP', '= 0.5.4-beta1' end