From c9e25043491dbaf97f7820a2dc0d7a97d1fcdec6 Mon Sep 17 00:00:00 2001 From: Patrick Dattilio Date: Mon, 27 Apr 2026 10:25:47 -0400 Subject: [PATCH] Store profile keys from group requesting members (#2031) When filling or updating a V2 group, profile keys were copied from DecryptedGroup.members into the local profile store but not from requestingMembers. Admins who never had a prior session with a user in the join queue then lacked profile keys and could not decrypt profiles (e.g. for listContacts). Also process DecryptedRequestingMember entries the same way as full members, using DecryptedMember / DecryptedRequestingMember types so the lib module does not require a direct protobuf dependency. Made-with: Cursor --- .../signal/manager/helper/GroupHelper.java | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 024e84fc..7d475929 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -38,6 +38,8 @@ import org.signal.storageservice.storage.protos.groups.GroupChangeResponse; import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup; import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo; +import org.signal.storageservice.storage.protos.groups.local.DecryptedMember; +import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog; @@ -557,17 +559,44 @@ public class GroupHelper { private void storeProfileKeysFromMembers(final DecryptedGroup group) { for (var member : group.members) { - final var serviceId = ServiceId.parseOrThrow(member.aciBytes); - final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId); - final var profileStore = account.getProfileStore(); - if (profileStore.getProfileKey(recipientId) != null) { - // We already have a profile key, not updating it from a non-authoritative source - continue; - } - try { - profileStore.storeProfileKey(recipientId, new ProfileKey(member.profileKey.toByteArray())); - } catch (InvalidInputException ignored) { - } + storeProfileKeyForDecryptedMemberIfMissing(member); + } + for (var member : group.requestingMembers) { + storeProfileKeyForDecryptedRequestingMemberIfMissing(member); + } + } + + private void storeProfileKeyForDecryptedMemberIfMissing(final DecryptedMember member) { + if (member == null) { + return; + } + final var serviceId = ServiceId.parseOrThrow(member.aciBytes); + final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId); + final var profileStore = account.getProfileStore(); + if (profileStore.getProfileKey(recipientId) != null) { + // We already have a profile key, not updating it from a non-authoritative source + return; + } + try { + profileStore.storeProfileKey(recipientId, new ProfileKey(member.profileKey.toByteArray())); + } catch (InvalidInputException ignored) { + } + } + + private void storeProfileKeyForDecryptedRequestingMemberIfMissing(final DecryptedRequestingMember member) { + if (member == null) { + return; + } + final var serviceId = ServiceId.parseOrThrow(member.aciBytes); + final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId); + final var profileStore = account.getProfileStore(); + if (profileStore.getProfileKey(recipientId) != null) { + // We already have a profile key, not updating it from a non-authoritative source + return; + } + try { + profileStore.storeProfileKey(recipientId, new ProfileKey(member.profileKey.toByteArray())); + } catch (InvalidInputException ignored) { } }