session-ios/SessionMessagingKit/Database/Models/BlindedIdLookup.swift

186 lines
7.2 KiB
Swift
Raw Normal View History

Merge branch 'feature/session-id-blinding-part-2' into feature/database-refactor # Conflicts: # Podfile # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Closed Groups/NewClosedGroupVC.swift # Session/Conversations/Context Menu/ContextMenuVC+Action.swift # Session/Conversations/Context Menu/ContextMenuVC.swift # Session/Conversations/ConversationMessageMapping.swift # Session/Conversations/ConversationSearch.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewItem.h # Session/Conversations/ConversationViewItem.m # Session/Conversations/ConversationViewModel.m # Session/Conversations/Input View/InputView.swift # Session/Conversations/Input View/MentionSelectionView.swift # Session/Conversations/LongTextViewController.swift # Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift # Session/Conversations/Message Cells/MessageCell.swift # Session/Conversations/Message Cells/VisibleMessageCell.swift # Session/Conversations/Settings/OWSConversationSettingsViewController.m # Session/Conversations/Views & Modals/ConversationTitleView.swift # Session/Conversations/Views & Modals/DownloadAttachmentModal.swift # Session/Conversations/Views & Modals/JoinOpenGroupModal.swift # Session/Conversations/Views & Modals/LinkPreviewModal.swift # Session/Conversations/Views & Modals/MessagesTableView.swift # Session/Conversations/Views & Modals/URLModal.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/Message Requests/MessageRequestsViewController.swift # Session/Media Viewing & Editing/MediaDetailViewController.m # Session/Media Viewing & Editing/MediaPageViewController.swift # Session/Meta/AppDelegate.m # Session/Meta/AppDelegate.swift # Session/Meta/AppEnvironment.swift # Session/Meta/Signal-Bridging-Header.h # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Notifications/AppNotifications.swift # Session/Open Groups/JoinOpenGroupVC.swift # Session/Settings/NukeDataModal.swift # Session/Settings/SeedModal.swift # Session/Settings/SettingsVC.swift # Session/Settings/ShareLogsModal.swift # Session/Shared/ConversationCell.swift # Session/Shared/UserSelectionVC.swift # Session/Utilities/BackgroundPoller.swift # Session/Utilities/MentionUtilities.swift # Session/Utilities/MockDataGenerator.swift # SessionMessagingKit/Database/OWSPrimaryStorage.m # SessionMessagingKit/Database/SSKPreferences.swift # SessionMessagingKit/Database/Storage+Contacts.swift # SessionMessagingKit/Database/Storage+Jobs.swift # SessionMessagingKit/Database/Storage+Messaging.swift # SessionMessagingKit/Database/Storage+OpenGroups.swift # SessionMessagingKit/Database/TSDatabaseView.m # SessionMessagingKit/File Server/FileServerAPIV2.swift # SessionMessagingKit/Jobs/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/AttachmentUploadJob.swift # SessionMessagingKit/Jobs/JobQueue.swift # SessionMessagingKit/Jobs/MessageReceiveJob.swift # SessionMessagingKit/Jobs/MessageSendJob.swift # SessionMessagingKit/Jobs/NotifyPNServerJob.swift # SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift # SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift # SessionMessagingKit/Messages/Message+Destination.swift # SessionMessagingKit/Messages/Signal/TSIncomingMessage.h # SessionMessagingKit/Messages/Signal/TSIncomingMessage.m # SessionMessagingKit/Messages/Signal/TSInfoMessage.h # SessionMessagingKit/Messages/Signal/TSInfoMessage.m # SessionMessagingKit/Messages/Signal/TSInteraction.h # SessionMessagingKit/Messages/Signal/TSInteraction.m # SessionMessagingKit/Messages/Signal/TSMessage.h # SessionMessagingKit/Messages/Signal/TSMessage.m # SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift # SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift # SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift # SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift # SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.h # SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Storage.swift # SessionMessagingKit/Threads/Notification+Thread.swift # SessionMessagingKit/Threads/TSContactThread.h # SessionMessagingKit/Threads/TSContactThread.m # SessionMessagingKit/Threads/TSGroupModel.h # SessionMessagingKit/Threads/TSGroupModel.m # SessionMessagingKit/Threads/TSGroupThread.m # SessionMessagingKit/Utilities/General.swift # SessionNotificationServiceExtension/NSENotificationPresenter.swift # SessionNotificationServiceExtension/NotificationServiceExtension.swift # SessionSnodeKit/OnionRequestAPI+Encryption.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionSnodeKit/SnodeAPI.swift # SessionSnodeKit/SnodeMessage.swift # SessionSnodeKit/Storage+SnodeAPI.swift # SessionSnodeKit/Storage.swift # SessionUtilitiesKit/General/Array+Utilities.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/General/SNUserDefaults.swift # SessionUtilitiesKit/General/Set+Utilities.swift # SessionUtilitiesKit/Meta/SessionUtilitiesKit.h # SessionUtilitiesKit/Utilities/Optional+Utilities.swift # SessionUtilitiesKit/Utilities/Sodium+Conversion.swift # SignalUtilitiesKit/Configuration.swift # SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift # SignalUtilitiesKit/Messaging/FullTextSearcher.swift # SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift # SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift # SignalUtilitiesKit/To Do/OWSProfileManager.m # SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift # SignalUtilitiesKit/Utilities/UIView+OWS.swift
2022-06-08 06:29:51 +02:00
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
/// This lookup is created when the user interacts with a blinded id
public struct BlindedIdLookup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "blindedIdLookup" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case blindedId
case sessionId
case openGroupServer
case openGroupPublicKey
}
public var id: String { blindedId }
/// The blinded id for the user on this open group server
public let blindedId: String
/// The standard sessionId which can be used to generate this blindedId on this open group server
///
/// **Note:** This value will be null if the user owning the blinded id hasnt accepted the message request
public let sessionId: String?
/// The server for the Open Group server this blinded id belongs to
public let openGroupServer: String
/// The public key for the Open Group server this blinded id belongs to
public let openGroupPublicKey: String
// MARK: - Initialization
public init(
blindedId: String,
sessionId: String? = nil,
openGroupServer: String,
openGroupPublicKey: String
) {
self.blindedId = blindedId
self.sessionId = sessionId
self.openGroupServer = openGroupServer
self.openGroupPublicKey = openGroupPublicKey
}
}
// MARK: - Mutation
public extension BlindedIdLookup {
func with(sessionId: String) -> BlindedIdLookup {
return BlindedIdLookup(
blindedId: self.blindedId,
sessionId: sessionId,
openGroupServer: self.openGroupServer,
openGroupPublicKey: self.openGroupPublicKey
)
}
}
// MARK: - GRDB Interactions
public extension BlindedIdLookup {
/// Unfortunately the whole point of id-blinding is to make it hard to reverse-engineer a standard sessionId, as a result in order
/// to see if there is an unblinded contact for this blindedId we can only really generate blinded ids for each contact and check
/// if any match
///
/// If we can't find a match this method will still store a lookup, just with no standard sessionId value (this gives us a method to
/// link back to the open group the blindedId originated from)
static func fetchOrCreate(
_ db: Database,
blindedId: String,
sessionId: String? = nil,
openGroupServer: String,
openGroupPublicKey: String,
isCheckingForOutbox: Bool,
using dependencies: Dependencies = Dependencies()
) throws -> BlindedIdLookup {
var lookup: BlindedIdLookup = (try? BlindedIdLookup
.fetchOne(db, id: blindedId))
.defaulting(
to: BlindedIdLookup(
blindedId: blindedId,
openGroupServer: openGroupServer.lowercased(),
openGroupPublicKey: openGroupPublicKey
)
)
// If the lookup already has a resolved sessionId then just return it immediately
guard lookup.sessionId == nil else { return lookup }
// If we we given a sessionId then validate it is correct and if so save it
if
let sessionId: String = sessionId,
dependencies.crypto.verify(
.sessionId(
sessionId,
matchesBlindedId: blindedId,
serverPublicKey: openGroupPublicKey,
using: dependencies
)
)
{
lookup = try lookup
.with(sessionId: sessionId)
.saved(db)
return lookup
}
// We now need to try to match the blinded id to an existing contact, this can only be done by looping
// through all approved contacts and generating a blinded id for the provided open group for each to
// see if it matches the provided blindedId
let contactsThatApprovedMeCursor: RecordCursor<Contact> = try Contact
.filter(Contact.Columns.didApproveMe == true)
.fetchCursor(db)
while let contact: Contact = try contactsThatApprovedMeCursor.next() {
guard
dependencies.crypto.verify(
.sessionId(
contact.id,
matchesBlindedId: blindedId,
serverPublicKey: openGroupPublicKey,
using: dependencies
)
)
else { continue }
// We found a match so update the lookup and leave the loop
lookup = try lookup
.with(sessionId: contact.id)
.saved(db)
// There is an edge-case where the contact might not have their 'isApproved' flag set to true
// but if we have a `BlindedIdLookup` for them and are performing the lookup from the outbox
// then that means we sent them a message request and the 'isApproved' flag should be true
if isCheckingForOutbox && !contact.isApproved {
try Contact
.filter(id: contact.id)
.updateAllAndConfig(db, Contact.Columns.isApproved.set(to: true))
}
break
}
// Finish if we have a result
guard lookup.sessionId == nil else { return lookup }
// Lastly loop through existing id lookups (in case the user is looking at a different SOGS but once had
// a thread with this contact in a different SOGS and had cached the lookup) - we really should never hit
// this case since the contact approval status is sync'ed (the only situation I can think of is a config
// message hasn't been handled correctly?)
let blindedIdLookupCursor: RecordCursor<BlindedIdLookup> = try BlindedIdLookup
.filter(BlindedIdLookup.Columns.sessionId != nil)
.filter(BlindedIdLookup.Columns.openGroupServer != openGroupServer.lowercased())
.fetchCursor(db)
while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
guard
let sessionId: String = otherLookup.sessionId,
dependencies.crypto.verify(
.sessionId(
sessionId,
matchesBlindedId: blindedId,
serverPublicKey: openGroupPublicKey,
using: dependencies
)
)
else { continue }
// We found a match so update the lookup and leave the loop
lookup = try lookup
.with(sessionId: sessionId)
.saved(db)
break
}
// Want to save the lookup even if it doesn't have a sessionId so it can be used when handling
// MessageRequestResponse messages
return try lookup
.saved(db)
}
}