From 3d29480c29a86bb13c1ef4514a90760a3785a86a Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 10 Feb 2020 12:01:23 +1100 Subject: [PATCH] Don't include an auth token with encrypted file uploads Also refactor a bit --- .../ConversationView/Cells/OWSMessageCell.m | 2 +- .../ConversationView/ConversationViewItem.m | 2 +- .../src/Loki/API/LokiDotNetAPI.swift | 26 +++++++---- .../src/Loki/API/LokiStorageAPI.swift | 2 +- .../API/Public Chat/LokiPublicChatAPI.swift | 44 ++++++++++--------- 5 files changed, 44 insertions(+), 32 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 9f557822a..5d008a6c3 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -302,7 +302,7 @@ NS_ASSUME_NONNULL_BEGIN publicChat = [LKDatabaseUtilities getPublicChatForThreadID:self.viewItem.interaction.uniqueThreadId transaction: transaction]; }]; if (publicChat != nil) { - BOOL isModerator = [LKPublicChatAPI isUserModerator:incomingMessage.authorId forGroup:publicChat.channel onServer:publicChat.server]; + BOOL isModerator = [LKPublicChatAPI isUserModerator:incomingMessage.authorId forChannel:publicChat.channel onServer:publicChat.server]; UIImage *moderatorIcon = [UIImage imageNamed:@"Crown"]; self.moderatorIconImageView.image = moderatorIcon; self.moderatorIconImageView.hidden = !isModerator; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index 2a4387478..0dd09d203 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -1271,7 +1271,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if (interationType == OWSInteractionType_IncomingMessage) { // Only allow deletion on incoming messages if the user has moderation permission - return [LKPublicChatAPI isUserModerator:self.userHexEncodedPublicKey forGroup:publicChat.channel onServer:publicChat.server]; + return [LKPublicChatAPI isUserModerator:self.userHexEncodedPublicKey forChannel:publicChat.channel onServer:publicChat.server]; } // Only allow deletion on outgoing messages if the user was the sender (i.e. it was not sent from another linked device) diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift index 6c2530c9d..7ba93fcb6 100644 --- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift @@ -41,10 +41,11 @@ public class LokiDotNetAPI : NSObject { public static func uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> Promise { let isEncryptionRequired = (server == LokiStorageAPI.server) return Promise() { seal in - getAuthToken(for: server).done(on: DispatchQueue.global()) { token in + func proceed(with token: String? = nil) { + // Get the attachment let data: Data guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else { - print("[Loki] Couldn't read attachment data from disk.") + print("[Loki] Couldn't read attachment from disk.") return seal.reject(Error.generic) } // Encrypt the attachment if needed @@ -68,10 +69,12 @@ public class LokiDotNetAPI : NSObject { var request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in formData.appendPart(withFileData: data, name: "content", fileName: UUID().uuidString, mimeType: "application/binary") }, error: &error) - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + if let token = token { + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } if let error = error { print("[Loki] Couldn't upload attachment due to error: \(error).") - throw error + return seal.reject(error) } // Send the request func parseResponse(_ response: Any) { @@ -117,9 +120,16 @@ public class LokiDotNetAPI : NSObject { }) task.resume() } - }.catch(on: DispatchQueue.global()) { error in - print("[Loki] Couldn't upload attachment.") - seal.reject(error) + } + if server == LokiStorageAPI.server { + proceed() // Uploads to the Loki File Server shouldn't include any personally identifiable information so don't include an auth token + } else { + getAuthToken(for: server).done(on: DispatchQueue.global()) { token in + proceed(with: token) + }.catch(on: DispatchQueue.global()) { error in + print("[Loki] Couldn't upload attachment due to error: \(error).") + seal.reject(error) + } } } } @@ -148,7 +158,7 @@ public class LokiDotNetAPI : NSObject { throw Error.parsingFailed } // Discard the "05" prefix if needed - if (serverPublicKey.count == 33) { + if serverPublicKey.count == 33 { let hexEncodedServerPublicKey = serverPublicKey.toHexString() serverPublicKey = Data.data(fromHex: hexEncodedServerPublicKey.substring(from: 2))! } diff --git a/SignalServiceKit/src/Loki/API/LokiStorageAPI.swift b/SignalServiceKit/src/Loki/API/LokiStorageAPI.swift index 7efa46b65..85a429f91 100644 --- a/SignalServiceKit/src/Loki/API/LokiStorageAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiStorageAPI.swift @@ -168,7 +168,7 @@ public final class LokiStorageAPI : LokiDotNetAPI { }) task.resume() }.catch { error in - print("[Loki] Couldn't upload profile picture.") + print("[Loki] Couldn't upload profile picture due to error: \(error).") seal.reject(error) } } diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index 7a0b1f3b1..fc97ab67c 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -12,13 +12,10 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { // MARK: Public Chat private static let channelInfoType = "net.patter-app.settings" private static let attachmentType = "net.app.core.oembed" - public static let avatarType = "network.loki.messenger.avatar" + public static let profilePictureType = "network.loki.messenger.avatar" @objc public static let publicChatMessageType = "network.loki.messenger.publicChat" - @objc public static let defaultChats: [LokiPublicChat] = { - var result: [LokiPublicChat] = [] - return result - }() + @objc public static let defaultChats: [LokiPublicChat] = [] // Currently unused // MARK: Convenience private static var userDisplayName: String { @@ -70,6 +67,11 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } } + public static func clearCaches(for channel: UInt64, on server: String) { + removeLastMessageServerID(for: channel, on: server) + removeLastDeletionServerID(for: channel, on: server) + } + // MARK: Public API public static func getMessages(for channel: UInt64, on server: String) -> Promise<[LokiPublicChatMessage]> { var queryParameters = "include_annotations=1" @@ -97,23 +99,26 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } var avatar: LokiPublicChatMessage.Avatar? = nil let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "") - if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnotation = userAnnotations.first(where: { $0["type"] as? String == avatarType }), + if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnotation = userAnnotations.first(where: { $0["type"] as? String == profilePictureType }), let avatarValue = avatarAnnotation["value"] as? JSON, let profileKeyString = avatarValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = avatarValue["url"] as? String { avatar = LokiPublicChatMessage.Avatar(profileKey: profileKey, url: url) } let lastMessageServerID = getLastMessageServerID(for: channel, on: server) if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) } let quote: LokiPublicChatMessage.Quote? - if let quoteAsJSON = value["quote"] as? JSON, let quotedMessageTimestamp = quoteAsJSON["id"] as? UInt64, let quoteeHexEncodedPublicKey = quoteAsJSON["author"] as? String, let quotedMessageBody = quoteAsJSON["text"] as? String { + if let quoteAsJSON = value["quote"] as? JSON, let quotedMessageTimestamp = quoteAsJSON["id"] as? UInt64, let quoteeHexEncodedPublicKey = quoteAsJSON["author"] as? String, + let quotedMessageBody = quoteAsJSON["text"] as? String { let quotedMessageServerID = message["reply_to"] as? UInt64 - quote = LokiPublicChatMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteeHexEncodedPublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, quotedMessageServerID: quotedMessageServerID) + quote = LokiPublicChatMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteeHexEncodedPublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, + quotedMessageServerID: quotedMessageServerID) } else { quote = nil } let signature = LokiPublicChatMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion) let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType } let attachments: [LokiPublicChatMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in - guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = LokiPublicChatMessage.Attachment.Kind(rawValue: kindAsString), let serverID = value["id"] as? UInt64, let contentType = value["contentType"] as? String, let size = value["size"] as? UInt, let url = value["url"] as? String else { return nil } + guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = LokiPublicChatMessage.Attachment.Kind(rawValue: kindAsString), + let serverID = value["id"] as? UInt64, let contentType = value["contentType"] as? String, let size = value["size"] as? UInt, let url = value["url"] as? String else { return nil } let fileName = value["fileName"] as? String ?? UUID().description let width = value["width"] as? UInt ?? 0 let height = value["height"] as? UInt ?? 0 @@ -127,9 +132,11 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return nil } } - return LokiPublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle) + return LokiPublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, + width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle) } - let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature) + let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, + body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature) guard result.hasValidSignature() else { print("[Loki] Ignoring public chat message with invalid signature.") return nil @@ -260,7 +267,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { public static func getUserCount(for channel: UInt64, on server: String) -> Promise { return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in - let queryParameters = "count=2500" + let queryParameters = "count=200" let url = URL(string: "\(server)/channels/\(channel)/subscribers?\(queryParameters)")! let request = TSRequest(url: url) request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] @@ -306,7 +313,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } } - @objc (isUserModerator:forGroup:onServer:) + @objc(isUserModerator:forChannel:onServer:) public static func isUserModerator(_ hexEncodedPublicString: String, for channel: UInt64, on server: String) -> Bool { return moderators[server]?[channel]?.contains(hexEncodedPublicString) ?? false } @@ -322,13 +329,13 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { print("Couldn't update display name due to error: \(error).") throw error } - }.retryingIfNeeded(maxRetryCount: 3) + }.retryingIfNeeded(maxRetryCount: maxRetryCount) } public static func setProfilePictureURL(to url: String?, using profileKey: Data, on server: String) -> Promise { print("[Loki] Updating profile picture on server: \(server).") return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in - var annotation: JSON = [ "type" : avatarType ] + var annotation: JSON = [ "type" : profilePictureType ] if let url = url { annotation["value"] = [ "profileKey" : profileKey.base64EncodedString(), "url" : url ] } @@ -340,7 +347,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { print("[Loki] Couldn't update profile picture due to error: \(error).") throw error } - }.retryingIfNeeded(maxRetryCount: 3) + }.retryingIfNeeded(maxRetryCount: maxRetryCount) } public static func getInfo(for channel: UInt64, on server: String) -> Promise { @@ -366,11 +373,6 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request).map { _ in } } - @objc public static func clearCaches(for channel: UInt64, on server: String) { - removeLastMessageServerID(for: channel, on: server) - removeLastDeletionServerID(for: channel, on: server) - } - // MARK: Public API (Obj-C) @objc(getMessagesForGroup:onServer:) public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise {