Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malware protection 6: Malware integration #1099

Merged
merged 75 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
4b8a1f0
rename PhishingDetection to MaliciousSiteProtection
mallexxx Nov 22, 2024
7c1df2a
`navigateTo(url` -> `navigateFromOnboarding(to url)`; fix build
mallexxx Nov 22, 2024
a7b26cc
fix linter issues
mallexxx Nov 22, 2024
0f1c065
fix tests build
mallexxx Nov 22, 2024
2e72aae
MaliciousSiteError: Equatable
mallexxx Nov 22, 2024
9a6efec
fix build
mallexxx Nov 22, 2024
180ecc6
cleanup todos
mallexxx Nov 25, 2024
993a45f
minor cleanup
mallexxx Nov 25, 2024
50ed41c
refactor APIClient
mallexxx Nov 25, 2024
9a32e36
fix linter issues
mallexxx Nov 25, 2024
b2bdcc7
fix failing test
mallexxx Nov 25, 2024
ed8ebb9
Refactor Data storing
mallexxx Nov 26, 2024
1509f0c
add missing public and var
mallexxx Nov 26, 2024
e89a467
Refactor update manager
mallexxx Nov 26, 2024
5f32de0
cleanup
mallexxx Nov 26, 2024
c19e6f6
cleanup
mallexxx Nov 26, 2024
0c45e98
Refactor Special Error Types
mallexxx Nov 27, 2024
31a4888
fix build
mallexxx Nov 27, 2024
a05423b
fix linter issues
mallexxx Nov 27, 2024
cd9b1a0
remove TODO
mallexxx Nov 27, 2024
5c07924
fix linter issue
mallexxx Nov 27, 2024
0638803
define SSLErrorCodeKey const
mallexxx Nov 27, 2024
a76c632
fix typo
mallexxx Nov 27, 2024
c82310e
fix linter issue
mallexxx Nov 27, 2024
32fd9d9
Add malware threat, special page kinds
mallexxx Nov 27, 2024
0f3e7ba
fix API hash mathing
mallexxx Nov 27, 2024
587859e
fix retain cycle in SpecialErrorPageUserScript
mallexxx Nov 27, 2024
515df1c
fix double config refresh at the start in DefaultConfigurationManager
mallexxx Nov 27, 2024
e6d3d57
fix linter issues
mallexxx Nov 27, 2024
cc07dd9
assign threatKind before returning
mallexxx Nov 27, 2024
9a392fe
Merge branch 'alex/malware-protection-3' into alex/malware-protection-4
mallexxx Nov 27, 2024
318619a
Merge branch 'alex/malware-protection-4' into alex/malware-protection-5
mallexxx Nov 27, 2024
a83e346
Merge branch 'alex/malware-protection-5' into alex/malware-protection-6
mallexxx Nov 27, 2024
620ce5b
WKError.isWebContentProcessTerminated; onChangeMalwareStatus
mallexxx Nov 27, 2024
056f37d
remove .dev api client config; change privacy dashboard script interface
mallexxx Nov 28, 2024
6def39a
remove api client .dev env
mallexxx Nov 28, 2024
1c969fe
temporarily disable early return after checking api matches
mallexxx Nov 28, 2024
47d9dae
fix typo
mallexxx Nov 28, 2024
6e1c710
add a comment
mallexxx Nov 28, 2024
a2d11c0
Improve Type naming
mallexxx Nov 28, 2024
f4b01a7
Update type naming
mallexxx Nov 28, 2024
89488e0
Improve naming and types visibility
mallexxx Nov 28, 2024
0855491
Evaluate local filters first before making the API call; Improve docs
mallexxx Nov 28, 2024
e18392f
fix linter issues
mallexxx Nov 28, 2024
5975454
fix iOS compatibility
mallexxx Nov 28, 2024
694a945
Int32
mallexxx Nov 28, 2024
b65fd02
Int32
mallexxx Nov 28, 2024
c37f175
more info
mallexxx Nov 28, 2024
49f022e
Malware protection 2: refactor APIClient (#1092)
mallexxx Nov 28, 2024
e2ef57c
Merge remote-tracking branch 'origin/main' into alex/malware-protecti…
mallexxx Nov 28, 2024
32064a8
Merge branch 'alex/malware-protection-1' into alex/malware-protection-3
mallexxx Nov 28, 2024
5349ce1
Merge branch 'alex/malware-protection-3' into alex/malware-protection-4
mallexxx Nov 28, 2024
1e5cdc9
Merge branch 'alex/malware-protection-4' into alex/malware-protection-5
mallexxx Nov 28, 2024
fc7bfa0
Merge branch 'alex/malware-protection-5' into alex/malware-protection-6
mallexxx Nov 28, 2024
ba5ac55
Malware protection 3: Refactor Data storing (#1093)
mallexxx Nov 28, 2024
e48c6df
Merge branch 'alex/malware-protection-1' into alex/malware-protection-5
mallexxx Nov 28, 2024
1e046d1
Merge branch 'alex/malware-protection-5' into alex/malware-protection-6
mallexxx Nov 29, 2024
5c7306a
cleanup
mallexxx Nov 29, 2024
22f3d8f
remove MaliciousSiteProtectionSubfeature.allowPreferencesToggle
mallexxx Nov 29, 2024
b82bb45
Merge branch 'alex/malware-protection-5' into alex/malware-protection-6
mallexxx Nov 29, 2024
dfaf4be
check for positive update interval
mallexxx Nov 29, 2024
ccef5ee
more debug info
mallexxx Nov 29, 2024
ed19ac8
set Matches request timeout to 1
mallexxx Nov 29, 2024
be3293e
allow mocking APIService
mallexxx Nov 29, 2024
4aa6744
allow mocking api service
mallexxx Nov 29, 2024
eef6a09
rollback APIClient debugging changes
mallexxx Nov 29, 2024
fe6ed95
Merge branch 'alex/malware-protection-5' into alex/malware-protection-6
mallexxx Dec 2, 2024
fb335bd
fix test
mallexxx Dec 2, 2024
fcbe2c2
fix test
mallexxx Dec 2, 2024
30bc650
Merge remote-tracking branch 'origin/main' into alex/malware-protecti…
mallexxx Dec 3, 2024
ef703e7
Merge remote-tracking branch 'origin/main' into alex/malware-protecti…
mallexxx Dec 6, 2024
2b92bca
increase matches api timeout to 10s
mallexxx Dec 6, 2024
684db7b
C-S-S and Dashboard version bump (#1121)
mgurgel Dec 6, 2024
a5bcba0
configurable timeout
mallexxx Dec 10, 2024
dcc05ce
Merge remote-tracking branch 'origin/main' into alex/malware-protecti…
mallexxx Dec 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/content-scope-scripts",
"state" : {
"revision" : "c4bb146afdf0c7a93fb9a7d95b1cb255708a470d",
"version" : "6.41.0"
"revision" : "93ea6c3e771bc0b743b38cefbff548c10add9898",
"version" : "6.42.0"
}
},
{
Expand Down Expand Up @@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/privacy-dashboard",
"state" : {
"revision" : "49db79829dcb166b3524afdbc1c680890452ce1c",
"version" : "7.2.1"
"revision" : "022c845b06ace6a4aa712a4fa3e79da32193d5c6",
"version" : "7.4.0"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ let package = Package(
.package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "3.0.0"),
.package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.3.0"),
.package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "3.0.0"),
.package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "6.41.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "7.2.1"),
.package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "6.42.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "7.4.0"),
.package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"),
.package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"),
.package(url: "https://github.com/1024jp/GzipSwift.git", exact: "6.0.1"),
Expand Down
3 changes: 2 additions & 1 deletion Sources/Configuration/DefaultConfigurationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ open class DefaultConfigurationManager: NSObject {

public func start() {
Logger.config.debug("Starting configuration refresh timer")
refreshTask = Task.periodic(interval: Constants.refreshCheckIntervalSeconds) { [weak self] in
refreshTask = Task.periodic(delay: Constants.refreshCheckIntervalSeconds, // add initial delay as we‘re firing `refreshNow`
interval: Constants.refreshCheckIntervalSeconds) { [weak self] in
Self.queue.async { [weak self] in
self?.lastRefreshCheckTime = Date()
self?.refreshIfNeeded()
Expand Down
8 changes: 7 additions & 1 deletion Sources/MaliciousSiteProtection/API/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ extension APIClient: APIClient.Mockable {}
public protocol APIClientEnvironment {
func headers(for requestType: APIRequestType) -> APIRequestV2.HeadersV2
func url(for requestType: APIRequestType) -> URL
func timeout(for requestType: APIRequestType) -> TimeInterval?
}

public extension APIClientEnvironment {
func timeout(for requestType: APIRequestType) -> TimeInterval? { nil }
}

public extension MaliciousSiteDetector {
Expand Down Expand Up @@ -100,8 +105,9 @@ struct APIClient {
let requestType = requestConfig.requestType
let headers = environment.headers(for: requestType)
let url = environment.url(for: requestType)
let timeout = environment.timeout(for: requestType) ?? requestConfig.defaultTimeout ?? 60

let apiRequest = APIRequestV2(url: url, method: .get, headers: headers, timeoutInterval: requestConfig.timeout ?? 60)
let apiRequest = APIRequestV2(url: url, method: .get, headers: headers, timeoutInterval: timeout)
let response = try await service.fetch(request: apiRequest)
let result: R.Response = try response.decodeBody()

Expand Down
6 changes: 3 additions & 3 deletions Sources/MaliciousSiteProtection/API/APIRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension APIClient {
protocol Request {
associatedtype Response: Decodable // Strongly-typed response type
var requestType: APIRequestType { get } // Enumerated type of request being made
var timeout: TimeInterval? { get }
var defaultTimeout: TimeInterval? { get }
}

// Protocol for requests that modify a set of malicious site detection data
Expand All @@ -40,7 +40,7 @@ extension APIClient {
}
}
extension APIClient.Request {
var timeout: TimeInterval? { nil }
var defaultTimeout: TimeInterval? { nil }
}

public extension APIRequestType {
Expand Down Expand Up @@ -101,7 +101,7 @@ public extension APIRequestType {
.matches(self)
}

var timeout: TimeInterval? { 1 }
var defaultTimeout: TimeInterval? { 10 }
}
}
/// extension to call generic `load(_: some Request)` method like this: `load(.matches(…))`
Expand Down
12 changes: 6 additions & 6 deletions Sources/MaliciousSiteProtection/Model/MaliciousSiteError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public struct MaliciousSiteError: Error, Equatable {

public enum Code: Int {
case phishing = 1
// case malware = 2
case malware = 2
}
public let code: Code
public let failingUrl: URL
Expand All @@ -37,16 +37,16 @@ public struct MaliciousSiteError: Error, Equatable {
switch threat {
case .phishing:
code = .phishing
// case .malware:
// code = .malware
case .malware:
code = .malware
}
self.init(code: code, failingUrl: failingUrl)
}

public var threatKind: ThreatKind {
switch code {
case .phishing: .phishing
// case .malware: .malware
case .malware: .malware
}
}

Expand All @@ -70,8 +70,8 @@ extension MaliciousSiteError: LocalizedError {
switch code {
case .phishing:
return "Phishing detected"
// case .malware:
// return "Malware detected"
case .malware:
return "Malware detected"
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/MaliciousSiteProtection/Model/ThreatKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public enum ThreatKind: String, CaseIterable, Codable, CustomStringConvertible {
public var description: String { rawValue }

case phishing
// case malware
case malware

}
4 changes: 4 additions & 0 deletions Sources/Navigation/Extensions/WKErrorExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
public var isServerCertificateUntrusted: Bool {
_nsError.isServerCertificateUntrusted
}

public var isWebContentProcessTerminated: Bool {
code == .webContentProcessTerminated && _nsError is WKError
}
}
extension NSError {
public var isServerCertificateUntrusted: Bool {
Expand All @@ -60,7 +64,7 @@
#endif
}

extension WKError: LocalizedError {

Check warning on line 67 in Sources/Navigation/Extensions/WKErrorExtension.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (macOS)

extension declares a conformance of imported type 'WKError' to imported protocol 'LocalizedError'; this will not behave correctly if the owners of 'WebKit' introduce this conformance in the future

Check warning on line 67 in Sources/Navigation/Extensions/WKErrorExtension.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (macOS)

extension declares a conformance of imported type 'WKError' to imported protocol 'LocalizedError'; this will not behave correctly if the owners of 'WebKit' introduce this conformance in the future

Check warning on line 67 in Sources/Navigation/Extensions/WKErrorExtension.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (macOS)

extension declares a conformance of imported type 'WKError' to imported protocol 'LocalizedError'; this will not behave correctly if the owners of 'WebKit' introduce this conformance in the future

Check warning on line 67 in Sources/Navigation/Extensions/WKErrorExtension.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (macOS)

extension declares a conformance of imported type 'WKError' to imported protocol 'LocalizedError'; this will not behave correctly if the owners of 'WebKit' introduce this conformance in the future

public var errorDescription: String? {
"<WKError \((self as NSError).domain) error \(code.rawValue) \"\(self.localizedDescription)\"" +
Expand Down
11 changes: 7 additions & 4 deletions Sources/PrivacyDashboard/PrivacyDashboardUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,15 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript {
}

func setMaliciousSiteDetectedThreatKind(_ detectedThreatKind: MaliciousSiteProtection.ThreatKind?, webView: WKWebView) {
let phishingStatus = ["phishingStatus": detectedThreatKind == .phishing]
guard let phishingStatusJson = try? JSONEncoder().encode(phishingStatus).utf8String() else {
assertionFailure("Can't encode phishingStatus into JSON")
let statusJson: String
do {
let obj = ["kind": detectedThreatKind?.rawValue ?? NSNull() as Any]
statusJson = try JSONSerialization.data(withJSONObject: obj).utf8String()!
} catch {
assertionFailure("Can't encode status: \(error)")
return
}
evaluate(js: "window.onChangePhishingStatus(\(phishingStatusJson))", in: webView)
evaluate(js: "window.onChangeMaliciousSiteStatus(\(statusJson))", in: webView)
}

func setIsPendingUpdates(_ isPendingUpdates: Bool, webView: WKWebView) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/SpecialErrorPages/SpecialErrorData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import MaliciousSiteProtection
public enum SpecialErrorKind: String, Encodable {
case ssl
case phishing
// case malware
case malware
}

public enum SpecialErrorData: Encodable, Equatable {
Expand Down Expand Up @@ -70,7 +70,7 @@ public enum SpecialErrorData: Encodable, Equatable {
public extension MaliciousSiteProtection.ThreatKind {
var errorPageKind: SpecialErrorKind {
switch self {
// case .malware: .malware
case .malware: .malware
case .phishing: .phishing
}
}
Expand Down
25 changes: 13 additions & 12 deletions Sources/SpecialErrorPages/SpecialErrorPageUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,22 @@ public final class SpecialErrorPageUserScript: NSObject, Subfeature {
self.languageCode = languageCode
}

@MainActor
public func handler(forMethodNamed methodName: String) -> Subfeature.Handler? {
guard isEnabled, let messageName = MessageName(rawValue: methodName) else { return nil }
return methodHandlers[messageName]
guard isEnabled else { return nil }

switch MessageName(rawValue: methodName) {
case .initialSetup: return initialSetup
case .reportPageException: return reportPageException
case .reportInitException: return reportInitException
case .leaveSite: return handleLeaveSiteAction
case .visitSite: return handleVisitSiteAction
case .advancedInfo: return handleAdvancedInfoPresented
default:
assertionFailure("SpecialErrorPageUserScript: Failed to parse User Script message: \(methodName)")
return nil
}
}

private lazy var methodHandlers: [MessageName: Handler] = [
.initialSetup: initialSetup,
.reportPageException: reportPageException,
.reportInitException: reportInitException,
.leaveSite: handleLeaveSiteAction,
.visitSite: handleVisitSiteAction,
.advancedInfo: handleAdvancedInfoPresented
]

@MainActor
private func initialSetup(params: Any, original: WKScriptMessage) async throws -> Encodable? {
#if DEBUG
Expand Down
10 changes: 5 additions & 5 deletions Sources/Suggestions/SuggestionLoading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ public class SuggestionLoader: SuggestionLoading {
guard let self = self else { return }
let processor = SuggestionProcessing(platform: dataSource.platform, urlFactory: self.urlFactory)
let result = processor.result(for: query,
from: history,
bookmarks: bookmarks,
internalPages: internalPages,
openTabs: openTabs,
apiResult: apiResult)
from: history,
bookmarks: bookmarks,
internalPages: internalPages,
openTabs: openTabs,
apiResult: apiResult)
DispatchQueue.main.async {
if let result = result {
completion(result, apiError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ class MaliciousSiteProtectionEmbeddedDataProviderTest: XCTestCase {
switch key.threatKind {
case .phishing:
"4fd2868a4f264501ec175ab866504a2a96c8d21a3b5195b405a4a83b51eae504"
case .malware:
"7f80bcae89250c4ecc4ed91b5a4c3a09fe6f098622369f2f46a8ab69023a7683"
}
case .hashPrefixSet(let key):
switch key.threatKind {
case .phishing:
"21b047a9950fcaf86034a6b16181e18815cb8d276386d85c8977ca8c5f8aa05f"
case .malware:
"ef31819296d83136cdb131e877e08fd120571d4c82512ba8c3eb964885ec07bc"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ class MaliciousSiteProtectionUpdateManagerTests: XCTestCase {
XCTestExpectation(description: "Will Sleep 3"),
]
self.willSleep = { _ in
sleepExpectations[sleepIndex].fulfill()
sleepExpectations[safe: sleepIndex]?.fulfill()
sleepIndex += 1
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ final class MockMaliciousSiteProtectionEmbeddedDataProvider: MaliciousSiteProtec
}

func data(withContentsOf url: URL) throws -> Data {
let data: Data
switch url.absoluteString {
case "filterSet":
self.loadFilterSetCalled = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"hash": "e4753ddad954dafd4ff4ef67f82b3c1a2db6ef4a51bda43513260170e558bd13",
"regex": "(?i)^https?\\:\\/\\/privacy-test-pages\\.site(?:\\:(?:80|443))?\\/security\\/badware\\/malware\\.html$"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"e4753dda",
"012db806",
"56fbfbf2"
]
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
// limitations under the License.
//

import XCTest
import Common
import os
import WebKit
@testable import PrivacyDashboard
import XCTest

@testable import BrowserServicesKit
@testable import PrivacyDashboard

@MainActor
final class PrivacyDashboardControllerTests: XCTestCase {
Expand Down Expand Up @@ -268,8 +270,8 @@ final class PrivacyDashboardControllerTests: XCTestCase {

privacyDashboardController.privacyInfo!.malicousSiteThreatKind = .phishing

wait(for: [expectation], timeout: 100)
XCTAssertEqual(mockWebView.capturedJavaScriptString, "window.onChangePhishingStatus({\"phishingStatus\":true})")
wait(for: [expectation], timeout: 5)
XCTAssertEqual(mockWebView.capturedJavaScriptString, "window.onChangeMaliciousSiteStatus({\"kind\":\"phishing\"})")
}
}

Expand All @@ -287,8 +289,8 @@ class MockWebView: WKWebView {
}

override func evaluateJavaScript(_ javaScriptString: String) async throws -> Any {
print(javaScriptString)
if javaScriptString.contains("window.onChangePhishingStatus") {
Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DDGTest").info("received javaScriptString \(javaScriptString, privacy: .public)")
if javaScriptString.contains("window.onChangeMaliciousSiteStatus") {
capturedJavaScriptString = javaScriptString
expectation.fulfill()
}
Expand Down
Loading