Accept group invitations addressed to the PNI (#2146)

An invitation by phone number lists the account's PNI as the pending member. acceptInvite compared pending members only with the self ACI address and failed with 'Cannot find service ID for self to accept invite'. It now matches either the ACI or the PNI, makes the change as the ACI, and for a PNI promotion decrypts the server's signed change, since the locally built action has no member ciphertexts to decrypt.
This commit is contained in:
wakamex 2026-10-14 05:56:36 -04:00 committed by GitHub
parent 1da2a51c1e
commit c3b0fe902e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -480,15 +480,16 @@ class GroupV2Helper {
}
// We need to accept the invite with the ACI or PNI that was used for the invitation
final var selfAddress = context.getRecipientHelper().resolveSignalServiceAddress(selfRecipientId);
final var account = context.getAccount();
final var selfServiceId = groupInfoV2.getPendingMemberAddresses()
.stream()
.filter(s -> s.matches(selfAddress))
.map(SignalServiceAddress::getServiceId)
.filter(s -> s.equals(account.getAci()) || s.equals(account.getPni()))
.findFirst();
if (selfServiceId.isEmpty()) {
throw new IOException("Cannot find service ID for self to accept invite");
}
final var serviceId = selfServiceId.get().getServiceId();
final var serviceId = selfServiceId.get();
final GroupChange.Actions.Builder change;
if (serviceId instanceof ACI) {
@ -497,7 +498,8 @@ class GroupV2Helper {
change = groupOperations.createAcceptPniInviteChange(profileKeyCredential);
}
change.sourceUserId(serviceId.toByteString());
// The change is made by the account's ACI, also when it promotes a PNI invitation
change.sourceUserId(account.getAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -634,6 +636,22 @@ class GroupV2Helper {
final DecryptedGroupChange decryptedChange;
final DecryptedGroup decryptedGroupState;
if (!changeActions.promote_members_pending_pni_aci_profile_key.isEmpty()) {
// The server fills in the ACI and PNI ciphertexts of a PNI promotion, so decrypt its signed change
final var signedGroupChange = dependencies.getGroupsV2Api()
.patchGroup(changeActions, getGroupAuthForToday(groupSecretParams), Optional.empty());
final var groupId = groupSecretParams.getPublicParams().getGroupIdentifier();
try {
decryptedChange = groupOperations.decryptChange(signedGroupChange.group_change,
DecryptChangeVerificationMode.verify(groupId)).orElseThrow();
decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
throw new IOException(e);
}
groupInfoV2.setGroup(decryptedGroupState);
return new Pair<>(decryptedGroupState, signedGroupChange);
}
try {
decryptedChange = groupOperations.decryptChange(changeActions, getSelfAci());
decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);