From 69e5ec95f84d31100a5e2006f355a3276f964e7b Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Wed, 11 Dec 2024 12:24:56 +0100 Subject: [PATCH 1/2] Update privacy icon for special error pages --- DuckDuckGo/OmniBar.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 } From 014ea134126b342fe62007775574d6cf9cb61c6b Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Wed, 11 Dec 2024 14:32:40 +0100 Subject: [PATCH 2/2] Update Privacy Dashboard and omnibar icon for malicious websites --- 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/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 +++++++ 17 files changed, 201 insertions(+), 21 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 32d078fad0..ac95549fc2 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -775,6 +775,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 */; }; @@ -2645,6 +2646,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 = ""; }; @@ -5129,6 +5131,7 @@ 9F254B002CF9FA8D0063B308 /* SpecialErrorPageActionHandler.swift */, 9F254B022CF9FB2E0063B308 /* SpecialErrorPageNavigationDelegate.swift */, 9F254B042CF9FB890063B308 /* SpecialErrorPageContextHandling.swift */, + 9F38A28B2D09BDE500EB100E /* SpecialErrorPageThreatProvider.swift */, ); path = SpecialErrorPageInterfaces; sourceTree = ""; @@ -8150,6 +8153,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 */, @@ -11857,8 +11861,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 221.3.0; + branch = "alessandro/malicious-site-protection-address-bar-and-privacy-dashboard"; + 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 4eaad818db..61471c4115 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" : "b71ed70ce9b0ef3ce51d4f96da0193ab70493944", - "version" : "221.3.0" + "branch" : "alessandro/malicious-site-protection-address-bar-and-privacy-dashboard", + "revision" : "5bc2e8b352103675dcc038d450d936d34a4101d6" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "022c845b06ace6a4aa712a4fa3e79da32193d5c6", - "version" : "7.4.0" + "branch" : "pr-releases/pr-252", + "revision" : "8b9ae746ea7bb2e46588fd4b1a81efabf69c20a4" } }, { 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/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 91c14f6695..3b92191aed 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 createMaliciousSiteDetectionTask(for navigationAction: WKNavigationAction, webView: WKWebView) { diff --git a/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift b/DuckDuckGo/SpecialErrorPage/SpecialErrorPageNavigationHandler.swift index c5d596ec77..7d45799a87 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 252b000f50..6887f438bb 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1064,6 +1064,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 96b5a06854..b166e299df 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..bb36674f60 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.handleDecidePolicyFor(navigationAction: navigationAction, webView: webView) + let response = MockNavigationResponse.with(url: url) + _ = await sut.handleDecidePolicyfor(navigationResponse: 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 ccb5edb02c..145279f231 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 6ece26386b..2cf79c6a2c 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 createMaliciousSiteDetectionTask(for navigationAction: WKNavigationAction, webView: WKWebView) { didCallHandleMaliciousSiteProtectionForNavigationAction = true capturedNavigationAction = navigationAction