Add GV2 accept by PNI invite.

This commit is contained in:
Cody Henthorne
2022-07-11 15:20:00 -04:00
parent b223ebe95e
commit c4bef8099f
71 changed files with 1468 additions and 1016 deletions

View File

@@ -177,7 +177,6 @@ public final class GroupManager {
*/
@WorkerThread
public static void updateGroupFromServer(@NonNull Context context,
@NonNull ServiceId authServiceId,
@NonNull GroupMasterKey groupMasterKey,
int revision,
long timestamp,
@@ -185,18 +184,18 @@ public final class GroupManager {
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateLocalToServerRevision(authServiceId, revision, timestamp, signedGroupChange);
updater.updateLocalToServerRevision(revision, timestamp, signedGroupChange);
}
}
@WorkerThread
public static V2GroupServerStatus v2GroupStatus(@NonNull Context context,
@NonNull ServiceId authServiceserviceId,
@NonNull ServiceId authServiceId,
@NonNull GroupMasterKey groupMasterKey)
throws IOException
{
try {
new GroupManagerV2(context).groupServerQuery(authServiceserviceId, groupMasterKey);
new GroupManagerV2(context).groupServerQuery(authServiceId, groupMasterKey);
return V2GroupServerStatus.FULL_OR_PENDING_MEMBER;
} catch (GroupNotAMemberException e) {
return V2GroupServerStatus.NOT_A_MEMBER;

View File

@@ -19,8 +19,8 @@ import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
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.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
@@ -51,6 +51,8 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
@@ -64,6 +66,7 @@ import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2Change
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.ServiceIds;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.util.UuidUtil;
@@ -97,6 +100,7 @@ final class GroupManagerV2 {
private final GroupsV2Operations groupsV2Operations;
private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor;
private final ServiceIds serviceIds;
private final ACI selfAci;
private final PNI selfPni;
private final GroupCandidateHelper groupCandidateHelper;
@@ -109,9 +113,8 @@ final class GroupManagerV2 {
ApplicationDependencies.getGroupsV2Operations(),
ApplicationDependencies.getGroupsV2Authorization(),
ApplicationDependencies.getGroupsV2StateProcessor(),
SignalStore.account().requireAci(),
SignalStore.account().requirePni(),
new GroupCandidateHelper(context),
SignalStore.account().getServiceIds(),
new GroupCandidateHelper(),
new SendGroupUpdateHelper(context));
}
@@ -121,8 +124,7 @@ final class GroupManagerV2 {
GroupsV2Operations groupsV2Operations,
GroupsV2Authorization authorization,
GroupsV2StateProcessor groupsV2StateProcessor,
ACI selfAci,
PNI selfPni,
ServiceIds serviceIds,
GroupCandidateHelper groupCandidateHelper,
SendGroupUpdateHelper sendGroupUpdateHelper)
{
@@ -132,8 +134,9 @@ final class GroupManagerV2 {
this.groupsV2Operations = groupsV2Operations;
this.authorization = authorization;
this.groupsV2StateProcessor = groupsV2StateProcessor;
this.selfAci = selfAci;
this.selfPni = selfPni;
this.serviceIds = serviceIds;
this.selfAci = serviceIds.getAci();
this.selfPni = serviceIds.requirePni();
this.groupCandidateHelper = groupCandidateHelper;
this.sendGroupUpdateHelper = sendGroupUpdateHelper;
}
@@ -145,7 +148,7 @@ final class GroupManagerV2 {
return groupsV2Api.getGroupJoinInfo(groupSecretParams,
Optional.ofNullable(password).map(GroupLinkPassword::serialize),
authorization.getAuthorizationForToday(selfAci, groupSecretParams));
authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
}
@WorkerThread
@@ -159,7 +162,7 @@ final class GroupManagerV2 {
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
return groupsV2Api.getGroupExternalCredential(authorization.getAuthorizationForToday(selfAci, groupSecretParams));
return groupsV2Api.getGroupExternalCredential(authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
}
@WorkerThread
@@ -212,7 +215,7 @@ final class GroupManagerV2 {
void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.getCurrentGroupStateFromServer();
}
@@ -220,7 +223,7 @@ final class GroupManagerV2 {
@NonNull DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey)
throws GroupNotAMemberException, IOException, GroupDoesNotExistException
{
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey);
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey);
DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer();
if (latest.getRevision() == 0) {
@@ -291,7 +294,7 @@ final class GroupManagerV2 {
}
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
GroupId.V2 groupId = groupDatabase.create(authServiceId, masterKey, decryptedGroup);
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
@@ -344,7 +347,7 @@ final class GroupManagerV2 {
Set<GroupCandidate> groupCandidates = groupCandidateHelper.recipientIdsToCandidates(new HashSet<>(newMembers));
if (SignalStore.internalValues().gv2ForceInvites()) {
groupCandidates = GroupCandidate.withoutProfileKeyCredentials(groupCandidates);
groupCandidates = GroupCandidate.withoutExpiringProfileKeyCredentials(groupCandidates);
}
return commitChangeWithConflictResolution(selfAci, groupOperations.createModifyGroupMembershipChange(groupCandidates, bannedMembers, selfAci.uuid()));
@@ -391,7 +394,7 @@ final class GroupManagerV2 {
}
if (avatarChanged) {
String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(selfAci, groupSecretParams))
String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
: "";
change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder()
.setAvatar(cdnKey));
@@ -524,13 +527,13 @@ final class GroupManagerV2 {
GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!groupCandidate.hasProfileKeyCredential()) {
if (!groupCandidate.hasValidProfileKeyCredential()) {
Log.w(TAG, "No credential available, repairing");
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
return null;
}
return commitChangeWithConflictResolution(selfAci, groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.requireProfileKeyCredential()));
return commitChangeWithConflictResolution(selfAci, groupOperations.createUpdateProfileKeyCredentialChange(groupCandidate.requireExpiringProfileKeyCredential()));
}
@WorkerThread
@@ -545,14 +548,23 @@ final class GroupManagerV2 {
return null;
}
Optional<DecryptedPendingMember> aciInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> pniInPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfPni.uuid());
GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!groupCandidate.hasProfileKeyCredential()) {
if (!groupCandidate.hasValidProfileKeyCredential()) {
Log.w(TAG, "No credential available");
return null;
}
return commitChangeWithConflictResolution(selfAci, groupOperations.createAcceptInviteChange(groupCandidate.requireProfileKeyCredential()));
if (aciInPending.isPresent()) {
return commitChangeWithConflictResolution(selfAci, groupOperations.createAcceptInviteChange(groupCandidate.requireExpiringProfileKeyCredential()));
} else if (pniInPending.isPresent() && FeatureFlags.phoneNumberPrivacy()) {
return commitChangeWithConflictResolution(selfPni, groupOperations.createAcceptPniInviteChange(groupCandidate.requireExpiringProfileKeyCredential()));
}
throw new GroupChangeFailedException("Unable to accept invite when not in pending list");
}
public GroupManager.GroupActionResult ban(UUID uuid)
@@ -629,13 +641,19 @@ final class GroupManagerV2 {
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
boolean refetchedAddMemberCredentials = false;
change.setSourceUuid(UuidUtil.toByteString(authServiceId.uuid()));
for (int attempt = 0; attempt < 5; attempt++) {
try {
return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers);
} catch (GroupPatchNotAcceptedException e) {
throw new GroupChangeFailedException(e);
if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) {
refetchedAddMemberCredentials = true;
change = refetchAddMemberCredentials(change);
} else {
throw new GroupChangeFailedException(e);
}
} catch (ConflictException e) {
Log.w(TAG, "Invalid group patch or conflict", e);
@@ -657,7 +675,7 @@ final class GroupManagerV2 {
private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change)
throws IOException, GroupNotAMemberException, GroupChangeFailedException
{
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(authServiceId, groupMasterKey)
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null);
if (groupUpdateResult.getLatestServer() == null) {
@@ -685,6 +703,27 @@ final class GroupManagerV2 {
}
}
private GroupChange.Actions.Builder refetchAddMemberCredentials(@NonNull GroupChange.Actions.Builder change) {
try {
List<RecipientId> ids = groupOperations.decryptAddMembers(change.getAddMembersList())
.stream()
.map(RecipientId::from)
.collect(java.util.stream.Collectors.toList());
for (RecipientId id : ids) {
ProfileUtil.updateExpiringProfileKeyCredential(Recipient.resolved(id));
}
List<GroupCandidate> groupCandidates = groupCandidateHelper.recipientIdsToCandidatesList(ids);
return groupOperations.replaceAddMembers(change, groupCandidates);
} catch (InvalidInputException | VerificationFailedException | IOException e) {
Log.w(TAG, "Unable to refetch credentials for added members, failing change", e);
}
return change;
}
private GroupManager.GroupActionResult commitChange(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
@@ -726,7 +765,7 @@ final class GroupManagerV2 {
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
try {
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(authServiceId, groupSecretParams), Optional.empty());
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(serviceIds, groupSecretParams), Optional.empty());
} catch (NotInGroupException e) {
Log.w(TAG, e);
throw new GroupNotAMemberException(e);
@@ -751,10 +790,10 @@ final class GroupManagerV2 {
}
@WorkerThread
void updateLocalToServerRevision(@NonNull ServiceId authServiceId, int revision, long timestamp, @Nullable byte[] signedGroupChange)
void updateLocalToServerRevision(int revision, long timestamp, @Nullable byte[] signedGroupChange)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(authServiceId, groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange));
}
@@ -792,10 +831,10 @@ final class GroupManagerV2 {
if (SignalStore.internalValues().gv2ForceInvites()) {
Log.w(TAG, "Forcing GV2 invites due to internal setting");
candidates = GroupCandidate.withoutProfileKeyCredentials(candidates);
candidates = GroupCandidate.withoutExpiringProfileKeyCredentials(candidates);
}
if (!self.hasProfileKeyCredential()) {
if (!self.hasValidProfileKeyCredential()) {
Log.w(TAG, "Cannot create a V2 group as self does not have a versioned profile");
throw new MembershipNotSuitableForV2Exception("Cannot create a V2 group as self does not have a versioned profile");
}
@@ -809,9 +848,9 @@ final class GroupManagerV2 {
disappearingMessageTimerSeconds);
try {
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(selfAci, groupSecretParams));
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, ApplicationDependencies.getGroupsV2Authorization().getAuthorizationForToday(selfAci, groupSecretParams));
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, ApplicationDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
if (decryptedGroup == null) {
throw new GroupChangeFailedException();
}
@@ -907,7 +946,7 @@ final class GroupManagerV2 {
groupDatabase.update(groupId, updatedGroup);
} else {
groupDatabase.create(selfAci, groupMasterKey, decryptedGroup);
groupDatabase.create(groupMasterKey, decryptedGroup);
Log.i(TAG, "Created local group with placeholder");
}
@@ -951,7 +990,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException
{
try {
new GroupsV2StateProcessor(context).forGroup(selfAci, groupMasterKey)
new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
.updateLocalGroupToRevision(decryptedChange.getRevision(),
System.currentTimeMillis(),
decryptedChange);
@@ -1030,14 +1069,14 @@ final class GroupManagerV2 {
GroupCandidate self = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
if (!self.hasProfileKeyCredential()) {
if (!self.hasValidProfileKeyCredential()) {
throw new MembershipNotSuitableForV2Exception("No profile key credential for self");
}
ProfileKeyCredential profileKeyCredential = self.requireProfileKeyCredential();
ExpiringProfileKeyCredential expiringProfileKeyCredential = self.requireExpiringProfileKeyCredential();
GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(profileKeyCredential)
: groupOperations.createGroupJoinDirect(profileKeyCredential);
GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(expiringProfileKeyCredential)
: groupOperations.createGroupJoinDirect(expiringProfileKeyCredential);
change.setSourceUuid(selfAci.toByteString());
@@ -1083,7 +1122,7 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
{
try {
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(selfAci, groupSecretParams), Optional.ofNullable(password).map(GroupLinkPassword::serialize));
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(serviceIds, groupSecretParams), Optional.ofNullable(password).map(GroupLinkPassword::serialize));
} catch (NotInGroupException | VerificationFailedException e) {
Log.w(TAG, e);
throw new GroupChangeFailedException(e);
@@ -1127,7 +1166,7 @@ final class GroupManagerV2 {
throws IOException, VerificationFailedException, InvalidGroupStateException
{
try {
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(selfAci, groupSecretParams));
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
return true;
} catch (NotInGroupException ex) {
return false;

View File

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

View File

@@ -4,69 +4,53 @@ import androidx.annotation.NonNull;
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.auth.AuthCredentialWithPniResponse;
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.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
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 aciCache;
private final ValueCache pniCache;
private final ValueCache authCache;
private final GroupsV2Api groupsV2Api;
public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache aciCache, @NonNull ValueCache pniCache) {
public GroupsV2Authorization(@NonNull GroupsV2Api groupsV2Api, @NonNull ValueCache authCache) {
this.groupsV2Api = groupsV2Api;
this.aciCache = aciCache;
this.pniCache = pniCache;
this.authCache = authCache;
}
public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceId authServiceId,
public GroupsV2AuthorizationString getAuthorizationForToday(@NonNull ServiceIds serviceIds,
@NonNull GroupSecretParams groupSecretParams)
throws IOException, VerificationFailedException
{
boolean isPni = Objects.equals(authServiceId, SignalStore.account().getPni());
ValueCache cache = isPni ? pniCache : aciCache;
final long today = currentDaySeconds();
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();
Map<Long, AuthCredentialWithPniResponse> credentials = authCache.read();
try {
return getAuthorization(authServiceId, groupSecretParams, credentials, today);
return getAuthorization(serviceIds, groupSecretParams, credentials, today);
} catch (NoCredentialForRedemptionTimeException e) {
Log.i(TAG, "Auth out of date, will update auth and try again");
cache.clear();
authCache.clear();
} catch (VerificationFailedException e) {
Log.w(TAG, "Verification failed, will update auth and try again", e);
cache.clear();
authCache.clear();
}
Log.i(TAG, "Getting new auth credential responses");
credentials = groupsV2Api.getCredentials(today, isAci);
cache.write(credentials);
credentials = groupsV2Api.getCredentials(today);
authCache.write(credentials);
try {
return getAuthorization(authServiceId, groupSecretParams, credentials, today);
return getAuthorization(serviceIds, 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");
@@ -74,35 +58,34 @@ public class GroupsV2Authorization {
}
public void clear() {
aciCache.clear();
pniCache.clear();
authCache.clear();
}
private static int currentTimeDays() {
return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
private static long currentDaySeconds() {
return TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()));
}
private GroupsV2AuthorizationString getAuthorization(ServiceId authServiceId,
private GroupsV2AuthorizationString getAuthorization(ServiceIds serviceIds,
GroupSecretParams groupSecretParams,
Map<Integer, AuthCredentialResponse> credentials,
int today)
Map<Long, AuthCredentialWithPniResponse> credentials,
long todaySeconds)
throws NoCredentialForRedemptionTimeException, VerificationFailedException
{
AuthCredentialResponse authCredentialResponse = credentials.get(today);
AuthCredentialWithPniResponse authCredentialWithPniResponse = credentials.get(todaySeconds);
if (authCredentialResponse == null) {
if (authCredentialWithPniResponse == null) {
throw new NoCredentialForRedemptionTimeException();
}
return groupsV2Api.getGroupsV2AuthorizationString(authServiceId, today, groupSecretParams, authCredentialResponse);
return groupsV2Api.getGroupsV2AuthorizationString(serviceIds.getAci(), serviceIds.requirePni(), todaySeconds, groupSecretParams, authCredentialWithPniResponse);
}
public interface ValueCache {
void clear();
@NonNull Map<Integer, AuthCredentialResponse> read();
@NonNull Map<Long, AuthCredentialWithPniResponse> read();
void write(@NonNull Map<Integer, AuthCredentialResponse> values);
void write(@NonNull Map<Long, AuthCredentialWithPniResponse> values);
}
}

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull;
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse;
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
import java.util.Collections;
import java.util.HashMap;
@@ -10,8 +10,8 @@ import java.util.Map;
public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Authorization.ValueCache {
private final GroupsV2Authorization.ValueCache inner;
private Map<Integer, AuthCredentialResponse> values;
private final GroupsV2Authorization.ValueCache inner;
private Map<Long, AuthCredentialWithPniResponse> values;
public GroupsV2AuthorizationMemoryValueCache(@NonNull GroupsV2Authorization.ValueCache inner) {
this.inner = inner;
@@ -24,11 +24,11 @@ public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Auth
}
@Override
public @NonNull synchronized Map<Integer, AuthCredentialResponse> read() {
Map<Integer, AuthCredentialResponse> map = values;
public @NonNull synchronized Map<Long, AuthCredentialWithPniResponse> read() {
Map<Long, AuthCredentialWithPniResponse> map = values;
if (map == null) {
map = inner.read();
map = inner.read();
values = map;
}
@@ -36,7 +36,7 @@ public final class GroupsV2AuthorizationMemoryValueCache implements GroupsV2Auth
}
@Override
public synchronized void write(@NonNull Map<Integer, AuthCredentialResponse> values) {
public synchronized void write(@NonNull Map<Long, AuthCredentialWithPniResponse> values) {
inner.write(values);
this.values = Collections.unmodifiableMap(new HashMap<>(values));
}

View File

@@ -1,27 +1,25 @@
package org.thoughtcrime.securesms.groups.v2;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
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.ServiceId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -29,7 +27,7 @@ public class GroupCandidateHelper {
private final SignalServiceAccountManager signalServiceAccountManager;
private final RecipientDatabase recipientDatabase;
public GroupCandidateHelper(@NonNull Context context) {
public GroupCandidateHelper() {
signalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
recipientDatabase = SignalDatabase.recipients();
}
@@ -52,27 +50,17 @@ public class GroupCandidateHelper {
throw new AssertionError("Non UUID members should have need detected by now");
}
Optional<ProfileKeyCredential> profileKeyCredential = Optional.ofNullable(recipient.getProfileKeyCredential());
GroupCandidate candidate = new GroupCandidate(serviceId.uuid(), profileKeyCredential);
Optional<ExpiringProfileKeyCredential> expiringProfileKeyCredential = Optional.ofNullable(recipient.getExpiringProfileKeyCredential());
GroupCandidate candidate = new GroupCandidate(serviceId.uuid(), expiringProfileKeyCredential);
if (!candidate.hasProfileKeyCredential()) {
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
if (!candidate.hasValidProfileKeyCredential()) {
recipientDatabase.clearProfileKeyCredential(recipient.getId());
if (profileKey != null) {
Log.i(TAG, String.format("No profile key credential on recipient %s, fetching", recipient.getId()));
Optional<ProfileKeyCredential> profileKeyCredentialOptional = signalServiceAccountManager.resolveProfileKeyCredential(serviceId, profileKey, Locale.getDefault());
if (profileKeyCredentialOptional.isPresent()) {
boolean updatedProfileKey = recipientDatabase.setProfileKeyCredential(recipient.getId(), profileKey, profileKeyCredentialOptional.get());
if (!updatedProfileKey) {
Log.w(TAG, String.format("Failed to update the profile key credential on recipient %s", recipient.getId()));
} else {
Log.i(TAG, String.format("Got new profile key credential for recipient %s", recipient.getId()));
candidate = candidate.withProfileKeyCredential(profileKeyCredentialOptional.get());
}
}
Optional<ExpiringProfileKeyCredential> credential = ProfileUtil.updateExpiringProfileKeyCredential(recipient);
if (credential.isPresent()) {
candidate = candidate.withExpiringProfileKeyCredential(credential.get());
} else {
candidate = candidate.withoutExpiringProfileKeyCredential();
}
}
@@ -91,4 +79,17 @@ public class GroupCandidateHelper {
return result;
}
@WorkerThread
public @NonNull List<GroupCandidate> recipientIdsToCandidatesList(@NonNull Collection<RecipientId> recipientIds)
throws IOException
{
List<GroupCandidate> result = new ArrayList<>(recipientIds.size());
for (RecipientId recipientId : recipientIds) {
result.add(recipientIdToCandidate(recipientId));
}
return result;
}
}

View File

@@ -54,6 +54,7 @@ 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.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException;
@@ -103,10 +104,10 @@ public class GroupsV2StateProcessor {
this.groupDatabase = SignalDatabase.groups();
}
public StateProcessorForGroup forGroup(@NonNull ServiceId serviceId, @NonNull GroupMasterKey groupMasterKey) {
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceId, groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
public StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey) {
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceIds.getAci(), groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
return new StateProcessorForGroup(serviceId, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
return new StateProcessorForGroup(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
}
public enum GroupState {
@@ -140,7 +141,7 @@ public class GroupsV2StateProcessor {
}
public static final class StateProcessorForGroup {
private final ServiceId serviceId;
private final ServiceIds serviceIds;
private final Context context;
private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api;
@@ -150,7 +151,7 @@ public class GroupsV2StateProcessor {
private final GroupSecretParams groupSecretParams;
private final ProfileAndMessageHelper profileAndMessageHelper;
@VisibleForTesting StateProcessorForGroup(@NonNull ServiceId serviceId,
@VisibleForTesting StateProcessorForGroup(@NonNull ServiceIds serviceIds,
@NonNull Context context,
@NonNull GroupDatabase groupDatabase,
@NonNull GroupsV2Api groupsV2Api,
@@ -158,7 +159,7 @@ public class GroupsV2StateProcessor {
@NonNull GroupMasterKey groupMasterKey,
@NonNull ProfileAndMessageHelper profileAndMessageHelper)
{
this.serviceId = serviceId;
this.serviceIds = serviceIds;
this.context = context;
this.groupDatabase = groupDatabase;
this.groupsV2Api = groupsV2Api;
@@ -196,17 +197,17 @@ public class GroupsV2StateProcessor {
{
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.");
warn("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");
warn( "Ignoring P2P group change by setting");
} else {
try {
Log.i(TAG, "Applying P2P group change");
info("Applying P2P group change");
DecryptedGroup newState = DecryptedGroupUtil.apply(localState, signedGroupChange);
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange)));
} catch (NotAbleToApplyGroupV2ChangeException e) {
Log.w(TAG, "Unable to apply P2P group change", e);
warn( "Unable to apply P2P group change", e);
}
}
}
@@ -218,24 +219,24 @@ public class GroupsV2StateProcessor {
if (localState != null && signedGroupChange != null) {
try {
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange)) {
Log.w(TAG, "Server says we're not a member. Ignoring P2P group change because we're not currently in the group and this change doesn't add us in.");
warn( "Server says we're not a member. Ignoring P2P group change because we're not currently in the group and this change doesn't add us in.");
} else {
Log.i(TAG, "Server says we're not a member. Applying P2P group change.");
info("Server says we're not a member. Applying P2P group change.");
DecryptedGroup newState = DecryptedGroupUtil.applyWithoutRevisionCheck(localState, signedGroupChange);
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(newState, signedGroupChange)));
}
} catch (NotAbleToApplyGroupV2ChangeException failed) {
Log.w(TAG, "Unable to apply P2P group change when not a member", failed);
warn( "Unable to apply P2P group change when not a member", failed);
}
}
if (inputGroupState == null) {
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");
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, serviceIds)) {
warn( "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");
warn( "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
insertGroupLeave();
}
throw e;
@@ -252,7 +253,7 @@ public class GroupsV2StateProcessor {
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder");
info("Inserting single update message for restore placeholder");
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
} else {
profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
@@ -261,7 +262,7 @@ public class GroupsV2StateProcessor {
GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState();
if (remainingWork.getServerHistory().size() > 0) {
Log.i(TAG, String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.getRevision() + 1, remainingWork.getLatestRevisionNumber()));
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.getRevision() + 1, remainingWork.getLatestRevisionNumber()));
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, remainingWork.getLatestRevisionNumber()));
}
@@ -276,14 +277,14 @@ public class GroupsV2StateProcessor {
.map(DecryptedMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid()));
.anyMatch(serviceIds::matches);
boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList()
.stream()
.map(DecryptedPendingMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid()));
.anyMatch(serviceIds::matches);
return !currentlyInGroup && !addedAsMember && !addedAsPendingMember;
}
@@ -294,7 +295,7 @@ public class GroupsV2StateProcessor {
.map(DecryptedPendingMemberRemoval::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.anyMatch(u -> u.equals(serviceId.uuid()));
.anyMatch(serviceIds::matches);
return !havingInviteRevoked;
}
@@ -305,44 +306,43 @@ public class GroupsV2StateProcessor {
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);
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
info("Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
PartialDecryptedGroup latestServerGroup;
GlobalGroupState inputGroupState;
try {
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams));
latestServerGroup = groupsV2Api.getPartialDecryptedGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, 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(serviceId.uuid(), localState.getMembersList())) {
Log.i(TAG, "Local state is at or later than server");
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci().uuid(), localState.getMembersList())) {
info("Local state is at or later than server");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
}
if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceId.uuid(), latestServerGroup.getMembersList())) {
Log.i(TAG, "Latest revision or not a member, use latest only");
if (latestRevisionOnly || !GroupProtoUtil.isMember(serviceIds.getAci().uuid(), latestServerGroup.getMembersList())) {
info("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, serviceId.uuid());
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci().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()) ||
!GroupProtoUtil.isMember(serviceIds.getAci().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, serviceId, logsNeededFrom, includeFirstState);
info("Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") +
" logsNeededFrom: " + logsNeededFrom +
" includeFirstState: " + includeFirstState +
" forceIncludeFirst: " + forceIncludeFirst);
inputGroupState = getFullMemberHistoryPage(localState, logsNeededFrom, includeFirstState);
}
ProfileKeySet profileKeys = new ProfileKeySet();
@@ -354,13 +354,13 @@ public class GroupsV2StateProcessor {
while (hasMore) {
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
Log.i(TAG, "Advanced group to revision: " + (newLocalState != null ? newLocalState.getRevision() : "null"));
info("Advanced group to revision: " + (newLocalState != null ? newLocalState.getRevision() : "null"));
if (newLocalState != null && !inputGroupState.hasMore() && !forceIncludeFirst) {
int newLocalRevision = newLocalState.getRevision();
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 + "]");
warn( "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);
}
}
@@ -389,20 +389,20 @@ public class GroupsV2StateProcessor {
hasMore = inputGroupState.hasMore();
if (hasMore) {
Log.i(TAG, "Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, serviceId, inputGroupState.getNextPageRevision(), false);
info("Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, inputGroupState.getNextPageRevision(), false);
}
}
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder");
info("Inserting single update message for restore placeholder");
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null)));
}
profileAndMessageHelper.persistLearnedProfileKeys(profileKeys);
if (finalGlobalGroupState.getServerHistory().size() > 0) {
Log.i(TAG, String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber()));
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber()));
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, finalGlobalGroupState.getLatestRevisionNumber()));
}
@@ -414,7 +414,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams));
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
} catch (GroupNotFoundException e) {
throw new GroupDoesNotExistException(e);
} catch (NotInGroupException e) {
@@ -429,7 +429,7 @@ public class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), true)
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), true)
.getResults()
.get(0)
.getGroup()
@@ -445,12 +445,12 @@ public class GroupsV2StateProcessor {
private void insertGroupLeave() {
if (!groupDatabase.isActive(groupId)) {
Log.w(TAG, "Group has already been left.");
warn("Group has already been left.");
return;
}
Recipient groupRecipient = Recipient.externalGroupExact(groupId);
UUID selfUuid = serviceId.uuid();
UUID selfUuid = serviceIds.getAci().uuid();
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
.requireV2GroupProperties()
@@ -485,7 +485,7 @@ public class GroupsV2StateProcessor {
mmsDatabase.markAsSent(id, true);
threadDatabase.update(threadId, false, false);
} catch (MmsException e) {
Log.w(TAG, "Failed to insert leave message.", e);
warn( "Failed to insert leave message.", e);
}
groupDatabase.setActive(groupId, false);
@@ -509,7 +509,7 @@ public class GroupsV2StateProcessor {
boolean needsAvatarFetch;
if (inputGroupState.getLocalState() == null) {
groupDatabase.create(serviceId, masterKey, newLocalState);
groupDatabase.create(masterKey, newLocalState);
needsAvatarFetch = !TextUtils.isEmpty(newLocalState.getAvatar());
} else {
groupDatabase.update(masterKey, newLocalState);
@@ -523,14 +523,14 @@ public class GroupsV2StateProcessor {
profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState);
}
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ServiceId serviceId, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
try {
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceId, groupSecretParams), includeFirstState);
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState);
ArrayList<ServerGroupLogEntry> history = new ArrayList<>(groupHistoryPage.getResults().size());
boolean ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges();
if (ignoreServerChanges) {
Log.w(TAG, "Server change logs are ignored by setting");
warn( "Server change logs are ignored by setting");
}
for (DecryptedGroupHistoryEntry entry : groupHistoryPage.getResults()) {
@@ -547,6 +547,22 @@ public class GroupsV2StateProcessor {
throw new IOException(e);
}
}
private void info(String message) {
info(message, null);
}
private void info(String message, Throwable t) {
Log.i(TAG, "[" + groupId.toString() + "] " + message, t);
}
private void warn(String message) {
warn(message, null);
}
private void warn(String message, Throwable e) {
Log.w(TAG, "[" + groupId.toString() + "] " + message, e);
}
}
@VisibleForTesting
@@ -615,7 +631,7 @@ public class GroupsV2StateProcessor {
.map(uuid -> Recipient.externalPush(ServiceId.from(uuid))));
if (addedBy.isPresent() && addedBy.get().isBlocked()) {
Log.i(TAG, String.format( "Added to group %s by a blocked user %s. Leaving group.", groupId, addedBy.get().getId()));
Log.i(TAG, String.format("Added to group %s by a blocked user %s. Leaving group.", groupId, addedBy.get().getId()));
ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId));
//noinspection UnnecessaryReturnStatement
return;