mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-23 18:49:37 +00:00
Support for retrieving and storing profile information
Initial support for sharing profile keys // FREEBIE
This commit is contained in:
@@ -25,6 +25,8 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
@@ -81,6 +83,8 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -164,6 +168,10 @@ public class PushDecryptJob extends ContextJob {
|
||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
|
||||
handleUnknownGroupMessage(envelope, message.getGroupInfo().get());
|
||||
}
|
||||
|
||||
if (message.getProfileKey().isPresent()) {
|
||||
handleProfileKey(envelope, message);
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
|
||||
|
||||
@@ -794,6 +802,24 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleProfileKey(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message)
|
||||
{
|
||||
RecipientPreferenceDatabase database = DatabaseFactory.getRecipientPreferenceDatabase(context);
|
||||
Address sourceAddress = Address.fromExternal(context, envelope.getSource());
|
||||
Optional<RecipientsPreferences> preferences = database.getRecipientsPreferences(sourceAddress);
|
||||
|
||||
if (!preferences.isPresent() || preferences.get().getProfileKey() == null ||
|
||||
!MessageDigest.isEqual(message.getProfileKey().get(), preferences.get().getProfileKey()))
|
||||
{
|
||||
database.setProfileKey(sourceAddress, message.getProfileKey().get());
|
||||
|
||||
Recipient recipient = RecipientFactory.getRecipientFor(context, sourceAddress, true);
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileJob(context, recipient));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
|
@@ -162,7 +162,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
SignalServiceDataMessage groupMessage = new SignalServiceDataMessage(message.getSentTimeMillis(), group,
|
||||
attachmentStreams, message.getBody(), false,
|
||||
(int)(message.getExpiresIn() / 1000),
|
||||
message.isExpirationUpdate());
|
||||
message.isExpirationUpdate(), null);
|
||||
|
||||
messageSender.sendMessage(addresses, groupMessage);
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
@@ -113,11 +114,13 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments);
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient().getAddress());
|
||||
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
.withAttachments(attachmentStreams)
|
||||
.withTimestamp(message.getSentTimeMillis())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.build();
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
@@ -10,12 +11,16 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@@ -60,6 +65,28 @@ public abstract class PushSendJob extends SendJob {
|
||||
onPushSend(masterSecret);
|
||||
}
|
||||
|
||||
protected Optional<byte[]> getProfileKey(Address address) {
|
||||
try {
|
||||
Optional<RecipientsPreferences> recipientsPreferences = DatabaseFactory.getRecipientPreferenceDatabase(context)
|
||||
.getRecipientsPreferences(address);
|
||||
|
||||
if (recipientsPreferences.isPresent() && !TextUtils.isEmpty(recipientsPreferences.get().getSystemDisplayName())) {
|
||||
String profileKey = TextSecurePreferences.getProfileKey(context);
|
||||
|
||||
if (profileKey == null) {
|
||||
profileKey = Util.getSecret(32);
|
||||
TextSecurePreferences.setProfileKey(context, profileKey);
|
||||
}
|
||||
|
||||
return Optional.of(Base64.decode(profileKey));
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected SignalServiceAddress getPushAddress(Address address) {
|
||||
// String relay = TextSecureDirectory.getInstance(context).getRelay(address.toPhoneString());
|
||||
String relay = null;
|
||||
|
@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
@@ -101,10 +102,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
try {
|
||||
SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress());
|
||||
SignalServiceMessageSender messageSender = messageSenderFactory.create();
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getIndividualRecipient().getAddress());
|
||||
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(message.getDateSent())
|
||||
.withBody(message.getBody().getBody())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.build();
|
||||
|
||||
|
@@ -0,0 +1,109 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class RetrieveProfileAvatarJob extends ContextJob implements InjectableType {
|
||||
|
||||
private static final String TAG = RetrieveProfileAvatarJob.class.getSimpleName();
|
||||
|
||||
private static final int MAX_PROFILE_SIZE_BYTES = 20 * 1024 * 1024;
|
||||
|
||||
@Inject SignalServiceMessageReceiver receiver;
|
||||
|
||||
private final String profileAvatar;
|
||||
private final Recipient recipient;
|
||||
|
||||
public RetrieveProfileAvatarJob(Context context, Recipient recipient, String profileAvatar) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withGroupId(RetrieveProfileAvatarJob.class.getSimpleName() + recipient.getAddress().serialize())
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.create());
|
||||
|
||||
this.recipient = recipient;
|
||||
this.profileAvatar = profileAvatar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
RecipientPreferenceDatabase database = DatabaseFactory.getRecipientPreferenceDatabase(context);
|
||||
Optional<RecipientsPreferences> recipientsPreferences = database.getRecipientsPreferences(recipient.getAddress());
|
||||
File avatarDirectory = new File(context.getFilesDir(), "avatars");
|
||||
File avatarFile = new File(avatarDirectory, new File(recipient.getAddress().serialize()).getName());
|
||||
|
||||
avatarDirectory.mkdirs();
|
||||
|
||||
if (!recipientsPreferences.isPresent()) {
|
||||
Log.w(TAG, "Recipient preference row is gone!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (recipientsPreferences.get().getProfileKey() == null) {
|
||||
Log.w(TAG, "Recipient profile key is gone!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Util.equals(profileAvatar, recipientsPreferences.get().getProfileAvatar())) {
|
||||
Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(profileAvatar)) {
|
||||
Log.w(TAG, "Removing profile avatar for: " + recipient.getAddress().serialize());
|
||||
avatarFile.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
File downloadDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||
|
||||
try {
|
||||
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, recipientsPreferences.get().getProfileKey(), MAX_PROFILE_SIZE_BYTES);
|
||||
File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||
|
||||
Util.copy(avatarStream, new FileOutputStream(decryptDestination));
|
||||
decryptDestination.renameTo(avatarFile);
|
||||
} finally {
|
||||
if (downloadDestination != null) downloadDestination.delete();
|
||||
}
|
||||
|
||||
database.setProfileAvatar(recipient.getAddress(), profileAvatar);
|
||||
RecipientFactory.clearCache(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(Exception e) {
|
||||
Log.w(TAG, e);
|
||||
if (e instanceof PushNetworkException) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
|
||||
}
|
||||
}
|
@@ -6,17 +6,23 @@ import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.service.MessageRetrievalService;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
@@ -66,25 +72,13 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
|
||||
private void handleIndividualRecipient(Recipient recipient)
|
||||
throws IOException, InvalidKeyException, InvalidNumberException
|
||||
{
|
||||
String number = recipient.getAddress().toPhoneString();
|
||||
SignalServiceProfile profile = retrieveProfile(number);
|
||||
String number = recipient.getAddress().toPhoneString();
|
||||
SignalServiceProfile profile = retrieveProfile(number);
|
||||
Optional<RecipientsPreferences> recipientPreferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipient.getAddress());
|
||||
|
||||
if (TextUtils.isEmpty(profile.getIdentityKey())) {
|
||||
Log.w(TAG, "Identity key is missing on profile!");
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityKey identityKey = new IdentityKey(Base64.decode(profile.getIdentityKey()), 0);
|
||||
|
||||
if (!DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipient.getAddress())
|
||||
.isPresent())
|
||||
{
|
||||
Log.w(TAG, "Still first use...");
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityUtil.saveIdentity(context, number, identityKey);
|
||||
setIdentityKey(recipient, profile.getIdentityKey());
|
||||
setProfileName(recipient, recipientPreferences, profile.getName());
|
||||
setProfileAvatar(recipient, recipientPreferences, profile.getAvatar());
|
||||
}
|
||||
|
||||
private void handleGroupRecipient(Recipient group)
|
||||
@@ -110,4 +104,59 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
|
||||
|
||||
return receiver.retrieveProfile(new SignalServiceAddress(number));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileName(Recipient recipient, Optional<RecipientsPreferences> recipientPreferences, String profileName) {
|
||||
try {
|
||||
if (!recipientPreferences.isPresent()) return;
|
||||
if (recipientPreferences.get().getProfileKey() == null) return;
|
||||
|
||||
String plaintextProfileName = null;
|
||||
|
||||
if (profileName != null) {
|
||||
ProfileCipher profileCipher = new ProfileCipher(recipientPreferences.get().getProfileKey());
|
||||
plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName)));
|
||||
}
|
||||
|
||||
if (!Util.equals(plaintextProfileName, recipientPreferences.get().getProfileName())) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(context).setProfileName(recipient.getAddress(), plaintextProfileName);
|
||||
RecipientFactory.clearCache(context);
|
||||
}
|
||||
} catch (ProfileCipher.InvalidCiphertextException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileAvatar(Recipient recipient, Optional<RecipientsPreferences> recipientPreferences, String profileAvatar) {
|
||||
if (!recipientPreferences.isPresent()) return;
|
||||
if (recipientPreferences.get().getProfileKey() == null) return;
|
||||
|
||||
if (!Util.equals(profileAvatar, recipientPreferences.get().getProfileAvatar())) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new RetrieveProfileAvatarJob(context, recipient, profileAvatar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user