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

Malicious Site Protection - Make clientSideHit optional parameter #1199

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions Sources/MaliciousSiteProtection/MaliciousSiteDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public final class MaliciousSiteDetector: MaliciousSiteDetecting {
for threatKind in hashPrefixMatchingThreatKinds {
let matches = await checkLocalFilters(hostHash: hostHash, canonicalUrl: canonicalUrl, for: threatKind)
if matches {
eventMapping.fire(.errorPageShown(category: threatKind, clientSideHit: true))
await fireErrorPageShown(threatKind: threatKind, clientSideHit: true)
return threatKind
}
}
Expand All @@ -123,11 +123,18 @@ public final class MaliciousSiteDetector: MaliciousSiteDetecting {
let match = await checkApiMatches(hostHash: hostHash, canonicalUrl: canonicalUrl)
if let match {
let threatKind = match.category.flatMap(ThreatKind.init) ?? hashPrefixMatchingThreatKinds[0]
eventMapping.fire(.errorPageShown(category: threatKind, clientSideHit: false))
await fireErrorPageShown(threatKind: threatKind, clientSideHit: false)
return threatKind
}

return .none
}

private func fireErrorPageShown(threatKind: ThreatKind, clientSideHit: Bool) async {
let filterSet = await dataManager.dataSet(for: .filterSet(threatKind: threatKind))
// Send Pixel clientSideHit parameter only if filterSet size is greater than 100
// https://app.asana.com/0/0/1209113403594297/1209141231997704/f
let sanitisedClientSideHit = filterSet.filters.count > 100 ? clientSideHit : nil
eventMapping.fire(.errorPageShown(category: threatKind, clientSideHit: sanitisedClientSideHit))
}
}
17 changes: 12 additions & 5 deletions Sources/MaliciousSiteProtection/Model/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public extension PixelKit {
}

public enum Event: PixelKitEventV2 {
case errorPageShown(category: ThreatKind, clientSideHit: Bool)
case errorPageShown(category: ThreatKind, clientSideHit: Bool?)
case visitSite(category: ThreatKind)
case iframeLoaded(category: ThreatKind)
case settingToggled(to: Bool)
Expand Down Expand Up @@ -59,10 +59,17 @@ public enum Event: PixelKitEventV2 {
public var parameters: [String: String]? {
switch self {
case .errorPageShown(category: let category, clientSideHit: let clientSideHit):
return [
PixelKit.Parameters.category: category.rawValue,
PixelKit.Parameters.clientSideHit: String(clientSideHit),
]
let parameters = if let clientSideHit {
[
PixelKit.Parameters.category: category.rawValue,
PixelKit.Parameters.clientSideHit: String(clientSideHit),
]
} else {
[
PixelKit.Parameters.category: category.rawValue,
]
}
return parameters
case .visitSite(category: let category),
.iframeLoaded(category: let category):
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,88 @@ class MaliciousSiteDetectorTests: XCTestCase {
XCTFail("Unexpected event \(event)")
}
}

func testWhenLocalFilterHitAndFilterSetSmallerThanHundredThenClientSideHitParameterIsNil() async throws {
// GIVEN
let filter = Filter(hash: "255a8a793097aeea1f06a19c08cde28db0eb34c660c6e4e7480c9525d034b16d", regex: ".*malicious.*")
try await mockDataManager.store(FilterDictionary(revision: 0, items: [filter]), for: .filterSet(threatKind: .phishing))
try await mockDataManager.store(HashPrefixSet(revision: 0, items: ["255a8a79"]), for: .hashPrefixes(threatKind: .phishing))
let url = URL(string: "https://malicious.com/")!

// WHEN
_ = await detector.evaluate(url)

// THEN
XCTAssertEqual(mockEventMapping.events.count, 1)
switch mockEventMapping.events.last {
case let .errorPageShown(_, clientSideHit):
XCTAssertNil(clientSideHit)
default:
XCTFail("Wrong event fired")
}
}

func testWhenLocalFilterHitAndFilterSetGreaterThanHundredThenClientSideHitParameterIsSent() async throws {
// GIVEN
let maliciousFilter = Filter(hash: "255a8a793097aeea1f06a19c08cde28db0eb34c660c6e4e7480c9525d034b16d", regex: ".*malicious.*")
let filters = (1...100).map { i in
Filter(hash: "255a8a793097aeea1f06a19c08cde28db0eb34c660c6e4e7480c9525d034b16d\(i)", regex: ".*malicious.*")
} + [maliciousFilter]
try await mockDataManager.store(FilterDictionary(revision: 0, items: filters), for: .filterSet(threatKind: .phishing))
try await mockDataManager.store(HashPrefixSet(revision: 0, items: ["255a8a79"]), for: .hashPrefixes(threatKind: .phishing))
let url = URL(string: "https://malicious.com/")!

// WHEN
_ = await detector.evaluate(url)

// THEN
XCTAssertEqual(mockEventMapping.events.count, 1)
switch mockEventMapping.events.last {
case let .errorPageShown(_, clientSideHit):
XCTAssertNotNil(clientSideHit)
default:
XCTFail("Wrong event fired")
}
}

func testWhenMatchesAPIAndFilterSetSmallerThanHundredThenClientSideHitParameterIsNil() async throws {
// GIVEN
try await mockDataManager.store(HashPrefixSet(revision: 0, items: ["a379a6f6"]), for: .hashPrefixes(threatKind: .phishing))
let url = URL(string: "https://example.com/mal")!

// WHEN
_ = await detector.evaluate(url)

// THEN
XCTAssertEqual(mockEventMapping.events.count, 1)
switch mockEventMapping.events.last {
case let .errorPageShown(_, clientSideHit):
XCTAssertNil(clientSideHit)
default:
XCTFail("Wrong event fired")
}
}

func testWhenMatchesAPIAndFilterSetGreaterThanHundredThenClientSideHitParameterIsSet() async throws {
// GIVEN
let maliciousFilter = Filter(hash: "255a8a793097aeea1f06a19c08cde28db0eb34c660c6e4e7480c9525d034b16d", regex: ".*malicious.*")
let filters = (1...100).map { i in
Filter(hash: "255a8a793097aeea1f06a19c08cde28db0eb34c660c6e4e7480c9525d034b16d\(i)", regex: ".*malicious.*")
} + [maliciousFilter]
try await mockDataManager.store(FilterDictionary(revision: 0, items: filters), for: .filterSet(threatKind: .phishing))
try await mockDataManager.store(HashPrefixSet(revision: 0, items: ["a379a6f6"]), for: .hashPrefixes(threatKind: .phishing))
let url = URL(string: "https://example.com/mal")!

// WHEN
_ = await detector.evaluate(url)

// THEN
XCTAssertEqual(mockEventMapping.events.count, 1)
switch mockEventMapping.events.last {
case let .errorPageShown(_, clientSideHit):
XCTAssertNotNil(clientSideHit)
default:
XCTFail("Wrong event fired")
}
}
}
Loading