Signal-Android/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJo...

156 wiersze
6.8 KiB
Java

package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Sends a {@link SenderKeyDistributionMessage} to a target recipient.
*
* Will re-check group membership at send time and send the proper distribution message if they're still a member.
*/
public final class SenderKeyDistributionSendJob extends BaseJob {
private static final String TAG = Log.tag(SenderKeyDistributionSendJob.class);
public static final String KEY = "SenderKeyDistributionSendJob";
private static final String KEY_TARGET_RECIPIENT_ID = "recipient_id";
private static final String KEY_THREAD_RECIPIENT_ID = "thread_recipient_id";
private final RecipientId targetRecipientId;
private final RecipientId threadRecipientId;
public SenderKeyDistributionSendJob(@NonNull RecipientId targetRecipientId, RecipientId threadRecipientId) {
this(targetRecipientId, threadRecipientId, new Parameters.Builder()
.setQueue(targetRecipientId.toQueueKey())
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.setMaxInstancesForQueue(1)
.build());
}
private SenderKeyDistributionSendJob(@NonNull RecipientId targetRecipientId, @NonNull RecipientId threadRecipientId, @NonNull Parameters parameters) {
super(parameters);
this.targetRecipientId = targetRecipientId;
this.threadRecipientId = threadRecipientId;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_TARGET_RECIPIENT_ID, targetRecipientId.serialize())
.putString(KEY_THREAD_RECIPIENT_ID, threadRecipientId.serialize())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
protected void onRun() throws Exception {
Recipient targetRecipient = Recipient.resolved(targetRecipientId);
Recipient threadRecipient = Recipient.resolved(threadRecipientId);
if (targetRecipient.isUnregistered()) {
Log.w(TAG, threadRecipient.getId() + " not registered!");
return;
}
GroupId.V2 groupId;
DistributionId distributionId;
if (threadRecipient.isPushV2Group()) {
groupId = threadRecipient.requireGroupId().requireV2();
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
} else if (threadRecipient.isDistributionList()) {
groupId = null;
distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipientId);
} else {
warn(TAG, "Recipient is not a group or distribution list! Skipping.");
return;
}
if (distributionId == null) {
warn(TAG, "Failed to find a distributionId! Skipping.");
return;
}
if (groupId != null && !SignalDatabase.groups().isCurrentMember(groupId, targetRecipientId)) {
Log.w(TAG, targetRecipientId + " is no longer a member of " + groupId + "! Not sending.");
return;
} else if (groupId == null) {
DistributionListRecord listRecord = SignalDatabase.distributionLists().getList(threadRecipientId);
if (listRecord == null || !listRecord.getMembers().contains(targetRecipientId)) {
Log.w(TAG, targetRecipientId + " is no longer a member of the distribution list! Not sending.");
return;
}
}
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient));
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false, false).get(0);
if (result.isSuccess()) {
List<SignalProtocolAddress> addresses = result.getSuccess()
.getDevices()
.stream()
.map(device -> targetRecipient.requireServiceId().toProtocolAddress(device))
.collect(Collectors.toList());
ApplicationDependencies.getProtocolStore().aci().markSenderKeySharedWith(distributionId, addresses);
}
}
@Override
protected boolean onShouldRetry(@NonNull Exception e) {
return false;
}
@Override
public void onFailure() {
}
public static final class Factory implements Job.Factory<SenderKeyDistributionSendJob> {
@Override
public @NonNull SenderKeyDistributionSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new SenderKeyDistributionSendJob(RecipientId.from(data.getString(KEY_TARGET_RECIPIENT_ID)),
RecipientId.from(data.getString(KEY_THREAD_RECIPIENT_ID)),
parameters);
}
}
}