2017-05-20 01:01:40 +00:00
|
|
|
package org.thoughtcrime.securesms.jobs;
|
|
|
|
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
import android.app.Application;
|
2017-05-20 01:01:40 +00:00
|
|
|
import android.support.annotation.NonNull;
|
2017-06-14 16:35:32 +00:00
|
|
|
import android.text.TextUtils;
|
2018-08-09 14:15:43 +00:00
|
|
|
|
2017-08-15 01:11:13 +00:00
|
|
|
import org.thoughtcrime.securesms.ApplicationContext;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|
|
|
import org.thoughtcrime.securesms.database.Address;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
|
|
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
2019-03-28 15:56:35 +00:00
|
|
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
|
|
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
|
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
2018-10-15 20:27:21 +00:00
|
|
|
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Base64;
|
2017-06-23 20:57:38 +00:00
|
|
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
2017-08-15 01:11:13 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.whispersystems.libsignal.IdentityKey;
|
|
|
|
import org.whispersystems.libsignal.InvalidKeyException;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
|
|
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
2019-05-01 16:00:26 +00:00
|
|
|
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
2017-08-15 01:11:13 +00:00
|
|
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
|
|
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
2017-08-08 23:37:15 +00:00
|
|
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
2018-10-11 23:45:22 +00:00
|
|
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
2017-05-31 21:51:23 +00:00
|
|
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
import java.io.IOException;
|
2017-08-01 15:56:00 +00:00
|
|
|
import java.util.List;
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
import javax.inject.Inject;
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
public class RetrieveProfileJob extends BaseJob implements InjectableType {
|
2018-08-09 14:15:43 +00:00
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
public static final String KEY = "RetrieveProfileJob";
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
private static final String TAG = RetrieveProfileJob.class.getSimpleName();
|
|
|
|
|
2018-08-09 14:15:43 +00:00
|
|
|
private static final String KEY_ADDRESS = "address";
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
@Inject SignalServiceMessageReceiver receiver;
|
2017-05-20 01:01:40 +00:00
|
|
|
|
2018-08-09 14:15:43 +00:00
|
|
|
private Recipient recipient;
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
public RetrieveProfileJob(@NonNull Recipient recipient) {
|
|
|
|
this(new Job.Parameters.Builder()
|
|
|
|
.addConstraint(NetworkConstraint.KEY)
|
|
|
|
.setMaxAttempts(3)
|
|
|
|
.build(),
|
|
|
|
recipient);
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
2017-05-20 01:01:40 +00:00
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
private RetrieveProfileJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient) {
|
|
|
|
super(parameters);
|
2017-08-01 15:56:00 +00:00
|
|
|
this.recipient = recipient;
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-03-28 15:56:35 +00:00
|
|
|
public @NonNull Data serialize() {
|
|
|
|
return new Data.Builder().putString(KEY_ADDRESS, recipient.getAddress().serialize()).build();
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-03-28 15:56:35 +00:00
|
|
|
public @NonNull String getFactoryKey() {
|
|
|
|
return KEY;
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRun() throws IOException, InvalidKeyException {
|
2017-05-31 21:51:23 +00:00
|
|
|
try {
|
2017-08-01 15:56:00 +00:00
|
|
|
if (recipient.isGroupRecipient()) handleGroupRecipient(recipient);
|
|
|
|
else handleIndividualRecipient(recipient);
|
2017-05-31 21:51:23 +00:00
|
|
|
} catch (InvalidNumberException e) {
|
|
|
|
Log.w(TAG, e);
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-05-22 16:51:56 +00:00
|
|
|
public boolean onShouldRetry(@NonNull Exception e) {
|
2017-05-20 01:01:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCanceled() {}
|
|
|
|
|
|
|
|
private void handleIndividualRecipient(Recipient recipient)
|
2017-05-31 21:51:23 +00:00
|
|
|
throws IOException, InvalidKeyException, InvalidNumberException
|
2017-05-20 01:01:40 +00:00
|
|
|
{
|
2018-05-22 09:13:10 +00:00
|
|
|
String number = recipient.getAddress().toPhoneString();
|
|
|
|
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(recipient);
|
|
|
|
|
|
|
|
SignalServiceProfile profile;
|
|
|
|
|
|
|
|
try {
|
|
|
|
profile = retrieveProfile(number, unidentifiedAccess);
|
2018-10-11 23:45:22 +00:00
|
|
|
} catch (NonSuccessfulResponseCodeException e) {
|
2018-05-22 09:13:10 +00:00
|
|
|
if (unidentifiedAccess.isPresent()) {
|
|
|
|
profile = retrieveProfile(number, Optional.absent());
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2017-06-14 16:35:32 +00:00
|
|
|
|
2017-08-15 01:11:13 +00:00
|
|
|
setIdentityKey(recipient, profile.getIdentityKey());
|
2017-08-22 17:44:04 +00:00
|
|
|
setProfileName(recipient, profile.getName());
|
|
|
|
setProfileAvatar(recipient, profile.getAvatar());
|
2018-05-22 09:13:10 +00:00
|
|
|
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void handleGroupRecipient(Recipient group)
|
2017-05-31 21:51:23 +00:00
|
|
|
throws IOException, InvalidKeyException, InvalidNumberException
|
2017-05-20 01:01:40 +00:00
|
|
|
{
|
2017-08-01 15:56:00 +00:00
|
|
|
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.getAddress().toGroupString(), false);
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
for (Recipient recipient : recipients) {
|
|
|
|
handleIndividualRecipient(recipient);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:13:10 +00:00
|
|
|
private SignalServiceProfile retrieveProfile(@NonNull String number, Optional<UnidentifiedAccess> unidentifiedAccess)
|
|
|
|
throws IOException
|
|
|
|
{
|
2018-10-11 23:45:22 +00:00
|
|
|
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
|
|
|
|
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
|
|
|
|
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
|
|
|
|
: authPipe;
|
2017-05-20 01:01:40 +00:00
|
|
|
|
|
|
|
if (pipe != null) {
|
|
|
|
try {
|
2018-05-22 09:13:10 +00:00
|
|
|
return pipe.getProfile(new SignalServiceAddress(number), unidentifiedAccess);
|
2017-05-20 01:01:40 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:13:10 +00:00
|
|
|
return receiver.retrieveProfile(new SignalServiceAddress(number), unidentifiedAccess);
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|
2017-08-15 01:11:13 +00:00
|
|
|
|
|
|
|
private void setIdentityKey(Recipient recipient, String identityKeyValue) {
|
|
|
|
try {
|
|
|
|
if (TextUtils.isEmpty(identityKeyValue)) {
|
|
|
|
Log.w(TAG, "Identity key is missing on profile!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyValue), 0);
|
|
|
|
|
|
|
|
if (!DatabaseFactory.getIdentityDatabase(context)
|
|
|
|
.getIdentity(recipient.getAddress())
|
|
|
|
.isPresent())
|
|
|
|
{
|
|
|
|
Log.w(TAG, "Still first use...");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
IdentityUtil.saveIdentity(context, recipient.getAddress().toPhoneString(), identityKey);
|
|
|
|
} catch (InvalidKeyException | IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:13:10 +00:00
|
|
|
private void setUnidentifiedAccessMode(Recipient recipient, String unidentifiedAccessVerifier, boolean unrestrictedUnidentifiedAccess) {
|
|
|
|
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
|
|
|
byte[] profileKey = recipient.getProfileKey();
|
|
|
|
|
2018-11-06 17:58:45 +00:00
|
|
|
if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
|
2018-10-11 23:45:22 +00:00
|
|
|
Log.i(TAG, "Marking recipient UD status as unrestricted.");
|
2018-05-22 09:13:10 +00:00
|
|
|
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
|
|
|
|
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
|
2018-10-11 23:45:22 +00:00
|
|
|
Log.i(TAG, "Marking recipient UD status as disabled.");
|
2018-05-22 09:13:10 +00:00
|
|
|
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
|
|
|
|
} else {
|
|
|
|
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
|
|
|
boolean verifiedUnidentifiedAccess;
|
|
|
|
|
|
|
|
try {
|
|
|
|
verifiedUnidentifiedAccess = profileCipher.verifyUnidentifiedAccess(Base64.decode(unidentifiedAccessVerifier));
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
verifiedUnidentifiedAccess = false;
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:45:22 +00:00
|
|
|
UnidentifiedAccessMode mode = verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED;
|
|
|
|
Log.i(TAG, "Marking recipient UD status as " + mode.name() + " after verification.");
|
|
|
|
recipientDatabase.setUnidentifiedAccessMode(recipient, mode);
|
2018-05-22 09:13:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-22 17:44:04 +00:00
|
|
|
private void setProfileName(Recipient recipient, String profileName) {
|
2017-08-15 01:11:13 +00:00
|
|
|
try {
|
2017-08-22 17:44:04 +00:00
|
|
|
byte[] profileKey = recipient.getProfileKey();
|
|
|
|
if (profileKey == null) return;
|
2017-08-15 01:11:13 +00:00
|
|
|
|
|
|
|
String plaintextProfileName = null;
|
|
|
|
|
|
|
|
if (profileName != null) {
|
2017-08-22 17:44:04 +00:00
|
|
|
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
2017-08-15 01:11:13 +00:00
|
|
|
plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName)));
|
|
|
|
}
|
|
|
|
|
2017-08-22 17:44:04 +00:00
|
|
|
if (!Util.equals(plaintextProfileName, recipient.getProfileName())) {
|
|
|
|
DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient, plaintextProfileName);
|
2017-08-15 01:11:13 +00:00
|
|
|
}
|
2019-05-01 16:00:26 +00:00
|
|
|
} catch (InvalidCiphertextException | IOException e) {
|
2017-08-15 01:11:13 +00:00
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-22 17:44:04 +00:00
|
|
|
private void setProfileAvatar(Recipient recipient, String profileAvatar) {
|
|
|
|
if (recipient.getProfileKey() == null) return;
|
2017-08-15 01:11:13 +00:00
|
|
|
|
2017-08-22 17:44:04 +00:00
|
|
|
if (!Util.equals(profileAvatar, recipient.getProfileAvatar())) {
|
2017-08-15 01:11:13 +00:00
|
|
|
ApplicationContext.getInstance(context)
|
|
|
|
.getJobManager()
|
2019-03-28 15:56:35 +00:00
|
|
|
.add(new RetrieveProfileAvatarJob(recipient, profileAvatar));
|
2017-08-15 01:11:13 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-22 09:13:10 +00:00
|
|
|
|
|
|
|
private Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
|
|
|
|
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
|
|
|
|
|
|
|
if (unidentifiedAccess.isPresent()) {
|
|
|
|
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Optional.absent();
|
|
|
|
}
|
2019-03-28 15:56:35 +00:00
|
|
|
|
|
|
|
public static final class Factory implements Job.Factory<RetrieveProfileJob> {
|
|
|
|
|
|
|
|
private final Application application;
|
|
|
|
|
|
|
|
public Factory(Application application) {
|
|
|
|
this.application = application;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NonNull RetrieveProfileJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
|
|
return new RetrieveProfileJob(parameters, Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true));
|
|
|
|
}
|
|
|
|
}
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|