Add support for PNIs in storage service.

This commit is contained in:
Greyson Parrelli
2022-08-17 11:26:47 -04:00
committed by Cody Henthorne
parent cb057968ee
commit 95fc9d6c3c
12 changed files with 584 additions and 90 deletions

View File

@@ -523,6 +523,27 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
@VisibleForTesting
fun getAndPossiblyMergePnp(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId {
require(!(serviceId == null && e164 == null)) { "Must provide an ACI or E164!" }
return getAndPossiblyMergePnp(serviceId = serviceId, pni = null, e164 = e164, pniVerified = false, changeSelf = changeSelf)
}
/**
* Gets and merges a (serviceId, pni, e164) tuple, doing merges/updates as needed, and giving you back the final RecipientId.
* It is assumed that the tuple is verified. Do not give this method an untrusted association.
*/
fun getAndPossiblyMergePnpVerified(serviceId: ServiceId?, pni: PNI?, e164: String?): RecipientId {
if (!FeatureFlags.phoneNumberPrivacy()) {
throw AssertionError()
}
return getAndPossiblyMergePnp(serviceId = serviceId, pni = pni, e164 = e164, pniVerified = true, changeSelf = false)
}
private fun getAndPossiblyMergePnp(serviceId: ServiceId?, pni: PNI?, e164: String?, pniVerified: Boolean = false, changeSelf: Boolean = false): RecipientId {
require(!(serviceId == null && e164 == null)) { "Must provide an ACI or E164!" }
if ((serviceId is PNI) && pni != null && serviceId != pni) {
throw AssertionError("Provided two non-matching PNIs! serviceId: $serviceId, pni: $pni")
}
val db = writableDatabase
var transactionSuccessful = false
@@ -531,11 +552,13 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
db.beginTransaction()
try {
result = when {
serviceId is PNI -> processPnpTuple(e164, serviceId, null, pniVerified = false, changeSelf = changeSelf)
serviceId is ACI -> processPnpTuple(e164, null, serviceId, pniVerified = false, changeSelf = changeSelf)
serviceId == null -> processPnpTuple(e164, null, null, pniVerified = false, changeSelf = changeSelf)
getByPni(PNI.from(serviceId.uuid())).isPresent -> processPnpTuple(e164, PNI.from(serviceId.uuid()), null, pniVerified = false, changeSelf = changeSelf)
else -> processPnpTuple(e164, null, ACI.fromNullable(serviceId), pniVerified = false, changeSelf = changeSelf)
serviceId is ACI -> processPnpTuple(e164 = e164, pni = pni, aci = serviceId, pniVerified = pniVerified, changeSelf = changeSelf)
serviceId is PNI -> processPnpTuple(e164 = e164, pni = serviceId, aci = null, pniVerified = pniVerified, changeSelf = changeSelf)
serviceId == null -> processPnpTuple(e164 = e164, pni = pni, aci = null, pniVerified = pniVerified, changeSelf = changeSelf)
serviceId == pni -> processPnpTuple(e164 = e164, pni = pni, aci = null, pniVerified = pniVerified, changeSelf = changeSelf)
pni != null -> processPnpTuple(e164 = e164, pni = pni, aci = ACI.from(serviceId.uuid()), pniVerified = pniVerified, changeSelf = changeSelf)
getByPni(PNI.from(serviceId.uuid())).isPresent -> processPnpTuple(e164 = e164, pni = PNI.from(serviceId.uuid()), aci = null, pniVerified = pniVerified, changeSelf = changeSelf)
else -> processPnpTuple(e164 = e164, pni = pni, aci = ACI.fromNullable(serviceId), pniVerified = pniVerified, changeSelf = changeSelf)
}
if (result.operations.isNotEmpty()) {
@@ -917,16 +940,20 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
val recipientId: RecipientId
if (id < 0) {
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.")
recipientId = getAndPossiblyMerge(if (insert.address.hasValidServiceId()) insert.address.serviceId else null, insert.address.number.orElse(null))
if (FeatureFlags.phoneNumberPrivacy()) {
recipientId = getAndPossiblyMerge(if (insert.serviceId.isValid) insert.serviceId else null, insert.number.orElse(null))
} else {
recipientId = getAndPossiblyMergePnpVerified(if (insert.serviceId.isValid) insert.serviceId else null, insert.pni.orElse(null), insert.number.orElse(null))
}
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
} else {
recipientId = RecipientId.from(id)
}
if (insert.identityKey.isPresent && insert.address.hasValidServiceId()) {
if (insert.identityKey.isPresent && insert.serviceId.isValid) {
try {
val identityKey = IdentityKey(insert.identityKey.get(), 0)
identities.updateIdentityAfterSync(insert.address.identifier, recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState))
identities.updateIdentityAfterSync(insert.serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState))
} catch (e: InvalidKeyException) {
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e)
}
@@ -954,7 +981,11 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
var recipientId = getByColumn(STORAGE_SERVICE_ID, Base64.encodeBytes(update.old.id.raw)).get()
Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.")
recipientId = getAndPossiblyMerge(if (update.new.address.hasValidServiceId()) update.new.address.serviceId else null, update.new.address.number.orElse(null))
if (FeatureFlags.phoneNumberPrivacy()) {
recipientId = getAndPossiblyMergePnpVerified(if (update.new.serviceId.isValid) update.new.serviceId else null, update.new.pni.orElse(null), update.new.number.orElse(null))
} else {
recipientId = getAndPossiblyMerge(if (update.new.serviceId.isValid) update.new.serviceId else null, update.new.number.orElse(null))
}
Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId")
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
@@ -970,9 +1001,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
try {
val oldIdentityRecord = identityStore.getIdentityRecord(recipientId)
if (update.new.identityKey.isPresent && update.new.address.hasValidServiceId()) {
if (update.new.identityKey.isPresent && update.new.serviceId.isValid) {
val identityKey = IdentityKey(update.new.identityKey.get(), 0)
identities.updateIdentityAfterSync(update.new.address.identifier, recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState))
identities.updateIdentityAfterSync(update.new.serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState))
}
val newIdentityRecord = identityStore.getIdentityRecord(recipientId)
@@ -3564,11 +3595,15 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
val profileName = ProfileName.fromParts(contact.givenName.orElse(null), contact.familyName.orElse(null))
val username = contact.username.orElse(null)
if (contact.address.hasValidServiceId()) {
put(SERVICE_ID, contact.address.serviceId.toString())
if (contact.serviceId.isValid) {
put(SERVICE_ID, contact.serviceId.toString())
}
put(PHONE, contact.address.number.orElse(null))
if (FeatureFlags.phoneNumberPrivacy()) {
put(PNI_COLUMN, contact.pni.toString())
}
put(PHONE, contact.number.orElse(null))
put(PROFILE_GIVEN_NAME, profileName.givenName)
put(PROFILE_FAMILY_NAME, profileName.familyName)
put(PROFILE_JOINED_NAME, profileName.toString())

View File

@@ -391,12 +391,10 @@ public class StorageSyncJob extends BaseJob {
}
private static void processKnownRecords(@NonNull Context context, @NonNull StorageRecordCollection records) throws IOException {
Recipient self = freshSelf();
new ContactRecordProcessor(context, self).process(records.contacts, StorageSyncHelper.KEY_GENERATOR);
new ContactRecordProcessor().process(records.contacts, StorageSyncHelper.KEY_GENERATOR);
new GroupV1RecordProcessor(context).process(records.gv1, StorageSyncHelper.KEY_GENERATOR);
new GroupV2RecordProcessor(context).process(records.gv2, StorageSyncHelper.KEY_GENERATOR);
self = freshSelf();
new AccountRecordProcessor(context, self).process(records.account, StorageSyncHelper.KEY_GENERATOR);
new AccountRecordProcessor(context, freshSelf()).process(records.account, StorageSyncHelper.KEY_GENERATOR);
if (getKnownTypes().contains(ManifestRecord.Identifier.Type.STORY_DISTRIBUTION_LIST_VALUE)) {
new StoryDistributionListRecordProcessor().process(records.storyDistributionLists, StorageSyncHelper.KEY_GENERATOR);

View File

@@ -238,6 +238,39 @@ public class Recipient {
return externalPush(serviceId, null);
}
/**
* Create a recipient with a full (ACI, PNI, E164) tuple. It is assumed that the association between the PNI and serviceId is trusted.
* That means it must be from either storage service or a PNI verification message.
*/
public static @NonNull Recipient trustedPush(@NonNull ServiceId serviceId, @Nullable PNI pni, @Nullable String e164) {
if (ServiceId.UNKNOWN.equals(serviceId)) {
throw new AssertionError("Unknown serviceId!");
}
RecipientDatabase db = SignalDatabase.recipients();
RecipientId recipientId;
if (FeatureFlags.phoneNumberPrivacy()) {
recipientId = db.getAndPossiblyMergePnpVerified(serviceId, pni, e164);
} else {
recipientId = db.getAndPossiblyMerge(serviceId, e164);
}
Recipient resolved = resolved(recipientId);
if (!resolved.getId().equals(recipientId)) {
Log.w(TAG, "Resolved " + recipientId + ", but got back a recipient with " + resolved.getId());
}
if (!resolved.isRegistered()) {
Log.w(TAG, "External push was locally marked unregistered. Marking as registered.");
db.markRegistered(recipientId, serviceId);
}
return resolved;
}
/**
* Returns a fully-populated {@link Recipient} based off of a ServiceId and phone number, creating one
* in the database if necessary. We want both piece of information so we're able to associate them

View File

@@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
@@ -29,23 +30,24 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
private static final String TAG = Log.tag(ContactRecordProcessor.class);
private final Recipient self;
private final RecipientDatabase recipientDatabase;
private final ACI selfAci;
private final PNI selfPni;
private final String selfE164;
public ContactRecordProcessor(@NonNull Context context, @NonNull Recipient self) {
this(self, SignalDatabase.recipients());
public ContactRecordProcessor() {
this(SignalStore.account().getAci(),
SignalStore.account().getPni(),
SignalStore.account().getE164(),
SignalDatabase.recipients());
}
ContactRecordProcessor(@NonNull Recipient self, @NonNull RecipientDatabase recipientDatabase) {
this.self = self;
ContactRecordProcessor(@Nullable ACI selfAci, @Nullable PNI selfPni, @Nullable String selfE164, @NonNull RecipientDatabase recipientDatabase) {
this.recipientDatabase = recipientDatabase;
this.selfAci = SignalStore.account().getAci();
this.selfPni = SignalStore.account().getPni();
this.selfE164 = SignalStore.account().getE164();
this.selfAci = selfAci;
this.selfPni = selfPni;
this.selfE164 = selfE164;
}
/**
@@ -57,20 +59,22 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
*/
@Override
boolean isInvalid(@NonNull SignalContactRecord remote) {
SignalServiceAddress address = remote.getAddress();
if (address == null) {
if (remote.getServiceId() == null) {
Log.w(TAG, "No address on the ContentRecord -- marking as invalid.");
return true;
} else if (!address.hasValidServiceId()) {
} else if (remote.getServiceId().isUnknown()) {
Log.w(TAG, "Found a ContactRecord without a UUID -- marking as invalid.");
return true;
} else if (address.getServiceId().equals(selfAci) ||
address.getServiceId().equals(selfPni) ||
(selfE164 != null && address.getNumber().isPresent() && address.getNumber().get().equals(selfE164)))
} else if (remote.getServiceId().equals(selfAci) ||
remote.getServiceId().equals(selfPni) ||
(selfPni != null && selfPni.equals(remote.getPni().orElse(null))) ||
(selfE164 != null && remote.getNumber().isPresent() && remote.getNumber().get().equals(selfE164)))
{
Log.w(TAG, "Found a ContactRecord for ourselves -- marking as invalid.");
return true;
} else if (!FeatureFlags.phoneNumberPrivacy() && remote.getServiceId().equals(remote.getPni().orElse(null))) {
Log.w(TAG, "Found a PNI-only ContactRecord when PNP is disabled -- marking as invalid.");
return true;
} else {
return false;
}
@@ -78,28 +82,46 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
@Override
@NonNull Optional<SignalContactRecord> getMatching(@NonNull SignalContactRecord remote, @NonNull StorageKeyGenerator keyGenerator) {
SignalServiceAddress address = remote.getAddress();
Optional<RecipientId> byUuid = recipientDatabase.getByServiceId(address.getServiceId());
Optional<RecipientId> byE164 = address.getNumber().isPresent() ? recipientDatabase.getByE164(address.getNumber().get()) : Optional.empty();
if (!FeatureFlags.phoneNumberPrivacy()) {
remote = remote.withoutPni();
}
return OptionalUtil.or(byUuid, byE164)
.map(recipientDatabase::getRecordForSync)
.map(settings -> {
if (settings.getStorageId() != null) {
return StorageSyncModels.localToRemoteRecord(settings);
} else {
Log.w(TAG, "Newly discovering a registered user via storage service. Saving a storageId for them.");
recipientDatabase.updateStorageId(settings.getId(), keyGenerator.generate());
Optional<RecipientId> found = recipientDatabase.getByServiceId(remote.getServiceId());
RecipientRecord updatedSettings = Objects.requireNonNull(recipientDatabase.getRecordForSync(settings.getId()));
return StorageSyncModels.localToRemoteRecord(updatedSettings);
}
})
.map(r -> r.getContact().get());
if (!found.isPresent() && remote.getNumber().isPresent()) {
found = recipientDatabase.getByE164(remote.getNumber().get());
}
if (!found.isPresent() && remote.getPni().isPresent()) {
found = recipientDatabase.getByServiceId(remote.getPni().get());
}
if (!found.isPresent() && remote.getPni().isPresent()) {
found = recipientDatabase.getByPni(remote.getPni().get());
}
return found.map(recipientDatabase::getRecordForSync)
.map(settings -> {
if (settings.getStorageId() != null) {
return StorageSyncModels.localToRemoteRecord(settings);
} else {
Log.w(TAG, "Newly discovering a registered user via storage service. Saving a storageId for them.");
recipientDatabase.updateStorageId(settings.getId(), keyGenerator.generate());
RecipientRecord updatedSettings = Objects.requireNonNull(recipientDatabase.getRecordForSync(settings.getId()));
return StorageSyncModels.localToRemoteRecord(updatedSettings);
}
})
.map(r -> r.getContact().get());
}
@Override
@NonNull SignalContactRecord merge(@NonNull SignalContactRecord remote, @NonNull SignalContactRecord local, @NonNull StorageKeyGenerator keyGenerator) {
if (!FeatureFlags.phoneNumberPrivacy()) {
local = local.withoutPni();
remote = remote.withoutPni();
}
String givenName;
String familyName;
@@ -125,14 +147,47 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
}
if (identityKey != null && remote.getIdentityKey().isPresent() && !Arrays.equals(identityKey, remote.getIdentityKey().get())) {
Log.w(TAG, "The local and remote identity keys do not match for " + local.getAddress().getIdentifier() + ". Enqueueing a profile fetch.");
RetrieveProfileJob.enqueue(Recipient.externalPush(local.getAddress()).getId());
Log.w(TAG, "The local and remote identity keys do not match for " + local.getServiceId() + ". Enqueueing a profile fetch.");
RetrieveProfileJob.enqueue(Recipient.trustedPush(local.getServiceId(), local.getPni().orElse(null), local.getNumber().orElse(null)).getId());
}
PNI pni;
String e164;
if (FeatureFlags.phoneNumberPrivacy()) {
boolean e164sMatchButPnisDont = local.getNumber().isPresent() &&
local.getNumber().get().equals(remote.getNumber().orElse(null)) &&
local.getPni().isPresent() &&
remote.getPni().isPresent() &&
!local.getPni().get().equals(remote.getPni().get());
boolean pnisMatchButE164sDont = local.getPni().isPresent() &&
local.getPni().get().equals(remote.getPni().orElse(null)) &&
local.getNumber().isPresent() &&
remote.getNumber().isPresent() &&
!local.getNumber().get().equals(remote.getNumber().get());
if (e164sMatchButPnisDont) {
Log.w(TAG, "Matching E164s, but the PNIs differ! Trusting our local pair.");
// TODO [pnp] Schedule CDS fetch?
pni = local.getPni().get();
e164 = local.getNumber().get();
} else if (pnisMatchButE164sDont) {
Log.w(TAG, "Matching PNIs, but the E164s differ! Trusting our local pair.");
// TODO [pnp] Schedule CDS fetch?
pni = local.getPni().get();
e164 = local.getNumber().get();
} else {
pni = OptionalUtil.or(remote.getPni(), local.getPni()).orElse(null);
e164 = OptionalUtil.or(remote.getNumber(), local.getNumber()).orElse(null);
}
} else {
pni = null;
e164 = OptionalUtil.or(remote.getNumber(), local.getNumber()).orElse(null);
}
byte[] unknownFields = remote.serializeUnknownFields();
ServiceId serviceId = local.getAddress().getServiceId() == ServiceId.UNKNOWN ? remote.getAddress().getServiceId() : local.getAddress().getServiceId();
String e164 = OptionalUtil.or(remote.getAddress().getNumber(), local.getAddress().getNumber()).orElse(null);
SignalServiceAddress address = new SignalServiceAddress(serviceId, e164);
ServiceId serviceId = local.getServiceId() == ServiceId.UNKNOWN ? remote.getServiceId() : local.getServiceId();
byte[] profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
String username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse("");
boolean blocked = remote.isBlocked();
@@ -141,15 +196,17 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
boolean forcedUnread = remote.isForcedUnread();
long muteUntil = remote.getMuteUntil();
boolean hideStory = remote.shouldHideStory();
boolean matchesRemote = doParamsMatch(remote, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory);
boolean matchesLocal = doParamsMatch(local, unknownFields, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory);
boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory);
boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory);
if (matchesRemote) {
return remote;
} else if (matchesLocal) {
return local;
} else {
return new SignalContactRecord.Builder(keyGenerator.generate(), address, unknownFields)
return new SignalContactRecord.Builder(keyGenerator.generate(), serviceId, unknownFields)
.setE164(e164)
.setPni(pni)
.setGivenName(givenName)
.setFamilyName(familyName)
.setProfileKey(profileKey)
@@ -178,8 +235,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
@Override
public int compare(@NonNull SignalContactRecord lhs, @NonNull SignalContactRecord rhs) {
if (Objects.equals(lhs.getAddress().getServiceId(), rhs.getAddress().getServiceId()) ||
Objects.equals(lhs.getAddress().getNumber(), rhs.getAddress().getNumber()))
if (Objects.equals(lhs.getServiceId(), rhs.getServiceId()) ||
(lhs.getNumber().isPresent() && Objects.equals(lhs.getNumber(), rhs.getNumber())) ||
(lhs.getPni().isPresent() && Objects.equals(lhs.getPni(), rhs.getPni())))
{
return 0;
} else {
@@ -189,7 +247,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
private static boolean doParamsMatch(@NonNull SignalContactRecord contact,
@Nullable byte[] unknownFields,
@NonNull SignalServiceAddress address,
@NonNull ServiceId serviceId,
@Nullable PNI pni,
@Nullable String e164,
@NonNull String givenName,
@NonNull String familyName,
@Nullable byte[] profileKey,
@@ -203,19 +263,21 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
long muteUntil,
boolean hideStory)
{
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
Objects.equals(contact.getAddress(), address) &&
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
Objects.equals(contact.getServiceId(), serviceId) &&
Objects.equals(contact.getPni().orElse(null), pni) &&
Objects.equals(contact.getNumber().orElse(null), e164) &&
Objects.equals(contact.getGivenName().orElse(""), givenName) &&
Objects.equals(contact.getFamilyName().orElse(""), familyName) &&
Arrays.equals(contact.getProfileKey().orElse(null), profileKey) &&
Objects.equals(contact.getUsername().orElse(""), username) &&
Objects.equals(contact.getIdentityState(), identityState) &&
Objects.equals(contact.getIdentityState(), identityState) &&
Arrays.equals(contact.getIdentityKey().orElse(null), identityKey) &&
contact.isBlocked() == blocked &&
contact.isProfileSharingEnabled() == profileSharing &&
contact.isArchived() == archived &&
contact.isForcedUnread() == forcedUnread &&
contact.getMuteUntil() == muteUntil &&
contact.isBlocked() == blocked &&
contact.isProfileSharingEnabled() == profileSharing &&
contact.isArchived() == archived &&
contact.isForcedUnread() == forcedUnread &&
contact.getMuteUntil() == muteUntil &&
contact.shouldHideStory() == hideStory;
}
}

View File

@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.subscription.Subscriber;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
@@ -109,7 +110,9 @@ public final class StorageSyncModels {
ServiceId serviceId = recipient.getServiceId() != null ? recipient.getServiceId() : ServiceId.UNKNOWN;
boolean hideStory = recipient.getExtras() != null && recipient.getExtras().hideStory();
return new SignalContactRecord.Builder(rawStorageId, new SignalServiceAddress(serviceId, recipient.getE164()), recipient.getSyncExtras().getStorageProto())
return new SignalContactRecord.Builder(rawStorageId, serviceId, recipient.getSyncExtras().getStorageProto())
.setE164(recipient.getE164())
.setPni(recipient.getPni())
.setProfileKey(recipient.getProfileKey())
.setGivenName(recipient.getProfileName().getGivenName())
.setFamilyName(recipient.getProfileName().getFamilyName())

View File

@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64;
import org.signal.core.util.SetUtil;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
@@ -141,8 +142,12 @@ public final class StorageSyncValidations {
}
if (insert.getContact().isPresent()) {
SignalServiceAddress address = insert.getContact().get().getAddress();
if (self.requireE164().equals(address.getNumber().orElse("")) || self.requireServiceId().equals(address.getServiceId())) {
SignalContactRecord contact = insert.getContact().get();
if (self.requireServiceId().equals(contact.getServiceId()) ||
self.requirePni().equals(contact.getPni().orElse(null)) ||
self.requireE164().equals(contact.getNumber().orElse("")))
{
throw new SelfAddedAsContactError();
}
}