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 5: Refactor Special Error Types #1098

Merged
merged 43 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
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
0f3e7ba
fix API hash mathing
mallexxx Nov 27, 2024
e6d3d57
fix linter issues
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
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
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
e48c6df
Merge branch 'alex/malware-protection-1' into alex/malware-protection-5
mallexxx Nov 28, 2024
22f3d8f
remove MaliciousSiteProtectionSubfeature.allowPreferencesToggle
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
b226cb2
Merge branch 'alex/malware-protection-1' into alex/malware-protection-5
mallexxx Dec 2, 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
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ let package = Package(
dependencies: [
"Common",
"UserScript",
"BrowserServicesKit"
"BrowserServicesKit",
"MaliciousSiteProtection",
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
Expand All @@ -414,7 +415,6 @@ let package = Package(
dependencies: [
"Common",
"Networking",
"SpecialErrorPages",
"PixelKit",
],
swiftSettings: [
Expand Down
4 changes: 2 additions & 2 deletions Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
//

import Common
import WebKit
import UserScript
import os.log
import UserScript
@preconcurrency import WebKit

var previousIncontextSignupPermanentlyDismissedAt: Double?
var previousEmailSignedIn: Bool?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
// limitations under the License.
//

import WebKit
import Common
import ContentBlocking
import TrackerRadarKit
import UserScript
import ContentBlocking
import Common
@preconcurrency import WebKit

public protocol SurrogatesUserScriptDelegate: NSObjectProtocol {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

import Combine
import Common
import UserScript
import WebKit
import QuartzCore
import os.log
import QuartzCore
import UserScript
@preconcurrency import WebKit

public protocol UserContentControllerDelegate: AnyObject {
@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ public enum DuckPlayerSubfeature: String, PrivacySubfeature {
public enum MaliciousSiteProtectionSubfeature: String, PrivacySubfeature {
public var parent: PrivacyFeature { .maliciousSiteProtection }
case allowErrorPage
case allowPreferencesToggle
}

public enum SyncPromotionSubfeature: String, PrivacySubfeature {
Expand Down
2 changes: 1 addition & 1 deletion Sources/MaliciousSiteProtection/API/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct APIClient {
let headers = environment.headers(for: requestType)
let url = environment.url(for: requestType)

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

Expand Down
6 changes: 6 additions & 0 deletions Sources/MaliciousSiteProtection/API/APIRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +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 }
}

// Protocol for requests that modify a set of malicious site detection data
Expand All @@ -38,6 +39,9 @@ extension APIClient {
init(threatKind: ThreatKind, revision: Int?)
}
}
extension APIClient.Request {
var timeout: TimeInterval? { nil }
}

public extension APIRequestType {
struct HashPrefixes: APIClient.ChangeSetRequest {
Expand Down Expand Up @@ -96,6 +100,8 @@ public extension APIRequestType {
var requestType: APIRequestType {
.matches(self)
}

var timeout: TimeInterval? { 1 }
}
}
/// extension to call generic `load(_: some Request)` method like this: `load(.matches(…))`
Expand Down
5 changes: 3 additions & 2 deletions Sources/MaliciousSiteProtection/MaliciousSiteDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import Common
import CryptoKit
import Foundation
import Networking

public protocol MaliciousSiteDetecting {
/// Evaluates the given URL to determine its malicious category (e.g., phishing, malware).
Expand All @@ -43,8 +44,8 @@ public final class MaliciousSiteDetector: MaliciousSiteDetecting {
private let dataManager: DataManaging
private let eventMapping: EventMapping<Event>

public convenience init(apiEnvironment: APIClientEnvironment, dataManager: DataManager, eventMapping: EventMapping<Event>) {
self.init(apiClient: APIClient(environment: apiEnvironment), dataManager: dataManager, eventMapping: eventMapping)
public convenience init(apiEnvironment: APIClientEnvironment, service: APIService = DefaultAPIService(urlSession: .shared), dataManager: DataManager, eventMapping: EventMapping<Event>) {
self.init(apiClient: APIClient(environment: apiEnvironment, service: service), dataManager: dataManager, eventMapping: eventMapping)
}

init(apiClient: APIClient.Mockable, dataManager: DataManaging, eventMapping: EventMapping<Event>) {
Expand Down
13 changes: 10 additions & 3 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 @@ -43,6 +43,13 @@ public struct MaliciousSiteError: Error, Equatable {
self.init(code: code, failingUrl: failingUrl)
}

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

}

extension MaliciousSiteError: _ObjectiveCBridgeableError {
Expand All @@ -63,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
14 changes: 1 addition & 13 deletions Sources/MaliciousSiteProtection/Model/ThreatKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,11 @@
//

import Foundation
import SpecialErrorPages

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

case phishing
// case malware

}

public extension ThreatKind {

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
// limitations under the License.
//

import CryptoKit
import Foundation
import CryptoKit

public protocol EmbeddedDataProviding {
func revision(for dataType: DataManager.StoredDataType) -> Int
Expand Down
12 changes: 8 additions & 4 deletions Sources/MaliciousSiteProtection/Services/UpdateManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
// limitations under the License.
//

import Foundation
import Common
import Foundation
import Networking
import os

protocol UpdateManaging {
Expand All @@ -35,8 +36,8 @@ public struct UpdateManager: UpdateManaging {
private let updateIntervalProvider: UpdateIntervalProvider
private let sleeper: Sleeper

public init(apiEnvironment: APIClientEnvironment, dataManager: DataManager, updateIntervalProvider: @escaping UpdateIntervalProvider) {
self.init(apiClient: APIClient(environment: apiEnvironment), dataManager: dataManager, updateIntervalProvider: updateIntervalProvider)
public init(apiEnvironment: APIClientEnvironment, service: APIService = DefaultAPIService(urlSession: .shared), dataManager: DataManager, updateIntervalProvider: @escaping UpdateIntervalProvider) {
self.init(apiClient: APIClient(environment: apiEnvironment, service: service), dataManager: dataManager, updateIntervalProvider: updateIntervalProvider)
}

init(apiClient: APIClient.Mockable, dataManager: DataManaging, sleeper: Sleeper = .default, updateIntervalProvider: @escaping UpdateIntervalProvider) {
Expand Down Expand Up @@ -80,7 +81,10 @@ public struct UpdateManager: UpdateManaging {
for dataType in DataManager.StoredDataType.allCases {
// get update interval from provider
guard let updateInterval = updateIntervalProvider(dataType) else { continue }
assert(updateInterval > 0)
guard updateInterval > 0 else {
assertionFailure("Update interval for \(dataType) must be positive")
continue
}

group.addTask {
// run periodically until the parent task is cancelled
Expand Down
8 changes: 6 additions & 2 deletions Sources/Navigation/Extensions/WKErrorExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@
}

public var isServerCertificateUntrusted: Bool {
code.rawValue == NSURLErrorServerCertificateUntrusted && _nsError.domain == NSURLErrorDomain
_nsError.isServerCertificateUntrusted
}
}
extension NSError {
public var isServerCertificateUntrusted: Bool {
code == NSURLErrorServerCertificateUntrusted && domain == NSURLErrorDomain
}

}

extension WKError {
Expand All @@ -56,7 +60,7 @@
#endif
}

extension WKError: LocalizedError {

Check warning on line 63 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
7 changes: 1 addition & 6 deletions Sources/PrivacyDashboard/PrivacyDashboardController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,7 @@ extension PrivacyDashboardController: WKNavigationDelegate {
.receive(on: DispatchQueue.main )
.sink(receiveValue: { [weak self] detectedThreatKind in
guard let self, let webView else { return }
for threatKind in MaliciousSiteProtection.ThreatKind.allCases {
switch threatKind {
case .phishing:
script.setIsPhishing(detectedThreatKind == threatKind, webView: webView)
}
}
script.setMaliciousSiteDetectedThreatKind(detectedThreatKind, webView: webView)
})
.store(in: &cancellables)
}
Expand Down
11 changes: 6 additions & 5 deletions Sources/PrivacyDashboard/PrivacyDashboardUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
// limitations under the License.
//

import BrowserServicesKit
import Common
import Foundation
import WebKit
import MaliciousSiteProtection
import TrackerRadarKit
import UserScript
import Common
import BrowserServicesKit
import WebKit

@MainActor
protocol PrivacyDashboardUserScriptDelegate: AnyObject {
Expand Down Expand Up @@ -425,8 +426,8 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript {
evaluate(js: "window.onChangeCertificateData(\(certificateDataJson))", in: webView)
}

func setIsPhishing(_ isPhishing: Bool, webView: WKWebView) {
let phishingStatus = ["phishingStatus": isPhishing]
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")
return
Expand Down
38 changes: 25 additions & 13 deletions Sources/SpecialErrorPages/SSLErrorType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,27 @@
//

import Foundation
import WebKit

public enum SSLErrorType: String {
public let SSLErrorCodeKey = "_kCFStreamErrorCodeKey"

public enum SSLErrorType: String, Encodable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mallexxx this requires a PR for iOS as it will break SSL implementation.


case expired
case wrongHost
case selfSigned
case wrongHost
case invalid

public static func forErrorCode(_ errorCode: Int) -> Self {
switch Int32(errorCode) {
case errSSLCertExpired:
return .expired
case errSSLHostNameMismatch:
return .wrongHost
case errSSLXCertChainInvalid:
return .selfSigned
default:
return .invalid
init(errorCode: Int32) {
self = switch errorCode {
case errSSLCertExpired: .expired
case errSSLXCertChainInvalid: .selfSigned
case errSSLHostNameMismatch: .wrongHost
default: .invalid
}
}

public var rawParameter: String {
mallexxx marked this conversation as resolved.
Show resolved Hide resolved
public var pixelParameter: String {
switch self {
case .expired: return "expired"
case .wrongHost: return "wrong_host"
Expand All @@ -48,3 +47,16 @@ public enum SSLErrorType: String {
}

}

extension WKError {
public var sslErrorType: SSLErrorType? {
mallexxx marked this conversation as resolved.
Show resolved Hide resolved
_nsError.sslErrorType
}
}
extension NSError {
public var sslErrorType: SSLErrorType? {
guard let errorCode = self.userInfo[SSLErrorCodeKey] as? Int32 else { return nil }
let sslErrorType = SSLErrorType(errorCode: errorCode)
return sslErrorType
}
}
Loading
Loading