Skip to content

Commit

Permalink
Malicious site protection - extract special error page navigation log…
Browse files Browse the repository at this point in the history
…ic (#3624)

Task/Issue URL: https://app.asana.com/0/1206329551987282/1208836682251611/f
Tech Design URL: https://app.asana.com/0/1206329551987282/1207273224076495/f

**Description**:
This PR extracts the navigation logic for special error pages in its own class.
This PR focuses on creating the the entites used to encapsulate the logic for SSL and Malicious site protection
  • Loading branch information
alessandroboron committed Dec 10, 2024
1 parent c8bf749 commit 8f39f6b
Show file tree
Hide file tree
Showing 21 changed files with 1,616 additions and 77 deletions.
128 changes: 127 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// MaliciousSiteProtectionManager.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

final class MaliciousSiteProtectionManager: MaliciousSiteDetecting {

func evaluate(_ url: URL) async -> ThreatKind? {
try? await Task.sleep(interval: 0.3)
return .none
}

}

// MARK: - To Remove

// These entities are copied from BSK and they will be used to mock the library
import SpecialErrorPages

protocol MaliciousSiteDetecting {
func evaluate(_ url: URL) async -> ThreatKind?
}

public enum ThreatKind: String, CaseIterable, CustomStringConvertible {
public var description: String { rawValue }

case phishing
case malware
}

public extension ThreatKind {

var errorPageType: SpecialErrorKind {
switch self {
case .malware: .phishing // WIP in BSK
case .phishing: .phishing
}
}

}
31 changes: 31 additions & 0 deletions DuckDuckGo/SpecialErrorPage/Model/SpecialErrorModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// SpecialErrorModel.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 SpecialErrorPages

struct SpecialErrorModel: Equatable {
let url: URL
let errorData: SpecialErrorData
}

struct SSLSpecialError {
let type: SSLErrorType
let error: SpecialErrorModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// SpecialErrorPageActionHandler.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

/// A type that defines actions for handling special error pages.
///
/// This protocol is intended to be adopted by types that need to manage user interactions
/// with special error pages, such as navigating to a site, leaving a site, or presenting
/// advanced information related to the error.
protocol SpecialErrorPageActionHandler {
/// Handles the action of navigating to the site associated with the error page
func visitSite()

/// Handles the action of leaving the site associated with the error page
func leaveSite()

/// Handles the action of requesting more detailed information about the error
func advancedInfoPresented()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// SpecialErrorPageContextHandling.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 WebKit
import SpecialErrorPages

/// A type that defines the base functionality for handling navigation related to special error pages.
protocol SpecialErrorPageContextHandling: AnyObject {
/// The delegate that handles navigation actions for special error pages.
var delegate: SpecialErrorPageNavigationDelegate? { get set }

/// A Boolean value indicating whether the special error page is currently visible.
var isSpecialErrorPageVisible: Bool { get }

/// The URL that failed to load, if any.
var failedURL: URL? { get }

/// Attaches a web view to the special error page handling.
func attachWebView(_ webView: WKWebView)

/// Sets the user script for the special error page.
func setUserScript(_ userScript: SpecialErrorPageUserScript?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// SpecialErrorPageNavigationDelegate.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

/// A delegate for handling navigation actions related to special error pages.
protocol SpecialErrorPageNavigationDelegate: AnyObject {
/// Asks the delegate to close the special error page tab when the web view can't navigate back.
func closeSpecialErrorPageTab()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// WebViewNavigationHandling.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 WebKit

// MARK: - WebViewNavigation

/// For testing purposes.
protocol WebViewNavigation {}

// Used in tests. WKNavigation() crashes on deinit when initialising it manually.
// As workaround we used to Swizzle the implementation of deinit in tests.
// The problem with that approach is that when running different test suites it is possible that unrelated tests re-set the original implementation of deinit while other tests are running.
// This cause the app to crash as the original implementation is executed.
// Defining a protocol for WKNavigation and using mocks such as DummyWKNavigation in tests resolves the problem.
extension WKNavigation: WebViewNavigation {}

// MARK: - WebViewNavigationHandling

/// A protocol that defines methods for handling navigation events of `WKWebView`.
protocol WebViewNavigationHandling: AnyObject {
/// Decides whether to cancel navigation to prevent opening a site and show a special error page based on the specified action information.
///
/// - Parameters:
/// - navigationAction: Details about the action that triggered the navigation request.
/// - webView: The web view from which the navigation request began.
/// - Returns: A Boolean value that indicates whether the navigation action was handled.
func handleSpecialErrorNavigation(navigationAction: WKNavigationAction, webView: WKWebView) async -> Bool

/// Handles authentication challenges received by the web view.
///
/// - Parameters:
/// - webView: The web view that receives the authentication challenge.
/// - challenge: The authentication challenge.
/// - completionHandler: A completion handler block to execute with the response.
func handleWebView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

/// Handles failures during provisional navigation.
///
/// - Parameters:
/// - webView: The `WKWebView` instance that failed the navigation.
/// - navigation: The navigation object for the operation.
/// - error: The error that occurred.
func handleWebView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WebViewNavigation, withError error: NSError)

/// Handles the successful completion of a navigation in the web view.
///
/// - Parameters:
/// - webView: The web view that loaded the content.
/// - navigation: The navigation object that finished.
func handleWebView(_ webView: WKWebView, didFinish navigation: WebViewNavigation)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// SpecialErrorPageNavigationHandler+MaliciousSite.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 BrowserServicesKit
import Core
import SpecialErrorPages
import WebKit

enum MaliciousSiteProtectionNavigationResult: Equatable {
case navigationHandled(SpecialErrorModel)
case navigationNotHandled
}

protocol MaliciousSiteProtectionNavigationHandling: AnyObject {
/// Decides whether to cancel navigation to prevent opening the YouTube app from the web view.
///
/// - Parameters:
/// - navigationAction: The navigation action to evaluate.
/// - webView: The web view where navigation is occurring.
/// - Returns: `true` if the navigation should be canceled, `false` otherwise.
func handleMaliciousSiteProtectionNavigation(for navigationAction: WKNavigationAction, webView: WKWebView) async -> MaliciousSiteProtectionNavigationResult
}

final class MaliciousSiteProtectionNavigationHandler {
private let maliciousSiteProtectionManager: MaliciousSiteDetecting
private let storageCache: StorageCache

init(
maliciousSiteProtectionManager: MaliciousSiteDetecting = MaliciousSiteProtectionManager(),
storageCache: StorageCache = AppDependencyProvider.shared.storageCache
) {
self.maliciousSiteProtectionManager = maliciousSiteProtectionManager
self.storageCache = storageCache
}
}

// MARK: - MaliciousSiteProtectionNavigationHandling

extension MaliciousSiteProtectionNavigationHandler: MaliciousSiteProtectionNavigationHandling {

@MainActor
func handleMaliciousSiteProtectionNavigation(for navigationAction: WKNavigationAction, webView: WKWebView) async -> MaliciousSiteProtectionNavigationResult {
// Implement logic to use `maliciousSiteProtectionManager.evaluate(url)`
// Return navigationNotHandled for the time being
return .navigationNotHandled
}

}

// MARK: - SpecialErrorPageActionHandler

extension MaliciousSiteProtectionNavigationHandler: SpecialErrorPageActionHandler {

func visitSite() {
// Fire Pixel
}

func leaveSite() {
// Fire Pixel
}

func advancedInfoPresented() {
// Fire Pixel
}

}
Loading

0 comments on commit 8f39f6b

Please sign in to comment.