Merge pull request #187 from loki-project/closed-groups

Closed Group Bug Fixes
pull/188/head
Niels Andriesse 5 years ago committed by GitHub
commit 0fb2e33b61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,7 +14,6 @@ public final class LokiAPI : NSObject {
internal static let workQueue = DispatchQueue(label: "LokiAPI.workQueue", qos: .userInitiated) internal static let workQueue = DispatchQueue(label: "LokiAPI.workQueue", qos: .userInitiated)
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
internal static var userHexEncodedPublicKey: String { getUserHexEncodedPublicKey() }
// MARK: Settings // MARK: Settings
private static let maxRetryCount: UInt = 4 private static let maxRetryCount: UInt = 4
@ -63,16 +62,16 @@ public final class LokiAPI : NSObject {
internal static func getRawMessages(from target: LokiAPITarget, usingLongPolling useLongPolling: Bool) -> RawResponsePromise { internal static func getRawMessages(from target: LokiAPITarget, usingLongPolling useLongPolling: Bool) -> RawResponsePromise {
let lastHashValue = getLastMessageHashValue(for: target) ?? "" let lastHashValue = getLastMessageHashValue(for: target) ?? ""
let parameters = [ "pubKey" : userHexEncodedPublicKey, "lastHash" : lastHashValue ] let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "lastHash" : lastHashValue ]
let headers: [String:String]? = useLongPolling ? [ "X-Loki-Long-Poll" : "true" ] : nil let headers: [String:String]? = useLongPolling ? [ "X-Loki-Long-Poll" : "true" ] : nil
let timeout: TimeInterval? = useLongPolling ? longPollingTimeout : nil let timeout: TimeInterval? = useLongPolling ? longPollingTimeout : nil
return invoke(.getMessages, on: target, associatedWith: userHexEncodedPublicKey, parameters: parameters, headers: headers, timeout: timeout) return invoke(.getMessages, on: target, associatedWith: getUserHexEncodedPublicKey(), parameters: parameters, headers: headers, timeout: timeout)
} }
// MARK: Public API // MARK: Public API
public static func getMessages() -> Promise<Set<MessageListPromise>> { public static func getMessages() -> Promise<Set<MessageListPromise>> {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: workQueue) { return attempt(maxRetryCount: maxRetryCount, recoveringOn: workQueue) {
getTargetSnodes(for: userHexEncodedPublicKey).mapValues { targetSnode in getTargetSnodes(for: getUserHexEncodedPublicKey()).mapValues { targetSnode in
getRawMessages(from: targetSnode, usingLongPolling: false).map { parseRawMessagesResponse($0, from: targetSnode) } getRawMessages(from: targetSnode, usingLongPolling: false).map { parseRawMessagesResponse($0, from: targetSnode) }
}.map { Set($0) } }.map { Set($0) }
} }
@ -157,7 +156,7 @@ public final class LokiAPI : NSObject {
setLastMessageHashValue(for: target, hashValue: hashValue, expirationDate: UInt64(expirationDate)) setLastMessageHashValue(for: target, hashValue: hashValue, expirationDate: UInt64(expirationDate))
// FIXME: Move this out of here // FIXME: Move this out of here
if UserDefaults.standard[.isUsingFullAPNs] { if UserDefaults.standard[.isUsingFullAPNs] {
LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: hashValue, expiration: expirationDate, hexEncodedPublicKey: userHexEncodedPublicKey) LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: hashValue, expiration: expirationDate, hexEncodedPublicKey: getUserHexEncodedPublicKey())
} }
} else if (!rawMessages.isEmpty) { } else if (!rawMessages.isEmpty) {
print("[Loki] Failed to update last message hash value from: \(rawMessages).") print("[Loki] Failed to update last message hash value from: \(rawMessages).")

@ -6,7 +6,6 @@ public class LokiDotNetAPI : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
internal static var userKeyPair: ECKeyPair { OWSIdentityManager.shared().identityKeyPair()! } internal static var userKeyPair: ECKeyPair { OWSIdentityManager.shared().identityKeyPair()! }
internal static var userHexEncodedPublicKey: String { userKeyPair.hexEncodedPublicKey }
// MARK: Settings // MARK: Settings
private static let attachmentType = "network.loki" private static let attachmentType = "network.loki"
@ -60,7 +59,7 @@ public class LokiDotNetAPI : NSObject {
// MARK: Private API // MARK: Private API
private static func requestNewAuthToken(for server: String) -> Promise<String> { private static func requestNewAuthToken(for server: String) -> Promise<String> {
print("[Loki] Requesting auth token for server: \(server).") print("[Loki] Requesting auth token for server: \(server).")
let queryParameters = "pubKey=\(userHexEncodedPublicKey)" let queryParameters = "pubKey=\(getUserHexEncodedPublicKey())"
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")! let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
let request = TSRequest(url: url) let request = TSRequest(url: url)
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: LokiAPI.workQueue).map(on: LokiAPI.workQueue) { rawResponse in return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: LokiAPI.workQueue).map(on: LokiAPI.workQueue) { rawResponse in
@ -85,7 +84,7 @@ public class LokiDotNetAPI : NSObject {
private static func submitAuthToken(_ token: String, for server: String) -> Promise<String> { private static func submitAuthToken(_ token: String, for server: String) -> Promise<String> {
print("[Loki] Submitting auth token for server: \(server).") print("[Loki] Submitting auth token for server: \(server).")
let url = URL(string: "\(server)/loki/v1/submit_challenge")! let url = URL(string: "\(server)/loki/v1/submit_challenge")!
let parameters = [ "pubKey" : userHexEncodedPublicKey, "token" : token ] let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "token" : token ]
let request = TSRequest(url: url, method: "POST", parameters: parameters) let request = TSRequest(url: url, method: "POST", parameters: parameters)
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map(on: DispatchQueue.global()) { _ in token } return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map(on: DispatchQueue.global()) { _ in token }
} }

@ -84,14 +84,18 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
return deviceLink return deviceLink
} }
}) })
}.map(on: DispatchQueue.global()) { deviceLinks in }.then(on: DispatchQueue.global()) { deviceLinks -> Promise<Set<DeviceLink>> in
let (promise, seal) = Promise<Set<DeviceLink>>.pending()
// Dispatch async on the main queue to avoid nested write transactions // Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async { DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in storage.dbReadWriteConnection.readWrite { transaction in
storage.setDeviceLinks(deviceLinks, in: transaction) storage.setDeviceLinks(deviceLinks, in: transaction)
} }
// We have to wait for the device links to be stored because a lot of our logic relies
// on them being in the database
seal.fulfill(deviceLinks)
} }
return deviceLinks return promise
} }
} }
} }
@ -99,7 +103,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
public static func setDeviceLinks(_ deviceLinks: Set<DeviceLink>) -> Promise<Void> { public static func setDeviceLinks(_ deviceLinks: Set<DeviceLink>) -> Promise<Void> {
print("[Loki] Updating device links.") print("[Loki] Updating device links.")
return getAuthToken(for: server).then { token -> Promise<Void> in return getAuthToken(for: server).then { token -> Promise<Void> in
let isMaster = deviceLinks.contains { $0.master.hexEncodedPublicKey == userHexEncodedPublicKey } let isMaster = deviceLinks.contains { $0.master.hexEncodedPublicKey == getUserHexEncodedPublicKey() }
let deviceLinksAsJSON = deviceLinks.map { $0.toJSON() } let deviceLinksAsJSON = deviceLinks.map { $0.toJSON() }
let value = !deviceLinksAsJSON.isEmpty ? [ "isPrimary" : isMaster ? 1 : 0, "authorisations" : deviceLinksAsJSON ] : nil let value = !deviceLinksAsJSON.isEmpty ? [ "isPrimary" : isMaster ? 1 : 0, "authorisations" : deviceLinksAsJSON ] : nil
let annotation: JSON = [ "type" : deviceLinkType, "value" : value ] let annotation: JSON = [ "type" : deviceLinkType, "value" : value ]
@ -120,7 +124,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> { public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = [] var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction)
} }
deviceLinks.insert(deviceLink) deviceLinks.insert(deviceLink)
return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in
@ -140,7 +144,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> { public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = [] var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction)
} }
deviceLinks.remove(deviceLink) deviceLinks.remove(deviceLink)
return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in

@ -33,7 +33,7 @@ extension OnionRequestAPI {
/// Encrypts `payload` for `snode` and returns the result. Use this to build the core of an onion request. /// Encrypts `payload` for `snode` and returns the result. Use this to build the core of an onion request.
internal static func encrypt(_ payload: JSON, forTargetSnode snode: LokiAPITarget) -> Promise<EncryptionResult> { internal static func encrypt(_ payload: JSON, forTargetSnode snode: LokiAPITarget) -> Promise<EncryptionResult> {
let (promise, seal) = Promise<EncryptionResult>.pending() let (promise, seal) = Promise<EncryptionResult>.pending()
DispatchQueue.global().async { DispatchQueue.global(qos: .userInitiated).async {
do { do {
guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) } guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) }
let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: []) let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [])
@ -53,7 +53,7 @@ extension OnionRequestAPI {
/// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request.
internal static func encryptHop(from lhs: LokiAPITarget, to rhs: LokiAPITarget, using previousEncryptionResult: EncryptionResult) -> Promise<EncryptionResult> { internal static func encryptHop(from lhs: LokiAPITarget, to rhs: LokiAPITarget, using previousEncryptionResult: EncryptionResult) -> Promise<EncryptionResult> {
let (promise, seal) = Promise<EncryptionResult>.pending() let (promise, seal) = Promise<EncryptionResult>.pending()
DispatchQueue.global().async { DispatchQueue.global(qos: .userInitiated).async {
let parameters: JSON = [ let parameters: JSON = [
"ciphertext" : previousEncryptionResult.ciphertext.base64EncodedString(), "ciphertext" : previousEncryptionResult.ciphertext.base64EncodedString(),
"ephemeral_key" : previousEncryptionResult.ephemeralPublicKey.toHexString(), "ephemeral_key" : previousEncryptionResult.ephemeralPublicKey.toHexString(),

@ -19,7 +19,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
// MARK: Convenience // MARK: Convenience
private static var userDisplayName: String { private static var userDisplayName: String {
return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: userHexEncodedPublicKey) ?? "Anonymous" return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: getUserHexEncodedPublicKey()) ?? "Anonymous"
} }
// MARK: Database // MARK: Database
@ -187,7 +187,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
throw LokiDotNetAPIError.parsingFailed throw LokiDotNetAPIError.parsingFailed
} }
let timestamp = UInt64(date.timeIntervalSince1970) * 1000 let timestamp = UInt64(date.timeIntervalSince1970) * 1000
return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature) return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: getUserHexEncodedPublicKey(), displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature)
} }
}.recover { error -> Promise<LokiPublicChatMessage> in }.recover { error -> Promise<LokiPublicChatMessage> in
if let error = error as? NetworkManagerError, error.statusCode == 401 { if let error = error as? NetworkManagerError, error.statusCode == 401 {

@ -139,6 +139,19 @@ public final class SessionManagementProtocol : NSObject {
return promise return promise
} }
@objc(repairSessionIfNeededForMessage:to:)
public static func repairSessionIfNeeded(for message: TSOutgoingMessage, to hexEncodedPublicKey: String) {
guard (message.thread as? TSGroupThread)?.groupModel.groupType == .closedGroup else { return }
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let sessionRequestMessage = SessionRequestMessage(thread: thread)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction)
}
}
}
// MARK: - Receiving // MARK: - Receiving
@objc(handleDecryptionError:forHexEncodedPublicKey:using:) @objc(handleDecryptionError:forHexEncodedPublicKey:using:)
public static func handleDecryptionError(_ rawValue: Int32, for hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { public static func handleDecryptionError(_ rawValue: Int32, for hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
@ -166,6 +179,27 @@ public final class SessionManagementProtocol : NSObject {
return dataMessage.flags & UInt32(sessionRequestFlag.rawValue) != 0 return dataMessage.flags & UInt32(sessionRequestFlag.rawValue) != 0
} }
@objc(handleSessionRequestMessage:wrappedIn:using:)
public static func handleSessionRequestMessage(_ dataMessage: SSKProtoDataMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) {
// The envelope source is set during UD decryption
// FIXME: Device links should probably update here if they're out of date
let hexEncodedPublicKey = envelope.source!
var validHEPKs: Set<String> = []
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
guard let group = object as? TSGroupThread, group.groupModel.groupType == .closedGroup,
group.shouldThreadBeVisible else { return }
let closedGroupMembersIncludingLinkedDevices = group.groupModel.groupMemberIds.flatMap {
LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: $0, in: transaction)
}
validHEPKs.formUnion(closedGroupMembersIncludingLinkedDevices)
}
guard validHEPKs.contains(hexEncodedPublicKey) else { return }
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let ephemeralMessage = EphemeralMessage(in: thread)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction)
}
// TODO: This needs an explanation of when we expect pre key bundles to be attached // TODO: This needs an explanation of when we expect pre key bundles to be attached
@objc(handlePreKeyBundleMessageIfNeeded:wrappedIn:using:) @objc(handlePreKeyBundleMessageIfNeeded:wrappedIn:using:)
public static func handlePreKeyBundleMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { public static func handlePreKeyBundleMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) {
@ -181,7 +215,7 @@ public final class SessionManagementProtocol : NSObject {
// If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session. // If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session.
// The envelope type is set during UD decryption. // The envelope type is set during UD decryption.
// TODO: Should this ignore session requests? // TODO: Should this ignore session requests?
if envelope.type == .friendRequest, if envelope.type == .friendRequest, // TODO: Should this check that the envelope doesn't have the session request flag set?
let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction), // TODO: Should this be getOrCreate? let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction), // TODO: Should this be getOrCreate?
thread.isContactFriend { thread.isContactFriend {
receiving_startSessionReset(in: thread, using: transaction) receiving_startSessionReset(in: thread, using: transaction)

@ -94,8 +94,7 @@ public final class SyncMessagesProtocol : NSObject {
public static func isValidSyncMessage(_ envelope: SSKProtoEnvelope, in transaction: YapDatabaseReadTransaction) -> Bool { public static func isValidSyncMessage(_ envelope: SSKProtoEnvelope, in transaction: YapDatabaseReadTransaction) -> Bool {
// The envelope source is set during UD decryption // The envelope source is set during UD decryption
let hexEncodedPublicKey = envelope.source! let hexEncodedPublicKey = envelope.source!
let linkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) return LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction)
return linkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey)
} }
// TODO: We should probably look at why sync messages are being duplicated rather than doing this // TODO: We should probably look at why sync messages are being duplicated rather than doing this

@ -468,7 +468,10 @@ NS_ASSUME_NONNULL_BEGIN
} else if (contentProto.dataMessage) { } else if (contentProto.dataMessage) {
// Loki: Don't process session request messages any further // Loki: Don't process session request messages any further
if ([LKSessionManagementProtocol isSessionRequestMessage:contentProto.dataMessage]) { return; } if ([LKSessionManagementProtocol isSessionRequestMessage:contentProto.dataMessage]) {
[LKSessionManagementProtocol handleSessionRequestMessage:contentProto.dataMessage wrappedIn:envelope using:transaction];
return;
}
// Loki: Don't process session restore messages any further // Loki: Don't process session restore messages any further
if ([LKSessionManagementProtocol isSessionRestoreMessage:contentProto.dataMessage]) { return; } if ([LKSessionManagementProtocol isSessionRestoreMessage:contentProto.dataMessage]) { return; }
@ -1271,7 +1274,7 @@ NS_ASSUME_NONNULL_BEGIN
}).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) { }).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) {
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}) retainUntilComplete]; }) retainUntilComplete];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 8 * NSEC_PER_SEC)); dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
} }
// FIXME: ======== // FIXME: ========

@ -1705,6 +1705,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
*/ */
if (!bundle) { if (!bundle) {
[LKSessionManagementProtocol repairSessionIfNeededForMessage:messageSend.message to:recipientID];
NSString *missingPrekeyBundleException = @"missingPrekeyBundleException"; NSString *missingPrekeyBundleException = @"missingPrekeyBundleException";
OWSRaiseException(missingPrekeyBundleException, @"Missing pre key bundle for: %@.", recipientID); OWSRaiseException(missingPrekeyBundleException, @"Missing pre key bundle for: %@.", recipientID);
} else { } else {

Loading…
Cancel
Save