Migrate whitelist to SignalRecipient.status
This commit is contained in:
parent
b9dee66b57
commit
a198b817be
@ -778,7 +778,6 @@
|
||||
50F54C2D2CE3FF7C005765EA /* GroupSendEndorsementStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F54C2C2CE3FF7C005765EA /* GroupSendEndorsementStore.swift */; };
|
||||
50F54C2F2CE3FF9E005765EA /* GroupSendEndorsementRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F54C2E2CE3FF9E005765EA /* GroupSendEndorsementRecord.swift */; };
|
||||
50F75E312AD9F18F0032530F /* RecipientDatabaseTableTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F75E302AD9F18F0032530F /* RecipientDatabaseTableTest.swift */; };
|
||||
50F77AA02AAA7B8A00FB70C5 /* ProfileWhitelistMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F77A9F2AAA7B8A00FB70C5 /* ProfileWhitelistMerger.swift */; };
|
||||
50F86FC42AFEFEC20045F58B /* TimeGatedBatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F86FC32AFEFEC20045F58B /* TimeGatedBatch.swift */; };
|
||||
50F946102AD768AF002EF293 /* MockIdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F9460F2AD768AF002EF293 /* MockIdentityManager.swift */; };
|
||||
5AA002E62CA24566002D1CC2 /* SessionStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA002E52CA2455F002D1CC2 /* SessionStoreTest.swift */; };
|
||||
@ -4927,7 +4926,6 @@
|
||||
50F54C2C2CE3FF7C005765EA /* GroupSendEndorsementStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSendEndorsementStore.swift; sourceTree = "<group>"; };
|
||||
50F54C2E2CE3FF9E005765EA /* GroupSendEndorsementRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSendEndorsementRecord.swift; sourceTree = "<group>"; };
|
||||
50F75E302AD9F18F0032530F /* RecipientDatabaseTableTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientDatabaseTableTest.swift; sourceTree = "<group>"; };
|
||||
50F77A9F2AAA7B8A00FB70C5 /* ProfileWhitelistMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileWhitelistMerger.swift; sourceTree = "<group>"; };
|
||||
50F86FC32AFEFEC20045F58B /* TimeGatedBatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeGatedBatch.swift; sourceTree = "<group>"; };
|
||||
50F9460F2AD768AF002EF293 /* MockIdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIdentityManager.swift; sourceTree = "<group>"; };
|
||||
50F96F3A28ECBC3200541EED /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = translations/ms.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@ -14690,7 +14688,6 @@
|
||||
F9C5C9F0289453B100548EEE /* PhoneNumberUtil.swift */,
|
||||
50C98A402B69D9340065BD2E /* PhoneNumberVisibilityFetcher.swift */,
|
||||
B95BBAC12BB36025009EFB4A /* ProfileName.swift */,
|
||||
50F77A9F2AAA7B8A00FB70C5 /* ProfileWhitelistMerger.swift */,
|
||||
506695E429C29C2F00B6D8D0 /* RecipientDatabaseTable.swift */,
|
||||
50AA3EC229F1C4B900EC50A3 /* RecipientFetcher.swift */,
|
||||
506695E229C29BCE00B6D8D0 /* RecipientMerger.swift */,
|
||||
@ -19212,7 +19209,6 @@
|
||||
503BD2892B44D666009624FC /* ProfileManager.swift in Sources */,
|
||||
72901D2D2C9B129B000406DC /* ProfileManagerProtocol.swift in Sources */,
|
||||
B93296692BBB3FF200B8BD39 /* ProfileName.swift in Sources */,
|
||||
50F77AA02AAA7B8A00FB70C5 /* ProfileWhitelistMerger.swift in Sources */,
|
||||
668A01322C2B6088007B8808 /* Promise.swift in Sources */,
|
||||
72901D2F2C9B1918000406DC /* ProtoUtils.swift in Sources */,
|
||||
F9C5CC9D289453B300548EEE /* Provisioning.pb.swift in Sources */,
|
||||
|
||||
@ -253,23 +253,23 @@ private extension ConversationViewController {
|
||||
return
|
||||
}
|
||||
}
|
||||
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in
|
||||
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let hidingManager = DependenciesBridge.shared.recipientHidingManager
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
func unblockThreadIfNeeded(transaction: DBWriteTransaction) {
|
||||
if unblockThread {
|
||||
SSKEnvironment.shared.blockingManagerRef.removeBlockedThread(
|
||||
blockingManager.removeBlockedThread(
|
||||
thread,
|
||||
wasLocallyInitiated: true,
|
||||
transaction: transaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if unhideRecipient, let thread = thread as? TSContactThread {
|
||||
DependenciesBridge.shared.recipientHidingManager.removeHiddenRecipient(
|
||||
thread.contactAddress,
|
||||
wasLocallyInitiated: true,
|
||||
tx: transaction,
|
||||
)
|
||||
}
|
||||
|
||||
func acceptMessageRequestIfNeeded(transaction: DBWriteTransaction) {
|
||||
/// If we're not in "unblock" mode, we should take "accept message
|
||||
/// request" actions. (Bleh.)
|
||||
if !unblockThread {
|
||||
@ -290,18 +290,36 @@ private extension ConversationViewController {
|
||||
transaction: transaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Whitelist the thread
|
||||
SSKEnvironment.shared.profileManagerRef.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: transaction,
|
||||
)
|
||||
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in
|
||||
switch thread {
|
||||
case let thread as TSGroupThread:
|
||||
unblockThreadIfNeeded(transaction: transaction)
|
||||
acceptMessageRequestIfNeeded(transaction: transaction)
|
||||
profileManager.addGroupId(
|
||||
toProfileWhitelist: thread.groupModel.groupId,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: transaction,
|
||||
)
|
||||
|
||||
if !thread.isGroupThread {
|
||||
case let thread as TSContactThread:
|
||||
unblockThreadIfNeeded(transaction: transaction)
|
||||
// Might be nil if thread.contactAddress isn't valid.
|
||||
var recipient = recipientFetcher.fetchOrCreate(address: thread.contactAddress, tx: transaction)
|
||||
if var innerRecipient = recipient {
|
||||
if unhideRecipient, !thread.contactAddress.isLocalAddress {
|
||||
hidingManager.removeHiddenRecipient(&innerRecipient, wasLocallyInitiated: true, tx: transaction)
|
||||
}
|
||||
recipient = innerRecipient
|
||||
}
|
||||
acceptMessageRequestIfNeeded(transaction: transaction)
|
||||
if var innerRecipient = recipient {
|
||||
profileManager.addRecipientToProfileWhitelist(&innerRecipient, userProfileWriter: .localUser, tx: transaction)
|
||||
recipient = innerRecipient
|
||||
}
|
||||
// If this is a contact thread, we should give the
|
||||
// now-unblocked contact our profile key.
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let profileKeyMessage = OWSProfileKeyMessage(
|
||||
thread: thread,
|
||||
profileKey: profileManager.localProfileKey(tx: transaction)!.serialize(),
|
||||
@ -311,6 +329,9 @@ private extension ConversationViewController {
|
||||
transientMessageWithoutAttachments: profileKeyMessage,
|
||||
)
|
||||
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: transaction)
|
||||
|
||||
default:
|
||||
owsFailDebug("can't accept message request for \(type(of: thread))")
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: ChatListViewController.clearSearch, object: nil)
|
||||
|
||||
@ -15,6 +15,7 @@ public class RegistrationStateChangeManagerImpl: RegistrationStateChangeManager
|
||||
private let backupCDNCredentialStore: BackupCDNCredentialStore
|
||||
private let backupSubscriptionManager: BackupSubscriptionManager
|
||||
private let backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager
|
||||
private let blockedRecipientStore: BlockedRecipientStore
|
||||
private var chatConnectionManager: any ChatConnectionManager {
|
||||
// TODO: Fix circular dependency.
|
||||
return DependenciesBridge.shared.chatConnectionManager
|
||||
@ -42,6 +43,7 @@ public class RegistrationStateChangeManagerImpl: RegistrationStateChangeManager
|
||||
backupCDNCredentialStore: BackupCDNCredentialStore,
|
||||
backupSubscriptionManager: BackupSubscriptionManager,
|
||||
backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager,
|
||||
blockedRecipientStore: BlockedRecipientStore,
|
||||
cron: Cron,
|
||||
db: DB,
|
||||
dmConfigurationStore: DisappearingMessagesConfigurationStore,
|
||||
@ -63,6 +65,7 @@ public class RegistrationStateChangeManagerImpl: RegistrationStateChangeManager
|
||||
self.backupCDNCredentialStore = backupCDNCredentialStore
|
||||
self.backupSubscriptionManager = backupSubscriptionManager
|
||||
self.backupTestFlightEntitlementManager = backupTestFlightEntitlementManager
|
||||
self.blockedRecipientStore = blockedRecipientStore
|
||||
self.cron = cron
|
||||
self.db = db
|
||||
self.dmConfigurationStore = dmConfigurationStore
|
||||
@ -349,6 +352,9 @@ public class RegistrationStateChangeManagerImpl: RegistrationStateChangeManager
|
||||
shouldUpdateStorageService: false,
|
||||
tx: tx,
|
||||
)
|
||||
// Always make sure we haven't blocked ourselves. (It's logically
|
||||
// impossible to do so, so we set the bit in the database directly.)
|
||||
blockedRecipientStore.setBlocked(false, recipientId: recipient.id, tx: tx)
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@ -204,7 +204,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
|
||||
visibility: { () -> BackupProto_Contact.Visibility in
|
||||
guard
|
||||
let hiddenRecipient = self.recipientHidingManager.fetchHiddenRecipient(
|
||||
signalRecipient: recipient,
|
||||
recipientId: recipient.id,
|
||||
tx: context.tx,
|
||||
)
|
||||
else {
|
||||
@ -669,7 +669,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
|
||||
}
|
||||
|
||||
let recipientProto = recipient
|
||||
let recipient: SignalRecipient
|
||||
var recipient: SignalRecipient
|
||||
do throws(GRDB.DatabaseError) {
|
||||
recipient = try SignalRecipient.insertRecord(
|
||||
aci: backupContactAddress.aci,
|
||||
@ -758,7 +758,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
|
||||
|
||||
if contactProto.profileSharing {
|
||||
// Add to the whitelist.
|
||||
profileManager.addToWhitelist(recipient.address, tx: context.tx)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, tx: context.tx)
|
||||
}
|
||||
|
||||
if contactProto.blocked {
|
||||
@ -768,7 +768,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit
|
||||
do {
|
||||
func addHiddenRecipient(isHiddenInKnownMessageRequestState: Bool) throws {
|
||||
try recipientHidingManager.addHiddenRecipient(
|
||||
recipient,
|
||||
&recipient,
|
||||
inKnownMessageRequestState: isHiddenInKnownMessageRequestState,
|
||||
wasLocallyInitiated: false,
|
||||
tx: context.tx,
|
||||
|
||||
@ -95,7 +95,7 @@ public class BackupArchiveLocalRecipientArchiver: BackupArchiveProtoStreamWriter
|
||||
) -> BackupArchive.RestoreLocalRecipientResult {
|
||||
context[recipient.recipientId] = .localAddress
|
||||
|
||||
let localSignalRecipient: SignalRecipient
|
||||
var localSignalRecipient: SignalRecipient
|
||||
do throws(GRDB.DatabaseError) {
|
||||
localSignalRecipient = try SignalRecipient.insertRecord(
|
||||
aci: context.localIdentifiers.aci,
|
||||
@ -125,8 +125,8 @@ public class BackupArchiveLocalRecipientArchiver: BackupArchiveProtoStreamWriter
|
||||
}
|
||||
}
|
||||
|
||||
profileManager.addToWhitelist(
|
||||
context.localIdentifiers.aciAddress,
|
||||
profileManager.addRecipientToProfileWhitelist(
|
||||
&localSignalRecipient,
|
||||
tx: context.tx,
|
||||
)
|
||||
|
||||
|
||||
@ -249,7 +249,7 @@ public protocol _MessageBackup_ProfileManagerShim {
|
||||
|
||||
func isGroupId(inProfileWhitelist groupId: Data, tx: DBReadTransaction) -> Bool
|
||||
|
||||
func addToWhitelist(_ address: SignalServiceAddress, tx: DBWriteTransaction)
|
||||
func addRecipientToProfileWhitelist(_ recipient: inout SignalRecipient, tx: DBWriteTransaction)
|
||||
|
||||
func addToWhitelist(_ thread: TSGroupThread, tx: DBWriteTransaction)
|
||||
|
||||
@ -301,17 +301,17 @@ public class _MessageBackup_ProfileManagerWrapper: _MessageBackup_ProfileManager
|
||||
profileManager.isGroupId(inProfileWhitelist: groupId, transaction: tx)
|
||||
}
|
||||
|
||||
public func addToWhitelist(_ address: SignalServiceAddress, tx: DBWriteTransaction) {
|
||||
profileManager.addUser(
|
||||
toProfileWhitelist: address,
|
||||
public func addRecipientToProfileWhitelist(_ recipient: inout SignalRecipient, tx: DBWriteTransaction) {
|
||||
profileManager.addRecipientToProfileWhitelist(
|
||||
&recipient,
|
||||
userProfileWriter: .backupRestore,
|
||||
transaction: tx,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
|
||||
public func addToWhitelist(_ thread: TSGroupThread, tx: DBWriteTransaction) {
|
||||
profileManager.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
profileManager.addGroupId(
|
||||
toProfileWhitelist: thread.groupModel.groupId,
|
||||
userProfileWriter: .backupRestore,
|
||||
transaction: tx,
|
||||
)
|
||||
|
||||
@ -925,13 +925,18 @@ extension OWSContactsManager: ContactManager {
|
||||
Logger.info("Updated \(signalAccountChanges.count) SignalAccounts; now have \(newSignalAccountsMap.count) total")
|
||||
}
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
// Add system contacts to the profile whitelist immediately so that they do
|
||||
// not see the "message request" UI.
|
||||
SSKEnvironment.shared.profileManagerRef.addUsers(
|
||||
toProfileWhitelist: newSignalAccountsMap.values.map { $0.recipientAddress },
|
||||
userProfileWriter: .systemContactsFetch,
|
||||
transaction: tx,
|
||||
)
|
||||
for phoneNumber in newSignalAccountsMap.keys {
|
||||
guard let phoneNumberObj = E164(phoneNumber) else {
|
||||
continue
|
||||
}
|
||||
var recipient = recipientFetcher.fetchOrCreate(phoneNumber: phoneNumberObj, tx: tx)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .systemContactsFetch, tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
// Once we've persisted new SignalAccount state, we should let
|
||||
@ -1185,7 +1190,7 @@ extension OWSContactsManager: ContactManager {
|
||||
) throws {
|
||||
let recipientHidingManager = DependenciesBridge.shared.recipientHidingManager
|
||||
let phoneNumbers = Set(addressBookPhoneNumbers.lazy.map { $0.rawValue.stringValue })
|
||||
for hiddenRecipient in recipientHidingManager.hiddenRecipients(tx: tx) {
|
||||
for var hiddenRecipient in recipientHidingManager.hiddenRecipients(tx: tx) {
|
||||
guard let phoneNumber = hiddenRecipient.phoneNumber else {
|
||||
continue // We can't unhide because of the address book w/o a phone number.
|
||||
}
|
||||
@ -1195,7 +1200,7 @@ extension OWSContactsManager: ContactManager {
|
||||
guard phoneNumbers.contains(phoneNumber.stringValue) else {
|
||||
continue // Not in the address book -- no unhiding.
|
||||
}
|
||||
recipientHidingManager.removeHiddenRecipient(hiddenRecipient, wasLocallyInitiated: true, tx: tx)
|
||||
recipientHidingManager.removeHiddenRecipient(&hiddenRecipient, wasLocallyInitiated: true, tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -309,27 +309,38 @@ extension OWSSyncManager: SyncManagerProtocol, SyncManagerProtocolSwift {
|
||||
return owsFailDebug("message request response couldn't find thread")
|
||||
}
|
||||
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let hidingManager = DependenciesBridge.shared.recipientHidingManager
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
switch syncMessage.type {
|
||||
case .accept:
|
||||
SSKEnvironment.shared.blockingManagerRef.removeBlockedThread(thread, wasLocallyInitiated: false, transaction: transaction)
|
||||
if let thread = thread as? TSContactThread {
|
||||
/// When we accept a message request on a linked device,
|
||||
/// we unhide the message sender. We will eventually also
|
||||
/// learn about the unhide via a StorageService contact sync,
|
||||
/// since the linked device should mark unhidden in
|
||||
/// StorageService. But it doesn't hurt to get ahead of the
|
||||
/// game and unhide here.
|
||||
DependenciesBridge.shared.recipientHidingManager.removeHiddenRecipient(
|
||||
thread.contactAddress,
|
||||
wasLocallyInitiated: false,
|
||||
tx: transaction,
|
||||
blockingManager.removeBlockedThread(thread, wasLocallyInitiated: false, transaction: transaction)
|
||||
switch thread {
|
||||
case let thread as TSGroupThread:
|
||||
// TODO: Fix userProfileWriter.
|
||||
profileManager.addGroupId(
|
||||
toProfileWhitelist: thread.groupModel.groupId,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: transaction,
|
||||
)
|
||||
|
||||
case let thread as TSContactThread:
|
||||
/// When we accept a message request on a linked device, we unhide the
|
||||
/// message sender. We will eventually also learn about the unhide via a
|
||||
/// StorageService contact sync, since the linked device should mark
|
||||
/// unhidden in StorageService. But it doesn't hurt to get ahead of the game
|
||||
/// and unhide here.
|
||||
if var recipient = recipientFetcher.fetchOrCreate(address: thread.contactAddress, tx: transaction) {
|
||||
hidingManager.removeHiddenRecipient(&recipient, wasLocallyInitiated: false, tx: transaction)
|
||||
// TODO: Fix userProfileWriter.
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: transaction)
|
||||
}
|
||||
|
||||
default:
|
||||
owsFailDebug("can't accept messages request for \(type(of: thread))")
|
||||
}
|
||||
SSKEnvironment.shared.profileManagerRef.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: transaction,
|
||||
)
|
||||
case .delete:
|
||||
DependenciesBridge.shared.threadSoftDeleteManager.softDelete(
|
||||
threads: [thread],
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ProfileWhitelistMerger: RecipientMergeObserver {
|
||||
private let profileManager: ProfileManager
|
||||
|
||||
init(profileManager: ProfileManager) {
|
||||
self.profileManager = profileManager
|
||||
}
|
||||
|
||||
func willBreakAssociation(for recipient: SignalRecipient, mightReplaceNonnilPhoneNumber: Bool, tx: DBWriteTransaction) {
|
||||
profileManager.normalizeRecipientInProfileWhitelist(recipient, tx: tx)
|
||||
}
|
||||
|
||||
func didLearnAssociation(mergedRecipient: MergedRecipient, tx: DBWriteTransaction) {
|
||||
if mergedRecipient.isLocalRecipient {
|
||||
return
|
||||
}
|
||||
profileManager.normalizeRecipientInProfileWhitelist(mergedRecipient.newRecipient, tx: tx)
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
public import LibSignalClient
|
||||
|
||||
public struct RecipientDatabaseTable {
|
||||
@ -122,6 +123,13 @@ public struct RecipientDatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchWhitelistedRecipients(tx: DBReadTransaction) -> [SignalRecipient] {
|
||||
let fetchRequest = SignalRecipient.filter(
|
||||
Column(SignalRecipient.CodingKeys.status.rawValue) == SignalRecipient.Status.whitelisted.rawValue,
|
||||
)
|
||||
return failIfThrows { try fetchRequest.fetchAll(tx.database) }
|
||||
}
|
||||
|
||||
public func fetchAllPhoneNumbers(tx: DBReadTransaction) -> [String: Bool] {
|
||||
var result = [String: Bool]()
|
||||
enumerateAll(tx: tx) { signalRecipient in
|
||||
|
||||
@ -43,4 +43,14 @@ public struct RecipientFetcher {
|
||||
searchableNameIndexer.insert(result, tx: tx)
|
||||
return result
|
||||
}
|
||||
|
||||
public func fetchOrCreate(address: SignalServiceAddress, tx: DBWriteTransaction) -> SignalRecipient? {
|
||||
if let serviceId = address.serviceId {
|
||||
return fetchOrCreate(serviceId: serviceId, tx: tx)
|
||||
}
|
||||
if let phoneNumber = address.e164 {
|
||||
return fetchOrCreate(phoneNumber: phoneNumber, tx: tx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +165,6 @@ class RecipientMergerImpl: RecipientMerger {
|
||||
signalServiceAddressCache,
|
||||
AuthorMergeObserver(authorMergeHelper: authorMergeHelper),
|
||||
SignalAccountMergeObserver(),
|
||||
ProfileWhitelistMerger(profileManager: profileManager),
|
||||
UserProfileMerger(userProfileStore: userProfileStore),
|
||||
],
|
||||
threadMerger: ThreadMerger(
|
||||
|
||||
@ -34,6 +34,11 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
public var isDiscoverable: Bool
|
||||
}
|
||||
|
||||
public enum Status: Int64, Codable {
|
||||
case unspecified = 0
|
||||
case whitelisted = 1
|
||||
}
|
||||
|
||||
public typealias RowId = Int64
|
||||
public let id: RowId
|
||||
|
||||
@ -53,6 +58,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
public var phoneNumber: PhoneNumber?
|
||||
public fileprivate(set) var deviceIds: [DeviceId]
|
||||
public fileprivate(set) var unregisteredAtTimestamp: UInt64?
|
||||
public var status: Status
|
||||
|
||||
public var aci: Aci? {
|
||||
get { Aci.parseFrom(aciString: aciString) }
|
||||
@ -84,6 +90,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
pni: Pni? = nil,
|
||||
deviceIds: [DeviceId] = [],
|
||||
unregisteredAtTimestamp: UInt64?? = nil,
|
||||
status: Status = .unspecified,
|
||||
tx: DBWriteTransaction,
|
||||
) throws(GRDB.DatabaseError) -> Self {
|
||||
do {
|
||||
@ -97,8 +104,9 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
\(signalRecipientColumn: .phoneNumber),
|
||||
\(signalRecipientColumn: .pni),
|
||||
\(signalRecipientColumn: .deviceIds),
|
||||
\(signalRecipientColumn: .unregisteredAtTimestamp)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *
|
||||
\(signalRecipientColumn: .unregisteredAtTimestamp),
|
||||
\(signalRecipientColumn: .status)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *
|
||||
""",
|
||||
arguments: [
|
||||
SDSRecordType.signalRecipient.rawValue,
|
||||
@ -108,6 +116,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
pni?.serviceIdUppercaseString,
|
||||
Data(deviceIds.map(\.uint8Value)),
|
||||
unregisteredAtTimestamp ?? (deviceIds.isEmpty ? Constants.distantPastUnregisteredTimestamp : nil),
|
||||
status.rawValue,
|
||||
],
|
||||
)!
|
||||
} catch {
|
||||
@ -125,6 +134,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
case deviceIds = "devices"
|
||||
case unregisteredAtTimestamp
|
||||
case isPhoneNumberDiscoverable
|
||||
case status
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
@ -151,6 +161,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
let encodedDeviceIds = try container.decode(Data.self, forKey: .deviceIds)
|
||||
deviceIds = encodedDeviceIds.compactMap(DeviceId.init(validating:))
|
||||
unregisteredAtTimestamp = try container.decodeIfPresent(UInt64.self, forKey: .unregisteredAtTimestamp)
|
||||
status = try container.decode(Status.self, forKey: .status)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -164,6 +175,7 @@ public struct SignalRecipient: FetchableRecord, PersistableRecord, Codable {
|
||||
try container.encodeIfPresent(phoneNumber?.isDiscoverable, forKey: .isPhoneNumberDiscoverable)
|
||||
try container.encode(Data(deviceIds.map(\.uint8Value)), forKey: .deviceIds)
|
||||
try container.encodeIfPresent(unregisteredAtTimestamp, forKey: .unregisteredAtTimestamp)
|
||||
try container.encode(status, forKey: .status)
|
||||
}
|
||||
|
||||
// MARK: - Fetching
|
||||
|
||||
@ -1124,6 +1124,7 @@ extension AppSetup.GlobalsContinuation {
|
||||
backupCDNCredentialStore: backupCDNCredentialStore,
|
||||
backupSubscriptionManager: backupSubscriptionManager,
|
||||
backupTestFlightEntitlementManager: backupTestFlightEntitlementManager,
|
||||
blockedRecipientStore: blockedRecipientStore,
|
||||
cron: cron,
|
||||
db: db,
|
||||
dmConfigurationStore: disappearingMessagesConfigurationStore,
|
||||
@ -1667,6 +1668,7 @@ extension AppSetup.GlobalsContinuation {
|
||||
backupSubscriptionManager: backupSubscriptionManager,
|
||||
backupTestFlightEntitlementManager: backupTestFlightEntitlementManager,
|
||||
badgeCountFetcher: badgeCountFetcher,
|
||||
blockedRecipientStore: blockedRecipientStore,
|
||||
callLinkStore: callLinkStore,
|
||||
callRecordDeleteManager: callRecordDeleteManager,
|
||||
callRecordMissedCallManager: callRecordMissedCallManager,
|
||||
|
||||
@ -85,6 +85,7 @@ public class DependenciesBridge {
|
||||
public let backupSubscriptionManager: BackupSubscriptionManager
|
||||
public let backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager
|
||||
public let badgeCountFetcher: BadgeCountFetcher
|
||||
let blockedRecipientStore: BlockedRecipientStore
|
||||
public let callLinkStore: any CallLinkRecordStore
|
||||
public let callRecordDeleteManager: any CallRecordDeleteManager
|
||||
public let callRecordMissedCallManager: CallRecordMissedCallManager
|
||||
@ -225,6 +226,7 @@ public class DependenciesBridge {
|
||||
backupSubscriptionManager: BackupSubscriptionManager,
|
||||
backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager,
|
||||
badgeCountFetcher: BadgeCountFetcher,
|
||||
blockedRecipientStore: BlockedRecipientStore,
|
||||
callLinkStore: any CallLinkRecordStore,
|
||||
callRecordDeleteManager: CallRecordDeleteManager,
|
||||
callRecordMissedCallManager: CallRecordMissedCallManager,
|
||||
@ -364,6 +366,7 @@ public class DependenciesBridge {
|
||||
self.backupSubscriptionManager = backupSubscriptionManager
|
||||
self.backupTestFlightEntitlementManager = backupTestFlightEntitlementManager
|
||||
self.badgeCountFetcher = badgeCountFetcher
|
||||
self.blockedRecipientStore = blockedRecipientStore
|
||||
self.callLinkStore = callLinkStore
|
||||
self.callRecordDeleteManager = callRecordDeleteManager
|
||||
self.callRecordMissedCallManager = callRecordMissedCallManager
|
||||
|
||||
@ -294,20 +294,25 @@ public class SSKEnvironment: NSObject {
|
||||
/// Pni (a one-time migration), but it also helps ensure that the value is
|
||||
/// always consistent with TSAccountManager's values.
|
||||
private func fixLocalRecipientIfNeeded(dependenciesBridge: DependenciesBridge) {
|
||||
self.databaseStorageRef.write { tx in
|
||||
guard let localIdentifiers = dependenciesBridge.tsAccountManager.localIdentifiers(tx: tx) else {
|
||||
let blockedRecipientStore = dependenciesBridge.blockedRecipientStore
|
||||
let databaseStorage = self.databaseStorageRef
|
||||
let recipientMerger = dependenciesBridge.recipientMerger
|
||||
let tsAccountManager = dependenciesBridge.tsAccountManager
|
||||
|
||||
databaseStorage.write { tx in
|
||||
guard let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx) else {
|
||||
return // Not registered yet.
|
||||
}
|
||||
guard let phoneNumber = E164(localIdentifiers.phoneNumber) else {
|
||||
return // Registered with an invalid phone number.
|
||||
}
|
||||
let recipientMerger = dependenciesBridge.recipientMerger
|
||||
_ = recipientMerger.applyMergeForLocalAccount(
|
||||
let localRecipient = recipientMerger.applyMergeForLocalAccount(
|
||||
aci: localIdentifiers.aci,
|
||||
phoneNumber: phoneNumber,
|
||||
pni: localIdentifiers.pni,
|
||||
tx: tx,
|
||||
)
|
||||
blockedRecipientStore.setBlocked(false, recipientId: localRecipient.id, tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,8 +183,8 @@ public class GroupManager: NSObject {
|
||||
spamReportingMetadata: .createdByLocalAction,
|
||||
transaction: tx,
|
||||
)
|
||||
SSKEnvironment.shared.profileManagerRef.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
SSKEnvironment.shared.profileManagerRef.addGroupId(
|
||||
toProfileWhitelist: groupModel.groupId,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
|
||||
@ -80,7 +80,11 @@ public class BlockingManager {
|
||||
guard let recipientId = recipientDatabaseTable.fetchRecipient(address: address, tx: transaction)?.id else {
|
||||
return false
|
||||
}
|
||||
return blockedRecipientStore.isBlocked(recipientId: recipientId, tx: transaction)
|
||||
return isRecipientBlocked(recipientId: recipientId, tx: transaction)
|
||||
}
|
||||
|
||||
public func isRecipientBlocked(recipientId: SignalRecipient.RowId, tx: DBReadTransaction) -> Bool {
|
||||
return blockedRecipientStore.isBlocked(recipientId: recipientId, tx: tx)
|
||||
}
|
||||
|
||||
public func isGroupIdBlocked(_ groupId: GroupIdentifier, transaction tx: DBReadTransaction) -> Bool {
|
||||
|
||||
@ -432,12 +432,10 @@ public final class MessageReceiver {
|
||||
legacyPhoneNumber: sent.destinationE164?.nilIfEmpty,
|
||||
cache: SSKEnvironment.shared.signalServiceAddressCacheRef,
|
||||
)
|
||||
if destinationAddress.isValid {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: destinationAddress,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
if var recipient = recipientFetcher.fetchOrCreate(address: destinationAddress, tx: tx) {
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -755,7 +755,7 @@ public class OWSIdentityManagerImpl: OWSIdentityManager {
|
||||
) -> ChangeVerificationStateResult {
|
||||
owsAssertDebug(identityKey.count == Constants.storedIdentityKeyLength)
|
||||
|
||||
guard let recipient = recipientDatabaseTable.fetchRecipient(address: address, tx: tx) else {
|
||||
guard var recipient = recipientDatabaseTable.fetchRecipient(address: address, tx: tx) else {
|
||||
owsFailDebug("Missing SignalRecipient")
|
||||
return .error
|
||||
}
|
||||
@ -805,11 +805,7 @@ public class OWSIdentityManagerImpl: OWSIdentityManager {
|
||||
// to the profile whitelist so they become a "Signal
|
||||
// Connection". (Other devices will learn about this via
|
||||
// Storage Service like normal.)
|
||||
profileManager.addUser(
|
||||
toProfileWhitelist: recipient.address,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: tx)
|
||||
case .noLongerVerified, .implicit:
|
||||
break
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ extension RecipientHidingManager {
|
||||
guard let recipient = recipient(from: address, tx: tx) else {
|
||||
return false
|
||||
}
|
||||
return isHiddenRecipient(recipient, tx: tx)
|
||||
return isHiddenRecipient(recipientId: recipient.id, tx: tx)
|
||||
}
|
||||
|
||||
// MARK: Write
|
||||
@ -73,17 +73,15 @@ extension RecipientHidingManager {
|
||||
throw RecipientHidingError.cannotHideLocalAddress
|
||||
}
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
let recipient = try { () throws -> SignalRecipient in
|
||||
if let serviceId = address.serviceId {
|
||||
return recipientFetcher.fetchOrCreate(serviceId: serviceId, tx: tx)
|
||||
}
|
||||
if let phoneNumber = address.e164 {
|
||||
return recipientFetcher.fetchOrCreate(phoneNumber: phoneNumber, tx: tx)
|
||||
var recipient = try { () throws -> SignalRecipient in
|
||||
let recipient = recipientFetcher.fetchOrCreate(address: address, tx: tx)
|
||||
if let recipient {
|
||||
return recipient
|
||||
}
|
||||
throw RecipientHidingError.invalidRecipientAddress(address)
|
||||
}()
|
||||
try addHiddenRecipient(
|
||||
recipient,
|
||||
&recipient,
|
||||
inKnownMessageRequestState: inKnownMessageRequestState,
|
||||
wasLocallyInitiated: wasLocallyInitiated,
|
||||
tx: tx,
|
||||
@ -109,8 +107,8 @@ extension RecipientHidingManager {
|
||||
owsFailDebug("Cannot unhide the local address")
|
||||
return
|
||||
}
|
||||
if let recipient = recipient(from: address, tx: tx) {
|
||||
removeHiddenRecipient(recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
if var recipient = recipient(from: address, tx: tx) {
|
||||
removeHiddenRecipient(&recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ public protocol RecipientHidingManager {
|
||||
/// Fetch the hidden-recipient state for the given `SignalRecipient`, if the
|
||||
/// `SignalRecipient` is currently hidden.
|
||||
func fetchHiddenRecipient(
|
||||
signalRecipient: SignalRecipient,
|
||||
recipientId: SignalRecipient.RowId,
|
||||
tx: DBReadTransaction,
|
||||
) -> HiddenRecipient?
|
||||
|
||||
@ -60,7 +60,7 @@ public protocol RecipientHidingManager {
|
||||
/// Whether this hide represents one initiated on this device, or one that
|
||||
/// occurred on a linked device.
|
||||
func addHiddenRecipient(
|
||||
_ recipient: SignalRecipient,
|
||||
_ recipient: inout SignalRecipient,
|
||||
inKnownMessageRequestState: Bool,
|
||||
wasLocallyInitiated: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
@ -73,7 +73,7 @@ public protocol RecipientHidingManager {
|
||||
/// the hide on this device (true) or a linked device (false).
|
||||
/// - Parameter tx: The transaction to use for database operations.
|
||||
func removeHiddenRecipient(
|
||||
_ recipient: SignalRecipient,
|
||||
_ recipient: inout SignalRecipient,
|
||||
wasLocallyInitiated: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
)
|
||||
@ -83,10 +83,10 @@ public extension RecipientHidingManager {
|
||||
|
||||
/// Whether the given `SignalRecipient` is currently hidden.
|
||||
func isHiddenRecipient(
|
||||
_ recipient: SignalRecipient,
|
||||
recipientId: SignalRecipient.RowId,
|
||||
tx: DBReadTransaction,
|
||||
) -> Bool {
|
||||
return fetchHiddenRecipient(signalRecipient: recipient, tx: tx) != nil
|
||||
return fetchHiddenRecipient(recipientId: recipientId, tx: tx) != nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,11 +167,11 @@ public final class RecipientHidingManagerImpl: RecipientHidingManager {
|
||||
}
|
||||
|
||||
public func fetchHiddenRecipient(
|
||||
signalRecipient: SignalRecipient,
|
||||
recipientId: SignalRecipient.RowId,
|
||||
tx: DBReadTransaction,
|
||||
) -> HiddenRecipient? {
|
||||
return failIfThrows {
|
||||
return try HiddenRecipient.fetchOne(tx.database, key: signalRecipient.id)
|
||||
return try HiddenRecipient.fetchOne(tx.database, key: recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,13 +252,13 @@ public final class RecipientHidingManagerImpl: RecipientHidingManager {
|
||||
// MARK: -
|
||||
|
||||
public func addHiddenRecipient(
|
||||
_ recipient: SignalRecipient,
|
||||
_ recipient: inout SignalRecipient,
|
||||
inKnownMessageRequestState: Bool,
|
||||
wasLocallyInitiated: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
) throws {
|
||||
Logger.info("Hiding recipient")
|
||||
guard !isHiddenRecipient(recipient, tx: tx) else {
|
||||
guard !isHiddenRecipient(recipientId: recipient.id, tx: tx) else {
|
||||
// This is a perhaps extraneous safeguard against
|
||||
// hiding an already-hidden address. I say extraneous
|
||||
// because theoretically the UI should not be available to
|
||||
@ -277,20 +277,20 @@ public final class RecipientHidingManagerImpl: RecipientHidingManager {
|
||||
try record.insert(tx.database)
|
||||
}
|
||||
|
||||
didSetAsHidden(recipient: recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
didSetAsHidden(recipient: &recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
}
|
||||
|
||||
public func removeHiddenRecipient(
|
||||
_ recipient: SignalRecipient,
|
||||
_ recipient: inout SignalRecipient,
|
||||
wasLocallyInitiated: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
if isHiddenRecipient(recipient, tx: tx) {
|
||||
if isHiddenRecipient(recipientId: recipient.id, tx: tx) {
|
||||
Logger.info("Unhiding recipient")
|
||||
failIfThrows {
|
||||
try HiddenRecipient.deleteOne(tx.database, key: recipient.id)
|
||||
}
|
||||
didSetAsUnhidden(recipient: recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
didSetAsUnhidden(recipient: &recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,7 +306,7 @@ private extension RecipientHidingManagerImpl {
|
||||
/// the hide on this device (true) or a linked device (false).
|
||||
/// - Parameter tx: The transaction to use for database operations.
|
||||
func didSetAsHidden(
|
||||
recipient: SignalRecipient,
|
||||
recipient: inout SignalRecipient,
|
||||
wasLocallyInitiated: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
@ -331,11 +331,7 @@ private extension RecipientHidingManagerImpl {
|
||||
|
||||
if wasLocallyInitiated {
|
||||
Logger.info("[Recipient hiding][side effects] Remove from whitelist.")
|
||||
profileManager.removeUser(
|
||||
fromProfileWhitelist: recipient.address,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
profileManager.removeRecipientFromProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: tx)
|
||||
Logger.info("[Recipient hiding][side effects] Remove from story distribution lists.")
|
||||
let storyRecipientManager = DependenciesBridge.shared.storyRecipientManager
|
||||
storyRecipientManager.removeRecipientIdFromAllPrivateStoryThreads(
|
||||
@ -390,18 +386,14 @@ private extension RecipientHidingManagerImpl {
|
||||
/// rule is in place that will also delete the corresponding
|
||||
/// `HiddenRecipient` entry. This method does not get hit in
|
||||
/// that case.
|
||||
func didSetAsUnhidden(recipient: SignalRecipient, wasLocallyInitiated: Bool, tx: DBWriteTransaction) {
|
||||
func didSetAsUnhidden(recipient: inout SignalRecipient, wasLocallyInitiated: Bool, tx: DBWriteTransaction) {
|
||||
// Triggers UI updates of recipient lists.
|
||||
NotificationCenter.default.postOnMainThread(name: Self.hideListDidChange, object: nil)
|
||||
|
||||
Logger.info("[Recipient hiding][side effects] Beginning side effects of setting as unhidden.")
|
||||
if wasLocallyInitiated {
|
||||
Logger.info("[Recipient hiding][side effects] Add to whitelist.")
|
||||
profileManager.addUser(
|
||||
toProfileWhitelist: recipient.address,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: tx)
|
||||
Logger.info("[Recipient hiding][side effects] Sync with storage service.")
|
||||
storageServiceManager.recordPendingUpdates(updatedAddresses: [recipient.address])
|
||||
}
|
||||
|
||||
@ -17,8 +17,6 @@ public class OWSProfileManager: ProfileManagerProtocol {
|
||||
public static let notificationKeyUserProfileWriter = "kNSNotificationKey_UserProfileWriter"
|
||||
|
||||
private let metadataStore = KeyValueStore(collection: "kOWSProfileManager_Metadata")
|
||||
private let whitelistedPhoneNumbersStore = KeyValueStore(collection: "kOWSProfileManager_UserWhitelistCollection")
|
||||
private let whitelistedServiceIdsStore = KeyValueStore(collection: "kOWSProfileManager_UserUUIDWhitelistCollection")
|
||||
private let whitelistedGroupsStore = KeyValueStore(collection: "kOWSProfileManager_GroupWhitelistCollection")
|
||||
private let settingsStore = KeyValueStore(collection: "kOWSProfileManager_SettingsStore")
|
||||
|
||||
@ -82,166 +80,83 @@ public class OWSProfileManager: ProfileManagerProtocol {
|
||||
localUserProfile.update(profileKey: .setTo(key), userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
|
||||
public func normalizeRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBWriteTransaction) {
|
||||
swift_normalizeRecipientInProfileWhitelist(recipient, tx: tx)
|
||||
}
|
||||
public func addRecipientToProfileWhitelist(
|
||||
_ recipient: inout SignalRecipient,
|
||||
userProfileWriter: UserProfileWriter,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let hidingManager = DependenciesBridge.shared.recipientHidingManager
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
|
||||
public func addUser(toProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
owsAssertDebug(address.isValid)
|
||||
addUsers(toProfileWhitelist: [address], userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
|
||||
public func addUsers(toProfileWhitelist addresses: [SignalServiceAddress], userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
let addressesToAdd = addressesNotBlockedOrInWhitelist(addresses, transaction: transaction)
|
||||
addConfirmedUnwhitelistedAddresses(addressesToAdd, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
|
||||
public func removeUser(fromProfileWhitelist address: SignalServiceAddress) {
|
||||
owsAssertDebug(address.isValid)
|
||||
|
||||
removeUsers(fromProfileWhitelist: [address])
|
||||
}
|
||||
|
||||
public func removeUser(fromProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
owsAssertDebug(address.isValid)
|
||||
|
||||
let addressesToRemove = addressesInWhitelist([address], transaction: transaction)
|
||||
removeConfirmedWhitelistedAddresses(addressesToRemove, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
|
||||
// TODO: We could add a userProfileWriter parameter.
|
||||
private func removeUsers(fromProfileWhitelist addresses: [SignalServiceAddress]) {
|
||||
// Try to avoid opening a write transaction.
|
||||
SSKEnvironment.shared.databaseStorageRef.asyncRead { readTransaction in
|
||||
let addressesToRemove = self.addressesInWhitelist(addresses, transaction: readTransaction)
|
||||
if addressesToRemove.isEmpty {
|
||||
return
|
||||
}
|
||||
SSKEnvironment.shared.databaseStorageRef.asyncWrite { writeTransaction in
|
||||
self.removeConfirmedWhitelistedAddresses(addressesToRemove, userProfileWriter: .localUser, transaction: writeTransaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addressesNotBlockedOrInWhitelist(_ addresses: [SignalServiceAddress], transaction: DBReadTransaction) -> Set<SignalServiceAddress> {
|
||||
var notBlockedOrInWhitelist = Set<SignalServiceAddress>()
|
||||
for address in addresses {
|
||||
// If the address is blocked, we don't want to include it
|
||||
if SSKEnvironment.shared.blockingManagerRef.isAddressBlocked(address, transaction: transaction) || RecipientHidingManagerObjcBridge.isHiddenAddress(address, tx: transaction) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isAddressInWhitelist(address, tx: transaction) {
|
||||
notBlockedOrInWhitelist.insert(address)
|
||||
}
|
||||
}
|
||||
|
||||
return notBlockedOrInWhitelist
|
||||
}
|
||||
|
||||
private func addressesInWhitelist(_ addresses: [SignalServiceAddress], transaction: DBReadTransaction) -> Set<SignalServiceAddress> {
|
||||
var whitelistedAddresses = Set<SignalServiceAddress>()
|
||||
|
||||
for address in addresses {
|
||||
if isAddressInWhitelist(address, tx: transaction) {
|
||||
whitelistedAddresses.insert(address)
|
||||
}
|
||||
}
|
||||
|
||||
return whitelistedAddresses
|
||||
}
|
||||
|
||||
private func isAddressInWhitelist(_ address: SignalServiceAddress, tx: DBReadTransaction) -> Bool {
|
||||
if let uppercaseServiceId = address.serviceIdUppercaseString, whitelistedServiceIdsStore.hasValue(uppercaseServiceId, transaction: tx) {
|
||||
return true
|
||||
}
|
||||
|
||||
if let phoneNumber = address.phoneNumber, whitelistedPhoneNumbersStore.hasValue(phoneNumber, transaction: tx) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func removeConfirmedWhitelistedAddresses(_ addressesToRemove: Set<SignalServiceAddress>, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
guard !addressesToRemove.isEmpty else {
|
||||
if blockingManager.isRecipientBlocked(recipientId: recipient.id, tx: tx) {
|
||||
return
|
||||
}
|
||||
|
||||
for address in addressesToRemove {
|
||||
// Historically we put both the ACI and phone number into their respective
|
||||
// stores. We currently save only the best identifier, but we should still
|
||||
// try and remove both to handle these historical cases.
|
||||
if let uppercaseServiceId = address.serviceIdUppercaseString {
|
||||
whitelistedServiceIdsStore.removeValue(forKey: uppercaseServiceId, transaction: transaction)
|
||||
}
|
||||
if let phoneNumber = address.phoneNumber {
|
||||
whitelistedPhoneNumbersStore.removeValue(forKey: phoneNumber, transaction: transaction)
|
||||
}
|
||||
|
||||
if let thread = TSContactThread.getWithContactAddress(address, transaction: transaction) {
|
||||
SSKEnvironment.shared.databaseStorageRef.touch(thread: thread, shouldReindex: false, tx: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
transaction.addSyncCompletion {
|
||||
// Mark the removed whitelisted addresses for update
|
||||
if OWSUserProfile.shouldUpdateStorageServiceForUserProfileWriter(userProfileWriter) {
|
||||
SSKEnvironment.shared.storageServiceManagerRef.recordPendingUpdates(updatedAddresses: Array(addressesToRemove))
|
||||
}
|
||||
|
||||
for address in addressesToRemove {
|
||||
NotificationCenter.default.postOnMainThread(name: UserProfileNotifications.profileWhitelistDidChange, object: nil, userInfo: [
|
||||
UserProfileNotifications.profileAddressKey: address,
|
||||
Self.notificationKeyUserProfileWriter: NSNumber(value: userProfileWriter.rawValue),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addConfirmedUnwhitelistedAddresses(_ addressesToAdd: Set<SignalServiceAddress>, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
guard !addressesToAdd.isEmpty else {
|
||||
if hidingManager.isHiddenRecipient(recipientId: recipient.id, tx: tx) {
|
||||
return
|
||||
}
|
||||
switch recipient.status {
|
||||
case .whitelisted:
|
||||
return
|
||||
case .unspecified:
|
||||
break
|
||||
}
|
||||
recipient.status = .whitelisted
|
||||
recipientStore.updateRecipient(recipient, transaction: tx)
|
||||
_didUpdateRecipientInWhitelist(recipient, userProfileWriter: userProfileWriter, tx: tx)
|
||||
}
|
||||
|
||||
for address in addressesToAdd {
|
||||
let serviceId = address.serviceId
|
||||
if let serviceId = serviceId as? Aci {
|
||||
whitelistedServiceIdsStore.setBool(true, key: serviceId.serviceIdUppercaseString, transaction: transaction)
|
||||
} else if let phoneNumber = address.phoneNumber {
|
||||
whitelistedPhoneNumbersStore.setBool(true, key: phoneNumber, transaction: transaction)
|
||||
} else if let serviceId = serviceId as? Pni {
|
||||
whitelistedServiceIdsStore.setBool(true, key: serviceId.serviceIdUppercaseString, transaction: transaction)
|
||||
}
|
||||
public func removeRecipientFromProfileWhitelist(
|
||||
_ recipient: inout SignalRecipient,
|
||||
userProfileWriter: UserProfileWriter,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
switch recipient.status {
|
||||
case .unspecified:
|
||||
return
|
||||
case .whitelisted:
|
||||
break
|
||||
}
|
||||
recipient.status = .unspecified
|
||||
recipientStore.updateRecipient(recipient, transaction: tx)
|
||||
_didUpdateRecipientInWhitelist(recipient, userProfileWriter: userProfileWriter, tx: tx)
|
||||
}
|
||||
|
||||
if let thread = TSContactThread.getWithContactAddress(address, transaction: transaction) {
|
||||
SSKEnvironment.shared.databaseStorageRef.touch(thread: thread, shouldReindex: false, tx: transaction)
|
||||
}
|
||||
private func _didUpdateRecipientInWhitelist(
|
||||
_ recipient: SignalRecipient,
|
||||
userProfileWriter: UserProfileWriter,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
|
||||
let storageServiceManager = SSKEnvironment.shared.storageServiceManagerRef
|
||||
|
||||
if let thread = TSContactThread.getWithContactAddress(recipient.address, transaction: tx) {
|
||||
databaseStorage.touch(thread: thread, shouldReindex: false, tx: tx)
|
||||
}
|
||||
|
||||
transaction.addSyncCompletion {
|
||||
tx.addSyncCompletion {
|
||||
// Mark the new whitelisted addresses for update
|
||||
if OWSUserProfile.shouldUpdateStorageServiceForUserProfileWriter(userProfileWriter) {
|
||||
SSKEnvironment.shared.storageServiceManagerRef.recordPendingUpdates(updatedAddresses: Array(addressesToAdd))
|
||||
storageServiceManager.recordPendingUpdates(updatedAddresses: [recipient.address])
|
||||
}
|
||||
|
||||
for address in addressesToAdd {
|
||||
NotificationCenter.default.postOnMainThread(name: UserProfileNotifications.profileWhitelistDidChange, object: nil, userInfo: [
|
||||
UserProfileNotifications.profileAddressKey: address,
|
||||
Self.notificationKeyUserProfileWriter: NSNumber(value: userProfileWriter.rawValue),
|
||||
])
|
||||
}
|
||||
NotificationCenter.default.postOnMainThread(name: UserProfileNotifications.profileWhitelistDidChange, object: nil, userInfo: [
|
||||
UserProfileNotifications.profileAddressKey: recipient.address,
|
||||
Self.notificationKeyUserProfileWriter: NSNumber(value: userProfileWriter.rawValue),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public func isUser(inProfileWhitelist address: SignalServiceAddress, transaction: DBReadTransaction) -> Bool {
|
||||
owsAssertDebug(address.isValid)
|
||||
public func isRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBReadTransaction) -> Bool {
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let hidingManager = DependenciesBridge.shared.recipientHidingManager
|
||||
|
||||
if SSKEnvironment.shared.blockingManagerRef.isAddressBlocked(address, transaction: transaction) || RecipientHidingManagerObjcBridge.isHiddenAddress(address, tx: transaction) {
|
||||
return false
|
||||
}
|
||||
return
|
||||
!blockingManager.isRecipientBlocked(recipientId: recipient.id, tx: tx)
|
||||
&& !hidingManager.isHiddenRecipient(recipientId: recipient.id, tx: tx)
|
||||
&& recipient.status == .whitelisted
|
||||
|
||||
return isAddressInWhitelist(address, tx: transaction)
|
||||
}
|
||||
|
||||
public func addGroupId(toProfileWhitelist groupId: Data, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
@ -303,14 +218,6 @@ public class OWSProfileManager: ProfileManagerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
public func addThread(toProfileWhitelist thread: TSThread, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
if thread.isGroupThread, let groupThread = thread as? TSGroupThread {
|
||||
addGroupId(toProfileWhitelist: groupThread.groupModel.groupId, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
} else if !thread.isGroupThread, let contactThread = thread as? TSContactThread {
|
||||
addUser(toProfileWhitelist: contactThread.contactAddress, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
public func isGroupId(inProfileWhitelist groupId: Data, transaction: DBReadTransaction) -> Bool {
|
||||
owsAssertDebug(!groupId.isEmpty)
|
||||
if SSKEnvironment.shared.blockingManagerRef.isGroupIdBlocked_deprecated(groupId, tx: transaction) {
|
||||
@ -320,16 +227,6 @@ public class OWSProfileManager: ProfileManagerProtocol {
|
||||
return whitelistedGroupsStore.hasValue(groupIdKey, transaction: transaction)
|
||||
}
|
||||
|
||||
public func isThread(inProfileWhitelist thread: TSThread, transaction: DBReadTransaction) -> Bool {
|
||||
if thread.isGroupThread, let groupThread = thread as? TSGroupThread {
|
||||
return isGroupId(inProfileWhitelist: groupThread.groupModel.groupId, transaction: transaction)
|
||||
} else if !thread.isGroupThread, let contactThread = thread as? TSContactThread {
|
||||
return isUser(inProfileWhitelist: contactThread.contactAddress, transaction: transaction)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Other User's Profiles
|
||||
|
||||
public func localUserProfile(tx: DBReadTransaction) -> OWSUserProfile? {
|
||||
@ -544,29 +441,13 @@ extension OWSProfileManager: ProfileManager {
|
||||
// MARK: -
|
||||
|
||||
public func allWhitelistedAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
|
||||
var addresses = Set<SignalServiceAddress>()
|
||||
for serviceIdString in whitelistedServiceIdsStore.allKeys(transaction: tx) {
|
||||
addresses.insert(SignalServiceAddress(serviceIdString: serviceIdString))
|
||||
}
|
||||
for phoneNumber in whitelistedPhoneNumbersStore.allKeys(transaction: tx) {
|
||||
addresses.insert(SignalServiceAddress.legacyAddress(serviceId: nil, phoneNumber: phoneNumber))
|
||||
}
|
||||
|
||||
return Array(addresses)
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
return recipientStore.fetchWhitelistedRecipients(tx: tx).map(\.address)
|
||||
}
|
||||
|
||||
public func allWhitelistedRegisteredAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
|
||||
return allWhitelistedAddresses(tx: tx).lazy.compactMap { address in
|
||||
guard
|
||||
let recipient = DependenciesBridge.shared.recipientDatabaseTable
|
||||
.fetchRecipient(address: address, tx: tx),
|
||||
recipient.isRegistered
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return recipient.address
|
||||
}
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
return recipientStore.fetchWhitelistedRecipients(tx: tx).lazy.filter(\.isRegistered).map(\.address)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@ -633,8 +514,7 @@ extension OWSProfileManager: ProfileManager {
|
||||
case blocklistChange(BlocklistChange)
|
||||
|
||||
struct BlocklistChange {
|
||||
let phoneNumbers: [String]
|
||||
let serviceIds: [ServiceId]
|
||||
let recipientIds: [SignalRecipient.RowId]
|
||||
let groupIds: [Data]
|
||||
}
|
||||
|
||||
@ -644,17 +524,15 @@ extension OWSProfileManager: ProfileManager {
|
||||
}
|
||||
|
||||
private func blocklistRotationTriggerIfNeeded(tx: DBReadTransaction) -> RotateProfileKeyTrigger? {
|
||||
let victimPhoneNumbers = self.blockedPhoneNumbersInWhitelist(tx: tx)
|
||||
let victimServiceIds = self.blockedServiceIdsInWhitelist(tx: tx)
|
||||
let victimRecipientIds = self.blockedRecipientIdsInWhitelist(tx: tx)
|
||||
let victimGroupIds = self.blockedGroupIDsInWhitelist(tx: tx)
|
||||
|
||||
if victimPhoneNumbers.isEmpty, victimServiceIds.isEmpty, victimGroupIds.isEmpty {
|
||||
if victimRecipientIds.isEmpty, victimGroupIds.isEmpty {
|
||||
// No need to rotate the profile key.
|
||||
return nil
|
||||
}
|
||||
return .blocklistChange(.init(
|
||||
phoneNumbers: victimPhoneNumbers,
|
||||
serviceIds: victimServiceIds,
|
||||
return .blocklistChange(RotateProfileKeyTrigger.BlocklistChange(
|
||||
recipientIds: victimRecipientIds,
|
||||
groupIds: victimGroupIds,
|
||||
))
|
||||
}
|
||||
@ -776,14 +654,15 @@ extension OWSProfileManager: ProfileManager {
|
||||
// It's absolutely essential that these values are persisted in the same transaction
|
||||
// in which we persist our new profile key, since storing them is what marks the
|
||||
// profile key rotation as "complete" (removing newly blocked users from the whitelist).
|
||||
self.whitelistedPhoneNumbersStore.removeValues(
|
||||
forKeys: trigger.phoneNumbers,
|
||||
transaction: tx,
|
||||
)
|
||||
self.whitelistedServiceIdsStore.removeValues(
|
||||
forKeys: trigger.serviceIds.map { $0.serviceIdUppercaseString },
|
||||
transaction: tx,
|
||||
)
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
trigger.recipientIds.forEach { recipientId in
|
||||
let recipient = recipientStore.fetchRecipient(rowId: recipientId, tx: tx)
|
||||
guard var recipient else {
|
||||
return
|
||||
}
|
||||
recipient.status = .unspecified
|
||||
recipientStore.updateRecipient(recipient, transaction: tx)
|
||||
}
|
||||
self.whitelistedGroupsStore.removeValues(
|
||||
forKeys: trigger.groupIds.map { self.groupKey(groupId: $0) },
|
||||
transaction: tx,
|
||||
@ -801,22 +680,12 @@ extension OWSProfileManager: ProfileManager {
|
||||
return true
|
||||
}
|
||||
|
||||
private func blockedPhoneNumbersInWhitelist(tx: DBReadTransaction) -> [String] {
|
||||
let allWhitelistedNumbers = whitelistedPhoneNumbersStore.allKeys(transaction: tx)
|
||||
private func blockedRecipientIdsInWhitelist(tx: DBReadTransaction) -> [SignalRecipient.RowId] {
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
|
||||
return allWhitelistedNumbers.filter { candidate in
|
||||
let address = SignalServiceAddress.legacyAddress(serviceId: nil, phoneNumber: candidate)
|
||||
return SSKEnvironment.shared.blockingManagerRef.isAddressBlocked(address, transaction: tx)
|
||||
}
|
||||
}
|
||||
|
||||
private func blockedServiceIdsInWhitelist(tx: DBReadTransaction) -> [ServiceId] {
|
||||
let allWhitelistedServiceIds = whitelistedServiceIdsStore.allKeys(transaction: tx).compactMap {
|
||||
try? ServiceId.parseFrom(serviceIdString: $0)
|
||||
}
|
||||
|
||||
return allWhitelistedServiceIds.filter { candidate in
|
||||
return SSKEnvironment.shared.blockingManagerRef.isAddressBlocked(SignalServiceAddress(candidate), transaction: tx)
|
||||
return blockingManager.blockedRecipientIds(tx: tx).filter {
|
||||
return recipientStore.fetchRecipient(rowId: $0, tx: tx)!.status == .whitelisted
|
||||
}
|
||||
}
|
||||
|
||||
@ -839,57 +708,6 @@ extension OWSProfileManager: ProfileManager {
|
||||
}
|
||||
}
|
||||
|
||||
func swift_normalizeRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBWriteTransaction) {
|
||||
Self.swift_normalizeRecipientInProfileWhitelist(
|
||||
recipient,
|
||||
serviceIdStore: whitelistedServiceIdsStore,
|
||||
phoneNumberStore: whitelistedPhoneNumbersStore,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
|
||||
public static func swift_normalizeRecipientInProfileWhitelist(
|
||||
_ recipient: SignalRecipient,
|
||||
serviceIdStore: KeyValueStore,
|
||||
phoneNumberStore: KeyValueStore,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
// First, we figure out which identifiers are whitelisted.
|
||||
let orderedIdentifiers: [(store: KeyValueStore, key: String, isInWhitelist: Bool)] = [
|
||||
(serviceIdStore, recipient.aci?.serviceIdUppercaseString),
|
||||
(phoneNumberStore, recipient.phoneNumber?.stringValue),
|
||||
(serviceIdStore, recipient.pni?.serviceIdUppercaseString),
|
||||
].compactMap { store, key -> (KeyValueStore, String, Bool)? in
|
||||
guard let key else { return nil }
|
||||
return (store, key, store.hasValue(key, transaction: tx))
|
||||
}
|
||||
|
||||
guard let preferredIdentifier = orderedIdentifiers.first else {
|
||||
return
|
||||
}
|
||||
|
||||
// If any identifier is in the whitelist, make sure the preferred
|
||||
// identifier is in the whitelist.
|
||||
let isAnyInWhitelist = orderedIdentifiers.contains(where: { $0.isInWhitelist })
|
||||
if isAnyInWhitelist {
|
||||
if !preferredIdentifier.isInWhitelist {
|
||||
preferredIdentifier.store.setBool(true, key: preferredIdentifier.key, transaction: tx)
|
||||
}
|
||||
} else {
|
||||
if preferredIdentifier.isInWhitelist {
|
||||
preferredIdentifier.store.removeValue(forKey: preferredIdentifier.key, transaction: tx)
|
||||
}
|
||||
}
|
||||
|
||||
// Always remove all the other identifiers from the whitelist. If the user
|
||||
// should be in the whitelist, we add the preferred identifier above.
|
||||
for remainingIdentifier in orderedIdentifiers.dropFirst() {
|
||||
if remainingIdentifier.isInWhitelist {
|
||||
remainingIdentifier.store.removeValue(forKey: remainingIdentifier.key, transaction: tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class func updateStorageServiceIfNecessary() {
|
||||
guard
|
||||
CurrentAppContext().isMainApp,
|
||||
|
||||
@ -14,14 +14,10 @@ public protocol ProfileManagerProtocol {
|
||||
/// Fetch the locally-cached profile for an address.
|
||||
func userProfile(for address: SignalServiceAddress, tx: DBReadTransaction) -> OWSUserProfile?
|
||||
|
||||
func isUser(inProfileWhitelist address: SignalServiceAddress, transaction: DBReadTransaction) -> Bool
|
||||
func normalizeRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBWriteTransaction)
|
||||
func isThread(inProfileWhitelist thread: TSThread, transaction: DBReadTransaction) -> Bool
|
||||
func addThread(toProfileWhitelist thread: TSThread, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
func addUser(toProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
func addUsers(toProfileWhitelist addresses: [SignalServiceAddress], userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
func removeUser(fromProfileWhitelist address: SignalServiceAddress)
|
||||
func removeUser(fromProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
func addRecipientToProfileWhitelist(_ recipient: inout SignalRecipient, userProfileWriter: UserProfileWriter, tx: DBWriteTransaction)
|
||||
func removeRecipientFromProfileWhitelist(_ recipient: inout SignalRecipient, userProfileWriter: UserProfileWriter, tx: DBWriteTransaction)
|
||||
func isRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBReadTransaction) -> Bool
|
||||
|
||||
func isGroupId(inProfileWhitelist groupId: Data, transaction: DBReadTransaction) -> Bool
|
||||
func addGroupId(toProfileWhitelist groupId: Data, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
func removeGroupId(fromProfileWhitelist groupId: Data, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction)
|
||||
@ -41,3 +37,24 @@ public protocol ProfileManagerProtocol {
|
||||
/// force a rotation independently of whitelist state.
|
||||
func forceRotateLocalProfileKeyForGroupDeparture(with transaction: DBWriteTransaction)
|
||||
}
|
||||
|
||||
extension ProfileManagerProtocol {
|
||||
public func isUser(inProfileWhitelist address: SignalServiceAddress, transaction: DBReadTransaction) -> Bool {
|
||||
owsAssertDebug(address.isValid)
|
||||
let recipientStore = DependenciesBridge.shared.recipientDatabaseTable
|
||||
guard let recipient = recipientStore.fetchRecipient(address: address, tx: transaction) else {
|
||||
return false
|
||||
}
|
||||
return isRecipientInProfileWhitelist(recipient, tx: transaction)
|
||||
}
|
||||
|
||||
public func isThread(inProfileWhitelist thread: TSThread, transaction: DBReadTransaction) -> Bool {
|
||||
if thread.isGroupThread, let groupThread = thread as? TSGroupThread {
|
||||
return isGroupId(inProfileWhitelist: groupThread.groupModel.groupId, transaction: transaction)
|
||||
} else if !thread.isGroupThread, let contactThread = thread as? TSContactThread {
|
||||
return isUser(inProfileWhitelist: contactThread.contactAddress, transaction: transaction)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,6 +316,7 @@ public class GRDBSchemaMigrator {
|
||||
case dropTSAttachment
|
||||
case deprecateStoredShouldStartExpireTimer
|
||||
case addSession
|
||||
case addRecipientStatus
|
||||
|
||||
// NOTE: Every time we add a migration id, consider
|
||||
// incrementing grdbSchemaVersionLatest.
|
||||
@ -439,7 +440,7 @@ public class GRDBSchemaMigrator {
|
||||
}
|
||||
|
||||
public static let grdbSchemaVersionDefault: UInt = 0
|
||||
public static let grdbSchemaVersionLatest: UInt = 138
|
||||
public static let grdbSchemaVersionLatest: UInt = 139
|
||||
|
||||
private class DatabaseMigratorWrapper {
|
||||
var migrator = DatabaseMigrator()
|
||||
@ -4985,6 +4986,12 @@ public class GRDBSchemaMigrator {
|
||||
return .success(())
|
||||
}
|
||||
|
||||
migrator.registerMigration(.addRecipientStatus) { tx in
|
||||
try addRecipientStatus(tx: tx)
|
||||
try migrateRecipientWhitelist(tx: tx)
|
||||
return .success(())
|
||||
}
|
||||
|
||||
// MARK: - Schema Migration Insertion Point
|
||||
}
|
||||
|
||||
@ -6285,12 +6292,12 @@ public class GRDBSchemaMigrator {
|
||||
var outdatedRecipientIds = Set<Int64>()
|
||||
|
||||
for blockedAciString in blockedAciStrings {
|
||||
let recipientId = try fetchOrCreateRecipientV1(aciString: blockedAciString, tx: tx)
|
||||
let recipientId = try fetchOrCreateRecipient(.V1, aciString: blockedAciString, tx: tx)
|
||||
blockedRecipientIds.insert(recipientId)
|
||||
}
|
||||
|
||||
for blockedPhoneNumber in blockedPhoneNumbers {
|
||||
let recipientId = try fetchOrCreateRecipientV1(phoneNumber: blockedPhoneNumber, tx: tx)
|
||||
let recipientId = try fetchOrCreateRecipient(.V1, phoneNumber: blockedPhoneNumber, tx: tx)
|
||||
if blockedRecipientIds.contains(recipientId) {
|
||||
// They're already blocked by their ACI.
|
||||
continue
|
||||
@ -6386,56 +6393,73 @@ public class GRDBSchemaMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
private static func fetchOrCreateRecipientV1(aciString: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
/// The representation of recipients has changed over time. Old migrations
|
||||
/// need to continue writing in the old format; new migrations need to write
|
||||
/// in the new format; when adding a migraton, you may need to define a new
|
||||
/// version if the latest version isn't compatible with the current schema.
|
||||
private enum RecipientVersion {
|
||||
case V1
|
||||
/// See migrateRecipientDeviceIds.
|
||||
case V2
|
||||
}
|
||||
|
||||
private static func fetchOrCreateRecipient(_ version: RecipientVersion, aciString: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
let db = tx.database
|
||||
let existingRecipientId = try Int64.fetchOne(db, sql: "SELECT id FROM model_SignalRecipient WHERE recipientUUID IS ?", arguments: [aciString])
|
||||
if let existingRecipientId {
|
||||
return existingRecipientId
|
||||
}
|
||||
return try createRecipientV1(aciString: aciString, phoneNumber: nil, pniString: nil, tx: tx)
|
||||
return try createRecipient(version, aciString: aciString, phoneNumber: nil, pniString: nil, tx: tx)
|
||||
}
|
||||
|
||||
private static func fetchOrCreateRecipientV1(phoneNumber: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
private static func fetchOrCreateRecipient(_ version: RecipientVersion, phoneNumber: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
let db = tx.database
|
||||
let existingRecipientId = try Int64.fetchOne(db, sql: "SELECT id FROM model_SignalRecipient WHERE recipientPhoneNumber IS ?", arguments: [phoneNumber])
|
||||
if let existingRecipientId {
|
||||
return existingRecipientId
|
||||
}
|
||||
return try createRecipientV1(aciString: nil, phoneNumber: phoneNumber, pniString: nil, tx: tx)
|
||||
return try createRecipient(version, aciString: nil, phoneNumber: phoneNumber, pniString: nil, tx: tx)
|
||||
}
|
||||
|
||||
private static func fetchOrCreateRecipientV1(pniString: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
private static func fetchOrCreateRecipient(_ version: RecipientVersion, pniString: String, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
let db = tx.database
|
||||
let existingRecipientId = try Int64.fetchOne(db, sql: "SELECT id FROM model_SignalRecipient WHERE pni IS ?", arguments: [pniString])
|
||||
if let existingRecipientId {
|
||||
return existingRecipientId
|
||||
}
|
||||
return try createRecipientV1(aciString: nil, phoneNumber: nil, pniString: pniString, tx: tx)
|
||||
return try createRecipient(version, aciString: nil, phoneNumber: nil, pniString: pniString, tx: tx)
|
||||
}
|
||||
|
||||
private static func fetchOrCreateRecipientV1(address: FrozenSignalServiceAddress, tx: DBWriteTransaction) throws -> SignalRecipient.RowId? {
|
||||
private static func fetchOrCreateRecipient(_ version: RecipientVersion, address: FrozenSignalServiceAddress, tx: DBWriteTransaction) throws -> SignalRecipient.RowId? {
|
||||
if let aci = address.serviceId as? Aci {
|
||||
let aciString = aci.serviceIdUppercaseString
|
||||
return try fetchOrCreateRecipientV1(aciString: aciString, tx: tx)
|
||||
return try fetchOrCreateRecipient(version, aciString: aciString, tx: tx)
|
||||
}
|
||||
if let phoneNumber = address.phoneNumber {
|
||||
return try fetchOrCreateRecipientV1(phoneNumber: phoneNumber, tx: tx)
|
||||
return try fetchOrCreateRecipient(version, phoneNumber: phoneNumber, tx: tx)
|
||||
}
|
||||
if let pni = address.serviceId as? Pni {
|
||||
let pniString = pni.serviceIdUppercaseString
|
||||
return try fetchOrCreateRecipientV1(pniString: pniString, tx: tx)
|
||||
return try fetchOrCreateRecipient(version, pniString: pniString, tx: tx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func createRecipientV1(aciString: String?, phoneNumber: String?, pniString: String?, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
private static func createRecipient(_ version: RecipientVersion, aciString: String?, phoneNumber: String?, pniString: String?, tx: DBWriteTransaction) throws -> SignalRecipient.RowId {
|
||||
let emptyDevices: Data
|
||||
switch version {
|
||||
case .V1:
|
||||
emptyDevices = try NSKeyedArchiver.archivedData(withRootObject: NSOrderedSet(array: [] as [NSNumber]), requiringSecureCoding: true)
|
||||
case .V2:
|
||||
emptyDevices = Data()
|
||||
}
|
||||
try tx.database.execute(
|
||||
sql: """
|
||||
INSERT INTO "model_SignalRecipient" ("recordType", "uniqueId", "devices", "recipientPhoneNumber", "recipientUUID", "pni") VALUES (31, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
arguments: [
|
||||
UUID().uuidString,
|
||||
NSKeyedArchiver.archivedData(withRootObject: NSOrderedSet(array: [] as [NSNumber]), requiringSecureCoding: true),
|
||||
emptyDevices,
|
||||
phoneNumber,
|
||||
aciString,
|
||||
pniString,
|
||||
@ -6992,7 +7016,7 @@ public class GRDBSchemaMigrator {
|
||||
continue
|
||||
}
|
||||
for address in addresses {
|
||||
guard let recipientId = try fetchOrCreateRecipientV1(address: address, tx: tx) else {
|
||||
guard let recipientId = try fetchOrCreateRecipient(.V1, address: address, tx: tx) else {
|
||||
owsFailDebug("Couldn't include empty story recipient address")
|
||||
continue
|
||||
}
|
||||
@ -7405,6 +7429,62 @@ public class GRDBSchemaMigrator {
|
||||
try tx.database.execute(sql: "DELETE FROM keyvalue WHERE collection = ?", arguments: [collection])
|
||||
}
|
||||
}
|
||||
|
||||
static func addRecipientStatus(tx: DBWriteTransaction) throws {
|
||||
try tx.database.alter(table: "model_SignalRecipient") {
|
||||
$0.add(column: "status", .integer).notNull().defaults(to: 0)
|
||||
}
|
||||
// For fetching whitelisted recipients.
|
||||
try tx.database.create(index: "Recipient_Status", on: "model_SignalRecipient", columns: ["status"])
|
||||
}
|
||||
|
||||
static func migrateRecipientWhitelist(tx: DBWriteTransaction) throws {
|
||||
let serviceIdCollection = "kOWSProfileManager_UserUUIDWhitelistCollection"
|
||||
let phoneNumberCollection = "kOWSProfileManager_UserWhitelistCollection"
|
||||
|
||||
var recipientIds = Set<SignalRecipient.RowId>()
|
||||
|
||||
let serviceIdStrings = try String.fetchAll(
|
||||
tx.database,
|
||||
sql: "SELECT key FROM keyvalue WHERE collection = ?",
|
||||
arguments: [serviceIdCollection],
|
||||
)
|
||||
for serviceIdString in serviceIdStrings {
|
||||
guard let serviceId = try? ServiceId.parseFrom(serviceIdString: serviceIdString) else {
|
||||
continue
|
||||
}
|
||||
switch serviceId.concreteType {
|
||||
case .aci(let aci):
|
||||
recipientIds.insert(try fetchOrCreateRecipient(.V2, aciString: aci.serviceIdUppercaseString, tx: tx))
|
||||
case .pni(let pni):
|
||||
recipientIds.insert(try fetchOrCreateRecipient(.V2, pniString: pni.serviceIdUppercaseString, tx: tx))
|
||||
}
|
||||
}
|
||||
|
||||
let phoneNumberStrings = try String.fetchAll(
|
||||
tx.database,
|
||||
sql: "SELECT key FROM keyvalue WHERE collection = ?",
|
||||
arguments: [phoneNumberCollection],
|
||||
)
|
||||
for phoneNumberString in phoneNumberStrings {
|
||||
guard let phoneNumber = E164(phoneNumberString) else {
|
||||
continue
|
||||
}
|
||||
recipientIds.insert(try fetchOrCreateRecipient(.V2, phoneNumber: phoneNumber.stringValue, tx: tx))
|
||||
}
|
||||
|
||||
if !recipientIds.isEmpty {
|
||||
Logger.info("adding \(recipientIds.count) recipients to the whitelist (from \(serviceIdStrings.count) service ids & \(phoneNumberStrings.count) phone numbers)")
|
||||
}
|
||||
|
||||
for recipientId in recipientIds {
|
||||
try tx.database.execute(sql: "UPDATE model_SignalRecipient SET status = 1 WHERE id = ?", arguments: [recipientId])
|
||||
}
|
||||
|
||||
for collection in [serviceIdCollection, phoneNumberCollection] {
|
||||
try tx.database.execute(sql: "DELETE FROM keyvalue WHERE collection = ?", arguments: [collection])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@ -233,7 +233,7 @@ public class ThreadFinder {
|
||||
tx: transaction,
|
||||
),
|
||||
let hiddenRecipient = recipientHidingManager.fetchHiddenRecipient(
|
||||
signalRecipient: signalRecipient,
|
||||
recipientId: signalRecipient.id,
|
||||
tx: transaction,
|
||||
)
|
||||
{
|
||||
|
||||
@ -296,7 +296,7 @@ class StorageServiceContactRecordUpdater: StorageServiceRecordUpdater {
|
||||
// This could be an ACI or a PNI address.
|
||||
let anyAddress = SignalServiceAddress(contact.serviceIds.aciOrElsePni)
|
||||
|
||||
let isInWhitelist = profileManager.isUser(inProfileWhitelist: anyAddress, transaction: tx)
|
||||
let isInWhitelist = profileManager.isRecipientInProfileWhitelist(recipient, tx: tx)
|
||||
builder.setWhitelisted(isInWhitelist)
|
||||
|
||||
builder.setBlocked(blockingManager.isAddressBlocked(anyAddress, transaction: tx))
|
||||
@ -491,7 +491,7 @@ class StorageServiceContactRecordUpdater: StorageServiceRecordUpdater {
|
||||
|
||||
return _mergeRecord(
|
||||
record,
|
||||
recipient: recipient,
|
||||
recipient: &recipient,
|
||||
serviceIds: serviceIds,
|
||||
// If we merge and don't end up with what's in Storage Service, then it
|
||||
// probably means that a linked device is wrong or we've hit a race
|
||||
@ -509,7 +509,7 @@ class StorageServiceContactRecordUpdater: StorageServiceRecordUpdater {
|
||||
|
||||
private func _mergeRecord(
|
||||
_ record: StorageServiceProtoContactRecord,
|
||||
recipient: SignalRecipient,
|
||||
recipient: inout SignalRecipient,
|
||||
serviceIds: AtLeastOneServiceId,
|
||||
needsUpdate: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
@ -521,7 +521,7 @@ class StorageServiceContactRecordUpdater: StorageServiceRecordUpdater {
|
||||
// Gather some local contact state to do comparisons against.
|
||||
let localIsBlocked = blockingManager.isAddressBlocked(anyAddress, transaction: tx)
|
||||
let localIsHidden = recipientHidingManager.isHiddenAddress(anyAddress, tx: tx)
|
||||
let localIsWhitelisted = profileManager.isUser(inProfileWhitelist: anyAddress, transaction: tx)
|
||||
let localIsWhitelisted = profileManager.isRecipientInProfileWhitelist(recipient, tx: tx)
|
||||
let localUserProfile = profileManager.userProfile(for: anyAddress, tx: tx)
|
||||
|
||||
// If our local profile key record differs from what's on the service, use the service's value.
|
||||
@ -622,17 +622,9 @@ class StorageServiceContactRecordUpdater: StorageServiceRecordUpdater {
|
||||
// If our local whitelisted state differs from the service state, use the service's value.
|
||||
if record.whitelisted != localIsWhitelisted {
|
||||
if record.whitelisted {
|
||||
profileManager.addUser(
|
||||
toProfileWhitelist: anyAddress,
|
||||
userProfileWriter: .storageService,
|
||||
transaction: tx,
|
||||
)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .storageService, tx: tx)
|
||||
} else {
|
||||
profileManager.removeUser(
|
||||
fromProfileWhitelist: anyAddress,
|
||||
userProfileWriter: .storageService,
|
||||
transaction: tx,
|
||||
)
|
||||
profileManager.removeRecipientFromProfileWhitelist(&recipient, userProfileWriter: .storageService, tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,8 +14,8 @@ class OWSFakeProfileManager {
|
||||
var localProfile: OWSUserProfile?
|
||||
var localProfileKey: Aes256Key?
|
||||
|
||||
private var recipientWhitelist: Set<SignalServiceAddress> = []
|
||||
private var threadWhitelist: Set<String> = []
|
||||
private var recipientWhitelist: Set<SignalRecipient.RowId> = []
|
||||
private var groupIdWhitelist: Set<Data> = []
|
||||
}
|
||||
|
||||
extension OWSFakeProfileManager: ProfileManagerProtocol {
|
||||
@ -28,51 +28,30 @@ extension OWSFakeProfileManager: ProfileManagerProtocol {
|
||||
return fakeUserProfiles![addressParam]
|
||||
}
|
||||
|
||||
func normalizeRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBWriteTransaction) {
|
||||
func addRecipientToProfileWhitelist(_ recipient: inout SignalRecipient, userProfileWriter: UserProfileWriter, tx: DBWriteTransaction) {
|
||||
recipient.status = .whitelisted
|
||||
recipientWhitelist.insert(recipient.id)
|
||||
}
|
||||
|
||||
func isUser(inProfileWhitelist address: SignalServiceAddress, transaction: DBReadTransaction) -> Bool {
|
||||
recipientWhitelist.contains(address)
|
||||
func removeRecipientFromProfileWhitelist(_ recipient: inout SignalRecipient, userProfileWriter: UserProfileWriter, tx: DBWriteTransaction) {
|
||||
recipient.status = .unspecified
|
||||
recipientWhitelist.remove(recipient.id)
|
||||
}
|
||||
|
||||
func isThread(inProfileWhitelist thread: TSThread, transaction: DBReadTransaction) -> Bool {
|
||||
threadWhitelist.contains(thread.uniqueId)
|
||||
}
|
||||
|
||||
func addUser(toProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
recipientWhitelist.insert(address)
|
||||
}
|
||||
|
||||
func addUsers(toProfileWhitelist addresses: [SignalServiceAddress], userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
recipientWhitelist.formUnion(addresses)
|
||||
}
|
||||
|
||||
func removeUser(fromProfileWhitelist address: SignalServiceAddress) {
|
||||
recipientWhitelist.remove(address)
|
||||
}
|
||||
|
||||
func removeUser(fromProfileWhitelist address: SignalServiceAddress, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
recipientWhitelist.remove(address)
|
||||
func isRecipientInProfileWhitelist(_ recipient: SignalRecipient, tx: DBReadTransaction) -> Bool {
|
||||
return recipientWhitelist.contains(recipient.id)
|
||||
}
|
||||
|
||||
func isGroupId(inProfileWhitelist groupId: Data, transaction: DBReadTransaction) -> Bool {
|
||||
threadWhitelist.contains(groupId.hexadecimalString)
|
||||
return groupIdWhitelist.contains(groupId)
|
||||
}
|
||||
|
||||
func addGroupId(toProfileWhitelist groupId: Data, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
threadWhitelist.insert(groupId.hexadecimalString)
|
||||
groupIdWhitelist.insert(groupId)
|
||||
}
|
||||
|
||||
func removeGroupId(fromProfileWhitelist groupId: Data, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
threadWhitelist.remove(groupId.hexadecimalString)
|
||||
}
|
||||
|
||||
func addThread(toProfileWhitelist thread: TSThread, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
if thread.isGroupThread, let groupThread = thread as? TSGroupThread {
|
||||
addGroupId(toProfileWhitelist: groupThread.groupModel.groupId, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
} else if !thread.isGroupThread, let contactThread = thread as? TSContactThread {
|
||||
addUser(toProfileWhitelist: contactThread.contactAddress, userProfileWriter: userProfileWriter, transaction: transaction)
|
||||
}
|
||||
groupIdWhitelist.remove(groupId)
|
||||
}
|
||||
|
||||
func setLocalProfileKey(_ key: Aes256Key, userProfileWriter: UserProfileWriter, transaction: DBWriteTransaction) {
|
||||
|
||||
@ -282,9 +282,13 @@ extension ThreadUtil {
|
||||
public class func addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimerWithSneakyTransaction(
|
||||
_ thread: TSThread,
|
||||
) -> Bool {
|
||||
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
let threadAsContactThread = thread as? TSContactThread
|
||||
|
||||
let (shouldSetUniversalTimer, shouldAddToProfileWhitelist) = SSKEnvironment.shared.databaseStorageRef.read { tx -> (Bool, Bool) in
|
||||
let (shouldSetUniversalTimer, shouldAddToProfileWhitelist) = databaseStorage.read { tx -> (Bool, Bool) in
|
||||
let universalTimer: Bool = {
|
||||
guard let threadAsContactThread else { return false }
|
||||
return Self.shouldSetUniversalTimer(contactThread: threadAsContactThread, tx: tx)
|
||||
@ -294,15 +298,28 @@ extension ThreadUtil {
|
||||
return (universalTimer, profileWhitelist)
|
||||
}
|
||||
if shouldSetUniversalTimer, let threadAsContactThread {
|
||||
SSKEnvironment.shared.databaseStorageRef.write { tx in setUniversalTimer(contactThread: threadAsContactThread, tx: tx) }
|
||||
databaseStorage.write { tx in setUniversalTimer(contactThread: threadAsContactThread, tx: tx) }
|
||||
}
|
||||
if shouldAddToProfileWhitelist {
|
||||
SSKEnvironment.shared.databaseStorageRef.write { tx in
|
||||
SSKEnvironment.shared.profileManagerRef.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
databaseStorage.write { tx in
|
||||
switch thread {
|
||||
case let thread as TSGroupThread:
|
||||
profileManager.addGroupId(
|
||||
toProfileWhitelist: thread.groupModel.groupId,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
case let thread as TSContactThread:
|
||||
if var recipient = recipientFetcher.fetchOrCreate(address: thread.contactAddress, tx: tx) {
|
||||
profileManager.addRecipientToProfileWhitelist(
|
||||
&recipient,
|
||||
userProfileWriter: .localUser,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
default:
|
||||
owsFailDebug("can't whitelist \(type(of: thread))")
|
||||
}
|
||||
}
|
||||
}
|
||||
return shouldAddToProfileWhitelist
|
||||
@ -314,6 +331,9 @@ extension ThreadUtil {
|
||||
setDefaultTimerIfNecessary: Bool,
|
||||
tx: DBWriteTransaction,
|
||||
) -> Bool {
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
if
|
||||
setDefaultTimerIfNecessary,
|
||||
let contactThread = thread as? TSContactThread,
|
||||
@ -323,11 +343,24 @@ extension ThreadUtil {
|
||||
}
|
||||
let shouldAddToProfileWhitelist = shouldAddThreadToProfileWhitelist(thread, tx: tx)
|
||||
if shouldAddToProfileWhitelist {
|
||||
SSKEnvironment.shared.profileManagerRef.addThread(
|
||||
toProfileWhitelist: thread,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
switch thread {
|
||||
case let thread as TSGroupThread:
|
||||
profileManager.addGroupId(
|
||||
toProfileWhitelist: thread.groupModel.groupId,
|
||||
userProfileWriter: .localUser,
|
||||
transaction: tx,
|
||||
)
|
||||
case let thread as TSContactThread:
|
||||
if var recipient = recipientFetcher.fetchOrCreate(address: thread.contactAddress, tx: tx) {
|
||||
profileManager.addRecipientToProfileWhitelist(
|
||||
&recipient,
|
||||
userProfileWriter: .localUser,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
default:
|
||||
owsFailDebug("can't whitelist \(type(of: thread))")
|
||||
}
|
||||
}
|
||||
return shouldAddToProfileWhitelist
|
||||
}
|
||||
|
||||
@ -9,67 +9,6 @@ import XCTest
|
||||
@testable import SignalServiceKit
|
||||
|
||||
class ProfileManagerTest: XCTestCase {
|
||||
func testNormalizeRecipientInProfileWhitelist() {
|
||||
let aci = Aci.constantForTesting("00000000-0000-4000-8000-000000000aaa")
|
||||
let phoneNumber = E164("+16505550100")!
|
||||
let pni = Pni.constantForTesting("PNI:00000000-0000-4000-8000-000000000bbb")
|
||||
|
||||
let serviceIdStore = KeyValueStore(collection: "serviceId")
|
||||
let phoneNumberStore = KeyValueStore(collection: "phoneNumber")
|
||||
|
||||
let db = InMemoryDB()
|
||||
|
||||
func normalizeRecipient(aci: Aci?, phoneNumber: E164?, pni: Pni?, tx: DBWriteTransaction) {
|
||||
try! SignalRecipient.deleteAll(tx.database)
|
||||
let recipient = try! SignalRecipient.insertRecord(
|
||||
aci: aci,
|
||||
phoneNumber: phoneNumber,
|
||||
pni: pni,
|
||||
tx: tx,
|
||||
)
|
||||
OWSProfileManager.swift_normalizeRecipientInProfileWhitelist(
|
||||
recipient,
|
||||
serviceIdStore: serviceIdStore,
|
||||
phoneNumberStore: phoneNumberStore,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
|
||||
// Don't add any values unless one is already present.
|
||||
db.write { tx in
|
||||
normalizeRecipient(aci: aci, phoneNumber: phoneNumber, pni: pni, tx: tx)
|
||||
XCTAssertFalse(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
||||
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
||||
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
||||
}
|
||||
|
||||
// Move the PNI identifier to the phone number.
|
||||
db.write { tx in
|
||||
serviceIdStore.setBool(true, key: pni.serviceIdUppercaseString, transaction: tx)
|
||||
normalizeRecipient(aci: nil, phoneNumber: phoneNumber, pni: pni, tx: tx)
|
||||
XCTAssertFalse(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
||||
XCTAssertTrue(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
||||
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
||||
}
|
||||
|
||||
// Clear lower priority identifiers when multiple are present.
|
||||
db.write { tx in
|
||||
serviceIdStore.setBool(true, key: aci.serviceIdUppercaseString, transaction: tx)
|
||||
normalizeRecipient(aci: aci, phoneNumber: phoneNumber, pni: pni, tx: tx)
|
||||
XCTAssertTrue(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
||||
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
||||
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
||||
}
|
||||
|
||||
// Keep the highest priority identifier if it's already present.
|
||||
db.write { tx in
|
||||
normalizeRecipient(aci: aci, phoneNumber: phoneNumber, pni: pni, tx: tx)
|
||||
XCTAssertTrue(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
||||
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
||||
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
||||
}
|
||||
}
|
||||
|
||||
func testEncodeDecodeProfileChanges() throws {
|
||||
let testCases: [(PendingProfileUpdate, String)] = [
|
||||
(
|
||||
|
||||
@ -1284,4 +1284,60 @@ class GRDBSchemaMigratorTest: XCTestCase {
|
||||
XCTAssertEqual(sessions[5]["serializedRecord"] as Data?, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateWhitelist() throws {
|
||||
let databaseQueue = DatabaseQueue()
|
||||
try databaseQueue.write { db in
|
||||
try db.execute(sql: """
|
||||
CREATE TABLE "model_SignalRecipient" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
"recordType" INTEGER NOT NULL,
|
||||
"uniqueId" TEXT NOT NULL,
|
||||
"recipientPhoneNumber" TEXT UNIQUE,
|
||||
"recipientUUID" TEXT UNIQUE,
|
||||
"pni" TEXT UNIQUE,
|
||||
"devices" BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "keyvalue" (
|
||||
"collection" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"value" BLOB NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO "model_SignalRecipient" (
|
||||
"id", "recordType", "uniqueId", "recipientPhoneNumber", "recipientUUID", "pni", "devices"
|
||||
) VALUES
|
||||
(1, 0, '', '+17635550100', '00000000-0000-4000-A000-000000000000', NULL, X''),
|
||||
(2, 0, '', '+17635550101', NULL, NULL, X''),
|
||||
(3, 0, '', NULL, NULL, 'PNI:00000000-0000-4000-A000-000000000FFF', X'');
|
||||
|
||||
INSERT INTO "keyvalue" (
|
||||
"collection", "key", "value"
|
||||
) VALUES
|
||||
('kOWSProfileManager_UserWhitelistCollection', '+17635550100', X''),
|
||||
('kOWSProfileManager_UserWhitelistCollection', '+17635550102', X''),
|
||||
('kOWSProfileManager_UserUUIDWhitelistCollection', '00000000-0000-4000-A000-000000000000', X''),
|
||||
('kOWSProfileManager_UserUUIDWhitelistCollection', '00000000-0000-4000-A000-000000000001', X''),
|
||||
('kOWSProfileManager_UserUUIDWhitelistCollection', 'PNI:00000000-0000-4000-A000-000000000FFF', X''),
|
||||
('kOWSProfileManager_UserUUIDWhitelistCollection', 'PNI:00000000-0000-4000-A000-000000000FFE', X'');
|
||||
""")
|
||||
|
||||
do {
|
||||
let tx = DBWriteTransaction(database: db)
|
||||
defer { tx.finalizeTransaction() }
|
||||
try GRDBSchemaMigrator.addRecipientStatus(tx: tx)
|
||||
try GRDBSchemaMigrator.migrateRecipientWhitelist(tx: tx)
|
||||
}
|
||||
|
||||
let recipients = try Row.fetchAll(db, sql: "SELECT * FROM model_SignalRecipient ORDER BY id")
|
||||
XCTAssertEqual(recipients.count, 6)
|
||||
XCTAssertEqual(recipients[0]["status"] as Int64, 1)
|
||||
XCTAssertEqual(recipients[1]["status"] as Int64, 0)
|
||||
XCTAssertEqual(recipients[2]["status"] as Int64, 1)
|
||||
XCTAssertEqual(recipients[3]["status"] as Int64, 1)
|
||||
XCTAssertEqual(recipients[4]["status"] as Int64, 1)
|
||||
XCTAssertEqual(recipients[5]["status"] as Int64, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,13 +34,11 @@ class StoryManagerTest: SSKBaseTest {
|
||||
profileManager.fakeUserProfiles = [
|
||||
SignalServiceAddress(author): OWSUserProfile(address: .otherUser(SignalServiceAddress(author))),
|
||||
]
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try StoryManager.processIncomingStoryMessage(
|
||||
storyMessage,
|
||||
@ -92,12 +90,12 @@ class StoryManagerTest: SSKBaseTest {
|
||||
let privateStoryMessage = try Self.makePrivateStory()
|
||||
let groupStoryMessage = try Self.makeGroupStory()
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
SSKEnvironment.shared.blockingManagerRef.addBlockedAddress(
|
||||
SignalServiceAddress(author),
|
||||
@ -140,12 +138,12 @@ class StoryManagerTest: SSKBaseTest {
|
||||
let groupMasterKey = try GroupMasterKey(contents: storyMessage.group!.masterKey!)
|
||||
let groupId = try GroupSecretParams.deriveFromMasterKey(groupMasterKey: groupMasterKey).getPublicParams().getGroupIdentifier().serialize()
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
TSGroupThread.forUnitTest(
|
||||
masterKey: groupMasterKey,
|
||||
@ -182,12 +180,12 @@ class StoryManagerTest: SSKBaseTest {
|
||||
|
||||
let secretParams = try GroupV2ContextInfo.deriveFrom(masterKeyData: storyMessage.group!.masterKey!).groupSecretParams
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try Self.makeGroupThread(secretParams: secretParams, transaction: $0)
|
||||
|
||||
@ -217,12 +215,12 @@ class StoryManagerTest: SSKBaseTest {
|
||||
|
||||
let secretParams = try GroupV2ContextInfo.deriveFrom(masterKeyData: storyMessage.group!.masterKey!).groupSecretParams
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try Self.makeGroupThread(secretParams: secretParams, announcementOnly: true, members: [author], transaction: $0)
|
||||
|
||||
@ -256,13 +254,11 @@ class StoryManagerTest: SSKBaseTest {
|
||||
profileManager.fakeUserProfiles = [
|
||||
SignalServiceAddress(author): OWSUserProfile(address: .otherUser(SignalServiceAddress(author))),
|
||||
]
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try Self.makeGroupThread(secretParams: secretParams, announcementOnly: true, admins: [author], transaction: $0)
|
||||
|
||||
@ -296,13 +292,11 @@ class StoryManagerTest: SSKBaseTest {
|
||||
profileManager.fakeUserProfiles = [
|
||||
SignalServiceAddress(author): OWSUserProfile(address: .otherUser(SignalServiceAddress(author))),
|
||||
]
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try Self.makeGroupThread(secretParams: secretParams, members: [author], transaction: $0)
|
||||
|
||||
@ -334,13 +328,11 @@ class StoryManagerTest: SSKBaseTest {
|
||||
profileManager.fakeUserProfiles = [
|
||||
SignalServiceAddress(author): OWSUserProfile(address: .otherUser(SignalServiceAddress(author))),
|
||||
]
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try StoryManager.processIncomingStoryMessage(
|
||||
storyMessage,
|
||||
@ -365,6 +357,9 @@ class StoryManagerTest: SSKBaseTest {
|
||||
let author = Aci.randomForTesting()
|
||||
let storyMessage = try Self.makePrivateStory()
|
||||
|
||||
let profileManager = SSKEnvironment.shared.profileManagerRef
|
||||
let recipientFetcher = DependenciesBridge.shared.recipientFetcher
|
||||
|
||||
try write {
|
||||
try StoryMessage.create(
|
||||
withIncomingStoryMessage: storyMessage,
|
||||
@ -374,11 +369,8 @@ class StoryManagerTest: SSKBaseTest {
|
||||
transaction: $0,
|
||||
)
|
||||
|
||||
SSKEnvironment.shared.profileManagerRef.addUser(
|
||||
toProfileWhitelist: SignalServiceAddress(author),
|
||||
userProfileWriter: .localUser,
|
||||
transaction: $0,
|
||||
)
|
||||
var recipient = recipientFetcher.fetchOrCreate(serviceId: author, tx: $0)
|
||||
profileManager.addRecipientToProfileWhitelist(&recipient, userProfileWriter: .localUser, tx: $0)
|
||||
|
||||
try StoryManager.processIncomingStoryMessage(
|
||||
storyMessage,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user