Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 899ad16

Browse files
Add feature flags for malicious site protection
1 parent 95a56b5 commit 899ad16

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

Core/FeatureFlag.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public enum FeatureFlag: String {
6363

6464
/// https://app.asana.com/0/0/1208767141940869/f
6565
case freeTrials
66+
67+
/// Feature flag to enable / disable phishing and malware protection
68+
/// https://app.asana.com/0/1206329551987282/1207149365636877/f
69+
case maliciousSiteProtection
6670
}
6771

6872
extension FeatureFlag: FeatureFlagDescribing {
@@ -146,6 +150,8 @@ extension FeatureFlag: FeatureFlagDescribing {
146150
return .remoteReleasable(.subfeature(PrivacyProSubfeature.isLaunchedROWOverride))
147151
case .freeTrials:
148152
return .remoteDevelopment(.subfeature(PrivacyProSubfeature.freeTrials))
153+
case .maliciousSiteProtection:
154+
return .internalOnly()
149155
}
150156
}
151157
}

DuckDuckGo.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,8 @@
749749
98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D7217B37010011A0D4 /* Theme.swift */; };
750750
98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; };
751751
98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; };
752+
9F06EB752D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */; };
753+
9F06EB7B2D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */; };
752754
9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; };
753755
9F1798572CD2443F0073018B /* AddToDockPromoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */; };
754756
9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; };
@@ -2618,6 +2620,8 @@
26182620
98F3A1D7217B37010011A0D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
26192621
98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = "<group>"; };
26202622
98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = "<group>"; };
2623+
9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaliciousSiteProtectionFeatureFlags.swift; sourceTree = "<group>"; };
2624+
9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaliciousSiteProtectionFeatureFlagsTests.swift; sourceTree = "<group>"; };
26212625
9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = "<group>"; };
26222626
9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoViewModelTests.swift; sourceTree = "<group>"; };
26232627
9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = "<group>"; };
@@ -4979,6 +4983,22 @@
49794983
name = Themes;
49804984
sourceTree = "<group>";
49814985
};
4986+
9F06EB742D09E8D200905426 /* FeatureFlags */ = {
4987+
isa = PBXGroup;
4988+
children = (
4989+
9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */,
4990+
);
4991+
path = FeatureFlags;
4992+
sourceTree = "<group>";
4993+
};
4994+
9F06EB7A2D09EC2000905426 /* MaliciousSiteProtection */ = {
4995+
isa = PBXGroup;
4996+
children = (
4997+
9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */,
4998+
);
4999+
path = MaliciousSiteProtection;
5000+
sourceTree = "<group>";
5001+
};
49825002
9F23B7FF2C2BABE000950875 /* OnboardingIntro */ = {
49835003
isa = PBXGroup;
49845004
children = (
@@ -5033,6 +5053,7 @@
50335053
9F254AA92CF47CD30063B308 /* MaliciousSiteProtection */ = {
50345054
isa = PBXGroup;
50355055
children = (
5056+
9F06EB742D09E8D200905426 /* FeatureFlags */,
50365057
9F254AAA2CF47DD50063B308 /* MaliciousSiteProtectionManager.swift */,
50375058
);
50385059
path = MaliciousSiteProtection;
@@ -6090,6 +6111,7 @@
60906111
83134D7F20E2E013006CE65D /* Feedback */,
60916112
8588026724E4249800C24AB6 /* iPad */,
60926113
851DFD88212C5ED600D95F20 /* Main */,
6114+
9F06EB7A2D09EC2000905426 /* MaliciousSiteProtection */,
60936115
EE56DE3A2A6038F500375C41 /* NetworkProtection */,
60946116
6F03CAFF2C32ED22004179A8 /* NewTabPage */,
60956117
F1D477C71F2139210031ED49 /* OmniBar */,
@@ -8078,6 +8100,7 @@
80788100
F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */,
80798101
8598D2E02CEB98B500C45685 /* Favicons.swift in Sources */,
80808102
8598D2E12CEB98B500C45685 /* NotFoundCachingDownloader.swift in Sources */,
8103+
9F06EB752D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift in Sources */,
80818104
8598D2E22CEB98B500C45685 /* FaviconRequestModifier.swift in Sources */,
80828105
8598D2E32CEB98B500C45685 /* FaviconUserScript.swift in Sources */,
80838106
8598D2E42CEB98B500C45685 /* FaviconSourcesProvider.swift in Sources */,
@@ -8360,6 +8383,7 @@
83608383
83EDCC411F86B89C005CDFCD /* StatisticsLoaderTests.swift in Sources */,
83618384
564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */,
83628385
C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */,
8386+
9F06EB7B2D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift in Sources */,
83638387
85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */,
83648388
9FE05CF12C36468A00D9046B /* OnboardingPixelReporterTests.swift in Sources */,
83658389
9FDEC7B42C8FD62F00C7A692 /* OnboardingAddressBarPositionPickerViewModelTests.swift in Sources */,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// MaliciousSiteProtectionFeatureFlags.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2024 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import BrowserServicesKit
22+
import Core
23+
24+
protocol MaliciousSiteProtectionFeatureFlagger {
25+
/// A Boolean value indicating whether malicious site protection is enabled.
26+
/// - Returns: `true` if malicious site protection is enabled; otherwise, `false`.
27+
var isMaliciousSiteProtectionEnabled: Bool { get }
28+
29+
/// Checks if should detect malicious threats for a specific domain.
30+
/// - Parameter domain: The domain to check for malicious threat.
31+
/// - Returns: `true` if should check for malicious threats for the specified domain; otherwise, `false`.
32+
func shouldDetectMaliciousThreat(forDomain domain: String?) -> Bool
33+
}
34+
35+
final class MaliciousSiteProtectionFeatureFlags {
36+
private let featureFlagger: FeatureFlagger
37+
private let privacyConfigManager: PrivacyConfigurationManaging
38+
39+
init(
40+
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger,
41+
privacyConfigManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager
42+
) {
43+
self.featureFlagger = featureFlagger
44+
self.privacyConfigManager = privacyConfigManager
45+
}
46+
}
47+
48+
// MARK: - MaliciousSiteProtectionFeatureFlagger
49+
50+
extension MaliciousSiteProtectionFeatureFlags: MaliciousSiteProtectionFeatureFlagger {
51+
52+
var isMaliciousSiteProtectionEnabled: Bool {
53+
featureFlagger.isFeatureOn(.maliciousSiteProtection)
54+
}
55+
56+
func shouldDetectMaliciousThreat(forDomain domain: String?) -> Bool {
57+
privacyConfigManager.privacyConfig.isFeature(.maliciousSiteProtection, enabledForDomain: domain)
58+
}
59+
60+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// MaliciousSiteProtectionFeatureFlagsTests.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2024 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Testing
21+
import BrowserServicesKit
22+
@testable import DuckDuckGo
23+
24+
@Suite("Malicious Site Protection - Feature Flags", .serialized)
25+
final class MaliciousSiteProtectionFeatureFlagsTests {
26+
private var sut: MaliciousSiteProtectionFeatureFlags!
27+
private var featureFlaggerMock: MockFeatureFlagger!
28+
private var configurationManagerMock: PrivacyConfigurationManagerMock!
29+
30+
init() async throws {
31+
featureFlaggerMock = MockFeatureFlagger()
32+
configurationManagerMock = PrivacyConfigurationManagerMock()
33+
sut = MaliciousSiteProtectionFeatureFlags(featureFlagger: featureFlaggerMock, privacyConfigManager: configurationManagerMock)
34+
}
35+
36+
deinit {
37+
featureFlaggerMock = nil
38+
configurationManagerMock = nil
39+
sut = nil
40+
}
41+
42+
// MARK: - Web Error Page
43+
44+
@Test("Check Threat Detection Enabled")
45+
func whenThreatDetectionEnabled_AndFeatureFlagIsOn_ThenReturnTrue() throws {
46+
// GIVEN
47+
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]
48+
49+
// WHEN
50+
let result = sut.isMaliciousSiteProtectionEnabled
51+
52+
// THEN
53+
#expect(result)
54+
}
55+
56+
@Test("Check Threat Detection Disabled")
57+
func whenThreatDetectionEnabled_AndFeatureFlagIsOff_ThenReturnFalse() throws {
58+
// GIVEN
59+
featureFlaggerMock.enabledFeatureFlags = []
60+
61+
// WHEN
62+
let result = sut.isMaliciousSiteProtectionEnabled
63+
64+
// THEN
65+
#expect(!result)
66+
}
67+
68+
@Test("Check Threat Detection Enabled For Domain")
69+
func whenThreatDetectionEnabledForDomain_AndFeatureIsAvailableForDomain_ThenReturnTrue() throws {
70+
// GIVEN
71+
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]
72+
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
73+
privacyConfigMock.enabledFeatures = [.maliciousSiteProtection: ["example.com"]]
74+
let domain = "example.com"
75+
76+
// WHEN
77+
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)
78+
79+
// THEN
80+
#expect(result)
81+
}
82+
83+
@Test("Check Threat Detection Disabled For Domain When Protection For Domain Is Not Enabled")
84+
func whenThreatDetectionCalledEnabledForDomain_AndFeatureIsNotAvailableForDomain_ThenReturnFalse() throws {
85+
// GIVEN
86+
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]
87+
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
88+
privacyConfigMock.enabledFeatures = [.maliciousSiteProtection: []]
89+
let domain = "example.com"
90+
91+
// WHEN
92+
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)
93+
94+
// THEN
95+
#expect(!result)
96+
}
97+
98+
@Test("Check Threat Detection Disabled For Domain When Feature Flag Is Off")
99+
func whenThreatDetectionEnabledForDomain_AndPrivacyConfigFeatureFlagIsOn_AndThreatDetectionSubFeatureIsOff_ThenReturnTrue() throws {
100+
// GIVEN
101+
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
102+
privacyConfigMock.enabledFeatures = [.adClickAttribution: ["example.com"]]
103+
let domain = "example.com"
104+
105+
// WHEN
106+
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)
107+
108+
// THEN
109+
#expect(!result)
110+
}
111+
112+
}

0 commit comments

Comments
 (0)