From acdb76158f68a27ef2ce83d75a8edfacb34bd849 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 27 Sep 2024 04:22:57 -0500 Subject: [PATCH 1/9] feat: added warning for suspicious message --- .semaphore/semaphore.yml | 2 +- FlowCrypt.xcodeproj/project.pbxproj | 4 + ...hreadDetailsViewController+TableView.swift | 15 +++- .../Mail Provider/Gmail/GmailService.swift | 1 + .../Gmail+MessageExtension.swift | 5 +- .../MessagesList Provider/Model/Message.swift | 5 +- .../Resources/en.lproj/Localizable.strings | 3 + .../Cell Nodes/SecurityWarningNode.swift | 43 +++++++++++ .../message-export-1922cd70bf323ea6.json | 74 +++++++++++++++++++ .../api-mocks/apis/google/google-messages.ts | 3 +- appium/config/wdio.live.conf.ts | 4 +- appium/config/wdio.mock.conf.ts | 4 +- appium/tests/screenobjects/email.screen.ts | 20 +++++ ...kSecurityWarningForSuspiciousEmail.spec.ts | 26 +++++++ 14 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 FlowCryptUI/Cell Nodes/SecurityWarningNode.swift create mode 100644 appium/api-mocks/apis/google/exported-messages/message-export-1922cd70bf323ea6.json create mode 100644 appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index a0a223c13..c0a3f6e0d 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -3,7 +3,7 @@ name: FlowCrypt iOS App agent: machine: type: a1-standard-4 - os_image: macos-xcode15 + os_image: macos-xcode16 execution_time_limit: minutes: 120 auto_cancel: diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 06f1b2b1a..2f867087b 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -420,6 +420,7 @@ D2FD0F692453245E00259FF0 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FD0F682453245E00259FF0 /* Either.swift */; }; D2FF6966243115EC007182F0 /* SetupImapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6965243115EC007182F0 /* SetupImapViewController.swift */; }; D2FF6968243115F9007182F0 /* SetupImapViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6967243115F9007182F0 /* SetupImapViewDecorator.swift */; }; + D741F9B22CA5661C00E1CAFF /* SecurityWarningNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D741F9B12CA5661400E1CAFF /* SecurityWarningNode.swift */; }; F191F621272511790053833E /* BlurViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F191F620272511790053833E /* BlurViewController.swift */; }; F8678DCC2722143300BB1710 /* GmailService+draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8678DCB2722143300BB1710 /* GmailService+draft.swift */; }; F8A72FA12729F82800E4BCAB /* DraftGatewayMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A72FA02729F82800E4BCAB /* DraftGatewayMock.swift */; }; @@ -886,6 +887,7 @@ D2FD0F682453245E00259FF0 /* Either.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; D2FF6965243115EC007182F0 /* SetupImapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImapViewController.swift; sourceTree = ""; }; D2FF6967243115F9007182F0 /* SetupImapViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImapViewDecorator.swift; sourceTree = ""; }; + D741F9B12CA5661400E1CAFF /* SecurityWarningNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWarningNode.swift; sourceTree = ""; }; E26D5E20275AA417007B8802 /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; }; F191F620272511790053833E /* BlurViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurViewController.swift; sourceTree = ""; }; F8678DCB2722143300BB1710 /* GmailService+draft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GmailService+draft.swift"; sourceTree = ""; }; @@ -2212,6 +2214,7 @@ D2A1D3D123FD9A9E00D626D6 /* Cell Nodes */ = { isa = PBXGroup; children = ( + D741F9B12CA5661400E1CAFF /* SecurityWarningNode.swift */, 9F8D5E61236B04E300186E43 /* CellNode.swift */, 9F1797652368EE50002BF770 /* SetupTitleNode.swift */, 5A39F433239EC61C001F4607 /* TitleCellNode.swift */, @@ -2904,6 +2907,7 @@ D2CDC3D72404704D002B045F /* RecipientEmailsCellNode.swift in Sources */, 5165ABCC27B526D100CCC379 /* RecipientEmailTextFieldNode.swift in Sources */, 51C56BE82901867D00610D12 /* ENSideMenu.swift in Sources */, + D741F9B22CA5661C00E1CAFF /* SecurityWarningNode.swift in Sources */, 955475FB2B0650AC00F52076 /* WebNode.swift in Sources */, D2717752242567EB00BDA9A9 /* KeyTextCellNode.swift in Sources */, 511D07E12769FBBA0050417B /* MessageActionCellNode.swift in Sources */, diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController+TableView.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController+TableView.swift index 2e2b2479d..a8ea7e780 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController+TableView.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController+TableView.swift @@ -27,7 +27,8 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { let processedMessage = input[section - 1].processedMessage let attachmentsCount = processedMessage?.attachments.count ?? 0 let pubkeysCount = processedMessage?.keyDetails.count ?? 0 - return Parts.allCases.count + attachmentsCount + pubkeysCount + let securityWarningBlockCount = processedMessage?.message.isSuspicious == true ? 1 : 0 + return Parts.allCases.count + attachmentsCount + pubkeysCount + securityWarningBlockCount } // swiftlint:disable cyclomatic_complexity function_body_length @@ -65,10 +66,16 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { } } + if message.rawMessage.isSuspicious, row == 1 { + return SecurityWarningNode() + } + guard message.isExpanded, let processedMessage = message.processedMessage else { return self.dividerNode(indexPath: indexPath) } - guard row > 1 else { + let securityWarningBlockCount = message.rawMessage.isSuspicious == true ? 1 : 0 + + guard row > 1 + securityWarningBlockCount else { if processedMessage.text.isHTMLString { return ThreadDetailWebNode( input: .init(message: processedMessage.text, quote: processedMessage.quote, index: messageIndex) @@ -85,7 +92,7 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { } let keyCount = processedMessage.keyDetails.count - let keyIndex = row - 2 + let keyIndex = row - 2 - securityWarningBlockCount if keyIndex < keyCount { let keyDetails = processedMessage.keyDetails[keyIndex] let node = PublicKeyDetailNode( @@ -97,7 +104,7 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { return node } - let attachmentIndex = row - 2 - keyCount + let attachmentIndex = row - 2 - keyCount - securityWarningBlockCount if let attachment = processedMessage.attachments[safe: attachmentIndex] { return AttachmentNode( input: .init( diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index 23ec145e5..ed40e5c2c 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -60,5 +60,6 @@ extension String { static let bcc = "bcc" static let replyTo = "reply-to" static let inReplyTo = "in-reply-to" + static let receivedSPF = "received-spf" static let identifier = "message-id" } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+MessageExtension.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+MessageExtension.swift index 78c25099d..0b7d6a6de 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+MessageExtension.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+MessageExtension.swift @@ -44,6 +44,7 @@ extension Message { var replyTo: String? var inReplyTo: String? var rfc822MsgId: String? + var isSuspicious = false for messageHeader in messageHeaders.compactMap({ $0 }) { guard let name = messageHeader.name?.lowercased(), @@ -58,6 +59,7 @@ extension Message { case .bcc: bcc = value case .replyTo: replyTo = value case .inReplyTo: inReplyTo = value + case .receivedSPF: isSuspicious = value.contains("softfail") case .identifier: rfc822MsgId = value default: break } @@ -81,7 +83,8 @@ extension Message { cc: cc, bcc: bcc, replyTo: replyTo, - inReplyTo: inReplyTo + inReplyTo: inReplyTo, + isSuspicious: isSuspicious ) } } diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index d16a2586e..a0c795e8b 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -28,6 +28,7 @@ struct Message: Hashable { let body: MessageBody let inReplyTo: String? let replyToMsgId: String? + let isSuspicious: Bool private(set) var labels: [MessageLabel] var isRead: Bool { @@ -83,7 +84,8 @@ struct Message: Hashable { bcc: String? = nil, replyTo: String? = nil, inReplyTo: String? = nil, - replyToMsgId: String? = nil + replyToMsgId: String? = nil, + isSuspicious: Bool = false ) { self.identifier = identifier self.date = date @@ -104,6 +106,7 @@ struct Message: Hashable { self.replyTo = Self.parseRecipients(replyTo) self.inReplyTo = inReplyTo self.replyToMsgId = replyToMsgId + self.isSuspicious = isSuspicious } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 44b652fa0..4a1f4f3e0 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -72,6 +72,9 @@ "message_mark_read_error" = "Could not mark message as read: %@"; "message_reply_all" = "Reply all"; "message_not_found_in_folder" = "Message not found in folder: "; +"message_security_warning_subject" = "Potentially suspicious message"; +"message_security_warning_message" = "It wasn't properly verified by the sender, so its authenticity can't be confirmed. +Be careful - avoid clicking links and downloading attachments, or sharing personal info."; // Passphrase Anti BruteForce Protection "passphrase_anti_brute_force_protection_hint" = "To protect you and your data, the next attempt will only be possible after the timer below finishes. Please wait until then before trying again."; diff --git a/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift b/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift new file mode 100644 index 000000000..32c2b62e2 --- /dev/null +++ b/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift @@ -0,0 +1,43 @@ +// +// SecurityWarningNode.swift +// FlowCrypt +// +// Created by Ioan Moldovan on 9/26/24 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit + +public final class SecurityWarningNode: CellNode { + + private lazy var subjectNode: ASTextNode2 = { + let textNode = ASTextNode2() + textNode.attributedText = "message_security_warning_subject".localized.attributed(.bold(18), color: .black, alignment: .left) + textNode.accessibilityIdentifier = "aid-security-warning-subject-node" + return textNode + }() + + private lazy var messageNode: ASTextNode2 = { + let textNode = ASTextNode2() + textNode.attributedText = "message_security_warning_message".localized.attributed(.thin(16), color: .black, alignment: .left) + textNode.accessibilityIdentifier = "aid-security-warning-message-node" + return textNode + }() + + override public init() { + super.init() + + backgroundColor = UIColor(hex: "FABD03") + } + + override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { + let stack = ASStackLayoutSpec.vertical() + stack.children = [subjectNode, messageNode] + stack.spacing = 10 + + return ASInsetLayoutSpec( + insets: .deviceSpecificTextInsets(top: 16, bottom: 16), + child: stack + ) + } +} diff --git a/appium/api-mocks/apis/google/exported-messages/message-export-1922cd70bf323ea6.json b/appium/api-mocks/apis/google/exported-messages/message-export-1922cd70bf323ea6.json new file mode 100644 index 000000000..b00b39cf7 --- /dev/null +++ b/appium/api-mocks/apis/google/exported-messages/message-export-1922cd70bf323ea6.json @@ -0,0 +1,74 @@ +{ + "acctEmail": "e2e.enterprise.test@flowcrypt.com", + "full": { + "id": "1922cd70bf323ea6", + "threadId": "1922cd70bf323ea6", + "labelIds": ["CATEGORY_PERSONAL", "INBOX"], + "snippet": "test -Mart at FlowCrypt", + "payload": { + "partId": "", + "mimeType": "text/plain", + "filename": "", + "headers": [ + { + "name": "To", + "value": "flowcrypt.compatibility@gmail.com" + }, + { + "name": "Subject", + "value": "Test Spoofed email by Mart" + }, + { + "name": "From", + "value": "sender@domain.com" + }, + { + "name": "X-Priority", + "value": "3 (Normal)" + }, + { + "name": "Importance", + "value": "Normal" + }, + { + "name": "Received-SPF", + "value": "softfail (google.com: domain of transitioning hacker@gmail.com does not designate 0.0.0.0 as permitted sender) client-ip=0.0.0.0;" + }, + { + "name": "Errors-To", + "value": "hacker@gmail.com" + }, + { + "name": "Reply-To", + "value": "hacker@gmail.com" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=utf-8" + }, + { + "name": "Date", + "value": "Thu, 26 Sep 2024 07:39:02 +0200 (CEST)" + } + ], + "body": { + "size": 28, + "data": "dGVzdA0KDQotTWFydCBhdCBGbG93Q3J5cHQNCg==" + } + }, + "sizeEstimate": 3183, + "historyId": "298166", + "internalDate": "1727329142000" + }, + "attachments": {}, + "raw": { + "id": "1922cd70bf323ea6", + "threadId": "1922cd70bf323ea6", + "labelIds": ["CATEGORY_PERSONAL", "SPAM"], + "snippet": "test -Mart at FlowCrypt", + "sizeEstimate": 3183, + "raw": "RGVsaXZlcmVkLVRvOiBpb2FuQGZsb3djcnlwdC5jb20NClJlY2VpdmVkOiBieSAyMDAyOmEwNTo2MzU5OjQ1OWE6YjA6MWJlOmI4N2E6NDZhNCB3aXRoIFNNVFAgaWQgbm8yNmNzcDE1MDk5MXJ3YjsNCiAgICAgICAgV2VkLCAyNSBTZXAgMjAyNCAyMjozOTowMyAtMDcwMCAoUERUKQ0KWC1Hb29nbGUtU210cC1Tb3VyY2U6IEFHSFQrSUhocFVnem42dE4ycnAyUDc0NkNKQ1JDZ1NXZXEyYndWdVFDNWFpcXFFSThOdGhhSTlER0JJb2V5V3VRTWFQeXVCOUV2WmoNClgtUmVjZWl2ZWQ6IGJ5IDIwMDI6YTE3OjkwYjozMTQ0OmIwOjJkODo3N2NjOjg1ZSB3aXRoIFNNVFAgaWQgOThlNjdlZDU5ZTFkMS0yZTA2YWZlYjc4ZW1yNjMzMTEzMGE5MS4zNy4xNzI3MzI5MTQzNTQ1Ow0KICAgICAgICBXZWQsIDI1IFNlcCAyMDI0IDIyOjM5OjAzIC0wNzAwIChQRFQpDQpBUkMtU2VhbDogaT0xOyBhPXJzYS1zaGEyNTY7IHQ9MTcyNzMyOTE0MzsgY3Y9bm9uZTsNCiAgICAgICAgZD1nb29nbGUuY29tOyBzPWFyYy0yMDI0MDYwNTsNCiAgICAgICAgYj1TOVRyZzluaUsyQURsQWQrWnYwZE9CbnhBVStvYlN5RkUyUkgvVFFucS9ZL2M2VmNCd3pkcmM4d3A4Y3BJL1ZUUWsNCiAgICAgICAgIDJjc3F6TEIzczRCQjBTMFB0U0pFd0VJTUUvd2J1SWtTUWl5RkpjOURsY3R2VFBST1RhSmNHc0xKTmQvWlBvRzhMQTJCDQogICAgICAgICBpZVJ6YlpsbWs2TmpKMFFPVURNZlVzYU8reXp0dzcwSmU1OWgwbjZQVUoyWTloNW5UdXFpYzFzbmpqZVc5b0dPcDF3Mg0KICAgICAgICAgWWJGQzFrK09GbjkyZElxeFZkNHZsNnp5blBrZytxSFoyQi9HSTE1ZHRjaFIyZFpXOEJlcWh3cU00TXIvdFFpVE9TQmgNCiAgICAgICAgIDRSdjBzTm1yam5LSVJCR2JPRWVBMHNMTndWam5kTWJjaDMwVGdIb1Y1NEoxakcyQVZnY1F0aDFTTjVuc0ovOVJoL0d3DQogICAgICAgICB6VWp3PT0NCkFSQy1NZXNzYWdlLVNpZ25hdHVyZTogaT0xOyBhPXJzYS1zaGEyNTY7IGM9cmVsYXhlZC9yZWxheGVkOyBkPWdvb2dsZS5jb207IHM9YXJjLTIwMjQwNjA1Ow0KICAgICAgICBoPWRhdGU6bWVzc2FnZS1pZDpyZXBseS10bzplcnJvcnMtdG86aW1wb3J0YW5jZTpmcm9tOnN1YmplY3Q6dG87DQogICAgICAgIGJoPWNXY3VTNmxuVTE4VTU4YTUwT3dqelFzMGEwK3FsNGxZbnBDSEZoZ3Yxalk9Ow0KICAgICAgICBmaD1yVjZaTXJhY21nOVg1TUdtam9YdVJ6YmZ4dGpEUldSZkEzWkZLQTNRZC9jPTsNCiAgICAgICAgYj1MaTRxMjRuNXUxajFPWEFqUG1JN0RDNlFXOVJTd2tSOUFjaHd6bEJkb0ZLbXYwdmpXclo3dVFQS2UyMUVGZnlJUVQNCiAgICAgICAgIFk5M3pQbWg5U1RacVFaV3FGTS9SaXJGTFlRRjV2UGpZTzdVRWpiSkoyS05lYlFtS3lNT1ZDN2UydlNxdzRsWVQ1bUh6DQogICAgICAgICAwUEpmbzNUK2cvWUE2UU0zbldmbjIzU1pONHRTS3hIU1c2VXh6N3F0S3FCNXFtQjJSalFrUFZod0FGMEw3SFEva0RWNA0KICAgICAgICAgWk1QaDVuMUR2SHdqWDlxRURWUmk1d3NQdGg4Y1NRbEY2dnFSdXZjZzhyVjdGajZnRzA1QVFyeFp2bTQ2TVVMU0t0ckINCiAgICAgICAgIG5NYjhQYmpreEdJUHdBQ1gyODNzbDRKWEh3Q1ZiTVdYRHEzeDZPck01a2tKdmxJcGdLd2l6UEhOOGs2cW9Ud1pmbHV2DQogICAgICAgICBvMjNnPT07DQogICAgICAgIGRhcmE9Z29vZ2xlLmNvbQ0KQVJDLUF1dGhlbnRpY2F0aW9uLVJlc3VsdHM6IGk9MTsgbXguZ29vZ2xlLmNvbTsNCiAgICAgICBzcGY9c29mdGZhaWwgKGdvb2dsZS5jb206IGRvbWFpbiBvZiB0cmFuc2l0aW9uaW5nIGhhY2tlckBnbWFpbC5jb20gZG9lcyBub3QgZGVzaWduYXRlIDExNC4yOS4yMzYuMjQ3IGFzIHBlcm1pdHRlZCBzZW5kZXIpIHNtdHAubWFpbGZyb209aGFja2VyQGdtYWlsLmNvbTsNCiAgICAgICBkbWFyYz1mYWlsIChwPU5PTkUgc3A9UVVBUkFOVElORSBkaXM9Tk9ORSkgaGVhZGVyLmZyb209Z21haWwuY29tDQpSZXR1cm4tUGF0aDogPGhhY2tlckBnbWFpbC5jb20-DQpSZWNlaXZlZDogZnJvbSBlbWtlaS5jeiAoZW1rZWkuY3ouIFsxMTQuMjkuMjM2LjI0N10pDQogICAgICAgIGJ5IG14Lmdvb2dsZS5jb20gd2l0aCBFU01UUFMgaWQgOThlNjdlZDU5ZTFkMS0yZTA2ZTJmMjQ2Y3NpMzAzNTc1NWE5MS4xNTkuMjAyNC4wOS4yNS4yMi4zOS4wMw0KICAgICAgICBmb3IgPGlvYW5AZmxvd2NyeXB0LmNvbT4NCiAgICAgICAgKHZlcnNpb249VExTMV8zIGNpcGhlcj1UTFNfQUVTXzI1Nl9HQ01fU0hBMzg0IGJpdHM9MjU2LzI1Nik7DQogICAgICAgIFdlZCwgMjUgU2VwIDIwMjQgMjI6Mzk6MDMgLTA3MDAgKFBEVCkNClJlY2VpdmVkLVNQRjogc29mdGZhaWwgKGdvb2dsZS5jb206IGRvbWFpbiBvZiB0cmFuc2l0aW9uaW5nIGhhY2tlckBnbWFpbC5jb20gZG9lcyBub3QgZGVzaWduYXRlIDExNC4yOS4yMzYuMjQ3IGFzIHBlcm1pdHRlZCBzZW5kZXIpIGNsaWVudC1pcD0xMTQuMjkuMjM2LjI0NzsNCkF1dGhlbnRpY2F0aW9uLVJlc3VsdHM6IG14Lmdvb2dsZS5jb207DQogICAgICAgc3BmPXNvZnRmYWlsIChnb29nbGUuY29tOiBkb21haW4gb2YgdHJhbnNpdGlvbmluZyBoYWNrZXJAZ21haWwuY29tIGRvZXMgbm90IGRlc2lnbmF0ZSAxMTQuMjkuMjM2LjI0NyBhcyBwZXJtaXR0ZWQgc2VuZGVyKSBzbXRwLm1haWxmcm9tPWhhY2tlckBnbWFpbC5jb207DQogICAgICAgZG1hcmM9ZmFpbCAocD1OT05FIHNwPVFVQVJBTlRJTkUgZGlzPU5PTkUpIGhlYWRlci5mcm9tPWdtYWlsLmNvbQ0KUmVjZWl2ZWQ6IGJ5IGVta2VpLmN6IChQb3N0Zml4LCBmcm9tIHVzZXJpZCAzMykNCglpZCA0NzU5QzFGMDk7IFRodSwgMjYgU2VwIDIwMjQgMDc6Mzk6MDIgKzAyMDAgKENFU1QpDQpUbzogaW9hbkBmbG93Y3J5cHQuY29tDQpTdWJqZWN0OiBUZXN0IFNwb29mZWQgZW1haWwgYnkgTWFydA0KRnJvbTogIkFkbWluaXN0cmF0b3IiIDxoYWNrZXJAZ21haWwuY29tPg0KWC1Qcmlvcml0eTogMyAoTm9ybWFsKQ0KSW1wb3J0YW5jZTogTm9ybWFsDQpFcnJvcnMtVG86IGhhY2tlckBnbWFpbC5jb20NClJlcGx5LVRvOiBoYWNrZXJAZ21haWwuY29tDQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgNCk1lc3NhZ2UtSWQ6IDwyMDI0MDkyNjA1MzkwMi40NzU5QzFGMDlAZW1rZWkuY3o-DQpEYXRlOiBUaHUsIDI2IFNlcCAyMDI0IDA3OjM5OjAyICswMjAwIChDRVNUKQ0KDQp0ZXN0DQoNCi1NYXJ0IGF0IEZsb3dDcnlwdA0K", + "historyId": "298166", + "internalDate": "1727329142000" + } +} diff --git a/appium/api-mocks/apis/google/google-messages.ts b/appium/api-mocks/apis/google/google-messages.ts index f5a9f6402..9c3f73a22 100644 --- a/appium/api-mocks/apis/google/google-messages.ts +++ b/appium/api-mocks/apis/google/google-messages.ts @@ -30,4 +30,5 @@ export type GoogleMockMessage = | 'Test forward message with attached pub key' | 'Encrypted email with public key attached' | 'Email with another user public key attached' - | 'Test remote images #2414'; + | 'Test remote images #2414' + | 'Test Spoofed email by Mart'; diff --git a/appium/config/wdio.live.conf.ts b/appium/config/wdio.live.conf.ts index 74503ffbd..aca3a4fa3 100644 --- a/appium/config/wdio.live.conf.ts +++ b/appium/config/wdio.live.conf.ts @@ -10,8 +10,8 @@ config.capabilities = [ platformName: 'iOS', hostname: '127.0.0.1', 'appium:automationName': 'XCUITest', - 'appium:deviceName': 'iPhone 15', - 'appium:platformVersion': '17.4', + 'appium:deviceName': 'iPhone 16', + 'appium:platformVersion': '18.0', 'appium:app': join(process.cwd(), './FlowCrypt.app'), }, ]; diff --git a/appium/config/wdio.mock.conf.ts b/appium/config/wdio.mock.conf.ts index 14df9240b..3e14a4f14 100644 --- a/appium/config/wdio.mock.conf.ts +++ b/appium/config/wdio.mock.conf.ts @@ -20,8 +20,8 @@ config.capabilities = [ 'appium:processArguments': { args: ['--mock-fes-api', '--mock-attester-api', '--mock-gmail-api'], }, - 'appium:deviceName': 'iPhone 15', - 'appium:platformVersion': '17.4', + 'appium:deviceName': 'iPhone 16', + 'appium:platformVersion': '18.0', 'appium:orientation': 'PORTRAIT', 'appium:app': join(process.cwd(), './FlowCrypt.app'), 'appium:newCommandTimeout': 240, diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index 611d4a180..18361adb3 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -37,6 +37,8 @@ const SELECTORS = { TOGGLE_PUBLIC_KEY_NODE: '~aid-toggle-public-key-node', PUBLIC_KEY_VALUE: '~aid-public-key-value', IMPORT_PUBLIC_KEY_BUTTON: '~aid-import-key-button', + SECURITY_WARNING_SUBJECT: '~aid-security-warning-subject-node', + SECURITY_WARNING_MESSSAGE: '~aid-security-warning-message-node', }; class EmailScreen extends BaseScreen { @@ -172,6 +174,14 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.ATTACHMENT_TEXT_VIEW); } + get securityWarningSubjectLabel() { + return $(SELECTORS.SECURITY_WARNING_SUBJECT); + } + + get securityWarningMessageLabel() { + return $(SELECTORS.SECURITY_WARNING_MESSSAGE); + } + checkEmailSender = async (sender: string, index = 0) => { const element = await this.senderEmail(index); await ElementHelper.waitElementVisible(element); @@ -203,6 +213,16 @@ class EmailScreen extends BaseScreen { } }; + checkSecurityWarningBlock = async () => { + await ElementHelper.waitForText(await this.securityWarningSubjectLabel, 'Potentially suspicious message'); + await ElementHelper.waitForText( + await this.securityWarningMessageLabel, + "It wasn't properly verified by the sender, so its authenticity can't be confirmed.", + 15000, + true, + ); + }; + checkOpenedEmail = async (email: string, subject: string, text: string, isHtml = false) => { await this.checkEmailSender(email); await this.checkEmailSubject(subject); diff --git a/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts b/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts new file mode 100644 index 000000000..d57144958 --- /dev/null +++ b/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts @@ -0,0 +1,26 @@ +import { MockApi } from 'api-mocks/mock'; +import { MockApiConfig } from 'api-mocks/mock-config'; +import { SplashScreen, SetupKeyScreen, MailFolderScreen, EmailScreen } from '../../../screenobjects/all-screens'; + +describe('INBOX: ', () => { + it('user is able to view correct signature badge for all cases', async () => { + const mockApi = new MockApi(); + + const subject = 'Test Spoofed email by Mart'; + mockApi.fesConfig = MockApiConfig.defaultEnterpriseFesConfiguration; + mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration; + mockApi.addGoogleAccount('e2e.enterprise.test@flowcrypt.com', { + messages: [subject], + }); + + await mockApi.withMockedApis(async () => { + await SplashScreen.mockLogin(); + await SetupKeyScreen.setPassPhrase(); + await MailFolderScreen.checkInboxScreen(); + + await MailFolderScreen.clickOnEmailBySubject(subject); + + await EmailScreen.checkSecurityWarningBlock(); + }); + }); +}); From c47ab529aba4988790691210b54cd8316e66f527 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 27 Sep 2024 04:26:20 -0500 Subject: [PATCH 2/9] fix: machine type --- .semaphore/semaphore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index c0a3f6e0d..87f524eb3 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -2,7 +2,7 @@ version: v1.0 name: FlowCrypt iOS App agent: machine: - type: a1-standard-4 + type: a2-standard-4 os_image: macos-xcode16 execution_time_limit: minutes: 120 From b5af863dce77d5a4eb824340b2469311098b2913 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 27 Sep 2024 04:35:21 -0500 Subject: [PATCH 3/9] fix: fastlane file --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 23616fc2d..74c3a3bfd 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,7 +12,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "Debug FlowCrypt", - device: "iPhone 15", + device: "iPhone 16", derived_data_path: "/var/tmp/derived_data", skip_detect_devices: true, build_for_testing: true, @@ -25,7 +25,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "FlowCryptAppTests", - device: "iPhone 15", + device: "iPhone 16", test_without_building: true, derived_data_path: "/var/tmp/derived_data", xcargs: "-skipPackagePluginValidation -skipMacroValidation", From 43280501b4f6bed14bb50e9817949a36dd0defaa Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 27 Sep 2024 04:43:08 -0500 Subject: [PATCH 4/9] fix: fastlane file --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 74c3a3bfd..d23e02b61 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,7 +12,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "Debug FlowCrypt", - device: "iPhone 16", + device: "iPhone 16(18.0)", derived_data_path: "/var/tmp/derived_data", skip_detect_devices: true, build_for_testing: true, @@ -25,7 +25,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "FlowCryptAppTests", - device: "iPhone 16", + device: "iPhone 16(18.0)", test_without_building: true, derived_data_path: "/var/tmp/derived_data", xcargs: "-skipPackagePluginValidation -skipMacroValidation", From 87ffe9935fb3a366f0ed7c16d77ee8972164a369 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 27 Sep 2024 04:47:35 -0500 Subject: [PATCH 5/9] fix: fastlane file --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d23e02b61..ed801a460 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,7 +12,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "Debug FlowCrypt", - device: "iPhone 16(18.0)", + device: "iPhone 16 (18.0)", derived_data_path: "/var/tmp/derived_data", skip_detect_devices: true, build_for_testing: true, @@ -25,7 +25,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "FlowCryptAppTests", - device: "iPhone 16(18.0)", + device: "iPhone 16 (18.0)", test_without_building: true, derived_data_path: "/var/tmp/derived_data", xcargs: "-skipPackagePluginValidation -skipMacroValidation", From 704192d0781bafde127f15edc873eedc6e7bf696 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Sun, 29 Sep 2024 21:17:33 -0500 Subject: [PATCH 6/9] fix: semaphoreci --- .semaphore/semaphore.yml | 13 ++++++------- appium/config/wdio.shared.conf.ts | 2 ++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 87f524eb3..e1176d0d9 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -9,6 +9,9 @@ execution_time_limit: auto_cancel: running: when: branch != 'master' +fail_fast: + stop: + when: branch != 'master' blocks: - name: Build + Swift Unit Test dependencies: [] @@ -86,22 +89,18 @@ blocks: - artifact push job ~/git/flowcrypt-ios/appium/tmp - test-results publish ~/git/flowcrypt-ios/appium/tmp/test-results jobs: - - name: Run Mock inbox tests + - name: Run Mock inbox + compose tests commands: - npm run-script test.mock.inbox - - name: Run Mock compose tests - commands: - npm run-script test.mock.compose - - name: Run Mock setup tests + - name: Run Mock setup + live + other test commands: - npm run-script test.mock.setup - - name: Run Mock other tests + Run Live tests - commands: - npm run-script test.mock.login-settings # temporary disabled because of e2e account login issue # - 'wget https://flowcrypt.s3.eu-central-1.amazonaws.com/release/flowcrypt-ios-old-version-for-ci-storage-compatibility-2022-05-09.zip -P ~/git/flowcrypt-ios/appium' # - unzip flowcrypt-ios-*.zip - # - npm run-script test.live.all + # - npm run-script test.live.all secrets: - name: flowcrypt-ios-ci-secrets run: diff --git a/appium/config/wdio.shared.conf.ts b/appium/config/wdio.shared.conf.ts index 76cf0109a..a336364fc 100644 --- a/appium/config/wdio.shared.conf.ts +++ b/appium/config/wdio.shared.conf.ts @@ -46,6 +46,8 @@ export const config: Options.Testrunner = { 'appium', { args: { + address: 'localhost', + port: 4723, relaxedSecurity: true, }, command: './node_modules/.bin/appium', From 41b38b6e853d689ddacac8020c505676c9455b94 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Sun, 29 Sep 2024 21:37:17 -0500 Subject: [PATCH 7/9] fix: use xcode 15 image --- .semaphore/semaphore.yml | 19 ++++++++++--------- appium/config/wdio.live.conf.ts | 4 ++-- appium/config/wdio.mock.conf.ts | 4 ++-- appium/config/wdio.shared.conf.ts | 2 -- fastlane/Fastfile | 4 ++-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index e1176d0d9..2a699b688 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -2,16 +2,13 @@ version: v1.0 name: FlowCrypt iOS App agent: machine: - type: a2-standard-4 - os_image: macos-xcode16 + type: a1-standard-4 + os_image: macos-xcode15 execution_time_limit: minutes: 120 auto_cancel: running: when: branch != 'master' -fail_fast: - stop: - when: branch != 'master' blocks: - name: Build + Swift Unit Test dependencies: [] @@ -89,18 +86,22 @@ blocks: - artifact push job ~/git/flowcrypt-ios/appium/tmp - test-results publish ~/git/flowcrypt-ios/appium/tmp/test-results jobs: - - name: Run Mock inbox + compose tests + - name: Run Mock inbox tests commands: - npm run-script test.mock.inbox + - name: Run Mock compose tests + commands: - npm run-script test.mock.compose - - name: Run Mock setup + live + other test + - name: Run Mock setup tests commands: - npm run-script test.mock.setup + - name: Run Mock other tests + Run Live tests + commands: - npm run-script test.mock.login-settings # temporary disabled because of e2e account login issue # - 'wget https://flowcrypt.s3.eu-central-1.amazonaws.com/release/flowcrypt-ios-old-version-for-ci-storage-compatibility-2022-05-09.zip -P ~/git/flowcrypt-ios/appium' # - unzip flowcrypt-ios-*.zip - # - npm run-script test.live.all + # - npm run-script test.live.all secrets: - name: flowcrypt-ios-ci-secrets run: @@ -110,4 +111,4 @@ after_pipeline: jobs: - name: Publish Results commands: - - test-results gen-pipeline-report + - test-results gen-pipeline-report \ No newline at end of file diff --git a/appium/config/wdio.live.conf.ts b/appium/config/wdio.live.conf.ts index aca3a4fa3..74503ffbd 100644 --- a/appium/config/wdio.live.conf.ts +++ b/appium/config/wdio.live.conf.ts @@ -10,8 +10,8 @@ config.capabilities = [ platformName: 'iOS', hostname: '127.0.0.1', 'appium:automationName': 'XCUITest', - 'appium:deviceName': 'iPhone 16', - 'appium:platformVersion': '18.0', + 'appium:deviceName': 'iPhone 15', + 'appium:platformVersion': '17.4', 'appium:app': join(process.cwd(), './FlowCrypt.app'), }, ]; diff --git a/appium/config/wdio.mock.conf.ts b/appium/config/wdio.mock.conf.ts index 3e14a4f14..14df9240b 100644 --- a/appium/config/wdio.mock.conf.ts +++ b/appium/config/wdio.mock.conf.ts @@ -20,8 +20,8 @@ config.capabilities = [ 'appium:processArguments': { args: ['--mock-fes-api', '--mock-attester-api', '--mock-gmail-api'], }, - 'appium:deviceName': 'iPhone 16', - 'appium:platformVersion': '18.0', + 'appium:deviceName': 'iPhone 15', + 'appium:platformVersion': '17.4', 'appium:orientation': 'PORTRAIT', 'appium:app': join(process.cwd(), './FlowCrypt.app'), 'appium:newCommandTimeout': 240, diff --git a/appium/config/wdio.shared.conf.ts b/appium/config/wdio.shared.conf.ts index a336364fc..76cf0109a 100644 --- a/appium/config/wdio.shared.conf.ts +++ b/appium/config/wdio.shared.conf.ts @@ -46,8 +46,6 @@ export const config: Options.Testrunner = { 'appium', { args: { - address: 'localhost', - port: 4723, relaxedSecurity: true, }, command: './node_modules/.bin/appium', diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ed801a460..23616fc2d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,7 +12,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "Debug FlowCrypt", - device: "iPhone 16 (18.0)", + device: "iPhone 15", derived_data_path: "/var/tmp/derived_data", skip_detect_devices: true, build_for_testing: true, @@ -25,7 +25,7 @@ platform :ios do scan( project: "FlowCrypt.xcodeproj", scheme: "FlowCryptAppTests", - device: "iPhone 16 (18.0)", + device: "iPhone 15", test_without_building: true, derived_data_path: "/var/tmp/derived_data", xcargs: "-skipPackagePluginValidation -skipMacroValidation", From 7740218d374729dbc534b83adc27ee30d159625c Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 30 Sep 2024 18:36:02 -0500 Subject: [PATCH 8/9] fix: pr reviews --- FlowCryptUI/Cell Nodes/SecurityWarningNode.swift | 2 +- .../specs/mock/inbox/CheckMessageProcessingErrors.spec.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift b/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift index 32c2b62e2..7a9b88812 100644 --- a/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift +++ b/FlowCryptUI/Cell Nodes/SecurityWarningNode.swift @@ -19,7 +19,7 @@ public final class SecurityWarningNode: CellNode { private lazy var messageNode: ASTextNode2 = { let textNode = ASTextNode2() - textNode.attributedText = "message_security_warning_message".localized.attributed(.thin(16), color: .black, alignment: .left) + textNode.attributedText = "message_security_warning_message".localized.attributed(.regular(15), color: .black, alignment: .left) textNode.accessibilityIdentifier = "aid-security-warning-message-node" return textNode }() diff --git a/appium/tests/specs/mock/inbox/CheckMessageProcessingErrors.spec.ts b/appium/tests/specs/mock/inbox/CheckMessageProcessingErrors.spec.ts index 9a4e6455d..23569697b 100644 --- a/appium/tests/specs/mock/inbox/CheckMessageProcessingErrors.spec.ts +++ b/appium/tests/specs/mock/inbox/CheckMessageProcessingErrors.spec.ts @@ -29,6 +29,7 @@ describe('INBOX: ', () => { const notIntegrityProtectedSender = CommonData.notIntegrityProtected.senderName; const notIntegrityProtectedText = CommonData.notIntegrityProtected.message; + const spoofedEmailSubject = 'Test Spoofed email by Mart'; const keyMismatchSubject = CommonData.keyMismatch.subject; const keyMismatchName = CommonData.keyMismatch.senderName; const keyMismatchText = CommonData.keyMismatch.message; @@ -49,6 +50,7 @@ describe('INBOX: ', () => { 'wrong checksum', 'not integrity protected - should show a warning and not decrypt automatically', 'key mismatch unexpectedly produces a modal', + spoofedEmailSubject, ], }); mockApi.attesterConfig = { @@ -83,6 +85,11 @@ describe('INBOX: ', () => { await EmailScreen.clickBackButton(); await MailFolderScreen.checkInboxScreen(); + // Check spoofed email to see if security warning block exists + await MailFolderScreen.clickOnEmailBySubject(spoofedEmailSubject); + await EmailScreen.checkSecurityWarningBlock(); + await EmailScreen.clickBackButton(); + // Checking error for wrong checksum message await MailFolderScreen.clickOnEmailBySubject(wrongChecksumSubject); await EmailScreen.checkOpenedEmail(wrongChecksumName, wrongChecksumSubject, wrongChecksumText); From 972ca6ac9db1a236d6cd0d64d3166f4e8c4754dd Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 30 Sep 2024 22:21:12 -0500 Subject: [PATCH 9/9] fix: removed unused test --- ...kSecurityWarningForSuspiciousEmail.spec.ts | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts diff --git a/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts b/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts deleted file mode 100644 index d57144958..000000000 --- a/appium/tests/specs/mock/inbox/CheckSecurityWarningForSuspiciousEmail.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { MockApi } from 'api-mocks/mock'; -import { MockApiConfig } from 'api-mocks/mock-config'; -import { SplashScreen, SetupKeyScreen, MailFolderScreen, EmailScreen } from '../../../screenobjects/all-screens'; - -describe('INBOX: ', () => { - it('user is able to view correct signature badge for all cases', async () => { - const mockApi = new MockApi(); - - const subject = 'Test Spoofed email by Mart'; - mockApi.fesConfig = MockApiConfig.defaultEnterpriseFesConfiguration; - mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration; - mockApi.addGoogleAccount('e2e.enterprise.test@flowcrypt.com', { - messages: [subject], - }); - - await mockApi.withMockedApis(async () => { - await SplashScreen.mockLogin(); - await SetupKeyScreen.setPassPhrase(); - await MailFolderScreen.checkInboxScreen(); - - await MailFolderScreen.clickOnEmailBySubject(subject); - - await EmailScreen.checkSecurityWarningBlock(); - }); - }); -});