Add ability to reject group invite by PNI.

This commit is contained in:
Cody Henthorne
2022-04-25 12:37:25 -04:00
parent e22560a794
commit 657a9c7b0a
41 changed files with 626 additions and 325 deletions

View File

@@ -6,8 +6,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.protobuf.ByteString;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
@@ -24,7 +22,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.Collection;
@@ -40,7 +38,8 @@ public final class GroupManager {
private static final String TAG = Log.tag(GroupManager.class);
@WorkerThread
public static @NonNull GroupActionResult createGroup(@NonNull Context context,
public static @NonNull GroupActionResult createGroup(@NonNull ServiceId authServiceId,
@NonNull Context context,
@NonNull Set<Recipient> members,
@Nullable byte[] avatar,
@Nullable String name,
@@ -54,7 +53,7 @@ public final class GroupManager {
if (shouldAttemptToCreateV2) {
try {
try (GroupManagerV2.GroupCreator groupCreator = new GroupManagerV2(context).create()) {
return groupCreator.createGroup(memberIds, name, avatar, disappearingMessagesTimer);
return groupCreator.createGroup(authServiceId, memberIds, name, avatar, disappearingMessagesTimer);
}
} catch (MembershipNotSuitableForV2Exception e) {
Log.w(TAG, "Attempted to make a GV2, but membership was not suitable, falling back to GV1", e);
@@ -178,6 +177,7 @@ public final class GroupManager {
*/
@WorkerThread
public static void updateGroupFromServer(@NonNull Context context,
@NonNull ServiceId authServiceId,
@NonNull GroupMasterKey groupMasterKey,
int revision,
long timestamp,
@@ -185,17 +185,18 @@ public final class GroupManager {
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateLocalToServerRevision(revision, timestamp, signedGroupChange);
updater.updateLocalToServerRevision(authServiceId, revision, timestamp, signedGroupChange);
}
}
@WorkerThread
public static V2GroupServerStatus v2GroupStatus(@NonNull Context context,
@NonNull ServiceId authServiceserviceId,
@NonNull GroupMasterKey groupMasterKey)
throws IOException
{
try {
new GroupManagerV2(context).groupServerQuery(groupMasterKey);
new GroupManagerV2(context).groupServerQuery(authServiceserviceId, groupMasterKey);
return V2GroupServerStatus.FULL_OR_PENDING_MEMBER;
} catch (GroupNotAMemberException e) {
return V2GroupServerStatus.NOT_A_MEMBER;
@@ -210,11 +211,12 @@ public final class GroupManager {
* If it fails to get the exact version, it will give the latest.
*/
@WorkerThread
public static DecryptedGroup addedGroupVersion(@NonNull Context context,
public static DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId,
@NonNull Context context,
@NonNull GroupMasterKey groupMasterKey)
throws IOException, GroupDoesNotExistException, GroupNotAMemberException
{
return new GroupManagerV2(context).addedGroupVersion(groupMasterKey);
return new GroupManagerV2(context).addedGroupVersion(authServiceId, groupMasterKey);
}
@WorkerThread
@@ -268,12 +270,13 @@ public final class GroupManager {
@WorkerThread
public static void revokeInvites(@NonNull Context context,
@NonNull ServiceId authServiceId,
@NonNull GroupId.V2 groupId,
@NonNull Collection<UuidCiphertext> uuidCipherTexts)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException
{
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.revokeInvites(uuidCipherTexts);
editor.revokeInvites(authServiceId, uuidCipherTexts, true);
}
}

View File

@@ -62,6 +62,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
@@ -97,6 +98,7 @@ final class GroupManagerV2 {
private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor;
private final ACI selfAci;
private final PNI selfPni;
private final GroupCandidateHelper groupCandidateHelper;
private final SendGroupUpdateHelper sendGroupUpdateHelper;
@@ -108,6 +110,7 @@ final class GroupManagerV2 {
ApplicationDependencies.getGroupsV2Authorization(),
ApplicationDependencies.getGroupsV2StateProcessor(),
SignalStore.account().requireAci(),
SignalStore.account().requirePni(),
new GroupCandidateHelper(context),
new SendGroupUpdateHelper(context));
}
@@ -119,6 +122,7 @@ final class GroupManagerV2 {
GroupsV2Authorization authorization,
GroupsV2StateProcessor groupsV2StateProcessor,
ACI selfAci,
PNI selfPni,
GroupCandidateHelper groupCandidateHelper,
SendGroupUpdateHelper sendGroupUpdateHelper)
{
@@ -129,6 +133,7 @@ final class GroupManagerV2 {
this.authorization = authorization;
this.groupsV2StateProcessor = groupsV2StateProcessor;
this.selfAci = selfAci;
this.selfPni = selfPni;
this.groupCandidateHelper = groupCandidateHelper;
this.sendGroupUpdateHelper = sendGroupUpdateHelper;
}
@@ -204,18 +209,18 @@ final class GroupManagerV2 {
}
@WorkerThread
void groupServerQuery(@NonNull GroupMasterKey groupMasterKey)
void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey)
.getCurrentGroupStateFromServer();
}
@WorkerThread
@NonNull DecryptedGroup addedGroupVersion(@NonNull GroupMasterKey groupMasterKey)
@NonNull DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(groupMasterKey);
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey);
DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer();
if (latest.getRevision() == 0) {
@@ -233,7 +238,7 @@ final class GroupManagerV2 {
if (joinedVersion != null) {
return joinedVersion;
} else {
Log.w(TAG, "Unable to retreive exact version joined at, using latest");
Log.w(TAG, "Unable to retrieve exact version joined at, using latest");
return latest;
}
}
@@ -269,7 +274,8 @@ final class GroupManagerV2 {
}
@WorkerThread
@NonNull GroupManager.GroupActionResult createGroup(@NonNull Collection<RecipientId> members,
@NonNull GroupManager.GroupActionResult createGroup(@NonNull ServiceId authServiceId,
@NonNull Collection<RecipientId> members,
@Nullable String name,
@Nullable byte[] avatar,
int disappearingMessagesTimer)
@@ -285,7 +291,7 @@ final class GroupManagerV2 {
}
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
GroupId.V2 groupId = groupDatabase.create(authServiceId, masterKey, decryptedGroup);
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
@@ -306,6 +312,7 @@ final class GroupManagerV2 {
}
}
@SuppressWarnings("UnusedReturnValue")
final class GroupEditor extends LockOwner {
private final GroupId.V2 groupId;
@@ -340,35 +347,35 @@ final class GroupManagerV2 {
groupCandidates = GroupCandidate.withoutProfileKeyCredentials(groupCandidates);
}
return commitChangeWithConflictResolution(groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid()));
}
@WorkerThread
@NonNull GroupManager.GroupActionResult updateGroupTimer(int expirationTime)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createModifyGroupTimerChange(expirationTime));
return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupTimerChange(expirationTime));
}
@WorkerThread
@NonNull GroupManager.GroupActionResult updateAttributesRights(@NonNull GroupAccessControl newRights)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createChangeAttributesRights(rightsToAccessControl(newRights)));
return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeAttributesRights(rightsToAccessControl(newRights)));
}
@WorkerThread
@NonNull GroupManager.GroupActionResult updateMembershipRights(@NonNull GroupAccessControl newRights)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createChangeMembershipRights(rightsToAccessControl(newRights)));
return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMembershipRights(rightsToAccessControl(newRights)));
}
@WorkerThread
@NonNull GroupManager.GroupActionResult updateAnnouncementGroup(boolean isAnnouncementGroup)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createAnnouncementGroupChange(isAnnouncementGroup));
return commitChangeWithConflictResolution(selfAci, groupOperations.createAnnouncementGroupChange(isAnnouncementGroup));
}
@WorkerThread
@@ -390,7 +397,7 @@ final class GroupManagerV2 {
.setAvatar(cdnKey));
}
GroupManager.GroupActionResult groupActionResult = commitChangeWithConflictResolution(change);
GroupManager.GroupActionResult groupActionResult = commitChangeWithConflictResolution(selfAci, change);
if (avatarChanged) {
AvatarHelper.setAvatar(context, Recipient.externalGroupExact(context, groupId).getId(), avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
@@ -404,10 +411,10 @@ final class GroupManagerV2 {
}
@WorkerThread
@NonNull GroupManager.GroupActionResult revokeInvites(@NonNull Collection<UuidCiphertext> uuidCipherTexts)
@NonNull GroupManager.GroupActionResult revokeInvites(@NonNull ServiceId authServiceId, @NonNull Collection<UuidCiphertext> uuidCipherTexts, boolean sendToMembers)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createRemoveInvitationChange(new HashSet<>(uuidCipherTexts)));
return commitChangeWithConflictResolution(authServiceId, groupOperations.createRemoveInvitationChange(new HashSet<>(uuidCipherTexts)), false, sendToMembers);
}
@WorkerThread
@@ -418,7 +425,7 @@ final class GroupManagerV2 {
.map(r -> Recipient.resolved(r).requireServiceId().uuid())
.collect(Collectors.toSet());
return commitChangeWithConflictResolution(groupOperations.createApproveGroupJoinRequest(uuids));
return commitChangeWithConflictResolution(selfAci, groupOperations.createApproveGroupJoinRequest(uuids));
}
@WorkerThread
@@ -429,7 +436,7 @@ final class GroupManagerV2 {
.map(r -> Recipient.resolved(r).requireServiceId().uuid())
.collect(Collectors.toSet());
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
}
@WorkerThread
@@ -438,33 +445,47 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
Recipient recipient = Recipient.resolved(recipientId);
return commitChangeWithConflictResolution(groupOperations.createChangeMemberRole(recipient.requireServiceId().uuid(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT));
return commitChangeWithConflictResolution(selfAci, groupOperations.createChangeMemberRole(recipient.requireServiceId().uuid(), admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT));
}
@WorkerThread
@NonNull GroupManager.GroupActionResult leaveGroup()
void leaveGroup()
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
GroupDatabase.GroupRecord groupRecord = groupDatabase.getGroup(groupId).get();
List<DecryptedPendingMember> pendingMembersList = groupRecord.requireV2GroupProperties().getDecryptedGroup().getPendingMembersList();
Optional<DecryptedPendingMember> selfPendingMember = DecryptedGroupUtil.findPendingByUuid(pendingMembersList, selfAci.uuid());
GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.uuid());
Optional<DecryptedPendingMember> selfPendingMember = Optional.empty();
ServiceId serviceId = selfAci;
if (aciPendingMember.isPresent()) {
selfPendingMember = aciPendingMember;
} else if (pniPendingMember.isPresent() && !selfMember.isPresent()) {
selfPendingMember = pniPendingMember;
serviceId = selfPni;
}
if (selfPendingMember.isPresent()) {
try {
return revokeInvites(Collections.singleton(new UuidCiphertext(selfPendingMember.get().getUuidCipherText().toByteArray())));
revokeInvites(serviceId, Collections.singleton(new UuidCiphertext(selfPendingMember.get().getUuidCipherText().toByteArray())), false);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
} else if (selfMember.isPresent()) {
ejectMember(serviceId, true, false);
} else {
return ejectMember(ServiceId.from(selfAci.uuid()), true, false);
Log.i(TAG, "Unable to leave group we are not pending or in");
}
}
@WorkerThread
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked, boolean ban)
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId authServiceId, boolean allowWhenBlocked, boolean ban)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()),
return commitChangeWithConflictResolution(authServiceId,
groupOperations.createRemoveMembersChange(Collections.singleton(authServiceId.uuid()),
ban,
ban ? v2GroupProperties.getDecryptedGroup().getBannedMembersList()
: Collections.emptyList()),
@@ -475,10 +496,9 @@ final class GroupManagerV2 {
@NonNull GroupManager.GroupActionResult addMemberAdminsAndLeaveGroup(Collection<RecipientId> newAdmins)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
Recipient self = Recipient.self();
List<UUID> newAdminRecipients = Stream.of(newAdmins).map(id -> Recipient.resolved(id).requireServiceId().uuid()).toList();
return commitChangeWithConflictResolution(groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci.uuid(),
return commitChangeWithConflictResolution(selfAci, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci.uuid(),
newAdminRecipients));
}
@@ -510,7 +530,7 @@ final class GroupManagerV2 {
return null;
}
return commitChangeWithConflictResolution(groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.getProfileKeyCredential().get()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.requireProfileKeyCredential()));
}
@WorkerThread
@@ -532,7 +552,7 @@ final class GroupManagerV2 {
return null;
}
return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createAcceptInviteChange(groupCandidate.requireProfileKeyCredential()));
}
public GroupManager.GroupActionResult ban(UUID uuid)
@@ -541,20 +561,20 @@ final class GroupManagerV2 {
ByteString uuidByteString = UuidUtil.toByteString(uuid);
boolean rejectJoinRequest = v2GroupProperties.getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getUuid().equals(uuidByteString));
return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(Collections.singleton(uuid), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createBanUuidsChange(Collections.singleton(uuid), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
}
public GroupManager.GroupActionResult unban(Set<UUID> uuids)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
return commitChangeWithConflictResolution(groupOperations.createUnbanUuidsChange(uuids));
return commitChangeWithConflictResolution(selfAci, groupOperations.createUnbanUuidsChange(uuids));
}
@WorkerThread
public GroupManager.GroupActionResult cycleGroupLinkPassword()
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
return commitChangeWithConflictResolution(groupOperations.createModifyGroupLinkPasswordChange(GroupLinkPassword.createNew().serialize()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupLinkPasswordChange(GroupLinkPassword.createNew().serialize()));
}
@WorkerThread
@@ -581,7 +601,7 @@ final class GroupManagerV2 {
}
}
commitChangeWithConflictResolution(change);
commitChangeWithConflictResolution(selfAci, change);
if (state != GroupManager.GroupLinkState.DISABLED) {
GroupDatabase.V2GroupProperties v2GroupProperties = groupDatabase.requireGroup(groupId).requireV2GroupProperties();
@@ -594,26 +614,32 @@ final class GroupManagerV2 {
}
}
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull GroupChange.Actions.Builder change)
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
return commitChangeWithConflictResolution(change, false);
return commitChangeWithConflictResolution(authServiceId, change, false);
}
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked)
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
change.setSourceUuid(UuidUtil.toByteString(selfAci.uuid()));
return commitChangeWithConflictResolution(authServiceId, change, allowWhenBlocked, true);
}
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
change.setSourceUuid(UuidUtil.toByteString(authServiceId.uuid()));
for (int attempt = 0; attempt < 5; attempt++) {
try {
return commitChange(change, allowWhenBlocked);
return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers);
} catch (GroupPatchNotAcceptedException e) {
throw new GroupChangeFailedException(e);
} catch (ConflictException e) {
Log.w(TAG, "Invalid group patch or conflict", e);
change = resolveConflict(change);
change = resolveConflict(authServiceId, change);
if (GroupChangeUtil.changeIsEmpty(change.build())) {
Log.i(TAG, "Change is empty after conflict resolution");
@@ -628,10 +654,10 @@ final class GroupManagerV2 {
throw new GroupChangeFailedException("Unable to apply change to group after conflicts");
}
private GroupChange.Actions.Builder resolveConflict(@NonNull GroupChange.Actions.Builder change)
private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change)
throws IOException, GroupNotAMemberException, GroupChangeFailedException
{
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(groupMasterKey)
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(authServiceId, groupMasterKey)
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null);
if (groupUpdateResult.getLatestServer() == null) {
@@ -652,14 +678,14 @@ final class GroupManagerV2 {
GroupChange.Actions changeActions = change.build();
return GroupChangeUtil.resolveConflict(groupUpdateResult.getLatestServer(),
groupOperations.decryptChange(changeActions, selfAci.uuid()),
groupOperations.decryptChange(changeActions, authServiceId.uuid()),
changeActions);
} catch (VerificationFailedException | InvalidGroupStateException ex) {
throw new GroupChangeFailedException(ex);
}
}
private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked)
private GroupManager.GroupActionResult commitChange(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
@@ -676,8 +702,9 @@ final class GroupManagerV2 {
previousGroupState = v2GroupProperties.getDecryptedGroup();
GroupChange signedGroupChange = commitToServer(changeActions);
GroupChange signedGroupChange = commitToServer(authServiceId, changeActions);
try {
//noinspection OptionalGetWithoutIsPresent
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
@@ -688,18 +715,18 @@ final class GroupManagerV2 {
groupDatabase.update(groupId, decryptedGroupState);
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange);
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
int newMembersCount = decryptedChange.getNewMembersCount();
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
}
private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
private @NonNull GroupChange commitToServer(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions change)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
try {
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(selfAci, groupSecretParams), Optional.empty());
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(authServiceId, groupSecretParams), Optional.empty());
} catch (NotInGroupException e) {
Log.w(TAG, e);
throw new GroupNotAMemberException(e);
@@ -724,10 +751,10 @@ final class GroupManagerV2 {
}
@WorkerThread
void updateLocalToServerRevision(int revision, long timestamp, @Nullable byte[] signedGroupChange)
void updateLocalToServerRevision(@NonNull ServiceId authServiceId, int revision, long timestamp, @Nullable byte[] signedGroupChange)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey)
.updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange));
}
@@ -880,7 +907,7 @@ final class GroupManagerV2 {
groupDatabase.update(groupId, updatedGroup);
} else {
groupDatabase.create(groupMasterKey, decryptedGroup);
groupDatabase.create(selfAci, groupMasterKey, decryptedGroup);
Log.i(TAG, "Created local group with placeholder");
}
@@ -924,7 +951,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException
{
try {
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(selfAci, groupMasterKey)
.updateLocalGroupToRevision(decryptedChange.getRevision(),
System.currentTimeMillis(),
decryptedChange);
@@ -956,6 +983,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException
{
try {
//noinspection OptionalGetWithoutIsPresent
return groupOperations.decryptChange(signedGroupChange, false).get();
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
Log.w(TAG, e);
@@ -1006,7 +1034,7 @@ final class GroupManagerV2 {
throw new MembershipNotSuitableForV2Exception("No profile key credential for self");
}
ProfileKeyCredential profileKeyCredential = self.getProfileKeyCredential().get();
ProfileKeyCredential profileKeyCredential = self.requireProfileKeyCredential();
GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(profileKeyCredential)
: groupOperations.createGroupJoinDirect(profileKeyCredential);
@@ -1123,6 +1151,7 @@ final class GroupManagerV2 {
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup();
try {
//noinspection OptionalGetWithoutIsPresent
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
@@ -1226,6 +1255,7 @@ final class GroupManagerV2 {
return new RecipientAndThread(groupRecipient, -1);
} else {
//noinspection IfStatementWithIdenticalBranches
if (sendToMembers) {
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null, null);
return new RecipientAndThread(groupRecipient, threadId);

View File

@@ -2,10 +2,23 @@ package org.thoughtcrime.securesms.groups;
public final class GroupNotAMemberException extends GroupChangeException {
private final boolean likelyPendingMember;
public GroupNotAMemberException(Throwable throwable) {
super(throwable);
this.likelyPendingMember = false;
}
public GroupNotAMemberException(GroupNotAMemberException throwable, boolean likelyPendingMember) {
super(throwable.getCause() != null ? throwable.getCause() : throwable);
this.likelyPendingMember = likelyPendingMember;
}
GroupNotAMemberException() {
this.likelyPendingMember = false;
}
public boolean isLikelyPendingMember() {
return likelyPendingMember;
}
}

View File

@@ -72,7 +72,7 @@ public final class GroupsV1MigrationUtil {
throw new InvalidMigrationStateException();
}
switch (GroupManager.v2GroupStatus(context, gv2MasterKey)) {
switch (GroupManager.v2GroupStatus(context, SignalStore.account().getAci(), gv2MasterKey)) {
case DOES_NOT_EXIST:
Log.i(TAG, "Group does not exist on the service.");
@@ -172,7 +172,7 @@ public final class GroupsV1MigrationUtil {
try (Closeable ignored = GroupsV2ProcessingLock.acquireGroupProcessingLock()){
DecryptedGroup decryptedGroup;
try {
decryptedGroup = GroupManager.addedGroupVersion(context, gv1Id.deriveV2MigrationMasterKey());
decryptedGroup = GroupManager.addedGroupVersion(SignalStore.account().requireAci(), context, gv1Id.deriveV2MigrationMasterKey());
} catch (GroupDoesNotExistException e) {
throw new IOException("[Local] The group should exist already!");
} catch (GroupNotAMemberException e) {
@@ -186,7 +186,7 @@ public final class GroupsV1MigrationUtil {
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision());
try {
GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null);
GroupManager.updateGroupFromServer(context, SignalStore.account().requireAci(), gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null);
} catch (GroupChangeBusyException | GroupNotAMemberException e) {
Log.w(TAG, e);
}

View File

@@ -6,37 +6,53 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class GroupsV2Authorization {
private static final String TAG = Log.tag(GroupsV2Authorization.class);
private final ValueCache cache;
private final ValueCache aciCache;
private final ValueCache pniCache;
private final GroupsV2Api groupsV2Api;
public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache cache) {
public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache aciCache, @NonNull ValueCache pniCache) {
this.groupsV2Api = groupsV2Api;
this.cache = cache;
this.aciCache = aciCache;
this.pniCache = pniCache;
}
public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ACI self,
public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceId authServiceId,
@NonNull GroupSecretParams groupSecretParams)
throws IOException, VerificationFailedException
{
boolean isPni = Objects.equals(authServiceId, SignalStore.account().getPni());
ValueCache cache = isPni ? pniCache : aciCache;
return getAuthorizationForToday(authServiceId, cache, groupSecretParams, !isPni);
}
private GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceId authServiceId,
@NonNull ValueCache cache,
@NonNull GroupSecretParams groupSecretParams,
boolean isAci)
throws IOException, VerificationFailedException
{
final int today = currentTimeDays();
Map<Integer, AuthCredentialResponse> credentials = cache.read();
try {
return getAuthorization(self, groupSecretParams, credentials, today);
return getAuthorization(authServiceId, groupSecretParams, credentials, today);
} catch (NoCredentialForRedemptionTimeException e) {
Log.i(TAG, "Auth out of date, will update auth and try again");
cache.clear();
@@ -46,11 +62,11 @@ public class GroupsV2Authorization {
}
Log.i(TAG, "Getting new auth credential responses");
credentials = groupsV2Api.getCredentials(today);
credentials = groupsV2Api.getCredentials(today, isAci);
cache.write(credentials);
try {
return getAuthorization(self, groupSecretParams, credentials, today);
return getAuthorization(authServiceId, groupSecretParams, credentials, today);
} catch (NoCredentialForRedemptionTimeException e) {
Log.w(TAG, "The credentials returned did not include the day requested");
throw new IOException("Failed to get credentials");
@@ -58,14 +74,15 @@ public class GroupsV2Authorization {
}
public void clear() {
cache.clear();
aciCache.clear();
pniCache.clear();
}
private static int currentTimeDays() {
return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
private GroupsV2AuthorizationString getAuthorization(ACI self,
private GroupsV2AuthorizationString getAuthorization(ServiceId authServiceId,
GroupSecretParams groupSecretParams,
Map<Integer, AuthCredentialResponse> credentials,
int today)
@@ -77,7 +94,7 @@ public class GroupsV2Authorization {
throw new NoCredentialForRedemptionTimeException();
}
return groupsV2Api.getGroupsV2AuthorizationString(self, today, groupSecretParams, authCredentialResponse);
return groupsV2Api.getGroupsV2AuthorizationString(authServiceId, today, groupSecretParams, authCredentialResponse);
}
public interface ValueCache {

View File

@@ -58,7 +58,8 @@ final class AddGroupDetailsRepository {
Set<Recipient> recipients = new HashSet<>(Stream.of(members).map(Recipient::resolved).toList());
try {
GroupManager.GroupActionResult result = GroupManager.createGroup(context,
GroupManager.GroupActionResult result = GroupManager.createGroup(SignalStore.account().requireAci(),
context,
recipients,
avatar,
name,

View File

@@ -100,7 +100,7 @@ final class PendingMemberInvitesRepository {
@WorkerThread
boolean revokeInvites(@NonNull Collection<UuidCiphertext> uuidCipherTexts) {
try {
GroupManager.revokeInvites(context, groupId, uuidCipherTexts);
GroupManager.revokeInvites(context, SignalStore.account().requireAci(), groupId, uuidCipherTexts);
return true;
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);

View File

@@ -18,6 +18,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.MessageDatabase;
@@ -52,7 +53,6 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
@@ -67,7 +67,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Advances a groups state to a specified revision.
@@ -104,11 +103,10 @@ public class GroupsV2StateProcessor {
this.groupDatabase = SignalDatabase.groups();
}
public StateProcessorForGroup forGroup(@NonNull GroupMasterKey groupMasterKey) {
ACI selfAci = SignalStore.account().requireAci();
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, selfAci, groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
public StateProcessorForGroup forGroup(@NonNull ServiceId serviceId, @NonNull GroupMasterKey groupMasterKey) {
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceId, groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
return new StateProcessorForGroup(selfAci, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
return new StateProcessorForGroup(serviceId, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
}
public enum GroupState {
@@ -142,7 +140,7 @@ public class GroupsV2StateProcessor {
}
public static final class StateProcessorForGroup {
private final ACI selfAci;
private final ServiceId serviceId;
private final Context context;
private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api;
@@ -152,7 +150,7 @@ public class GroupsV2StateProcessor {
private final GroupSecretParams groupSecretParams;
private final ProfileAndMessageHelper profileAndMessageHelper;
@VisibleForTesting StateProcessorForGroup(@NonNull ACI selfAci,
@VisibleForTesting StateProcessorForGroup(@NonNull ServiceId serviceId,
@NonNull Context context,
@NonNull GroupDatabase groupDatabase,
@NonNull GroupsV2Api groupsV2Api,
@@ -160,7 +158,7 @@ public class GroupsV2StateProcessor {
@NonNull GroupMasterKey groupMasterKey,
@NonNull ProfileAndMessageHelper profileAndMessageHelper)
{
this.selfAci = selfAci;
this.serviceId = serviceId;
this.context = context;
this.groupDatabase = groupDatabase;
this.groupsV2Api = groupsV2Api;
@@ -197,7 +195,7 @@ public class GroupsV2StateProcessor {
revision == signedGroupChange.getRevision())
{
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange)) {
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange) && notHavingInviteRevoked(signedGroupChange)) {
Log.w(TAG, "Ignoring P2P group change because we're not currently in the group and this change doesn't add us in. Falling back to a server fetch.");
} else if (SignalStore.internalValues().gv2IgnoreP2PChanges()) {
Log.w(TAG, "Ignoring P2P group change by setting");
@@ -233,8 +231,9 @@ public class GroupsV2StateProcessor {
}
if (inputGroupState == null) {
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, selfAci.uuid())) {
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, serviceId.uuid())) {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, but we think we are a pending or requesting member");
throw new GroupNotAMemberException(e, true);
} else {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
insertGroupLeave();
@@ -277,26 +276,34 @@ public class GroupsV2StateProcessor {
.map(DecryptedMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
.contains(selfAci.uuid());
.anyMatch(u -> u.equals(serviceId.uuid()));
boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList()
.stream()
.map(DecryptedPendingMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
.contains(selfAci.uuid());
.anyMatch(u -> u.equals(serviceId.uuid()));
return !currentlyInGroup && !addedAsMember && !addedAsPendingMember;
}
private boolean notHavingInviteRevoked(@NonNull DecryptedGroupChange signedGroupChange) {
boolean havingInviteRevoked = signedGroupChange.getDeletePendingMembersList()
.stream()
.map(DecryptedPendingMemberRemoval::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid()));
return !havingInviteRevoked;
}
/**
* Using network, attempt to bring the local copy of the group up to the revision specified via paging.
*/
private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, DecryptedGroup localState, long timestamp, boolean forceIncludeFirst) throws IOException, GroupNotAMemberException {
boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION);
ACI selfAci = this.selfAci;
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
@@ -304,37 +311,38 @@ public class GroupsV2StateProcessor {
GlobalGroupState inputGroupState;
try {
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams));
} catch (NotInGroupException | GroupNotFoundException e) {
throw new GroupNotAMemberException(e);
} catch (VerificationFailedException | InvalidGroupStateException e) {
throw new IOException(e);
}
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(selfAci.uuid(), localState.getMembersList())) {
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceId.uuid(), localState.getMembersList())) {
Log.i(TAG, "Local state is at or later than server");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
}
if (latestRevisionOnly || !GroupProtoUtil.isMember(selfAci.uuid(), latestServerGroup.getMembersList())) {
if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceId.uuid(), latestServerGroup.getMembersList())) {
Log.i(TAG, "Latest revision or not a member, use latest only");
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null)));
} else {
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, selfAci.uuid());
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
boolean includeFirstState = forceIncludeFirst ||
localState == null ||
localState.getRevision() < 0 ||
localState.getRevision() == revisionWeWereAdded ||
!GroupProtoUtil.isMember(selfAci.uuid(), localState.getMembersList()) ||
(revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision());
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceId.uuid());
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
boolean includeFirstState = forceIncludeFirst ||
localState == null ||
localState.getRevision() < 0 ||
localState.getRevision() == revisionWeWereAdded ||
!GroupProtoUtil.isMember(serviceId.uuid(), localState.getMembersList()) ||
(revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision());
Log.i(TAG,
"Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") +
" logsNeededFrom: " + logsNeededFrom +
" includeFirstState: " + includeFirstState +
" forceIncludeFirst: " + forceIncludeFirst);
inputGroupState = getFullMemberHistoryPage(localState, selfAci, logsNeededFrom, includeFirstState);
inputGroupState = getFullMemberHistoryPage(localState, serviceId, logsNeededFrom, includeFirstState);
}
ProfileKeySet profileKeys = new ProfileKeySet();
@@ -350,7 +358,7 @@ public class GroupsV2StateProcessor {
if (newLocalState != null && !inputGroupState.hasMore() && !forceIncludeFirst) {
int newLocalRevision = newLocalState.getRevision();
int requestRevision = (revision == LATEST) ? latestServerGroup.getRevision() : revision;
int requestRevision = (revision == LATEST) ? latestServerGroup.getRevision() : revision;
if (newLocalRevision < requestRevision) {
Log.w(TAG, "Paging again with force first snapshot enabled due to error processing changes. New local revision [" + newLocalRevision + "] hasn't reached our desired level [" + requestRevision + "]");
return updateLocalGroupFromServerPaged(revision, localState, timestamp, true);
@@ -382,7 +390,7 @@ public class GroupsV2StateProcessor {
if (hasMore) {
Log.i(TAG, "Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, selfAci, inputGroupState.getNextPageRevision(), false);
inputGroupState = getFullMemberHistoryPage(finalState, serviceId, inputGroupState.getNextPageRevision(), false);
}
}
@@ -406,7 +414,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams));
} catch (GroupNotFoundException e) {
throw new GroupDoesNotExistException(e);
} catch (NotInGroupException e) {
@@ -421,7 +429,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams), true)
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), true)
.getResults()
.get(0)
.getGroup()
@@ -442,7 +450,7 @@ public class GroupsV2StateProcessor {
}
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
UUID selfUuid = selfAci.uuid();
UUID selfUuid = serviceId.uuid();
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
.requireV2GroupProperties()
@@ -501,7 +509,7 @@ public class GroupsV2StateProcessor {
boolean needsAvatarFetch;
if (inputGroupState.getLocalState() == null) {
groupDatabase.create(masterKey, newLocalState);
groupDatabase.create(serviceId, masterKey, newLocalState);
needsAvatarFetch = !TextUtils.isEmpty(newLocalState.getAvatar());
} else {
groupDatabase.update(masterKey, newLocalState);
@@ -515,9 +523,9 @@ public class GroupsV2StateProcessor {
profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState);
}
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ACI selfAci, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ServiceId serviceId, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
try {
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams), includeFirstState);
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), includeFirstState);
ArrayList<ServerGroupLogEntry> history = new ArrayList<>(groupHistoryPage.getResults().size());
boolean ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges();
@@ -545,14 +553,14 @@ public class GroupsV2StateProcessor {
static class ProfileAndMessageHelper {
private final Context context;
private final ACI selfAci;
private final ServiceId serviceId;
private final GroupMasterKey masterKey;
private final GroupId.V2 groupId;
private final RecipientDatabase recipientDatabase;
ProfileAndMessageHelper(@NonNull Context context, @NonNull ACI selfAci, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientDatabase recipientDatabase) {
ProfileAndMessageHelper(@NonNull Context context, @NonNull ServiceId serviceId, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientDatabase recipientDatabase) {
this.context = context;
this.selfAci = selfAci;
this.serviceId = serviceId;
this.masterKey = masterKey;
this.groupId = groupId;
this.recipientDatabase = recipientDatabase;
@@ -560,15 +568,15 @@ public class GroupsV2StateProcessor {
void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull DecryptedGroup newLocalState) {
if (inputGroupState.getLocalState() != null) {
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), selfAci.uuid()).isPresent();
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), serviceId.uuid()).isPresent();
if (wasAMemberAlready) {
return;
}
}
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> selfAsPendingOptional = DecryptedGroupUtil.findPendingByUuid(newLocalState.getPendingMembersList(), selfAci.uuid());
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), serviceId.uuid());
Optional<DecryptedPendingMember> selfAsPendingOptional = DecryptedGroupUtil.findPendingByUuid(newLocalState.getPendingMembersList(), serviceId.uuid());
if (selfAsMemberOptional.isPresent()) {
DecryptedMember selfAsMember = selfAsMemberOptional.get();
@@ -587,7 +595,7 @@ public class GroupsV2StateProcessor {
Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId()));
if (addedBy.isBlocked() && (inputGroupState.getLocalState() == null || !DecryptedGroupUtil.isRequesting(inputGroupState.getLocalState(), selfAci.uuid()))) {
if (addedBy.isBlocked() && (inputGroupState.getLocalState() == null || !DecryptedGroupUtil.isRequesting(inputGroupState.getLocalState(), serviceId.uuid()))) {
Log.i(TAG, "Added by a blocked user. Leaving group.");
ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId));
//noinspection UnnecessaryReturnStatement
@@ -671,7 +679,7 @@ public class GroupsV2StateProcessor {
void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) {
Optional<ServiceId> editor = getEditor(decryptedGroupV2Context).map(ServiceId::from);
boolean outgoing = !editor.isPresent() || selfAci.equals(editor.get());
boolean outgoing = !editor.isPresent() || serviceId.equals(editor.get());
if (outgoing) {
try {
@@ -709,7 +717,7 @@ public class GroupsV2StateProcessor {
if (changeEditor.isPresent()) {
return changeEditor;
} else {
Optional<DecryptedPendingMember> pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), serviceId.uuid());
if (pendingByUuid.isPresent()) {
return Optional.ofNullable(UuidUtil.fromByteStringOrNull(pendingByUuid.get().getAddedByUuid()));
}