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 a311b8abc8..8d2c937dd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -21,6 +21,7 @@ import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; +import org.signal.network.exceptions.NonSuccessfulResponseCodeException; import org.signal.storageservice.storage.protos.groups.AccessControl; import org.signal.storageservice.storage.protos.groups.ExternalGroupCredential; import org.signal.storageservice.storage.protos.groups.GroupChange; @@ -887,6 +888,9 @@ final class GroupManagerV2 { } } + /** + * A 400 from the group service means a submitted profile key credential was expired. + */ @WorkerThread private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams, @Nullable String name, @@ -894,6 +898,28 @@ final class GroupManagerV2 { @NonNull Collection members, int disappearingMessageTimerSeconds) throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException + { + try { + return createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessageTimerSeconds, false); + } catch (NonSuccessfulResponseCodeException e) { + if (e.code != 400) { + throw e; + } + + Log.w(TAG, "[createGroupOnServer] Group was not accepted, refreshing all profile key credentials and retrying", e); + + return createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessageTimerSeconds, true); + } + } + + @WorkerThread + private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams, + @Nullable String name, + @Nullable byte[] avatar, + @NonNull Collection members, + int disappearingMessageTimerSeconds, + boolean forceRefreshProfileKeyCredentials) + throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException { if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(members)) { throw new MembershipNotSuitableForV2Exception("At least one potential new member does not support GV2 capability or we don't have their UUID"); @@ -901,6 +927,12 @@ final class GroupManagerV2 { SignalDatabase.recipients().clearProfileKeyCredential(Recipient.self().getId()); + if (forceRefreshProfileKeyCredentials) { + for (RecipientId member : members) { + SignalDatabase.recipients().clearProfileKeyCredential(member); + } + } + GroupCandidate self = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); Set candidates = new HashSet<>(groupCandidateHelper.recipientIdsToCandidates(members)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java index 4066a700c4..03bbe8c82b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupCandidateHelper.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.ProfileUtil; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; +import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.signal.core.models.ServiceId; import java.io.IOException; @@ -56,7 +57,15 @@ public class GroupCandidateHelper { if (!candidate.hasValidProfileKeyCredential()) { recipientTable.clearProfileKeyCredential(recipient.getId()); - Optional credential = ProfileUtil.updateExpiringProfileKeyCredential(recipient); + Optional credential; + try { + credential = ProfileUtil.updateExpiringProfileKeyCredential(recipient); + } catch (NotFoundException e) { + Log.w(TAG, "Profile not found for " + recipient.getId() + ". Marking unregistered and falling back to an invite."); + recipientTable.markUnregistered(recipient.getId()); + credential = Optional.empty(); + } + if (credential.isPresent()) { candidate = candidate.withExpiringProfileKeyCredential(credential.get()); } else {