Move more profile functionality to ProfileHelper

This commit is contained in:
AsamK 2021-08-26 10:56:30 +02:00
parent cd3741d236
commit e532a24cf8
3 changed files with 283 additions and 233 deletions

View file

@ -170,7 +170,7 @@ class RetrieveProfileAction implements HandleAction {
@Override @Override
public void execute(Manager m) throws Throwable { public void execute(Manager m) throws Throwable {
m.getRecipientProfile(recipientId, true); m.refreshRecipientProfile(recipientId);
} }
@Override @Override

View file

@ -58,14 +58,12 @@ import org.asamk.signal.manager.storage.stickers.StickerPackId;
import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.ProfileUtils;
import org.asamk.signal.manager.util.StickerUtils; import org.asamk.signal.manager.util.StickerUtils;
import org.asamk.signal.manager.util.Utils; import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException; import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
@ -106,8 +104,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.DeviceNameUtil;
@ -137,7 +133,6 @@ import java.nio.file.Files;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -205,26 +200,29 @@ public class Manager implements Closeable {
account.getSignalProtocolStore(), account.getSignalProtocolStore(),
executor, executor,
sessionLock); sessionLock);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
account.getProfileStore()::getProfileKey, account.getProfileStore()::getProfileKey,
this::getRecipientProfile, this::getRecipientProfile,
this::getSenderCertificate); this::getSenderCertificate);
this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey, this.profileHelper = new ProfileHelper(account,
dependencies,
avatarStore,
account.getProfileStore()::getProfileKey,
unidentifiedAccessHelper::getAccessFor, unidentifiedAccessHelper::getAccessFor,
dependencies::getProfileService, dependencies::getProfileService,
dependencies::getMessageReceiver, dependencies::getMessageReceiver,
this::resolveSignalServiceAddress); this::resolveSignalServiceAddress);
final GroupV2Helper groupV2Helper = new GroupV2Helper(this::getRecipientProfileKeyCredential, final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper::getRecipientProfileKeyCredential,
this::getRecipientProfile, this::getRecipientProfile,
account::getSelfRecipientId, account::getSelfRecipientId,
dependencies.getGroupsV2Operations(), dependencies.getGroupsV2Operations(),
dependencies.getGroupsV2Api(), dependencies.getGroupsV2Api(),
this::resolveSignalServiceAddress); this::resolveSignalServiceAddress);
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
this.sendHelper = new SendHelper(account, this.sendHelper = new SendHelper(account,
dependencies, dependencies,
unidentifiedAccessHelper, unidentifiedAccessHelper,
@ -246,7 +244,7 @@ public class Manager implements Closeable {
return account.getUsername(); return account.getUsername();
} }
public SignalServiceAddress getSelfAddress() { private SignalServiceAddress getSelfAddress() {
return account.getSelfAddress(); return account.getSelfAddress();
} }
@ -377,45 +375,12 @@ public class Manager implements Closeable {
public void setProfile( public void setProfile(
String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
) throws IOException { ) throws IOException {
var profile = getRecipientProfile(account.getSelfRecipientId()); profileHelper.setProfile(givenName, familyName, about, aboutEmoji, avatar);
var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
if (givenName != null) {
builder.withGivenName(givenName);
}
if (familyName != null) {
builder.withFamilyName(familyName);
}
if (about != null) {
builder.withAbout(about);
}
if (aboutEmoji != null) {
builder.withAboutEmoji(aboutEmoji);
}
var newProfile = builder.build();
try (final var streamDetails = avatar == null sendSyncFetchProfileMessage();
? avatarStore.retrieveProfileAvatar(getSelfAddress()) }
: avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) {
dependencies.getAccountManager()
.setVersionedProfile(account.getUuid(),
account.getProfileKey(),
newProfile.getInternalServiceName(),
newProfile.getAbout() == null ? "" : newProfile.getAbout(),
newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
Optional.absent(),
streamDetails);
}
if (avatar != null) {
if (avatar.isPresent()) {
avatarStore.storeProfileAvatar(getSelfAddress(),
outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream));
} else {
avatarStore.deleteProfileAvatar(getSelfAddress());
}
}
account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
private void sendSyncFetchProfileMessage() throws IOException {
sendHelper.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); sendHelper.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE));
} }
@ -522,134 +487,12 @@ public class Manager implements Closeable {
return record; return record;
} }
public Profile getRecipientProfile( public Profile getRecipientProfile(RecipientId recipientId) {
RecipientId recipientId return profileHelper.getRecipientProfile(recipientId);
) {
return getRecipientProfile(recipientId, false);
} }
private final Set<RecipientId> pendingProfileRequest = new HashSet<>(); public void refreshRecipientProfile(RecipientId recipientId) {
profileHelper.refreshRecipientProfile(recipientId);
Profile getRecipientProfile(
RecipientId recipientId, boolean force
) {
var profile = account.getProfileStore().getProfile(recipientId);
var now = System.currentTimeMillis();
// Profiles are cached for 24h before retrieving them again, unless forced
if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
return profile;
}
synchronized (pendingProfileRequest) {
if (pendingProfileRequest.contains(recipientId)) {
return profile;
}
pendingProfileRequest.add(recipientId);
}
final SignalServiceProfile encryptedProfile;
try {
encryptedProfile = retrieveEncryptedProfile(recipientId);
} finally {
synchronized (pendingProfileRequest) {
pendingProfileRequest.remove(recipientId);
}
}
if (encryptedProfile == null) {
return null;
}
profile = decryptProfileIfKeyKnown(recipientId, encryptedProfile);
account.getProfileStore().storeProfile(recipientId, profile);
return profile;
}
private Profile decryptProfileIfKeyKnown(
final RecipientId recipientId, final SignalServiceProfile encryptedProfile
) {
var profileKey = account.getProfileStore().getProfileKey(recipientId);
if (profileKey == null) {
return new Profile(System.currentTimeMillis(),
null,
null,
null,
null,
ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
ProfileUtils.getCapabilities(encryptedProfile));
}
return decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
}
private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) {
try {
return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile();
} catch (IOException e) {
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
return null;
}
}
private ProfileAndCredential retrieveProfileAndCredential(
final RecipientId recipientId, final SignalServiceProfile.RequestType requestType
) throws IOException {
final var profileAndCredential = profileHelper.retrieveProfileSync(recipientId, requestType);
final var profile = profileAndCredential.getProfile();
try {
var newIdentity = account.getIdentityKeyStore()
.saveIdentity(recipientId,
new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
new Date());
if (newIdentity) {
account.getSessionStore().archiveSessions(recipientId);
}
} catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}",
resolveSignalServiceAddress(recipientId).getIdentifier());
}
return profileAndCredential;
}
private ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
if (profileKeyCredential != null) {
return profileKeyCredential;
}
ProfileAndCredential profileAndCredential;
try {
profileAndCredential = retrieveProfileAndCredential(recipientId,
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
} catch (IOException e) {
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
return null;
}
profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
var profileKey = account.getProfileStore().getProfileKey(recipientId);
if (profileKey != null) {
final var profile = decryptProfileAndDownloadAvatar(recipientId,
profileKey,
profileAndCredential.getProfile());
account.getProfileStore().storeProfile(recipientId, profile);
}
return profileKeyCredential;
}
private Profile decryptProfileAndDownloadAvatar(
final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
) {
if (encryptedProfile.getAvatar() != null) {
downloadProfileAvatar(resolveSignalServiceAddress(recipientId), encryptedProfile.getAvatar(), profileKey);
}
return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
} }
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(SignalServiceAddress address) throws IOException { private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(SignalServiceAddress address) throws IOException {
@ -1784,7 +1627,7 @@ public class Manager implements Closeable {
if (syncMessage.getFetchType().isPresent()) { if (syncMessage.getFetchType().isPresent()) {
switch (syncMessage.getFetchType().get()) { switch (syncMessage.getFetchType().get()) {
case LOCAL_PROFILE: case LOCAL_PROFILE:
getRecipientProfile(account.getSelfRecipientId(), true); actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
case STORAGE_MANIFEST: case STORAGE_MANIFEST:
// TODO // TODO
} }
@ -1820,20 +1663,6 @@ public class Manager implements Closeable {
} }
} }
private void downloadProfileAvatar(
SignalServiceAddress address, String avatarPath, ProfileKey profileKey
) {
try {
avatarStore.storeProfileAvatar(address,
outputStream -> retrieveProfileAvatar(avatarPath, profileKey, outputStream));
} catch (Throwable e) {
if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
logger.warn("Failed to download profile avatar, ignoring: {}", e.getMessage());
}
}
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
return attachmentStore.getAttachmentFile(attachmentId); return attachmentStore.getAttachmentFile(attachmentId);
} }
@ -1862,28 +1691,6 @@ public class Manager implements Closeable {
} }
} }
private void retrieveProfileAvatar(
String avatarPath, ProfileKey profileKey, OutputStream outputStream
) throws IOException {
var tmpFile = IOUtils.createTempFile();
try (var input = dependencies.getMessageReceiver()
.retrieveProfileAvatar(avatarPath,
tmpFile,
profileKey,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
// Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
IOUtils.copyStream(input, outputStream, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
} finally {
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
tmpFile,
e.getMessage());
}
}
}
private void retrieveAttachment( private void retrieveAttachment(
final SignalServiceAttachment attachment, final OutputStream outputStream final SignalServiceAttachment attachment, final OutputStream outputStream
) throws IOException { ) throws IOException {
@ -2069,17 +1876,15 @@ public class Manager implements Closeable {
public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) { public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
final var recipientId = resolveRecipient(recipientIdentifier); final var recipientId = resolveRecipient(recipientIdentifier);
final var recipient = account.getRecipientStore().getRecipient(recipientId);
if (recipient == null) { final var contact = account.getRecipientStore().getContact(recipientId);
return null; if (contact != null && !Util.isEmpty(contact.getName())) {
return contact.getName();
} }
if (recipient.getContact() != null && !Util.isEmpty(recipient.getContact().getName())) { final var profile = getRecipientProfile(recipientId);
return recipient.getContact().getName(); if (profile != null) {
} return profile.getDisplayName();
if (recipient.getProfile() != null && recipient.getProfile() != null) {
return recipient.getProfile().getDisplayName();
} }
return null; return null;
@ -2188,7 +1993,7 @@ public class Manager implements Closeable {
} }
} else { } else {
// Retrieve profile to get the current identity key from the server // Retrieve profile to get the current identity key from the server
retrieveEncryptedProfile(recipientId); refreshRecipientProfile(recipientId);
} }
} }

View file

@ -1,7 +1,20 @@
package org.asamk.signal.manager.helper; package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.AvatarStore;
import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.ProfileUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
@ -12,29 +25,43 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.ServiceResponse;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.core.Single;
public final class ProfileHelper { public final class ProfileHelper {
private final static Logger logger = LoggerFactory.getLogger(ProfileHelper.class);
private final SignalAccount account;
private final SignalDependencies dependencies;
private final AvatarStore avatarStore;
private final ProfileKeyProvider profileKeyProvider; private final ProfileKeyProvider profileKeyProvider;
private final UnidentifiedAccessProvider unidentifiedAccessProvider; private final UnidentifiedAccessProvider unidentifiedAccessProvider;
private final ProfileServiceProvider profileServiceProvider; private final ProfileServiceProvider profileServiceProvider;
private final MessageReceiverProvider messageReceiverProvider; private final MessageReceiverProvider messageReceiverProvider;
private final SignalServiceAddressResolver addressResolver; private final SignalServiceAddressResolver addressResolver;
public ProfileHelper( public ProfileHelper(
final SignalAccount account,
final SignalDependencies dependencies,
final AvatarStore avatarStore,
final ProfileKeyProvider profileKeyProvider, final ProfileKeyProvider profileKeyProvider,
final UnidentifiedAccessProvider unidentifiedAccessProvider, final UnidentifiedAccessProvider unidentifiedAccessProvider,
final ProfileServiceProvider profileServiceProvider, final ProfileServiceProvider profileServiceProvider,
final MessageReceiverProvider messageReceiverProvider, final MessageReceiverProvider messageReceiverProvider,
final SignalServiceAddressResolver addressResolver final SignalServiceAddressResolver addressResolver
) { ) {
this.account = account;
this.dependencies = dependencies;
this.avatarStore = avatarStore;
this.profileKeyProvider = profileKeyProvider; this.profileKeyProvider = profileKeyProvider;
this.unidentifiedAccessProvider = unidentifiedAccessProvider; this.unidentifiedAccessProvider = unidentifiedAccessProvider;
this.profileServiceProvider = profileServiceProvider; this.profileServiceProvider = profileServiceProvider;
@ -42,7 +69,193 @@ public final class ProfileHelper {
this.addressResolver = addressResolver; this.addressResolver = addressResolver;
} }
public ProfileAndCredential retrieveProfileSync( public Profile getRecipientProfile(RecipientId recipientId) {
return getRecipientProfile(recipientId, false);
}
public void refreshRecipientProfile(RecipientId recipientId) {
getRecipientProfile(recipientId, true);
}
public ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
if (profileKeyCredential != null) {
return profileKeyCredential;
}
ProfileAndCredential profileAndCredential;
try {
profileAndCredential = retrieveProfileAndCredential(recipientId,
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
} catch (IOException e) {
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
return null;
}
profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
var profileKey = account.getProfileStore().getProfileKey(recipientId);
if (profileKey != null) {
final var profile = decryptProfileAndDownloadAvatar(recipientId,
profileKey,
profileAndCredential.getProfile());
account.getProfileStore().storeProfile(recipientId, profile);
}
return profileKeyCredential;
}
/**
* @param givenName if null, the previous givenName will be kept
* @param familyName if null, the previous familyName will be kept
* @param about if null, the previous about text will be kept
* @param aboutEmoji if null, the previous about emoji will be kept
* @param avatar if avatar is null the image from the local avatar store is used (if present),
*/
public void setProfile(
String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
) throws IOException {
var profile = getRecipientProfile(account.getSelfRecipientId());
var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
if (givenName != null) {
builder.withGivenName(givenName);
}
if (familyName != null) {
builder.withFamilyName(familyName);
}
if (about != null) {
builder.withAbout(about);
}
if (aboutEmoji != null) {
builder.withAboutEmoji(aboutEmoji);
}
var newProfile = builder.build();
try (final var streamDetails = avatar == null
? avatarStore.retrieveProfileAvatar(account.getSelfAddress())
: avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) {
dependencies.getAccountManager()
.setVersionedProfile(account.getUuid(),
account.getProfileKey(),
newProfile.getInternalServiceName(),
newProfile.getAbout() == null ? "" : newProfile.getAbout(),
newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
Optional.absent(),
streamDetails);
}
if (avatar != null) {
if (avatar.isPresent()) {
avatarStore.storeProfileAvatar(account.getSelfAddress(),
outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream));
} else {
avatarStore.deleteProfileAvatar(account.getSelfAddress());
}
}
account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
}
private final Set<RecipientId> pendingProfileRequest = new HashSet<>();
private Profile getRecipientProfile(RecipientId recipientId, boolean force) {
var profile = account.getProfileStore().getProfile(recipientId);
var now = System.currentTimeMillis();
// Profiles are cached for 24h before retrieving them again, unless forced
if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
return profile;
}
synchronized (pendingProfileRequest) {
if (pendingProfileRequest.contains(recipientId)) {
return profile;
}
pendingProfileRequest.add(recipientId);
}
final SignalServiceProfile encryptedProfile;
try {
encryptedProfile = retrieveEncryptedProfile(recipientId);
} finally {
synchronized (pendingProfileRequest) {
pendingProfileRequest.remove(recipientId);
}
}
if (encryptedProfile == null) {
return null;
}
profile = decryptProfileIfKeyKnown(recipientId, encryptedProfile);
account.getProfileStore().storeProfile(recipientId, profile);
return profile;
}
private Profile decryptProfileIfKeyKnown(
final RecipientId recipientId, final SignalServiceProfile encryptedProfile
) {
var profileKey = account.getProfileStore().getProfileKey(recipientId);
if (profileKey == null) {
return new Profile(System.currentTimeMillis(),
null,
null,
null,
null,
ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
ProfileUtils.getCapabilities(encryptedProfile));
}
return decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
}
private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) {
try {
return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile();
} catch (IOException e) {
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
return null;
}
}
private SignalServiceProfile retrieveProfileSync(String username) throws IOException {
return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent());
}
private ProfileAndCredential retrieveProfileAndCredential(
final RecipientId recipientId, final SignalServiceProfile.RequestType requestType
) throws IOException {
final var profileAndCredential = retrieveProfileSync(recipientId, requestType);
final var profile = profileAndCredential.getProfile();
try {
var newIdentity = account.getIdentityKeyStore()
.saveIdentity(recipientId,
new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
new Date());
if (newIdentity) {
account.getSessionStore().archiveSessions(recipientId);
}
} catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}",
addressResolver.resolveSignalServiceAddress(recipientId).getIdentifier());
}
return profileAndCredential;
}
private Profile decryptProfileAndDownloadAvatar(
final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
) {
if (encryptedProfile.getAvatar() != null) {
downloadProfileAvatar(addressResolver.resolveSignalServiceAddress(recipientId),
encryptedProfile.getAvatar(),
profileKey);
}
return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
}
private ProfileAndCredential retrieveProfileSync(
RecipientId recipientId, SignalServiceProfile.RequestType requestType RecipientId recipientId, SignalServiceProfile.RequestType requestType
) throws IOException { ) throws IOException {
try { try {
@ -58,11 +271,7 @@ public final class ProfileHelper {
} }
} }
public SignalServiceProfile retrieveProfileSync(String username) throws IOException { private Single<ProfileAndCredential> retrieveProfile(
return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent());
}
public Single<ProfileAndCredential> retrieveProfile(
RecipientId recipientId, SignalServiceProfile.RequestType requestType RecipientId recipientId, SignalServiceProfile.RequestType requestType
) throws IOException { ) throws IOException {
var unidentifiedAccess = getUnidentifiedAccess(recipientId); var unidentifiedAccess = getUnidentifiedAccess(recipientId);
@ -106,6 +315,42 @@ public final class ProfileHelper {
}); });
} }
private void downloadProfileAvatar(
SignalServiceAddress address, String avatarPath, ProfileKey profileKey
) {
try {
avatarStore.storeProfileAvatar(address,
outputStream -> retrieveProfileAvatar(avatarPath, profileKey, outputStream));
} catch (Throwable e) {
if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
logger.warn("Failed to download profile avatar, ignoring: {}", e.getMessage());
}
}
private void retrieveProfileAvatar(
String avatarPath, ProfileKey profileKey, OutputStream outputStream
) throws IOException {
var tmpFile = IOUtils.createTempFile();
try (var input = dependencies.getMessageReceiver()
.retrieveProfileAvatar(avatarPath,
tmpFile,
profileKey,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
// Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
IOUtils.copyStream(input, outputStream, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
} finally {
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
tmpFile,
e.getMessage());
}
}
}
private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) { private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId); var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId);