diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt index 9af6fcb06..18ee2ad81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt @@ -18,11 +18,10 @@ import org.thoughtcrime.securesms.database.model.IdentityRecord import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.GroupId -import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupProtoUtil import org.thoughtcrime.securesms.groups.LiveGroup -import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason -import org.thoughtcrime.securesms.jobs.RetrieveProfileJob +import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult +import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -30,12 +29,12 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil import org.thoughtcrime.securesms.util.FeatureFlags import java.io.IOException import java.util.Optional -import java.util.concurrent.TimeUnit private val TAG = Log.tag(ConversationSettingsRepository::class.java) class ConversationSettingsRepository( - private val context: Context + private val context: Context, + private val groupManagementRepository: GroupManagementRepository = GroupManagementRepository(context) ) { @WorkerThread @@ -152,36 +151,7 @@ class ConversationSettingsRepository( } fun addMembers(groupId: GroupId, selected: List, consumer: (GroupAddMembersResult) -> Unit) { - SignalExecutors.BOUNDED.execute { - val record: GroupDatabase.GroupRecord = SignalDatabase.groups.getGroup(groupId).get() - - if (record.isAnnouncementGroup) { - val needsResolve = selected - .map { Recipient.resolved(it) } - .filter { it.announcementGroupCapability != Recipient.Capability.SUPPORTED && !it.isSelf } - .map { it.id } - .toSet() - - ApplicationDependencies.getJobManager().runSynchronously(RetrieveProfileJob(needsResolve), TimeUnit.SECONDS.toMillis(10)) - - val updatedWithCapabilities = needsResolve.map { Recipient.resolved(it) } - - if (updatedWithCapabilities.any { it.announcementGroupCapability != Recipient.Capability.SUPPORTED }) { - consumer(GroupAddMembersResult.Failure(GroupChangeFailureReason.NOT_ANNOUNCEMENT_CAPABLE)) - return@execute - } - } - - consumer( - try { - val groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected) - GroupAddMembersResult.Success(groupActionResult.addedMemberCount, Recipient.resolvedList(groupActionResult.invitedMembers)) - } catch (e: Exception) { - Log.d(TAG, "Failure to add member", e) - GroupAddMembersResult.Failure(GroupChangeFailureReason.fromException(e)) - } - ) - } + groupManagementRepository.addMembers(groupId, selected, consumer) } fun setMuteUntil(groupId: GroupId, until: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt index 7bd143d89..7b73491e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.LiveGroup +import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/GroupAddMembersResult.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/GroupAddMembersResult.kt deleted file mode 100644 index 44661b0fe..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/GroupAddMembersResult.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.thoughtcrime.securesms.components.settings.conversation - -import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason -import org.thoughtcrime.securesms.recipients.Recipient - -sealed class GroupAddMembersResult { - class Success( - val numberOfMembersAdded: Int, - val newMembersInvited: List - ) : GroupAddMembersResult() - - class Failure( - val reason: GroupChangeFailureReason - ) : GroupAddMembersResult() -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index e74fef6e6..b21fb3ed0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -119,6 +119,7 @@ import org.thoughtcrime.securesms.groups.ui.GroupErrors; import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment; import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog; import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment; +import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult; import org.thoughtcrime.securesms.groups.v2.GroupDescriptionUtil; import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; @@ -1930,8 +1931,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect lifecycleDisposable.add( groupViewModel.blockJoinRequests(ConversationFragment.this.recipient.get(), recipient) .subscribe(result -> { - if (result instanceof GroupManagementRepository.GroupManagementResult.Failure) { - int failureReason = GroupErrors.getUserDisplayMessage(((GroupManagementRepository.GroupManagementResult.Failure) result).getReason()); + if (result.isFailure()) { + int failureReason = GroupErrors.getUserDisplayMessage(((GroupBlockJoinRequestResult.Failure) result).getReason()); Toast.makeText(requireContext(), failureReason, Toast.LENGTH_SHORT).show(); } }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java index f83aa2587..9715b4079 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java @@ -26,8 +26,8 @@ import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment; +import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult; import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository; -import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository.GroupManagementResult; import org.thoughtcrime.securesms.profiles.spoofing.ReviewRecipient; import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil; import org.thoughtcrime.securesms.recipients.Recipient; @@ -220,7 +220,7 @@ final class ConversationGroupViewModel extends ViewModel { GroupLinkInviteFriendsBottomSheetDialogFragment.show(supportFragmentManager, groupId); } - public Single blockJoinRequests(@NonNull Recipient groupRecipient, @NonNull Recipient recipient) { + public Single blockJoinRequests(@NonNull Recipient groupRecipient, @NonNull Recipient recipient) { return groupManagementRepository.blockJoinRequests(groupRecipient.requireGroupId().requireV2(), recipient) .observeOn(AndroidSchedulers.mainThread()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 3eb07c6fc..b5e9432e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -1350,6 +1350,10 @@ private static final String[] GROUP_PROJECTION = { return recipients; } + + public @NonNull Set getBannedMembers() { + return DecryptedGroupUtil.bannedMembersToUuidSet(getDecryptedGroup().getBannedMembersList()); + } } public @NonNull List getGroupsToDisplayAsStories() throws BadGroupIdException { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 90f56a833..1892e1166 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -379,8 +379,10 @@ public final class GroupManager { throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException, MembershipNotSuitableForV2Exception { if (groupId.isV2()) { + GroupDatabase.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); + try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - return editor.addMembers(newMembers); + return editor.addMembers(newMembers, groupRecord.requireV2GroupProperties().getBannedMembers()); } } else { GroupDatabase.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 538ddffe3..c572b8409 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -326,7 +326,7 @@ final class GroupManagerV2 { } @WorkerThread - @NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection newMembers) + @NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection newMembers, @NonNull Set bannedMembers) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, MembershipNotSuitableForV2Exception { if (!GroupsV2CapabilityChecker.allHaveServiceId(newMembers)) { @@ -339,7 +339,7 @@ final class GroupManagerV2 { groupCandidates = GroupCandidate.withoutProfileKeyCredentials(groupCandidates); } - return commitChangeWithConflictResolution(groupOperations.createModifyGroupMembershipChange(groupCandidates, selfAci.uuid())); + return commitChangeWithConflictResolution(groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid())); } @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java deleted file mode 100644 index 00a23b2e0..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms.groups.ui.addtogroup; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.groups.GroupChangeException; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.groups.GroupManager; -import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception; -import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback; -import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; - -import java.io.IOException; -import java.util.Collections; - -final class AddToGroupRepository { - - private static final String TAG = Log.tag(AddToGroupRepository.class); - - private final Context context; - - AddToGroupRepository() { - this.context = ApplicationDependencies.getApplication(); - } - - public void add(@NonNull RecipientId recipientId, - @NonNull Recipient groupRecipient, - @NonNull GroupChangeErrorCallback error, - @NonNull Runnable success) - { - SignalExecutors.UNBOUNDED.execute(() -> { - try { - GroupId.Push pushGroupId = groupRecipient.requireGroupId().requirePush(); - - GroupManager.addMembers(context, pushGroupId, Collections.singletonList(recipientId)); - - success.run(); - } catch (GroupChangeException | MembershipNotSuitableForV2Exception | IOException e) { - Log.w(TAG, e); - error.onError(GroupChangeFailureReason.fromException(e)); - } - }); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java index cf998a73b..40229da77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java @@ -9,25 +9,31 @@ import androidx.lifecycle.ViewModelProvider; import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.groups.ui.GroupErrors; +import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult; +import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.SingleLiveEvent; +import java.util.Collections; import java.util.List; import java.util.Objects; +import kotlin.Unit; + public final class AddToGroupViewModel extends ViewModel { - private final Application context; - private final AddToGroupRepository repository; - private final RecipientId recipientId; + private final Application context; + private final GroupManagementRepository repository; + private final RecipientId recipientId; private final SingleLiveEvent events = new SingleLiveEvent<>(); private AddToGroupViewModel(@NonNull RecipientId recipientId) { this.context = ApplicationDependencies.getApplication(); this.recipientId = recipientId; - this.repository = new AddToGroupRepository(); + this.repository = new GroupManagementRepository(); } public SingleLiveEvent getEvents() { @@ -58,13 +64,15 @@ public final class AddToGroupViewModel extends ViewModel { } void onAddToGroupsConfirmed(@NonNull Event.AddToSingleGroupConfirmationEvent event) { - repository.add(recipientId, - event.groupRecipient, - error -> events.postValue(new Event.ToastEvent(context.getResources().getString(GroupErrors.getUserDisplayMessage(error)))), - () -> { - events.postValue(new Event.ToastEvent(context.getResources().getString(R.string.AddToGroupActivity_s_added_to_s, event.recipientName, event.groupName))); - events.postValue(new Event.CloseEvent()); - }); + repository.addMembers(event.groupRecipient, Collections.singletonList(recipientId), result -> { + if (result.isFailure()) { + GroupChangeFailureReason reason = ((GroupAddMembersResult.Failure) result).getReason(); + events.postValue(new Event.ToastEvent(context.getResources().getString(GroupErrors.getUserDisplayMessage(reason)))); + } else { + events.postValue(new Event.ToastEvent(context.getResources().getString(R.string.AddToGroupActivity_s_added_to_s, event.recipientName, event.groupName))); + events.postValue(new Event.CloseEvent()); + } + }); } static abstract class Event { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementRepository.kt index 5c8074d20..2fdf7880b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementRepository.kt @@ -1,41 +1,102 @@ package org.thoughtcrime.securesms.groups.v2 import android.content.Context +import androidx.core.util.Consumer import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers +import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper +import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.GroupChangeException import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason +import org.thoughtcrime.securesms.jobs.RetrieveProfileJob import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId import java.io.IOException +import java.util.concurrent.TimeUnit private val TAG: String = Log.tag(GroupManagementRepository::class.java) /** - * Single source repository for managing groups. + * Single source repository for managing GV2 groups. */ class GroupManagementRepository @JvmOverloads constructor(private val context: Context = ApplicationDependencies.getApplication()) { - fun blockJoinRequests(groupId: GroupId.V2, recipient: Recipient): Single { + fun addMembers(groupRecipient: Recipient, selected: List, consumer: Consumer) { + addMembers(null, groupRecipient, selected, consumer) + } + + fun addMembers(groupId: GroupId, selected: List, consumer: Consumer) { + addMembers(groupId, null, selected, consumer) + } + + private fun addMembers(potentialGroupId: GroupId?, potentialGroupRecipient: Recipient?, selected: List, consumer: Consumer) { + SignalExecutors.UNBOUNDED.execute { + val groupId: GroupId.Push = potentialGroupId?.requirePush() ?: potentialGroupRecipient!!.requireGroupId().requirePush() + val record: GroupDatabase.GroupRecord = SignalDatabase.groups.getGroup(groupId).get() + + val recipients = selected.map(Recipient::resolved) + .filterNot { it.hasServiceId() && it.isRegistered } + .toList() + + try { + DirectoryHelper.refreshDirectoryFor(context, recipients, false) + recipients.forEach { Recipient.live(it.id).refresh() } + } catch (e: IOException) { + consumer.accept(GroupAddMembersResult.Failure(GroupChangeFailureReason.NETWORK)) + } + + if (record.isAnnouncementGroup) { + val needsResolve = selected + .map { Recipient.resolved(it) } + .filter { it.announcementGroupCapability != Recipient.Capability.SUPPORTED && !it.isSelf } + .map { it.id } + .toSet() + + ApplicationDependencies.getJobManager().runSynchronously(RetrieveProfileJob(needsResolve), TimeUnit.SECONDS.toMillis(10)) + + val updatedWithCapabilities = needsResolve.map { Recipient.resolved(it) } + + if (updatedWithCapabilities.any { it.announcementGroupCapability != Recipient.Capability.SUPPORTED }) { + consumer.accept(GroupAddMembersResult.Failure(GroupChangeFailureReason.NOT_ANNOUNCEMENT_CAPABLE)) + return@execute + } + } + + consumer.accept( + try { + val toAdd = selected.filter { Recipient.resolved(it).isRegistered } + if (toAdd.isNotEmpty()) { + val groupActionResult = GroupManager.addMembers(context, groupId, toAdd) + GroupAddMembersResult.Success(groupActionResult.addedMemberCount, Recipient.resolvedList(groupActionResult.invitedMembers)) + } else { + GroupAddMembersResult.Failure(GroupChangeFailureReason.NOT_GV2_CAPABLE) + } + } catch (e: Exception) { + Log.d(TAG, "Failure to add member", e) + GroupAddMembersResult.Failure(GroupChangeFailureReason.fromException(e)) + } + ) + } + } + + fun blockJoinRequests(groupId: GroupId.V2, recipient: Recipient): Single { return Single.fromCallable { try { GroupManager.ban(context, groupId, recipient.id) - GroupManagementResult.Success + GroupBlockJoinRequestResult.Success } catch (e: GroupChangeException) { Log.w(TAG, e) - GroupManagementResult.Failure(GroupChangeFailureReason.fromException(e)) + GroupBlockJoinRequestResult.Failure(GroupChangeFailureReason.fromException(e)) } catch (e: IOException) { Log.w(TAG, e) - GroupManagementResult.Failure(GroupChangeFailureReason.fromException(e)) + GroupBlockJoinRequestResult.Failure(GroupChangeFailureReason.fromException(e)) } }.subscribeOn(Schedulers.io()) } - - sealed class GroupManagementResult { - object Success : GroupManagementResult() - data class Failure(val reason: GroupChangeFailureReason) : GroupManagementResult() - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementResults.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementResults.kt new file mode 100644 index 000000000..2d67493f6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupManagementResults.kt @@ -0,0 +1,18 @@ +package org.thoughtcrime.securesms.groups.v2 + +import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason +import org.thoughtcrime.securesms.recipients.Recipient + +sealed class GroupBlockJoinRequestResult { + object Success : GroupBlockJoinRequestResult() + class Failure(val reason: GroupChangeFailureReason) : GroupBlockJoinRequestResult() + + fun isFailure() = this is Failure +} + +sealed class GroupAddMembersResult { + class Success(val numberOfMembersAdded: Int, val newMembersInvited: List) : GroupAddMembersResult() + class Failure(val reason: GroupChangeFailureReason) : GroupAddMembersResult() + + fun isFailure() = this is Failure +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java index 955fe0fa3..6691f9e30 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java @@ -132,6 +132,20 @@ public final class DecryptedGroupUtil { return uuidList; } + public static Set bannedMembersToUuidSet(Collection membersList) { + Set uuidSet = new HashSet<>(membersList.size()); + + for (DecryptedBannedMember member : membersList) { + UUID uuid = toUuid(member); + + if (!UuidUtil.UNKNOWN_UUID.equals(uuid)) { + uuidSet.add(uuid); + } + } + + return uuidSet; + } + public static UUID toUuid(DecryptedMember member) { return toUuid(member.getUuid()); } @@ -140,6 +154,10 @@ public final class DecryptedGroupUtil { return toUuid(member.getUuid()); } + public static UUID toUuid(DecryptedBannedMember member) { + return toUuid(member.getUuid()); + } + private static UUID toUuid(ByteString memberUuid) { return UuidUtil.fromByteStringOrUnknown(memberUuid); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java index 2ce0bdee6..3e06609ba 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java @@ -160,10 +160,13 @@ public final class GroupsV2Operations { return GroupChange.Actions.newBuilder().setModifyDescription(createModifyGroupDescriptionAction(description)); } - public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set membersToAdd, UUID selfUuid) { + public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set membersToAdd, Set bannedMembers, UUID selfUuid) { final GroupOperations groupOperations = forGroup(groupSecretParams); - GroupChange.Actions.Builder actions = createUnbanUuidsChange(membersToAdd.stream().map(GroupCandidate::getUuid).collect(Collectors.toSet())); + Set membersToUnban = membersToAdd.stream().map(GroupCandidate::getUuid).filter(bannedMembers::contains).collect(Collectors.toSet()); + + GroupChange.Actions.Builder actions = membersToUnban.isEmpty() ? GroupChange.Actions.newBuilder() + : createUnbanUuidsChange(membersToUnban); for (GroupCandidate credential : membersToAdd) { Member.Role newMemberRole = Member.Role.DEFAULT; @@ -479,7 +482,7 @@ public final class GroupsV2Operations { *

* Also, if you know it's version 0, do not verify because changes for version 0 * are not signed, but should be empty. - * @return {@link Optional#absent} if the epoch for the change is higher that this code can decrypt. + * @return {@link Optional#empty()} if the epoch for the change is higher that this code can decrypt. */ public Optional decryptChange(GroupChange groupChange, boolean verifySignature) throws InvalidProtocolBufferException, VerificationFailedException, InvalidGroupStateException diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java index e8b89025d..03e480b59 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java @@ -95,7 +95,7 @@ public final class GroupsV2Operations_decrypt_change_Test { ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); - assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), self) + assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), Collections.emptySet(), self) .setRevision(10), DecryptedGroupChange.newBuilder() .setRevision(10) @@ -103,8 +103,7 @@ public final class GroupsV2Operations_decrypt_change_Test { .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(10) - .setUuid(UuidUtil.toByteString(newMember))) - .addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build())); + .setUuid(UuidUtil.toByteString(newMember)))); } @Test @@ -131,7 +130,7 @@ public final class GroupsV2Operations_decrypt_change_Test { ProfileKey profileKey = newProfileKey(); GroupCandidate groupCandidate = groupCandidate(newMember, profileKey); - assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), self) + assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), Collections.emptySet(), self) .setRevision(10), DecryptedGroupChange.newBuilder() .setRevision(10) @@ -139,8 +138,7 @@ public final class GroupsV2Operations_decrypt_change_Test { .setRole(Member.Role.DEFAULT) .setProfileKey(ByteString.copyFrom(profileKey.serialize())) .setJoinedAtRevision(10) - .setUuid(UuidUtil.toByteString(newMember))) - .addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build())); + .setUuid(UuidUtil.toByteString(newMember)))); } @Test(expected = InvalidGroupStateException.class) @@ -222,7 +220,7 @@ public final class GroupsV2Operations_decrypt_change_Test { UUID newMember = UUID.randomUUID(); GroupCandidate groupCandidate = groupCandidate(newMember); - assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), self) + assertDecryption(groupOperations.createModifyGroupMembershipChange(Collections.singleton(groupCandidate), Collections.emptySet(), self) .setRevision(13), DecryptedGroupChange.newBuilder() .setRevision(13) @@ -230,8 +228,7 @@ public final class GroupsV2Operations_decrypt_change_Test { .setAddedByUuid(UuidUtil.toByteString(self)) .setUuidCipherText(groupOperations.encryptUuid(newMember)) .setRole(Member.Role.DEFAULT) - .setUuid(UuidUtil.toByteString(newMember))) - .addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build())); + .setUuid(UuidUtil.toByteString(newMember)))); } @Test