Skip to content

Commit

Permalink
Add feature flags for malicious site protection
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandroboron committed Dec 11, 2024
1 parent 95a56b5 commit f156fd5
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Core/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public enum FeatureFlag: String {

/// https://app.asana.com/0/0/1208767141940869/f
case freeTrials

/// Feature flag to enable / disable phishing and malware protection
/// https://app.asana.com/0/1206329551987282/1207149365636877/f
case maliciousSiteProtection
}

extension FeatureFlag: FeatureFlagDescribing {
Expand Down Expand Up @@ -146,6 +150,8 @@ extension FeatureFlag: FeatureFlagDescribing {
return .remoteReleasable(.subfeature(PrivacyProSubfeature.isLaunchedROWOverride))
case .freeTrials:
return .remoteDevelopment(.subfeature(PrivacyProSubfeature.freeTrials))
case .maliciousSiteProtection:
return .internalOnly()
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,8 @@
98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D7217B37010011A0D4 /* Theme.swift */; };
98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; };
98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; };
9F06EB752D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */; };
9F06EB7B2D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */; };
9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; };
9F1798572CD2443F0073018B /* AddToDockPromoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */; };
9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; };
Expand Down Expand Up @@ -2618,6 +2620,8 @@
98F3A1D7217B37010011A0D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = "<group>"; };
98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = "<group>"; };
9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaliciousSiteProtectionFeatureFlags.swift; sourceTree = "<group>"; };
9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaliciousSiteProtectionFeatureFlagsTests.swift; sourceTree = "<group>"; };
9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = "<group>"; };
9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoViewModelTests.swift; sourceTree = "<group>"; };
9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4979,6 +4983,22 @@
name = Themes;
sourceTree = "<group>";
};
9F06EB742D09E8D200905426 /* FeatureFlags */ = {
isa = PBXGroup;
children = (
9F06EB732D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift */,
);
path = FeatureFlags;
sourceTree = "<group>";
};
9F06EB7A2D09EC2000905426 /* MaliciousSiteProtection */ = {
isa = PBXGroup;
children = (
9F06EB792D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift */,
);
path = MaliciousSiteProtection;
sourceTree = "<group>";
};
9F23B7FF2C2BABE000950875 /* OnboardingIntro */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5033,6 +5053,7 @@
9F254AA92CF47CD30063B308 /* MaliciousSiteProtection */ = {
isa = PBXGroup;
children = (
9F06EB742D09E8D200905426 /* FeatureFlags */,
9F254AAA2CF47DD50063B308 /* MaliciousSiteProtectionManager.swift */,
);
path = MaliciousSiteProtection;
Expand Down Expand Up @@ -6090,6 +6111,7 @@
83134D7F20E2E013006CE65D /* Feedback */,
8588026724E4249800C24AB6 /* iPad */,
851DFD88212C5ED600D95F20 /* Main */,
9F06EB7A2D09EC2000905426 /* MaliciousSiteProtection */,
EE56DE3A2A6038F500375C41 /* NetworkProtection */,
6F03CAFF2C32ED22004179A8 /* NewTabPage */,
F1D477C71F2139210031ED49 /* OmniBar */,
Expand Down Expand Up @@ -8078,6 +8100,7 @@
F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */,
8598D2E02CEB98B500C45685 /* Favicons.swift in Sources */,
8598D2E12CEB98B500C45685 /* NotFoundCachingDownloader.swift in Sources */,
9F06EB752D09E8D200905426 /* MaliciousSiteProtectionFeatureFlags.swift in Sources */,
8598D2E22CEB98B500C45685 /* FaviconRequestModifier.swift in Sources */,
8598D2E32CEB98B500C45685 /* FaviconUserScript.swift in Sources */,
8598D2E42CEB98B500C45685 /* FaviconSourcesProvider.swift in Sources */,
Expand Down Expand Up @@ -8360,6 +8383,7 @@
83EDCC411F86B89C005CDFCD /* StatisticsLoaderTests.swift in Sources */,
564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */,
C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */,
9F06EB7B2D09EC2000905426 /* MaliciousSiteProtectionFeatureFlagsTests.swift in Sources */,
85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */,
9FE05CF12C36468A00D9046B /* OnboardingPixelReporterTests.swift in Sources */,
9FDEC7B42C8FD62F00C7A692 /* OnboardingAddressBarPositionPickerViewModelTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// MaliciousSiteProtectionFeatureFlags.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

protocol MaliciousSiteProtectionFeatureFlagger {
/// A Boolean value indicating whether malicious site protection is enabled.
/// - Returns: `true` if malicious site protection is enabled; otherwise, `false`.
var isMaliciousSiteProtectionEnabled: Bool { get }

/// Checks if should detect malicious threats for a specific domain.
/// - Parameter domain: The domain to check for malicious threat.
/// - Returns: `true` if should check for malicious threats for the specified domain; otherwise, `false`.
func shouldDetectMaliciousThreat(forDomain domain: String?) -> Bool
}

final class MaliciousSiteProtectionFeatureFlags {
private let featureFlagger: FeatureFlagger
private let privacyConfigManager: PrivacyConfigurationManaging

init(
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger,
privacyConfigManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager
) {
self.featureFlagger = featureFlagger
self.privacyConfigManager = privacyConfigManager
}
}

// MARK: - MaliciousSiteProtectionFeatureFlagger

extension MaliciousSiteProtectionFeatureFlags: MaliciousSiteProtectionFeatureFlagger {

var isMaliciousSiteProtectionEnabled: Bool {
featureFlagger.isFeatureOn(.maliciousSiteProtection)
}

func shouldDetectMaliciousThreat(forDomain domain: String?) -> Bool {
privacyConfigManager.privacyConfig.isFeature(.maliciousSiteProtection, enabledForDomain: domain)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// MaliciousSiteProtectionFeatureFlagsTests.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 Testing
import BrowserServicesKit
@testable import DuckDuckGo

@Suite("Malicious Site Protection - Feature Flags", .serialized)
final class MaliciousSiteProtectionFeatureFlagsTests {
private var sut: MaliciousSiteProtectionFeatureFlags!
private var featureFlaggerMock: MockFeatureFlagger!
private var configurationManagerMock: PrivacyConfigurationManagerMock!

init() async throws {
featureFlaggerMock = MockFeatureFlagger()
configurationManagerMock = PrivacyConfigurationManagerMock()
sut = MaliciousSiteProtectionFeatureFlags(featureFlagger: featureFlaggerMock, privacyConfigManager: configurationManagerMock)
}

deinit {
featureFlaggerMock = nil
configurationManagerMock = nil
sut = nil
}

// MARK: - Web Error Page

@Test("Check Threat Detection Enabled")
func whenThreatDetectionEnabled_AndFeatureFlagIsOn_ThenReturnTrue() throws {
// GIVEN
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]

// WHEN
let result = sut.isMaliciousSiteProtectionEnabled

// THEN
#expect(result)
}

@Test("Check Threat Detection Disabled")
func whenThreatDetectionEnabled_AndFeatureFlagIsOff_ThenReturnFalse() throws {
// GIVEN
featureFlaggerMock.enabledFeatureFlags = []

// WHEN
let result = sut.isMaliciousSiteProtectionEnabled

// THEN
#expect(!result)
}

@Test("Check Threat Detection Enabled For Domain")
func whenThreatDetectionEnabledForDomain_AndFeatureIsAvailableForDomain_ThenReturnTrue() throws {
// GIVEN
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
privacyConfigMock.enabledFeatures = [.maliciousSiteProtection: ["example.com"]]
let domain = "example.com"

// WHEN
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)

// THEN
#expect(result)
}

@Test("Check Threat Detection Disabled For Domain When Protection For Domain Is Not Enabled")
func whenThreatDetectionCalledEnabledForDomain_AndFeatureIsNotAvailableForDomain_ThenReturnFalse() throws {
// GIVEN
featureFlaggerMock.enabledFeatureFlags = [.maliciousSiteProtection]
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
privacyConfigMock.enabledFeatures = [.maliciousSiteProtection: []]
let domain = "example.com"

// WHEN
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)

// THEN
#expect(!result)
}

@Test("Check Threat Detection Disabled For Domain When Feature Flag Is Off")
func whenThreatDetectionEnabledForDomain_AndPrivacyConfigFeatureFlagIsOn_AndThreatDetectionSubFeatureIsOff_ThenReturnTrue() throws {
// GIVEN
let privacyConfigMock = try #require(configurationManagerMock.privacyConfig as? PrivacyConfigurationMock)
privacyConfigMock.enabledFeatures = [.adClickAttribution: ["example.com"]]
let domain = "example.com"

// WHEN
let result = sut.shouldDetectMaliciousThreat(forDomain: domain)

// THEN
#expect(!result)
}

}

0 comments on commit f156fd5

Please sign in to comment.