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

risky domains protection #3963

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions Core/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public enum FeatureFlag: String {
/// Feature flag to enable / disable phishing and malware protection
/// https://app.asana.com/0/1206329551987282/1207149365636877/f
case maliciousSiteProtection

case networkProtectionRiskyDomainsProtection
}

extension FeatureFlag: FeatureFlagDescribing {
Expand All @@ -100,6 +102,8 @@ extension FeatureFlag: FeatureFlagDescribing {
return true
case .testExperiment:
return true
case .networkProtectionRiskyDomainsProtection:
return true
default:
return false
}
Expand Down Expand Up @@ -185,6 +189,8 @@ extension FeatureFlag: FeatureFlagDescribing {
return .remoteReleasable(.subfeature(ExperimentTestSubfeatures.experimentTestAA))
case .maliciousSiteProtection:
return .remoteReleasable(.subfeature(MaliciousSiteProtectionSubfeature.onByDefault))
case .networkProtectionRiskyDomainsProtection:
return .remoteReleasable(.subfeature(NetworkProtectionSubfeature.riskyDomainsProtection))
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions DuckDuckGo-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,11 @@
569437362BE5160600C0881B /* SyncSettingsViewControllerErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569437352BE5160600C0881B /* SyncSettingsViewControllerErrorTests.swift */; };
56A061442BEE086700F24B36 /* CapturingAdapterErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569437262BDD467400C0881B /* CapturingAdapterErrorHandler.swift */; };
56A061452BEE086E00F24B36 /* CapturingSyncPausedStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569437372BE530D300C0881B /* CapturingSyncPausedStateManager.swift */; };
56CCBA2E2D5CF07D00EA0EB5 /* NetworkProtectionUIElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56CCBA2D2D5CF07D00EA0EB5 /* NetworkProtectionUIElements.swift */; };
56D060262C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D060252C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift */; };
56D060282C380D83003BAEB5 /* OnboardingSuggestedSearchesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D060272C380D83003BAEB5 /* OnboardingSuggestedSearchesProvider.swift */; };
56D0602D2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D0602C2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift */; };
56D2D65C2D5F748200C59354 /* NetworkProtectionDNSSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D2D65B2D5F748200C59354 /* NetworkProtectionDNSSettingsViewModelTests.swift */; };
56D7792C2CFF476800B619EF /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; };
56D7793A2CFFC7E800B619EF /* PixelExperimentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 56D779392CFFC7E800B619EF /* PixelExperimentKit */; };
56D7793C2CFFC7E800B619EF /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 56D7793B2CFFC7E800B619EF /* PixelKit */; };
Expand Down Expand Up @@ -1790,9 +1792,11 @@
569437322BE4E3DD00C0881B /* SyncErrorHandlerSyncErrorsAlertsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandlerSyncErrorsAlertsTests.swift; sourceTree = "<group>"; };
569437352BE5160600C0881B /* SyncSettingsViewControllerErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncSettingsViewControllerErrorTests.swift; sourceTree = "<group>"; };
569437372BE530D300C0881B /* CapturingSyncPausedStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturingSyncPausedStateManager.swift; sourceTree = "<group>"; };
56CCBA2D2D5CF07D00EA0EB5 /* NetworkProtectionUIElements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUIElements.swift; sourceTree = "<group>"; };
56D060252C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingDialogs.swift; sourceTree = "<group>"; };
56D060272C380D83003BAEB5 /* OnboardingSuggestedSearchesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSuggestedSearchesProvider.swift; sourceTree = "<group>"; };
56D0602C2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSuggestedSearchesProviderTests.swift; sourceTree = "<group>"; };
56D2D65B2D5F748200C59354 /* NetworkProtectionDNSSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModelTests.swift; sourceTree = "<group>"; };
56D855692BEA9169009F9698 /* CurrentDateProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentDateProviding.swift; sourceTree = "<group>"; };
56D8556B2BEA91C4009F9698 /* SyncAlertsPresenting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncAlertsPresenting.swift; sourceTree = "<group>"; };
653561012D4D2C680064F258 /* Logger+DuckPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DuckPlayer.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5314,6 +5318,7 @@
children = (
981FED682201FE69008488D7 /* AutoClearSettingsScreenTests.swift */,
8598F6792405EB8600FBC70C /* KeyboardSettingsTests.swift */,
56D2D65B2D5F748200C59354 /* NetworkProtectionDNSSettingsViewModelTests.swift */,
);
name = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -6418,6 +6423,7 @@
4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */,
BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */,
BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */,
56CCBA2D2D5CF07D00EA0EB5 /* NetworkProtectionUIElements.swift */,
);
name = VPNSettings;
sourceTree = "<group>";
Expand Down Expand Up @@ -8560,6 +8566,7 @@
C1935A122C88D1D8001AD72D /* AutofillHeaderViewFactory.swift in Sources */,
31B2F11F287846320040427A /* NoMicPermissionAlert.swift in Sources */,
310C4B45281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift in Sources */,
56CCBA2E2D5CF07D00EA0EB5 /* NetworkProtectionUIElements.swift in Sources */,
6F9FFE2D2C57AE8F00A238BE /* NewTabPageShortcutsSettingsModel.swift in Sources */,
CBF2597D2D41734700AC63E4 /* Terminating.swift in Sources */,
D62EC3C22C248AF800FC9D04 /* DuckPlayerNavigationHandling.swift in Sources */,
Expand Down Expand Up @@ -9070,6 +9077,7 @@
C14882EA27F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift in Sources */,
6594CCEA2D4A3E1200188B1A /* DuckPlayerSettingsTests.swift in Sources */,
1E05D1DB29C47B3300BF9A1F /* DailyPixelTests.swift in Sources */,
56D2D65C2D5F748200C59354 /* NetworkProtectionDNSSettingsViewModelTests.swift in Sources */,
564DE4552C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift in Sources */,
6F5AA3EF2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift in Sources */,
981FED7422046017008488D7 /* AutoClearTests.swift in Sources */,
Expand Down Expand Up @@ -12363,8 +12371,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit.git";
requirement = {
kind = exactVersion;
version = 237.1.0;
branch = "sabrina/risky-sites-protection";
kind = branch;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit.git",
"state" : {
"revision" : "92f57bfcf15258a360f6df8a48da756491683fe0",
"version" : "237.1.0"
"branch" : "sabrina/risky-sites-protection",
"revision" : "c933d36a84b488150eedce6425ed8c3f5c38ba1f"
}
},
{
Expand All @@ -59,8 +59,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/duckduckgo-autofill.git",
"state" : {
"revision" : "676d42679296a175169e433f2332e55644151edd",
"version" : "16.2.0"
"revision" : "3a9606fd26e9a54bf369cc241e6fa3b2571eb13a",
"version" : "16.2.1"
}
},
{
Expand Down Expand Up @@ -131,8 +131,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/privacy-dashboard",
"state" : {
"revision" : "099f7ed5faac946e4d80746703aaaf87fdfbee09",
"version" : "8.3.0"
"revision" : "c05d7ce7debe45593bfd9759d6bcae0cd740c52f",
"version" : "8.4.0"
}
},
{
Expand Down
42 changes: 16 additions & 26 deletions DuckDuckGo/NetworkProtectionDNSSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ import SwiftUI
import NetworkProtection

struct NetworkProtectionDNSSettingsView: View {
@StateObject var viewModel = NetworkProtectionDNSSettingsViewModel(settings: VPNSettings(defaults: .networkProtectionGroupDefaults))
@StateObject var viewModel = NetworkProtectionDNSSettingsViewModel(settings: VPNSettings(defaults: .networkProtectionGroupDefaults), controller: AppDependencyProvider.shared.networkProtectionTunnelController)
@Environment(\.dismiss) private var dismiss
@FocusState private var isCustomDNSServerFocused: Bool

var body: some View {
VStack {
List {
Section {
ChecklistItem(isSelected: !viewModel.isCustomDNSSelected) {
NetworkProtectionUIElements.ChecklistItem(isSelected: !viewModel.isCustomDNSSelected) {
viewModel.toggleDNSSettings()
} label: {
Text(UserText.vpnSettingDNSServerOptionRecommended)
.daxBodyRegular()
.foregroundStyle(Color(designSystemColor: .textPrimary))
}
ChecklistItem(isSelected: viewModel.isCustomDNSSelected) {
NetworkProtectionUIElements.ChecklistItem(isSelected: viewModel.isCustomDNSSelected) {
viewModel.toggleDNSSettings()
} label: {
Text(UserText.vpnSettingDNSServerOptionCustom)
Expand All @@ -57,6 +57,10 @@ struct NetworkProtectionDNSSettingsView: View {

if viewModel.isCustomDNSSelected {
customDNSSection()
} else {
if viewModel.isRiskySitesProtectionFeatureEnabled {
blockRiskyDomainsSection()
}
}
}
}
Expand Down Expand Up @@ -105,29 +109,15 @@ struct NetworkProtectionDNSSettingsView: View {
isCustomDNSServerFocused = true
}
}
}

private struct ChecklistItem<Content>: View where Content: View {
let isSelected: Bool
let action: () -> Void
@ViewBuilder let label: () -> Content

var body: some View {
Button(
action: action,
label: {
HStack(spacing: 12) {
label()
Spacer()
Image(systemName: "checkmark")
.tint(.init(designSystemColor: .accent))
.if(!isSelected) {
$0.hidden()
}
}
}
)
.tint(Color(designSystemColor: .textPrimary))
.listRowInsets(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16))
func blockRiskyDomainsSection() -> some View {
NetworkProtectionUIElements.ToggleSectionView(
text: "Block risky domains",
headerText: "Content Blocking and Filtering",
footerText: UserText.vpnContentBlockingFilteringFooter
) {
Toggle("", isOn: $viewModel.isBlockRiskyDomainsOn)
}
}

}
54 changes: 44 additions & 10 deletions DuckDuckGo/NetworkProtectionDNSSettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,67 @@ import Foundation
import Combine
import NetworkProtection
import Core
import BrowserServicesKit

final class NetworkProtectionDNSSettingsViewModel: ObservableObject {
private let settings: VPNSettings
private let controller: TunnelController
private let featureFlagger: FeatureFlagger
private var cancellables: Set<AnyCancellable> = []

@Published public var dnsSettings: NetworkProtectionDNSSettings = .default
@Published public var dnsSettings: NetworkProtectionDNSSettings

@Published public var isCustomDNSSelected = false

@Published public var customDNSServers = ""
@Published public var isCustomDNSSelected: Bool

@Published var isBlockRiskyDomainsOn: Bool {
didSet {
applyDNSSettings()
}
}

@Published public var customDNSServers: String

@Published public var isApplyButtonEnabled = false

init(settings: VPNSettings) {
var isRiskySitesProtectionFeatureEnabled: Bool {
featureFlagger.isFeatureOn(.networkProtectionRiskyDomainsProtection)
}

init(settings: VPNSettings, controller: TunnelController, featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger) {
self.settings = settings
self.controller = controller
self.featureFlagger = featureFlagger

dnsSettings = settings.dnsSettings
isBlockRiskyDomainsOn = settings.isBlockRiskyDomainsOn
isCustomDNSSelected = settings.dnsSettings.usesCustomDNS
customDNSServers = settings.customDnsServers.joined(separator: ", ")

subscribeToDNSSettingsChanges()
}

func subscribeToDNSSettingsChanges() {
settings.dnsSettingsPublisher
.receive(on: DispatchQueue.main)
.assign(to: \.dnsSettings, onWeaklyHeld: self)
.store(in: &cancellables)

isCustomDNSSelected = settings.dnsSettings.usesCustomDNS
customDNSServers = settings.dnsSettings.dnsServersText
}

func toggleDNSSettings() {
isCustomDNSSelected.toggle()
}

func toggleIsBlockRiskyDomainsOn() {
isBlockRiskyDomainsOn.toggle()
}

func applyDNSSettings() {
if isCustomDNSSelected {
settings.dnsSettings = .custom([customDNSServers])
} else {
settings.dnsSettings = .default
settings.dnsSettings = .ddg(blockRiskyDomains: isBlockRiskyDomainsOn)
}
reloadVPN()

/// Updating `dnsSettings` does an IPv4 conversion before actually commiting the change,
/// so we do a final check to see which outcome the user ends up with
Expand All @@ -73,12 +99,20 @@ final class NetworkProtectionDNSSettingsViewModel: ObservableObject {
isApplyButtonEnabled = true
}
}

private func reloadVPN() {
Task {
// We need to allow some time for the setting to propagate
try await Task.sleep(interval: 0.1)
try await controller.command(.restartAdapter)
}
}
}

extension NetworkProtectionDNSSettings {
fileprivate var dnsServersText: String {
switch self {
case .default: return ""
case .ddg: return ""
case .custom(let servers): return servers.joined(separator: ", ")
}
}
Expand Down
7 changes: 6 additions & 1 deletion DuckDuckGo/NetworkProtectionTunnelController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,12 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
}
options[NetworkProtectionOptionKey.selectedEnvironment] = AppDependencyProvider.shared.vpnSettings
.selectedEnvironment.rawValue as NSString
if let data = try? JSONEncoder().encode(AppDependencyProvider.shared.vpnSettings.dnsSettings) {

var dnsSettings = settings.dnsSettings
if dnsSettings == .ddg(blockRiskyDomains: true) && !featureFlagger.isFeatureOn(.networkProtectionRiskyDomainsProtection) {
dnsSettings = .ddg(blockRiskyDomains: false)
}
if let data = try? JSONEncoder().encode(dnsSettings) {
options[NetworkProtectionOptionKey.dnsSettings] = NSData(data: data)
}

Expand Down
Loading
Loading