From cd64847d5af6af76ec8e3ff206cde55c05cf3a5a Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Tue, 14 Jan 2025 14:25:52 +1100 Subject: [PATCH] Malicious site protection address bar and privacy dashboard changes (#3718) Task/Issue URL: https://app.asana.com/0/1206329551987282/1208959082985728/f Tech Design: https://app.asana.com/0/1206329551987282/1207273224076495/f **Description**: This PR addresses the following: 1. Updates the Privacy Icon to use the globe asset when visiting special error pages (SSL error included). 2. Updates the Privacy icon to use the alert asset when the user accepts the risk of visiting a malicious page. 3. Show an updated privacy dashboard for phishing and malware special error pages. --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++- .../xcshareddata/swiftpm/Package.resolved | 8 ++-- .../Alert-Recolorable-24.pdf | Bin 0 -> 1064 bytes .../Alert-Color-24.imageset/Contents.json | 12 ++++++ DuckDuckGo/Base.lproj/OmniBar.xib | 7 ++-- DuckDuckGo/OmniBar.swift | 10 ++++- DuckDuckGo/PrivacyIconLogic.swift | 2 + DuckDuckGo/PrivacyIconView.swift | 27 ++++++++++---- .../SpecialErrorPageContextHandling.swift | 2 +- .../SpecialErrorPageThreatProvider.swift | 29 +++++++++++++++ ...rPageNavigationHandler+MaliciousSite.swift | 7 +++- .../SpecialErrorPageNavigationHandler.swift | 4 ++ DuckDuckGo/TabViewController.swift | 1 + DuckDuckGoTests/PrivacyIconLogicTests.swift | 26 +++++++++++++ ...SiteProtectionNavigationHandlerTests.swift | 21 +++++++++++ ...ageNavigationHandlerIntegrationTests.swift | 35 +++++++++++++++++- ...ecialErrorPageNavigationHandlerTests.swift | 20 ++++++++++ ...ciousSiteProtectionNavigationHandler.swift | 13 +++++++ 18 files changed, 209 insertions(+), 23 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Alert-Recolorable-24.pdf create mode 100644 DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Contents.json create mode 100644 DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageThreatProvider.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 91ff0fbb48..9a2354de29 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -799,6 +799,7 @@ 9F254B032CF9FB2E0063B308 /* SpecialErrorPageNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F254B022CF9FB2E0063B308 /* SpecialErrorPageNavigationDelegate.swift */; }; 9F254B052CF9FB890063B308 /* SpecialErrorPageContextHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F254B042CF9FB890063B308 /* SpecialErrorPageContextHandling.swift */; }; 9F254B082CF9FC270063B308 /* SpecialErrorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F254B072CF9FC270063B308 /* SpecialErrorModel.swift */; }; + 9F38A28C2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F38A28B2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift */; }; 9F46BEF82CD8D7490092E0EF /* OnboardingView+AddToDockContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F46BEF72CD8D7490092E0EF /* OnboardingView+AddToDockContent.swift */; }; 9F4CC5152C47AD08006A96EB /* ContextualOnboardingPresenterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC5142C47AD08006A96EB /* ContextualOnboardingPresenterMock.swift */; }; 9F4CC5172C48B8D4006A96EB /* TabViewControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC5162C48B8D4006A96EB /* TabViewControllerDaxDialogTests.swift */; }; @@ -2694,6 +2695,7 @@ 9F254B022CF9FB2E0063B308 /* SpecialErrorPageNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageNavigationDelegate.swift; sourceTree = ""; }; 9F254B042CF9FB890063B308 /* SpecialErrorPageContextHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageContextHandling.swift; sourceTree = ""; }; 9F254B072CF9FC270063B308 /* SpecialErrorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorModel.swift; sourceTree = ""; }; + 9F38A28B2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageThreatProvider.swift; sourceTree = ""; }; 9F46BEF72CD8D7490092E0EF /* OnboardingView+AddToDockContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AddToDockContent.swift"; sourceTree = ""; }; 9F4CC5142C47AD08006A96EB /* ContextualOnboardingPresenterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingPresenterMock.swift; sourceTree = ""; }; 9F4CC5162C48B8D4006A96EB /* TabViewControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerDaxDialogTests.swift; sourceTree = ""; }; @@ -5230,6 +5232,7 @@ 9F254B002CF9FA8D0063B308 /* SpecialErrorPageActionHandler.swift */, 9F254B022CF9FB2E0063B308 /* SpecialErrorPageNavigationDelegate.swift */, 9F254B042CF9FB890063B308 /* SpecialErrorPageContextHandling.swift */, + 9F38A28B2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift */, ); path = SpecialErrorPageInterfaces; sourceTree = ""; @@ -8285,6 +8288,7 @@ F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */, EEC02C142B0519DE0045CE11 /* NetworkProtectionVPNLocationViewModel.swift in Sources */, D63FF8962C1B67E9006DE24D /* YoutubeOverlayUserScript.swift in Sources */, + 9F38A28C2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift in Sources */, F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */, 8598D2E02CEB98B500C45685 /* Favicons.swift in Sources */, 8598D2E12CEB98B500C45685 /* NotFoundCachingDownloader.swift in Sources */, @@ -12003,8 +12007,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 224.1.0; + branch = "alessandro/malicious-site-protection-ios"; + kind = branch; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b0bbd57449..27392ff148 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "14384f05a6e43842c4626e06736e2c4f5d758057", - "version" : "224.1.0" + "branch" : "alessandro/malicious-site-protection-ios", + "revision" : "797518c8dab1455af63f64844a202097c0ff8e40" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "2e2baf7d31c7d8e158a58bc1cb79498c1c727fd2", - "version" : "7.5.0" + "revision" : "bea4d750913ef82c10cd06e791686501c8e648e4", + "version" : "7.6.0" } }, { diff --git a/DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Alert-Recolorable-24.pdf b/DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Alert-Recolorable-24.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9d4374e614efc699f59d9a5984e328ac91b058e1 GIT binary patch literal 1064 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f^q3&Z?9HEp4R8FuI3467q9mT zEm)(sP=iaW&v~7!-E}gP1_XAf`u}Si?zfR)TI$xi>u!=1HJ@sAwCFQ0@ z+uN+-+e+$6_1|5#jj_q(O7fYrcP_``c`xl9Os*8jTcx1gJKL4@y3?s@Mt%I zivp8_zGGfqeo3)HbSyNoLn;eW74!oV(_yjio0^iD=#*cf5UpUKX8;BWhLH(O2*HAx z6qH(=Us{x$TC4y{fS{BEN=(lAc_pcNKpR1+4<;CpSOgSPFog;srBRR&JniaxXQlw1 zssQpqkOIse=loKjUPBD`7emr2EHKPaOaghZ7;YiRVGxfyCzd4Uni`ub0EMBz&_DsqQpke~83Wx30tg{fL!f`qgpAG6h0HO{GPgjORZ^6g znUh+?1&TdS7hrH`6zAurYAR@Crf5O}ML#G%zeEA#VQ>KJ2WM8L0$mTzxQRt2VDDI% M7;~wry862T08{N>*#H0l literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Contents.json new file mode 100644 index 0000000000..c54ece994d --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/DesignSystemIcons/24px/Alert-Color-24.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Alert-Recolorable-24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Base.lproj/OmniBar.xib b/DuckDuckGo/Base.lproj/OmniBar.xib index 96af3fcd2b..e227185855 100644 --- a/DuckDuckGo/Base.lproj/OmniBar.xib +++ b/DuckDuckGo/Base.lproj/OmniBar.xib @@ -166,7 +166,7 @@ - + @@ -185,9 +185,9 @@ - + @@ -458,7 +458,6 @@ - @@ -467,7 +466,7 @@ - + diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 6f20b39507..c29d57c6c8 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -29,6 +29,7 @@ extension OmniBar: NibLoading {} public enum OmniBarIcon: String { case duckPlayer = "DuckPlayerURLIcon" + case specialError = "Globe-24" } class OmniBar: UIView { @@ -300,10 +301,15 @@ class OmniBar: UIView { showCustomIcon(icon: .duckPlayer) return } - - privacyInfoContainer.privacyIcon.isHidden = privacyInfo.isSpecialErrorPageVisible + + if privacyInfo.isSpecialErrorPageVisible { + showCustomIcon(icon: .specialError) + return + } + let icon = PrivacyIconLogic.privacyIcon(for: privacyInfo) privacyInfoContainer.privacyIcon.updateIcon(icon) + privacyInfoContainer.privacyIcon.isHidden = false customIconView.isHidden = true } diff --git a/DuckDuckGo/PrivacyIconLogic.swift b/DuckDuckGo/PrivacyIconLogic.swift index 727d46480e..66af79832c 100644 --- a/DuckDuckGo/PrivacyIconLogic.swift +++ b/DuckDuckGo/PrivacyIconLogic.swift @@ -34,6 +34,8 @@ final class PrivacyIconLogic { static func privacyIcon(for privacyInfo: PrivacyInfo) -> PrivacyIcon { if privacyInfo.url.isDuckDuckGoSearch { return .daxLogo + } else if privacyInfo.malicousSiteThreatKind != .none { + return .alert } else { let config = ContentBlocking.shared.privacyConfigurationManager.privacyConfig let isUserUnprotected = config.isUserUnprotected(domain: privacyInfo.url.host) diff --git a/DuckDuckGo/PrivacyIconView.swift b/DuckDuckGo/PrivacyIconView.swift index 41bde7323d..565255bb90 100644 --- a/DuckDuckGo/PrivacyIconView.swift +++ b/DuckDuckGo/PrivacyIconView.swift @@ -22,12 +22,20 @@ import UIKit import Lottie enum PrivacyIcon { - case daxLogo, shield, shieldWithDot + case daxLogo, shield, shieldWithDot, alert + + fileprivate var staticImage: UIImage? { + switch self { + case .daxLogo: return UIImage(resource: .logoIcon) + case .alert: return UIImage(resource: .alertColor24) + default: return nil + } + } } class PrivacyIconView: UIView { - @IBOutlet var daxLogoImageView: UIImageView! + @IBOutlet var staticImageView: UIImageView! @IBOutlet var staticShieldAnimationView: LottieAnimationView! @IBOutlet var staticShieldDotAnimationView: LottieAnimationView! @@ -91,16 +99,17 @@ class PrivacyIconView: UIView { private func updateShieldImageView(for icon: PrivacyIcon) { switch icon { - case .daxLogo: - daxLogoImageView.isHidden = false + case .daxLogo, .alert: + staticImageView.isHidden = false + staticImageView.image = icon.staticImage staticShieldAnimationView.isHidden = true staticShieldDotAnimationView.isHidden = true case .shield: - daxLogoImageView.isHidden = true + staticImageView.isHidden = true staticShieldAnimationView.isHidden = false staticShieldDotAnimationView.isHidden = true case .shieldWithDot: - daxLogoImageView.isHidden = true + staticImageView.isHidden = true staticShieldAnimationView.isHidden = true staticShieldDotAnimationView.isHidden = false } @@ -116,6 +125,10 @@ class PrivacyIconView: UIView { accessibilityLabel = UserText.privacyIconShield accessibilityHint = UserText.privacyIconOpenDashboardHint accessibilityTraits = .button + case .alert: + accessibilityLabel = UserText.privacyIconShield + accessibilityHint = UserText.privacyIconOpenDashboardHint + accessibilityTraits = .button } } @@ -134,7 +147,7 @@ class PrivacyIconView: UIView { staticShieldAnimationView.isHidden = true staticShieldDotAnimationView.isHidden = true - daxLogoImageView.isHidden = true + staticImageView.isHidden = true } func shieldAnimationView(for icon: PrivacyIcon) -> LottieAnimationView? { diff --git a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageContextHandling.swift b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageContextHandling.swift index 6719cb0876..445f3691c0 100644 --- a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageContextHandling.swift +++ b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageContextHandling.swift @@ -22,7 +22,7 @@ import WebKit import SpecialErrorPages /// A type that defines the base functionality for handling navigation related to special error pages. -protocol SpecialErrorPageContextHandling: AnyObject { +protocol SpecialErrorPageContextHandling: SpecialErrorPageThreatProvider { /// The delegate that handles navigation actions for special error pages. var delegate: SpecialErrorPageNavigationDelegate? { get set } diff --git a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageThreatProvider.swift b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageThreatProvider.swift new file mode 100644 index 0000000000..3a867d9b3f --- /dev/null +++ b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageInterfaces/SpecialErrorPageThreatProvider.swift @@ -0,0 +1,29 @@ +// +// SpecialErrorPageThreatProvider.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MaliciousSiteProtection + +protocol SpecialErrorPageThreatProvider: AnyObject { + /// Provides the current threat kind detected. + /// + /// - Returns: An optional `ThreatKind` that indicates the current threat type, or `nil` if no threat is detected. + @MainActor + var currentThreatKind: ThreatKind? { get } +} diff --git a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler+MaliciousSite.swift b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler+MaliciousSite.swift index b63914ef26..5ba6553d11 100644 --- a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler+MaliciousSite.swift +++ b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler+MaliciousSite.swift @@ -34,7 +34,7 @@ enum MaliciousSiteProtectionNavigationResult: Equatable { } } -protocol MaliciousSiteProtectionNavigationHandling: AnyObject { +protocol MaliciousSiteProtectionNavigationHandling: SpecialErrorPageThreatProvider { /// Creates a task for detecting malicious sites based on the provided navigation action. /// /// - Parameters: @@ -78,6 +78,11 @@ final class MaliciousSiteProtectionNavigationHandler { extension MaliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandling { + @MainActor + var currentThreatKind: ThreatKind? { + bypassedMaliciousSiteThreatKind + } + @MainActor func makeMaliciousSiteDetectionTask(for navigationAction: WKNavigationAction, webView: WKWebView) { diff --git a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift index b7f05e26c3..99b6f2c2f5 100644 --- a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift +++ b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift @@ -38,6 +38,10 @@ final class SpecialErrorPageNavigationHandler: SpecialErrorPageContextHandling { private let sslErrorPageNavigationHandler: SSLSpecialErrorPageNavigationHandling & SpecialErrorPageActionHandler private let maliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandling & SpecialErrorPageActionHandler + var currentThreatKind: ThreatKind? { + maliciousSiteProtectionNavigationHandler.currentThreatKind + } + init( sslErrorPageNavigationHandler: SSLSpecialErrorPageNavigationHandling & SpecialErrorPageActionHandler = SSLErrorPageNavigationHandler(), maliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandling & SpecialErrorPageActionHandler = MaliciousSiteProtectionNavigationHandler() diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 9262c04dee..12abb532be 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1060,6 +1060,7 @@ class TabViewController: UIViewController { let privacyInfo = PrivacyInfo(url: url, parentEntity: entity, protectionStatus: makeProtectionStatus(for: host), + malicousSiteThreatKind: specialErrorPageNavigationHandler.currentThreatKind, shouldCheckServerTrust: shouldCheckServerTrust) let isValid = certificateTrustEvaluator.evaluateCertificateTrust(trust: webView.serverTrust) if let isValid { diff --git a/DuckDuckGoTests/PrivacyIconLogicTests.swift b/DuckDuckGoTests/PrivacyIconLogicTests.swift index b12ef295e8..8a2cfcf801 100644 --- a/DuckDuckGoTests/PrivacyIconLogicTests.swift +++ b/DuckDuckGoTests/PrivacyIconLogicTests.swift @@ -118,6 +118,32 @@ class PrivacyIconLogicTests: XCTestCase { XCTAssertEqual(icon, .shield) } + func testWhenPrivacyIconThreatKindIsPhishingThenPrivacyIconIsAlert() { + // GIVEN + let url = PrivacyIconLogicTests.pageURL + let protectionStatus = ProtectionStatus(unprotectedTemporary: false, enabledFeatures: [], allowlisted: true, denylisted: false) + let privacyInfo = PrivacyInfo(url: url, parentEntity: nil, protectionStatus: protectionStatus, malicousSiteThreatKind: .phishing) + + // WHEN + let icon = PrivacyIconLogic.privacyIcon(for: privacyInfo) + + // THEN + XCTAssertEqual(icon, .alert) + } + + func testWhenPrivacyIconThreatKindIsMalwareThenPrivacyIconIsAlert() { + // GIVEN + let url = PrivacyIconLogicTests.pageURL + let protectionStatus = ProtectionStatus(unprotectedTemporary: false, enabledFeatures: [], allowlisted: true, denylisted: false) + let privacyInfo = PrivacyInfo(url: url, parentEntity: nil, protectionStatus: protectionStatus, malicousSiteThreatKind: .malware) + + // WHEN + let icon = PrivacyIconLogic.privacyIcon(for: privacyInfo) + + // THEN + XCTAssertEqual(icon, .alert) + } + } final class MockSecTrust: SecurityTrust {} diff --git a/DuckDuckGoTests/SpecialErrorPage/MaliciousSiteProtectionNavigationHandlerTests.swift b/DuckDuckGoTests/SpecialErrorPage/MaliciousSiteProtectionNavigationHandlerTests.swift index b888d3f7f3..5cd39d8019 100644 --- a/DuckDuckGoTests/SpecialErrorPage/MaliciousSiteProtectionNavigationHandlerTests.swift +++ b/DuckDuckGoTests/SpecialErrorPage/MaliciousSiteProtectionNavigationHandlerTests.swift @@ -196,6 +196,27 @@ struct MaliciousSiteProtectionNavigationHandlerTests { #expect(sut.bypassedMaliciousSiteThreatKind == threat) } + @MainActor + @Test( + "Threat Kind Returns right value", + arguments: [ + ThreatKind.phishing, + .malware + ] + ) + func whenThreatKindIsCalledReturnRightValue(threat: ThreatKind) throws { + // GIVEN + let url = try #require(URL(string: "https://www.example.com")) + let error = SpecialErrorData.maliciousSite(kind: threat, url: url) + sut.visitSite(url: url, errorData: error) + + // WHEN + let result = sut.currentThreatKind + + // THEN + #expect(result == threat) + } + @Test("Leave Site Pixel", .disabled("Will be implmented in upcoming PR")) func whenLeaveSiteActionThenFirePixel() throws { diff --git a/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerIntegrationTests.swift b/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerIntegrationTests.swift index f655b398bb..940614617d 100644 --- a/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerIntegrationTests.swift +++ b/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerIntegrationTests.swift @@ -20,13 +20,15 @@ import Testing import WebKit import SpecialErrorPages +import MaliciousSiteProtection @testable import DuckDuckGo -@Suite("Special Error Pages - SSL Integration Tests", .serialized) +@Suite("Special Error Pages - Integration Tests", .serialized) final class SpecialErrorPageNavigationHandlerIntegrationTests { private var sut: SpecialErrorPageNavigationHandler! private var webView: MockSpecialErrorWebView! private var sslErrorPageNavigationHandler: SSLErrorPageNavigationHandler! + private var maliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandler! @MainActor init() { @@ -34,14 +36,16 @@ final class SpecialErrorPageNavigationHandlerIntegrationTests { featureFlagger.enabledFeatureFlags = [.sslCertificatesBypass] webView = MockSpecialErrorWebView(frame: CGRect(), configuration: .nonPersistent()) sslErrorPageNavigationHandler = SSLErrorPageNavigationHandler(featureFlagger: featureFlagger) + maliciousSiteProtectionNavigationHandler = MaliciousSiteProtectionNavigationHandler() sut = SpecialErrorPageNavigationHandler( sslErrorPageNavigationHandler: sslErrorPageNavigationHandler, - maliciousSiteProtectionNavigationHandler: MockMaliciousSiteProtectionNavigationHandler() + maliciousSiteProtectionNavigationHandler: maliciousSiteProtectionNavigationHandler ) } deinit { sslErrorPageNavigationHandler = nil + maliciousSiteProtectionNavigationHandler = nil sut = nil webView = nil } @@ -249,4 +253,31 @@ final class SpecialErrorPageNavigationHandlerIntegrationTests { // THEN #expect(script.isEnabled) } + + @MainActor + @Test( + "Test Current Threat Kind Returns Threat Kind", + arguments: [ + ("www.example.com", nil), + ("http://privacy-test-pages.site/security/badware/phishing.html", ThreatKind.phishing), + ("http://privacy-test-pages.site/security/badware/malware.html", .malware), + ] + ) + func whenCurrentThreatKindIsCalledThenAskMaliciousSiteProtectionNavigationHandlerForThreatKind(threatInfo: (path: String, threat: ThreatKind?)) async throws { + // GIVEN + let url = try #require(URL(string: threatInfo.path)) + webView.setCurrentURL(url) + sut.attachWebView(webView) + let navigationAction = MockNavigationAction(request: URLRequest(url: url)) + sut.handleDecidePolicy(for: navigationAction, webView: webView) + let response = MockNavigationResponse.with(url: url) + _ = await sut.handleDecidePolicy(for: response, webView: webView) + sut.visitSiteAction() + + // WHEN + let result = sut.currentThreatKind + + // THEN + #expect(result == threatInfo.threat) + } } diff --git a/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerTests.swift b/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerTests.swift index 33710032d5..861d3d8641 100644 --- a/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerTests.swift +++ b/DuckDuckGoTests/SpecialErrorPage/SpecialErrorPageNavigationHandlerTests.swift @@ -422,6 +422,26 @@ final class SpecialErrorPageNavigationHandlerTests { // THEN #expect(maliciousSiteProtectionNavigationHandler.didCallAdvancedInfoPresented) } + + @MainActor + @Test( + "Test Current Threat Kind asks Malicous forward event to Malicious Site Protection Navigation Handler", + arguments: [ + ThreatKind.phishing, + .malware, + nil + ] + ) + func whenCurrentThreatKindIsCalledThenAskMaliciousSiteProtectionNavigationHandlerForThreatKind(threat: ThreatKind?) throws { + // GIVEN + #expect(!maliciousSiteProtectionNavigationHandler.didCallCurrentThreatKind) + + // WHEN + let result = sut.currentThreatKind + + // THEN + #expect(maliciousSiteProtectionNavigationHandler.didCallCurrentThreatKind) + } } private extension NSError { diff --git a/DuckDuckGoTests/SpecialErrorPage/TestDoubles/MockMaliciousSiteProtectionNavigationHandler.swift b/DuckDuckGoTests/SpecialErrorPage/TestDoubles/MockMaliciousSiteProtectionNavigationHandler.swift index a861c7c247..ab5867b87c 100644 --- a/DuckDuckGoTests/SpecialErrorPage/TestDoubles/MockMaliciousSiteProtectionNavigationHandler.swift +++ b/DuckDuckGoTests/SpecialErrorPage/TestDoubles/MockMaliciousSiteProtectionNavigationHandler.swift @@ -20,9 +20,11 @@ import Foundation import WebKit import SpecialErrorPages +import MaliciousSiteProtection @testable import DuckDuckGo final class MockMaliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandling & SpecialErrorPageActionHandler { + private(set) var didCallCurrentThreatKind = false private(set) var didCallHandleMaliciousSiteProtectionForNavigationAction = false private(set) var capturedNavigationAction: WKNavigationAction? private(set) var capturedWebView: WKWebView? @@ -40,6 +42,17 @@ final class MockMaliciousSiteProtectionNavigationHandler: MaliciousSiteProtectio var task: Task? + private var _currentThreatKind: ThreatKind? + var currentThreatKind: ThreatKind? { + get { + didCallCurrentThreatKind = true + return _currentThreatKind + } + set { + _currentThreatKind = newValue + } + } + func makeMaliciousSiteDetectionTask(for navigationAction: WKNavigationAction, webView: WKWebView) { didCallHandleMaliciousSiteProtectionForNavigationAction = true capturedNavigationAction = navigationAction