diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift index 45fc3fac5..70eff427f 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -118,12 +118,13 @@ public final class ClosedGroupsProtocol : NSObject { } public static func leave(_ groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - removeMembers([ getUserHexEncodedPublicKey() ], from: groupPublicKey, using: transaction) + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() + removeMembers([ userPublicKey ], from: groupPublicKey, using: transaction) } public static func removeMembers(_ membersToRemove: Set, from groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { // Prepare - let userPublicKey = getUserHexEncodedPublicKey() + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() let isUserLeaving = membersToRemove.contains(userPublicKey) guard !isUserLeaving || membersToRemove.count == 1 else { return print("[Loki] Can't remove self and others simultaneously.") @@ -158,6 +159,7 @@ public final class ClosedGroupsProtocol : NSObject { // Establish sessions if needed establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device // Send out the user's new ratchet to all members (minus the removed ones) and their linked devices using established channels + let userPublicKey = getUserHexEncodedPublicKey() let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: userPublicKey) for member in members { // This internally takes care of multi device @@ -223,8 +225,9 @@ public final class ClosedGroupsProtocol : NSObject { /// Invoked upon receiving a group update. A group update is sent out when a group's name is changed, when new users are added, when users leave or are /// kicked, or if the group admins are changed. private static func handleInfoMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, - using transaction: YapDatabaseReadWriteTransaction) { + using transaction: YapDatabaseReadWriteTransaction) { // Unwrap the message + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let name = closedGroupUpdate.name let senderKeys = closedGroupUpdate.senderKeys @@ -244,27 +247,32 @@ public final class ClosedGroupsProtocol : NSObject { guard isSenderAdmin else { return print("[Loki] Ignoring closed group update from non-admin.") } - // Establish sessions if needed (it's important that this happens before the code below) - establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device // Store the ratchets for any new members (it's important that this happens before the code below) senderKeys.forEach { senderKey in let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey, ratchet: ratchet, using: transaction) } - // Delete all ratchets and send out the user's new ratchet using established channels if any member of the group left or was removed + // Delete all ratchets and either: + // • Send out the user's new ratchet using established channels if other members of the group left or were removed + // • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed let oldMembers = group.groupMemberIds + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() + let wasUserRemoved = !members.contains(userPublicKey) if Set(members).intersection(oldMembers) != Set(oldMembers) { Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) - let userPublicKey = getUserHexEncodedPublicKey() - let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: userPublicKey) - for member in members { - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + if wasUserRemoved { + Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) + } else { + establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device + let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: userPublicKey) + for member in members { + let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device + } } } // Update the group @@ -272,11 +280,12 @@ public final class ClosedGroupsProtocol : NSObject { let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupIDAsData, groupType: .closedGroup, adminIds: admins) thread.setGroupModel(newGroupModel, with: transaction) // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) + let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate + let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: infoMessageType) infoMessage.save(with: transaction) } - /// Invoked upon receiving a chain key from another user. + /// Invoked upon receiving a sender key from another user. private static func handleSenderKeyMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() guard let senderKey = closedGroupUpdate.senderKeys.first else { @@ -315,4 +324,3 @@ public final class ClosedGroupsProtocol : NSObject { return result } } -