Sync PNI verification status to storage service.

This commit is contained in:
Greyson Parrelli
2024-01-29 21:01:38 -05:00
committed by Nicholas Tinsley
parent 459607adae
commit 716afc98ac
11 changed files with 134 additions and 47 deletions

View File

@@ -18,6 +18,7 @@ import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil import org.signal.core.util.SqlUtil
import org.signal.core.util.exists import org.signal.core.util.exists
import org.signal.core.util.orNull import org.signal.core.util.orNull
import org.signal.core.util.readToSingleBoolean
import org.signal.core.util.requireLong import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString import org.signal.core.util.requireNonNullString
import org.signal.core.util.select import org.signal.core.util.select
@@ -109,6 +110,18 @@ class RecipientTableTest_getAndPossiblyMerge {
val record = SignalDatabase.recipients.getRecord(id) val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered) assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
} }
test("e164+pni+aci insert, pni verified") {
val id = process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
process(E164_A, PNI_A, ACI_A, pniVerified = false)
expectPniVerified()
}
} }
@Test @Test
@@ -164,6 +177,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent() expectNoSessionSwitchoverEvent()
expectPniVerified()
} }
test("no match, all fields") { test("no match, all fields") {
@@ -225,6 +239,8 @@ class RecipientTableTest_getAndPossiblyMerge {
given(E164_A, PNI_A, null, pniSession = true) given(E164_A, PNI_A, null, pniSession = true)
process(E164_A, PNI_A, ACI_A, pniVerified = true) process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
} }
test("e164 and aci matches, all provided, new pni") { test("e164 and aci matches, all provided, new pni") {
@@ -694,6 +710,8 @@ class RecipientTableTest_getAndPossiblyMerge {
expectDeleted() expectDeleted()
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
} }
test("merge, e164+pni & aci, pni session, pni verified") { test("merge, e164+pni & aci, pni session, pni verified") {
@@ -706,6 +724,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A) expectThreadMergeEvent(E164_A)
expectPniVerified()
} }
test("merge, e164+pni & e164+pni+aci, change number") { test("merge, e164+pni & e164+pni+aci, change number") {
@@ -1037,6 +1056,10 @@ class RecipientTableTest_getAndPossiblyMerge {
if (!test.sessionSwitchoverExpected) { if (!test.sessionSwitchoverExpected) {
test.expectNoSessionSwitchoverEvent() test.expectNoSessionSwitchoverEvent()
} }
if (!test.pniVerifiedExpected) {
test.expectPniNotVerified()
}
} catch (e: Throwable) { } catch (e: Throwable) {
if (e.javaClass != exception) { if (e.javaClass != exception) {
val error = java.lang.AssertionError("[$name] ${e.message}") val error = java.lang.AssertionError("[$name] ${e.message}")
@@ -1056,6 +1079,7 @@ class RecipientTableTest_getAndPossiblyMerge {
var changeNumberExpected = false var changeNumberExpected = false
var threadMergeExpected = false var threadMergeExpected = false
var sessionSwitchoverExpected = false var sessionSwitchoverExpected = false
var pniVerifiedExpected = false
init { init {
// Need to delete these first to prevent foreign key crash // Need to delete these first to prevent foreign key crash
@@ -1207,6 +1231,24 @@ class RecipientTableTest_getAndPossiblyMerge {
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId)) assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
} }
fun expectPniVerified() {
assertTrue("Expected PNI to be verified!", isPniVerified(outputRecipientId))
pniVerifiedExpected = true
}
fun expectPniNotVerified() {
assertFalse("Expected PNI to be not be verified!", isPniVerified(outputRecipientId))
}
private fun isPniVerified(recipientId: RecipientId): Boolean {
return SignalDatabase.rawDatabase
.select(RecipientTable.PNI_SIGNATURE_VERIFIED)
.from(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", recipientId)
.run()
.readToSingleBoolean(false)
}
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId { private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
val id: Long = SignalDatabase.rawDatabase.insert( val id: Long = SignalDatabase.rawDatabase.insert(
RecipientTable.TABLE_NAME, RecipientTable.TABLE_NAME,

View File

@@ -33,6 +33,7 @@ import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireString import org.signal.core.util.requireString
import org.signal.core.util.select import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.core.util.update import org.signal.core.util.update
import org.signal.core.util.updateAll import org.signal.core.util.updateAll
import org.signal.core.util.withinTransaction import org.signal.core.util.withinTransaction
@@ -183,6 +184,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
const val REPORTING_TOKEN = "reporting_token" const val REPORTING_TOKEN = "reporting_token"
const val PHONE_NUMBER_SHARING = "phone_number_sharing" const val PHONE_NUMBER_SHARING = "phone_number_sharing"
const val PHONE_NUMBER_DISCOVERABLE = "phone_number_discoverable" const val PHONE_NUMBER_DISCOVERABLE = "phone_number_discoverable"
const val PNI_SIGNATURE_VERIFIED = "pni_signature_verified"
const val SEARCH_PROFILE_NAME = "search_signal_profile" const val SEARCH_PROFILE_NAME = "search_signal_profile"
const val SORT_NAME = "sort_name" const val SORT_NAME = "sort_name"
@@ -250,7 +252,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
$NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0, $NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0,
$REPORTING_TOKEN BLOB DEFAULT NULL, $REPORTING_TOKEN BLOB DEFAULT NULL,
$PHONE_NUMBER_SHARING INTEGER DEFAULT ${PhoneNumberSharingState.UNKNOWN.id}, $PHONE_NUMBER_SHARING INTEGER DEFAULT ${PhoneNumberSharingState.UNKNOWN.id},
$PHONE_NUMBER_DISCOVERABLE INTEGER DEFAULT ${PhoneNumberDiscoverableState.UNKNOWN.id} $PHONE_NUMBER_DISCOVERABLE INTEGER DEFAULT ${PhoneNumberDiscoverableState.UNKNOWN.id},
$PNI_SIGNATURE_VERIFIED INTEGER DEFAULT 0
) )
""" """
@@ -829,7 +832,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val recipientId: RecipientId val recipientId: RecipientId
if (id < 0) { if (id < 0) {
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.") Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.")
recipientId = getAndPossiblyMergePnpVerified(insert.aci.orNull(), insert.pni.orNull(), insert.number.orNull()) recipientId = getAndPossiblyMerge(aci = insert.aci.orNull(), pni = insert.pni.orNull(), e164 = insert.number.orNull(), pniVerified = insert.isPniSignatureVerified)
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId)) db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
} else { } else {
recipientId = RecipientId.from(id) recipientId = RecipientId.from(id)
@@ -867,7 +870,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
var recipientId = getByColumn(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.old.id.raw)).get() var recipientId = getByColumn(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.old.id.raw)).get()
Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.") Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.")
recipientId = getAndPossiblyMergePnpVerified(update.new.aci.orElse(null), update.new.pni.orElse(null), update.new.number.orElse(null)) recipientId = getAndPossiblyMerge(aci = update.new.aci.orElse(null), pni = update.new.pni.orElse(null), e164 = update.new.number.orElse(null), pniVerified = update.new.isPniSignatureVerified)
Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId") Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId")
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId)) db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
@@ -1113,6 +1116,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
SYSTEM_NICKNAME, SYSTEM_NICKNAME,
"$TABLE_NAME.$STORAGE_SERVICE_PROTO", "$TABLE_NAME.$STORAGE_SERVICE_PROTO",
"$TABLE_NAME.$UNREGISTERED_TIMESTAMP", "$TABLE_NAME.$UNREGISTERED_TIMESTAMP",
"$TABLE_NAME.$PNI_SIGNATURE_VERIFIED",
"${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}", "${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}",
"${ThreadTable.TABLE_NAME}.${ThreadTable.ARCHIVED}", "${ThreadTable.TABLE_NAME}.${ThreadTable.ARCHIVED}",
"${ThreadTable.TABLE_NAME}.${ThreadTable.READ}", "${ThreadTable.TABLE_NAME}.${ThreadTable.READ}",
@@ -2372,7 +2376,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
} }
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pni) val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pni, pniVerified)
return ProcessPnpTupleResult( return ProcessPnpTupleResult(
finalId = finalId, finalId = finalId,
@@ -2386,7 +2390,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
@VisibleForTesting @VisibleForTesting
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, inputPni: PNI?): RecipientId { fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, inputPni: PNI?, pniVerified: Boolean): RecipientId {
var hadThreadMerge = false var hadThreadMerge = false
for (operation in changeSet.operations) { for (operation in changeSet.operations) {
@Exhaustive @Exhaustive
@@ -2402,7 +2406,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
is PnpOperation.RemovePni -> { is PnpOperation.RemovePni -> {
writableDatabase writableDatabase
.update(TABLE_NAME) .update(TABLE_NAME)
.values(PNI_COLUMN to null) .values(
PNI_COLUMN to null,
PNI_SIGNATURE_VERIFIED to 0
)
.where("$ID = ?", operation.recipientId) .where("$ID = ?", operation.recipientId)
.run() .run()
} }
@@ -2413,7 +2420,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.values( .values(
ACI_COLUMN to operation.aci.toString(), ACI_COLUMN to operation.aci.toString(),
REGISTERED to RegisteredState.REGISTERED.id, REGISTERED to RegisteredState.REGISTERED.id,
UNREGISTERED_TIMESTAMP to 0 UNREGISTERED_TIMESTAMP to 0,
PNI_SIGNATURE_VERIFIED to pniVerified.toInt()
) )
.where("$ID = ?", operation.recipientId) .where("$ID = ?", operation.recipientId)
.run() .run()
@@ -2433,14 +2441,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.values( .values(
PNI_COLUMN to operation.pni.toString(), PNI_COLUMN to operation.pni.toString(),
REGISTERED to RegisteredState.REGISTERED.id, REGISTERED to RegisteredState.REGISTERED.id,
UNREGISTERED_TIMESTAMP to 0 UNREGISTERED_TIMESTAMP to 0,
PNI_SIGNATURE_VERIFIED to 0
) )
.where("$ID = ?", operation.recipientId) .where("$ID = ?", operation.recipientId)
.run() .run()
} }
is PnpOperation.Merge -> { is PnpOperation.Merge -> {
val mergeResult: MergeResult = merge(operation.primaryId, operation.secondaryId, inputPni) val mergeResult: MergeResult = merge(operation.primaryId, operation.secondaryId, inputPni, pniVerified)
hadThreadMerge = hadThreadMerge || mergeResult.neededThreadMerge hadThreadMerge = hadThreadMerge || mergeResult.neededThreadMerge
} }
@@ -2519,7 +2528,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
is PnpIdResolver.PnpInsert -> { is PnpIdResolver.PnpInsert -> {
val id: Long = writableDatabase.insert(TABLE_NAME, null, buildContentValuesForNewUser(changeSet.id.e164, changeSet.id.pni, changeSet.id.aci)) val id: Long = writableDatabase.insert(TABLE_NAME, null, buildContentValuesForNewUser(changeSet.id.e164, changeSet.id.pni, changeSet.id.aci, pniVerified))
RecipientId.from(id) RecipientId.from(id)
} }
} }
@@ -3843,7 +3852,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
* Merges one ACI recipient with an E164 recipient. It is assumed that the E164 recipient does * Merges one ACI recipient with an E164 recipient. It is assumed that the E164 recipient does
* *not* have an ACI. * *not* have an ACI.
*/ */
private fun merge(primaryId: RecipientId, secondaryId: RecipientId, newPni: PNI? = null): MergeResult { private fun merge(primaryId: RecipientId, secondaryId: RecipientId, newPni: PNI? = null, pniVerified: Boolean): MergeResult {
ensureInTransaction() ensureInTransaction()
val db = writableDatabase val db = writableDatabase
val primaryRecord = getRecord(primaryId) val primaryRecord = getRecord(primaryId)
@@ -3904,7 +3913,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
SYSTEM_CONTACT_URI to secondaryRecord.systemContactUri, SYSTEM_CONTACT_URI to secondaryRecord.systemContactUri,
PROFILE_SHARING to (primaryRecord.profileSharing || secondaryRecord.profileSharing), PROFILE_SHARING to (primaryRecord.profileSharing || secondaryRecord.profileSharing),
CAPABILITIES to max(primaryRecord.capabilities.rawBits, secondaryRecord.capabilities.rawBits), CAPABILITIES to max(primaryRecord.capabilities.rawBits, secondaryRecord.capabilities.rawBits),
MENTION_SETTING to if (primaryRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id MENTION_SETTING to if (primaryRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id,
PNI_SIGNATURE_VERIFIED to pniVerified.toInt()
) )
if (primaryRecord.profileSharing || secondaryRecord.profileSharing) { if (primaryRecord.profileSharing || secondaryRecord.profileSharing) {
@@ -3929,13 +3939,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
check(writableDatabase.inTransaction()) { "Must be in a transaction!" } check(writableDatabase.inTransaction()) { "Must be in a transaction!" }
} }
private fun buildContentValuesForNewUser(e164: String?, pni: PNI?, aci: ACI?): ContentValues { private fun buildContentValuesForNewUser(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean): ContentValues {
check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" } check(e164 != null || pni != null || aci != null) { "Must provide some sort of identifier!" }
val values = contentValuesOf( val values = contentValuesOf(
E164 to e164, E164 to e164,
ACI_COLUMN to aci?.toString(), ACI_COLUMN to aci?.toString(),
PNI_COLUMN to pni?.toString(), PNI_COLUMN to pni?.toString(),
PNI_SIGNATURE_VERIFIED to pniVerified.toInt(),
STORAGE_SERVICE_ID to Base64.encodeWithPadding(StorageSyncHelper.generateKey()), STORAGE_SERVICE_ID to Base64.encodeWithPadding(StorageSyncHelper.generateKey()),
AVATAR_COLOR to AvatarColorHash.forAddress((aci ?: pni)?.toString(), e164).serialize() AVATAR_COLOR to AvatarColorHash.forAddress((aci ?: pni)?.toString(), e164).serialize()
) )
@@ -3971,6 +3982,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(MUTE_UNTIL, contact.muteUntil) put(MUTE_UNTIL, contact.muteUntil)
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(contact.id.raw)) put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(contact.id.raw))
put(HIDDEN, contact.isHidden) put(HIDDEN, contact.isHidden)
put(PNI_SIGNATURE_VERIFIED, contact.isPniSignatureVerified.toInt())
if (contact.hasUnknownFields()) { if (contact.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(Objects.requireNonNull(contact.serializeUnknownFields()))) put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(Objects.requireNonNull(contact.serializeUnknownFields())))

View File

@@ -209,25 +209,16 @@ object RecipientTableCursorUtil {
} }
fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras { fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras {
val storageProtoRaw = cursor.optionalString(RecipientTable.STORAGE_SERVICE_PROTO).orElse(null)
val storageProto = if (storageProtoRaw != null) Base64.decodeOrThrow(storageProtoRaw) else null
val archived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false)
val forcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false)
val groupMasterKey = cursor.optionalBlob(GroupTable.V2_MASTER_KEY).map { GroupUtil.requireMasterKey(it) }.orElse(null)
val identityKey = cursor.optionalString(RecipientTable.IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null)
val identityStatus = cursor.optionalInt(RecipientTable.IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT)
val unregisteredTimestamp = cursor.optionalLong(RecipientTable.UNREGISTERED_TIMESTAMP).orElse(0)
val systemNickname = cursor.optionalString(RecipientTable.SYSTEM_NICKNAME).orElse(null)
return RecipientRecord.SyncExtras( return RecipientRecord.SyncExtras(
storageProto = storageProto, storageProto = cursor.optionalString(RecipientTable.STORAGE_SERVICE_PROTO).orElse(null)?.let { Base64.decodeOrThrow(it) },
groupMasterKey = groupMasterKey, groupMasterKey = cursor.optionalBlob(GroupTable.V2_MASTER_KEY).map { GroupUtil.requireMasterKey(it) }.orElse(null),
identityKey = identityKey, identityKey = cursor.optionalString(RecipientTable.IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null),
identityStatus = identityStatus, identityStatus = cursor.optionalInt(RecipientTable.IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT),
isArchived = archived, isArchived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false),
isForcedUnread = forcedUnread, isForcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false),
unregisteredTimestamp = unregisteredTimestamp, unregisteredTimestamp = cursor.optionalLong(RecipientTable.UNREGISTERED_TIMESTAMP).orElse(0),
systemNickname = systemNickname systemNickname = cursor.optionalString(RecipientTable.SYSTEM_NICKNAME).orElse(null),
pniSignatureVerified = cursor.optionalBoolean(RecipientTable.PNI_SIGNATURE_VERIFIED).orElse(false)
) )
} }

View File

@@ -75,6 +75,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V214_PhoneNumberSha
import org.thoughtcrime.securesms.database.helpers.migration.V215_RemoveAttachmentUniqueId import org.thoughtcrime.securesms.database.helpers.migration.V215_RemoveAttachmentUniqueId
import org.thoughtcrime.securesms.database.helpers.migration.V216_PhoneNumberDiscoverable import org.thoughtcrime.securesms.database.helpers.migration.V216_PhoneNumberDiscoverable
import org.thoughtcrime.securesms.database.helpers.migration.V217_MessageTableExtrasColumn import org.thoughtcrime.securesms.database.helpers.migration.V217_MessageTableExtrasColumn
import org.thoughtcrime.securesms.database.helpers.migration.V218_RecipientPniSignatureVerified
/** /**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -152,10 +153,11 @@ object SignalDatabaseMigrations {
214 to V214_PhoneNumberSharingColumn, 214 to V214_PhoneNumberSharingColumn,
215 to V215_RemoveAttachmentUniqueId, 215 to V215_RemoveAttachmentUniqueId,
216 to V216_PhoneNumberDiscoverable, 216 to V216_PhoneNumberDiscoverable,
217 to V217_MessageTableExtrasColumn 217 to V217_MessageTableExtrasColumn,
218 to V218_RecipientPniSignatureVerified
) )
const val DATABASE_VERSION = 217 const val DATABASE_VERSION = 218
@JvmStatic @JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Adds a pni_signature_verified column to the recipient table, letting us track whether the ACI/PNI association is verified and sync that to storage service.
*/
@Suppress("ClassName")
object V218_RecipientPniSignatureVerified : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE recipient ADD COLUMN pni_signature_verified INTEGER DEFAULT 0")
}
}

View File

@@ -111,7 +111,8 @@ data class RecipientRecord(
val isArchived: Boolean, val isArchived: Boolean,
val isForcedUnread: Boolean, val isForcedUnread: Boolean,
val unregisteredTimestamp: Long, val unregisteredTimestamp: Long,
val systemNickname: String? val systemNickname: String?,
val pniSignatureVerified: Boolean
) )
data class Capabilities( data class Capabilities(

View File

@@ -216,8 +216,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
String systemGivenName = SignalStore.account().isPrimaryDevice() ? local.getSystemGivenName().orElse("") : remote.getSystemGivenName().orElse(""); String systemGivenName = SignalStore.account().isPrimaryDevice() ? local.getSystemGivenName().orElse("") : remote.getSystemGivenName().orElse("");
String systemFamilyName = SignalStore.account().isPrimaryDevice() ? local.getSystemFamilyName().orElse("") : remote.getSystemFamilyName().orElse(""); String systemFamilyName = SignalStore.account().isPrimaryDevice() ? local.getSystemFamilyName().orElse("") : remote.getSystemFamilyName().orElse("");
String systemNickname = remote.getSystemNickname().orElse(""); String systemNickname = remote.getSystemNickname().orElse("");
boolean matchesRemote = doParamsMatch(remote, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); boolean pniSignatureVerified = remote.isPniSignatureVerified() || local.isPniSignatureVerified();
boolean matchesLocal = doParamsMatch(local, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); boolean matchesRemote = doParamsMatch(remote, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden, pniSignatureVerified);
boolean matchesLocal = doParamsMatch(local, unknownFields, aci, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden, pniSignatureVerified);
if (matchesRemote) { if (matchesRemote) {
return remote; return remote;
@@ -244,6 +245,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
.setHideStory(hideStory) .setHideStory(hideStory)
.setUnregisteredTimestamp(unregisteredTimestamp) .setUnregisteredTimestamp(unregisteredTimestamp)
.setHidden(hidden) .setHidden(hidden)
.setPniSignatureVerified(pniSignatureVerified)
.build(); .build();
} }
} }
@@ -295,7 +297,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
long muteUntil, long muteUntil,
boolean hideStory, boolean hideStory,
long unregisteredTimestamp, long unregisteredTimestamp,
boolean hidden) boolean hidden,
boolean pniSignatureVerified)
{ {
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) && return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
Objects.equals(contact.getAci().orElse(null), aci) && Objects.equals(contact.getAci().orElse(null), aci) &&
@@ -317,6 +320,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
contact.getMuteUntil() == muteUntil && contact.getMuteUntil() == muteUntil &&
contact.shouldHideStory() == hideStory && contact.shouldHideStory() == hideStory &&
contact.getUnregisteredTimestamp() == unregisteredTimestamp && contact.getUnregisteredTimestamp() == unregisteredTimestamp &&
contact.isHidden() == hidden; contact.isHidden() == hidden &&
contact.isPniSignatureVerified() == pniSignatureVerified;
} }
} }

View File

@@ -155,6 +155,7 @@ public final class StorageSyncModels {
.setUnregisteredTimestamp(recipient.getSyncExtras().getUnregisteredTimestamp()) .setUnregisteredTimestamp(recipient.getSyncExtras().getUnregisteredTimestamp())
.setHidden(recipient.getHiddenState() != Recipient.HiddenState.NOT_HIDDEN) .setHidden(recipient.getHiddenState() != Recipient.HiddenState.NOT_HIDDEN)
.setUsername(recipient.getUsername()) .setUsername(recipient.getUsername())
.setPniSignatureVerified(recipient.getSyncExtras().getPniSignatureVerified())
.build(); .build();
} }

View File

@@ -68,14 +68,15 @@ object RecipientDatabaseTestUtils {
about: String? = null, about: String? = null,
aboutEmoji: String? = null, aboutEmoji: String? = null,
syncExtras: RecipientRecord.SyncExtras = RecipientRecord.SyncExtras( syncExtras: RecipientRecord.SyncExtras = RecipientRecord.SyncExtras(
null, storageProto = null,
null, groupMasterKey = null,
null, identityKey = null,
IdentityTable.VerifiedStatus.DEFAULT, identityStatus = IdentityTable.VerifiedStatus.DEFAULT,
false, isArchived = false,
false, isForcedUnread = false,
0, unregisteredTimestamp = 0,
null systemNickname = null,
pniSignatureVerified = false
), ),
extras: Recipient.Extras? = null, extras: Recipient.Extras? = null,
hasGroupsInCommon: Boolean = false, hasGroupsInCommon: Boolean = false,

View File

@@ -157,6 +157,10 @@ public final class SignalContactRecord implements SignalRecord {
diff.add("Hidden"); diff.add("Hidden");
} }
if (isPniSignatureVerified() != that.isPniSignatureVerified()) {
diff.add("PniSignatureVerified");
}
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) { if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
diff.add("UnknownFields"); diff.add("UnknownFields");
} }
@@ -265,6 +269,10 @@ public final class SignalContactRecord implements SignalRecord {
return proto.hidden; return proto.hidden;
} }
public boolean isPniSignatureVerified() {
return proto.pniSignatureVerified;
}
/** /**
* Returns the same record, but stripped of the PNI field. Only used while PNP is in development. * Returns the same record, but stripped of the PNI field. Only used while PNP is in development.
*/ */
@@ -401,6 +409,11 @@ public final class SignalContactRecord implements SignalRecord {
return this; return this;
} }
public Builder setPniSignatureVerified(boolean verified) {
builder.pniSignatureVerified(verified);
return this;
}
private static ContactRecord.Builder parseUnknowns(byte[] serializedUnknowns) { private static ContactRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
try { try {
return ContactRecord.ADAPTER.decode(serializedUnknowns).newBuilder(); return ContactRecord.ADAPTER.decode(serializedUnknowns).newBuilder();

View File

@@ -99,7 +99,8 @@ message ContactRecord {
string systemFamilyName = 18; string systemFamilyName = 18;
string systemNickname = 19; string systemNickname = 19;
bool hidden = 20; bool hidden = 20;
// NEXT ID: 21 bool pniSignatureVerified = 21;
// NEXT ID: 22
} }
message GroupV1Record { message GroupV1Record {