mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-04 15:35:38 +01:00
Add additional group terminate checks.
This commit is contained in:
@@ -744,7 +744,8 @@ final class GroupManagerV2 {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response));
|
||||
RecipientId terminatorRecipientId = (decryptedGroupState.terminated && !previousGroupState.terminated) ? Recipient.self().getId() : null;
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response), terminatorRecipientId);
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
@@ -986,7 +987,7 @@ final class GroupManagerV2 {
|
||||
}
|
||||
} else if (groupAlreadyExists && requestToJoin) {
|
||||
Log.i(TAG, "Group already exists, but we are requesting to join, updating with new placeholder, alreadyPending: " + isAlreadyPendingApproval);
|
||||
groupDatabase.update(groupMasterKey, decryptedGroup, null);
|
||||
groupDatabase.update(groupMasterKey, decryptedGroup, null, null);
|
||||
}
|
||||
|
||||
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
|
||||
@@ -1250,7 +1251,7 @@ final class GroupManagerV2 {
|
||||
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, DecryptChangeVerificationMode.alreadyTrusted()).get();
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
|
||||
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null);
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null, null);
|
||||
|
||||
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException;
|
||||
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
|
||||
import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupTerminatedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -18,11 +19,13 @@ public enum GroupChangeFailureReason {
|
||||
NOT_A_MEMBER,
|
||||
BUSY,
|
||||
NETWORK,
|
||||
GROUP_TERMINATED,
|
||||
OTHER;
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
public static @NonNull GroupChangeFailureReason fromException(@NonNull Throwable e) {
|
||||
if (e instanceof MembershipNotSuitableForV2Exception) return GroupChangeFailureReason.NOT_GV2_CAPABLE;
|
||||
if (e instanceof GroupTerminatedException) return GroupChangeFailureReason.GROUP_TERMINATED;
|
||||
if (e instanceof IOException) return GroupChangeFailureReason.NETWORK;
|
||||
if (e instanceof GroupNotAMemberException) return GroupChangeFailureReason.NOT_A_MEMBER;
|
||||
if (e instanceof GroupChangeBusyException) return GroupChangeFailureReason.BUSY;
|
||||
|
||||
@@ -21,6 +21,7 @@ public final class GroupErrors {
|
||||
case NOT_A_MEMBER : return R.string.ManageGroupActivity_youre_not_a_member_of_the_group;
|
||||
case BUSY : return R.string.ManageGroupActivity_failed_to_update_the_group_please_retry_later;
|
||||
case NETWORK : return R.string.ManageGroupActivity_failed_to_update_the_group_due_to_a_network_error_please_retry_later;
|
||||
case GROUP_TERMINATED : return R.string.MessageRecord_the_group_was_terminated;
|
||||
default : return R.string.ManageGroupActivity_failed_to_update_the_group;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,5 +3,6 @@ package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
enum FetchGroupDetailsError {
|
||||
GroupLinkNotActive,
|
||||
BannedFromGroup,
|
||||
NetworkError
|
||||
NetworkError,
|
||||
GroupTerminated
|
||||
}
|
||||
|
||||
+5
@@ -175,6 +175,10 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
groupName.setText(R.string.GroupJoinBottomSheetDialogFragment_link_error);
|
||||
groupDetails.setText(R.string.GroupJoinBottomSheetDialogFragment_joining_via_this_link_failed_try_joining_again_later);
|
||||
break;
|
||||
case GroupTerminated:
|
||||
groupName.setText(R.string.GroupJoinBottomSheetDialogFragment_cant_join_group);
|
||||
groupDetails.setText(R.string.GroupJoinBottomSheetDialogFragment_this_group_has_been_ended);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +188,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
case BANNED : return getString(R.string.GroupJoinBottomSheetDialogFragment_you_cant_join_this_group_via_the_group_link_because_an_admin_removed_you);
|
||||
case NETWORK_ERROR : return getString(R.string.GroupJoinBottomSheetDialogFragment_encountered_a_network_error);
|
||||
case LIMIT_REACHED : return getString(R.string.GroupJoinBottomSheetDialogFragment_group_limit_reached);
|
||||
case GROUP_TERMINATED : return getString(R.string.GroupJoinBottomSheetDialogFragment_this_group_has_been_ended);
|
||||
default : return getString(R.string.GroupJoinBottomSheetDialogFragment_unable_to_join_group_please_try_again_later);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupTerminatedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -38,6 +39,8 @@ final class GroupJoinRepository {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
callback.onComplete(getGroupDetails());
|
||||
} catch (GroupTerminatedException e) {
|
||||
callback.onError(FetchGroupDetailsError.GroupTerminated);
|
||||
} catch (IOException e) {
|
||||
callback.onError(FetchGroupDetailsError.NetworkError);
|
||||
} catch (GroupLinkNotActiveException e) {
|
||||
@@ -60,6 +63,9 @@ final class GroupJoinRepository {
|
||||
groupDetails.getAvatarBytes());
|
||||
|
||||
callback.onComplete(new JoinGroupSuccess(groupActionResult.getGroupRecipient(), groupActionResult.getThreadId()));
|
||||
} catch (GroupTerminatedException e) {
|
||||
Log.w(TAG, "Group is terminated", e);
|
||||
callback.onError(JoinGroupError.GROUP_TERMINATED);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Network error", e);
|
||||
callback.onError(JoinGroupError.NETWORK_ERROR);
|
||||
|
||||
+1
@@ -7,4 +7,5 @@ enum JoinGroupError {
|
||||
FAILED,
|
||||
LIMIT_REACHED,
|
||||
NETWORK_ERROR,
|
||||
GROUP_TERMINATED,
|
||||
}
|
||||
|
||||
+115
-14
@@ -41,10 +41,12 @@ import org.thoughtcrime.securesms.mms.MmsException
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
@@ -52,6 +54,7 @@ import org.whispersystems.signalservice.api.groupsv2.getChangedFields
|
||||
import org.whispersystems.signalservice.api.groupsv2.isSilent
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupTerminatedException
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
@@ -214,6 +217,21 @@ class GroupsV2StateProcessor private constructor(
|
||||
|
||||
if (currentLocalState != null && DecryptedGroupUtil.isPendingOrRequesting(currentLocalState, serviceIds)) {
|
||||
Log.w(TAG, "$logPrefix Unable to query server for group. Server says we're not in group, but we think we are a pending or requesting member")
|
||||
try {
|
||||
groupsApi.getGroupJoinInfo(groupSecretParams, Optional.empty(), groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
} catch (e: GroupTerminatedException) {
|
||||
Log.i(TAG, "$logPrefix Group was terminated while join request was pending, marking locally")
|
||||
profileAndMessageHelper.markTerminatedLocally()
|
||||
} catch (e: GroupLinkNotActiveException) {
|
||||
if (e.reason == GroupLinkNotActiveException.Reason.BANNED) {
|
||||
Log.i(TAG, "$logPrefix Join request was rejected (banned) while pending, marking locally")
|
||||
profileAndMessageHelper.markJoinRequestRejectedLocally()
|
||||
} else {
|
||||
Log.i(TAG, "$logPrefix Group link not active while checking pending join for group termination")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "$logPrefix Network error while checking if group terminated", e)
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "$logPrefix Unable to query server for group $groupId server says we're not in group, we agree, inserting leave message")
|
||||
profileAndMessageHelper.leaveGroupLocally(serviceIds)
|
||||
@@ -525,20 +543,20 @@ class GroupsV2StateProcessor private constructor(
|
||||
Log.i(TAG, "$logPrefix Local state (revision: ${currentLocalState?.revision}) does not match, updating to ${updatedGroupState.revision}")
|
||||
}
|
||||
|
||||
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (updatedGroupState.terminated && (currentLocalState == null || !currentLocalState.terminated)) {
|
||||
val terminatingChange = groupStateDiff.serverHistory
|
||||
val terminatorRecipientId: RecipientId? = if (updatedGroupState.terminated && (currentLocalState == null || !currentLocalState.terminated)) {
|
||||
groupStateDiff.serverHistory
|
||||
.mapNotNull { it.change }
|
||||
.firstOrNull { it.terminateGroup }
|
||||
?.let { ServiceId.parseOrNull(it.editorServiceIdBytes) }
|
||||
?.let { RecipientId.from(it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (terminatingChange != null) {
|
||||
val editorServiceId = ServiceId.parseOrNull(terminatingChange.editorServiceIdBytes)
|
||||
if (editorServiceId != null) {
|
||||
val terminatorRecipientId = RecipientId.from(editorServiceId)
|
||||
SignalDatabase.groups.setTerminatedBy(groupId, terminatorRecipientId)
|
||||
}
|
||||
}
|
||||
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
|
||||
|
||||
if (terminatorRecipientId != null) {
|
||||
profileAndMessageHelper.stopAllTypingForGroup()
|
||||
}
|
||||
|
||||
if (currentLocalState == null || currentLocalState.revision == RESTORE_PLACEHOLDER_REVISION) {
|
||||
@@ -561,7 +579,7 @@ class GroupsV2StateProcessor private constructor(
|
||||
return InternalUpdateResult.Updated(updatedGroupState)
|
||||
}
|
||||
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?, terminatorRecipientId: RecipientId? = null) {
|
||||
val previousGroupState = groupStateDiff.previousGroupState
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
@@ -573,12 +591,12 @@ class GroupsV2StateProcessor private constructor(
|
||||
|
||||
if (groupId == null) {
|
||||
Log.w(TAG, "$logPrefix Group create failed, trying to update")
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
|
||||
}
|
||||
|
||||
updatedGroupState.avatar.isNotEmpty()
|
||||
} else {
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
|
||||
|
||||
updatedGroupState.avatar != previousGroupState.avatar
|
||||
}
|
||||
@@ -735,6 +753,89 @@ class GroupsV2StateProcessor private constructor(
|
||||
SignalDatabase.groups.remove(groupId, Recipient.self().id)
|
||||
}
|
||||
|
||||
fun markTerminatedLocally() {
|
||||
val group = SignalDatabase.groups.getGroup(groupId).orNull()
|
||||
|
||||
if (group == null) {
|
||||
Log.w(TAG, "Group not found when inserting terminated message for $groupId")
|
||||
return
|
||||
}
|
||||
|
||||
if (group.isTerminated) {
|
||||
Log.w(TAG, "Group $groupId is already marked as terminated.")
|
||||
return
|
||||
}
|
||||
|
||||
val groupRecipient = Recipient.externalGroupExact(groupId)
|
||||
|
||||
val decryptedGroup = group.requireV2GroupProperties().decryptedGroup
|
||||
val simulatedGroupState = decryptedGroup.copy(terminated = true)
|
||||
val simulatedGroupChange = DecryptedGroupChange(terminateGroup = true)
|
||||
|
||||
val updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null)
|
||||
val terminateMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis(), isSelfGroupAdd = false)
|
||||
|
||||
try {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
val id = SignalDatabase.messages.insertMessageOutbox(terminateMessage, threadId, false, null).messageId
|
||||
SignalDatabase.messages.markAsSent(id, true)
|
||||
SignalDatabase.threads.update(threadId, unarchive = false, allowDeletion = false)
|
||||
} catch (e: MmsException) {
|
||||
Log.w(TAG, "Failed to insert terminated group message for $groupId", e)
|
||||
}
|
||||
|
||||
SignalDatabase.groups.update(masterKey, simulatedGroupState, null)
|
||||
}
|
||||
|
||||
fun markJoinRequestRejectedLocally() {
|
||||
val group = SignalDatabase.groups.getGroup(groupId).orNull()
|
||||
|
||||
if (group == null) {
|
||||
Log.w(TAG, "Group not found when inserting join request rejection message for $groupId")
|
||||
return
|
||||
}
|
||||
|
||||
val decryptedGroup = group.requireV2GroupProperties().decryptedGroup
|
||||
|
||||
if (decryptedGroup.requestingMembers.none { ACI.parseOrNull(it.aciBytes) == aci }) {
|
||||
Log.w(TAG, "Not a requesting member of $groupId, skipping rejection insert")
|
||||
return
|
||||
}
|
||||
|
||||
val groupRecipient = Recipient.externalGroupExact(groupId)
|
||||
|
||||
val simulatedGroupState = decryptedGroup.copy(
|
||||
requestingMembers = decryptedGroup.requestingMembers.filter { ACI.parseOrNull(it.aciBytes) != aci }
|
||||
)
|
||||
val simulatedGroupChange = DecryptedGroupChange(
|
||||
editorServiceIdBytes = ACI.UNKNOWN.toByteString(),
|
||||
deleteRequestingMembers = listOf(aci.toByteString())
|
||||
)
|
||||
|
||||
val updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null)
|
||||
val rejectedMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis(), isSelfGroupAdd = false)
|
||||
|
||||
try {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
val id = SignalDatabase.messages.insertMessageOutbox(rejectedMessage, threadId, false, null).messageId
|
||||
SignalDatabase.messages.markAsSent(id, true)
|
||||
SignalDatabase.threads.update(threadId, unarchive = false, allowDeletion = false)
|
||||
} catch (e: MmsException) {
|
||||
Log.w(TAG, "Failed to insert rejected join request message for $groupId", e)
|
||||
}
|
||||
|
||||
SignalDatabase.groups.update(masterKey, simulatedGroupState, null)
|
||||
}
|
||||
|
||||
fun stopAllTypingForGroup() {
|
||||
if (TextSecurePreferences.isTypingIndicatorsEnabled(AppDependencies.application)) {
|
||||
val threadId = SignalDatabase.threads.getThreadIdFor(SignalDatabase.recipients.getOrInsertFromGroupId(groupId))
|
||||
if (threadId != null) {
|
||||
AppDependencies.typingStatusRepository.stopAllTypingForThread(threadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun persistLearnedProfileKeys(groupStateDiff: GroupStateDiff) {
|
||||
val profileKeys = ProfileKeySet()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user