mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Added the MessageRequestsViewController Added a 'Message Requests' button to the settings screen Added accept/reject buttons for message requests to the ConversationVC Added the ability to hide the message request item on the HomeVC (re-appears if you get a new message request) Added code to handle an edge-case where the message request approval state wouldn't be returned to the sender due to the recipient running an old version of the app Prevented contacts which aren't associated with an approved thread from appearing when creating a closed group Disabled notifications for threads which aren't approved Updated the app notification count to exclude unapproved messages Updated the app to ignore closed group creation messages if the group has no admins which are approved contacts Fixed up the keyboard avoidance behaviour in the ConversationVC Fixed a couple of minor interaction issues which affected some devices Fixed an issue where the database migrations would run on the 2nd launch when creating a new account (causing odd behaviours)
179 lines
9.2 KiB
Swift
179 lines
9.2 KiB
Swift
import SessionUtilitiesKit
|
|
|
|
public enum MessageReceiver {
|
|
private static var lastEncryptionKeyPairRequest: [String:Date] = [:]
|
|
|
|
public enum Error : LocalizedError {
|
|
case duplicateMessage
|
|
case invalidMessage
|
|
case unknownMessage
|
|
case unknownEnvelopeType
|
|
case noUserX25519KeyPair
|
|
case noUserED25519KeyPair
|
|
case invalidSignature
|
|
case noData
|
|
case senderBlocked
|
|
case noThread
|
|
case selfSend
|
|
case decryptionFailed
|
|
case invalidGroupPublicKey
|
|
case noGroupKeyPair
|
|
|
|
public var isRetryable: Bool {
|
|
switch self {
|
|
case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType,
|
|
.invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed: return false
|
|
default: return true
|
|
}
|
|
}
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .duplicateMessage: return "Duplicate message."
|
|
case .invalidMessage: return "Invalid message."
|
|
case .unknownMessage: return "Unknown message type."
|
|
case .unknownEnvelopeType: return "Unknown envelope type."
|
|
case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair."
|
|
case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair."
|
|
case .invalidSignature: return "Invalid message signature."
|
|
case .noData: return "Received an empty envelope."
|
|
case .senderBlocked: return "Received a message from a blocked user."
|
|
case .noThread: return "Couldn't find thread for message."
|
|
case .selfSend: return "Message addressed at self."
|
|
case .decryptionFailed: return "Decryption failed."
|
|
// Shared sender keys
|
|
case .invalidGroupPublicKey: return "Invalid group public key."
|
|
case .noGroupKeyPair: return "Missing group key pair."
|
|
}
|
|
}
|
|
}
|
|
|
|
public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) {
|
|
let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
|
|
let isOpenGroupMessage = (openGroupMessageServerID != nil)
|
|
// Parse the envelope
|
|
let envelope = try SNProtoEnvelope.parseData(data)
|
|
let storage = SNMessagingKitConfiguration.shared.storage
|
|
// Decrypt the contents
|
|
guard let ciphertext = envelope.content else { throw Error.noData }
|
|
var plaintext: Data!
|
|
var sender: String!
|
|
var groupPublicKey: String? = nil
|
|
if isOpenGroupMessage {
|
|
(plaintext, sender) = (envelope.content!, envelope.source!)
|
|
} else {
|
|
switch envelope.type {
|
|
case .sessionMessage:
|
|
guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { throw Error.noUserX25519KeyPair }
|
|
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
|
|
case .closedGroupMessage:
|
|
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey }
|
|
var encryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: hexEncodedGroupPublicKey)
|
|
guard !encryptionKeyPairs.isEmpty else { throw Error.noGroupKeyPair }
|
|
// Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than
|
|
// likely be the one we want) but try older ones in case that didn't work)
|
|
var encryptionKeyPair = encryptionKeyPairs.removeLast()
|
|
func decrypt() throws {
|
|
do {
|
|
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: encryptionKeyPair)
|
|
} catch {
|
|
if !encryptionKeyPairs.isEmpty {
|
|
encryptionKeyPair = encryptionKeyPairs.removeLast()
|
|
try decrypt()
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
groupPublicKey = envelope.source
|
|
try decrypt()
|
|
/*
|
|
do {
|
|
try decrypt()
|
|
} catch {
|
|
do {
|
|
let now = Date()
|
|
// Don't spam encryption key pair requests
|
|
let shouldRequestEncryptionKeyPair = given(lastEncryptionKeyPairRequest[groupPublicKey!]) { now.timeIntervalSince($0) > 30 } ?? true
|
|
if shouldRequestEncryptionKeyPair {
|
|
try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction)
|
|
lastEncryptionKeyPairRequest[groupPublicKey!] = now
|
|
}
|
|
}
|
|
throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one)
|
|
}
|
|
*/
|
|
default: throw Error.unknownEnvelopeType
|
|
}
|
|
}
|
|
// Don't process the envelope any further if the sender is blocked
|
|
guard !isBlocked(sender) else { throw Error.senderBlocked }
|
|
// Parse the proto
|
|
let proto: SNProtoContent
|
|
do {
|
|
proto = try SNProtoContent.parseData((plaintext as NSData).removePadding())
|
|
} catch {
|
|
SNLog("Couldn't parse proto due to error: \(error).")
|
|
throw error
|
|
}
|
|
// Parse the message
|
|
let message: Message? = {
|
|
if let readReceipt = ReadReceipt.fromProto(proto) { return readReceipt }
|
|
if let typingIndicator = TypingIndicator.fromProto(proto) { return typingIndicator }
|
|
if let closedGroupControlMessage = ClosedGroupControlMessage.fromProto(proto) { return closedGroupControlMessage }
|
|
if let dataExtractionNotification = DataExtractionNotification.fromProto(proto) { return dataExtractionNotification }
|
|
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
|
|
if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage }
|
|
if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest }
|
|
if let messageRequestResponse = MessageRequestResponse.fromProto(proto) { return messageRequestResponse }
|
|
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
|
|
return nil
|
|
}()
|
|
if let message = message {
|
|
// Ignore self sends if needed
|
|
if !message.isSelfSendValid {
|
|
guard sender != userPublicKey else { throw Error.selfSend }
|
|
}
|
|
// Guard against control messages in open groups
|
|
if isOpenGroupMessage {
|
|
guard message is VisibleMessage else { throw Error.invalidMessage }
|
|
}
|
|
// Finish parsing
|
|
message.sender = sender
|
|
message.recipient = userPublicKey
|
|
message.sentTimestamp = envelope.timestamp
|
|
message.receivedTimestamp = NSDate.millisecondTimestamp()
|
|
if isOpenGroupMessage {
|
|
message.openGroupServerTimestamp = envelope.serverTimestamp
|
|
}
|
|
message.groupPublicKey = groupPublicKey
|
|
message.openGroupServerMessageID = openGroupMessageServerID
|
|
// Validate
|
|
var isValid = message.isValid
|
|
if message is VisibleMessage && !isValid && proto.dataMessage?.attachments.isEmpty == false {
|
|
isValid = true
|
|
}
|
|
guard isValid else {
|
|
throw Error.invalidMessage
|
|
}
|
|
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
|
|
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
|
|
// for this issue.
|
|
if let message = message as? ClosedGroupControlMessage, case .new = message.kind {
|
|
// Allow duplicates in this case to avoid the following situation:
|
|
// • The app performed a background poll or received a push notification
|
|
// • This method was invoked and the received message timestamps table was updated
|
|
// • Processing wasn't finished
|
|
// • The user doesn't see the new closed group
|
|
} else {
|
|
guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage }
|
|
storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction)
|
|
}
|
|
// Return
|
|
return (message, proto)
|
|
} else {
|
|
throw Error.unknownMessage
|
|
}
|
|
}
|
|
}
|