TM-SGNL-iOS/SignalServiceKit/Security/OWSRecipientIdentity.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

156 lines
6.1 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import GRDB
public import LibSignalClient
public enum VerificationState: Equatable {
case verified
case noLongerVerified
case implicit(isAcknowledged: Bool)
init(_ verificationState: OWSVerificationState) {
switch verificationState {
case .default:
self = .implicit(isAcknowledged: false)
case .defaultAcknowledged:
self = .implicit(isAcknowledged: true)
case .verified:
self = .verified
case .noLongerVerified:
self = .noLongerVerified
}
}
var rawValue: OWSVerificationState {
switch self {
case .implicit(isAcknowledged: false):
return .default
case .implicit(isAcknowledged: true):
return .defaultAcknowledged
case .verified:
return .verified
case .noLongerVerified:
return .noLongerVerified
}
}
}
extension OWSRecipientIdentity {
public var identityKeyObject: IdentityKey {
get throws {
try IdentityKey(publicKey: PublicKey(keyData: identityKey))
}
}
public class func groupContainsUnverifiedMember(
_ threadUniqueId: String,
transaction: SDSAnyReadTransaction
) -> Bool {
let identityKeys = groupMemberIdentityKeys(
in: threadUniqueId,
matching: .verified,
negated: true,
limit: 1,
tx: transaction
)
return !identityKeys.isEmpty
}
public class func noLongerVerifiedIdentityKeys(
in threadUniqueId: String,
tx: SDSAnyReadTransaction
) -> [SignalServiceAddress: Data] {
return groupMemberIdentityKeys(in: threadUniqueId, matching: .noLongerVerified, negated: false, tx: tx)
}
private class func sqlQueryToFetchIdentityKeys(
matching verificationState: OWSVerificationState,
negated: Bool,
limit: Int
) -> String {
let limitClause: String
if limit < Int.max {
limitClause = "LIMIT \(limit)"
} else {
limitClause = ""
}
let comparisonOperator = negated ? "!=" : "="
let stateClause = "\(recipientIdentityColumnFullyQualified: .verificationState) \(comparisonOperator) \(verificationState.rawValue)"
let groupMember_phoneNumber = TSGroupMember.columnName(.phoneNumber, fullyQualified: true)
let groupMember_groupThreadID = TSGroupMember.columnName(.groupThreadId, fullyQualified: true)
let groupMember_serviceIdString = TSGroupMember.columnName(.serviceId, fullyQualified: true)
let recipient_id = "\(signalRecipientColumnFullyQualified: .id)"
let recipient_phoneNumber = "\(signalRecipientColumnFullyQualified: .phoneNumber)"
let recipient_aciString = "\(signalRecipientColumnFullyQualified: .aciString)"
let recipient_pniString = "\(signalRecipientColumnFullyQualified: .pni)"
let recipient_uniqueID = "\(signalRecipientColumnFullyQualified: .uniqueId)"
let recipientIdentity_uniqueID = "\(recipientIdentityColumnFullyQualified: .uniqueId)"
let recipientIdentity_identityKey = "\(recipientIdentityColumnFullyQualified: .identityKey)"
let exceptClause = "\(recipient_aciString) != ?"
let sql =
"""
SELECT \(recipient_aciString), \(recipient_phoneNumber), \(recipient_pniString), \(recipientIdentity_identityKey)
FROM
\(SignalRecipient.databaseTableName),
\(RecipientIdentityRecord.databaseTableName),
\(TSGroupMember.databaseTableName)
WHERE
\(recipient_uniqueID) = \(recipientIdentity_uniqueID)
AND \(groupMember_groupThreadID) = ?
AND (
\(groupMember_serviceIdString) = \(recipient_aciString)
OR \(groupMember_serviceIdString) = \(recipient_pniString)
OR \(groupMember_phoneNumber) = \(recipient_phoneNumber)
)
AND \(exceptClause)
AND \(stateClause)
ORDER BY \(recipient_id)
\(limitClause)
"""
return sql
}
private class func groupMemberIdentityKeys(
in threadUniqueId: String,
matching verificationState: OWSVerificationState,
negated: Bool,
limit: Int = Int.max,
tx: SDSAnyReadTransaction
) -> [SignalServiceAddress: Data] {
switch tx.readTransaction {
case .grdbRead(let grdbTransaction):
// There should always be a recipient UUID, but just in case there isn't provide a fake value that won't
// affect the results of the query.
let localRecipientAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx.asV2Read)?.aci
let sql = sqlQueryToFetchIdentityKeys(matching: verificationState, negated: negated, limit: limit)
do {
let args = [threadUniqueId, localRecipientAci?.serviceIdUppercaseString ?? "fake"]
let cursor = try Row.fetchCursor(grdbTransaction.database, sql: sql, arguments: StatementArguments(args))
var result = [SignalServiceAddress: Data]()
while let row = try cursor.next() {
let normalizedAddress = NormalizedDatabaseRecordAddress(
aci: (row[0] as String?).flatMap { try? Aci.parseFrom(serviceIdString: $0) },
phoneNumber: row[1],
pni: (row[2] as String?).flatMap { try? Pni.parseFrom(serviceIdString: $0) }
)
let address = SignalServiceAddress(
serviceId: normalizedAddress?.serviceId,
phoneNumber: normalizedAddress?.phoneNumber
)
result[address] = row[3]
}
return result
} catch {
owsFailDebug("error: \(error)")
return [:]
}
}
}
}