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

risky domains protection #3865

Open
wants to merge 9 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
10 changes: 8 additions & 2 deletions DuckDuckGo-macOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1858,6 +1858,8 @@
56AC09C82C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; };
56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; };
56B234C02A84EFD800F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; };
56B33CE12D5F58D20062DBC2 /* VPNPreferencesModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B33CE02D5F58D20062DBC2 /* VPNPreferencesModelTests.swift */; };
56B33CE22D5F58D20062DBC2 /* VPNPreferencesModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B33CE02D5F58D20062DBC2 /* VPNPreferencesModelTests.swift */; };
56BA1E752BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */; };
56BA1E762BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */; };
56BA1E802BAB2E43001CF69F /* ErrorPageTabExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */; };
Expand Down Expand Up @@ -4297,6 +4299,7 @@
56A214AE2CB583BF00E5BC0E /* TrackerMessageProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerMessageProviderTests.swift; sourceTree = "<group>"; };
56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewControllerTests.swift; sourceTree = "<group>"; };
56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarUrlExtensionsTests.swift; sourceTree = "<group>"; };
56B33CE02D5F58D20062DBC2 /* VPNPreferencesModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNPreferencesModelTests.swift; sourceTree = "<group>"; };
56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageTabExtension.swift; sourceTree = "<group>"; };
56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPageTabExtensionTest.swift; sourceTree = "<group>"; };
56BA1E892BB1CB5B001CF69F /* CertificateTrustEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateTrustEvaluator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6522,6 +6525,7 @@
3714B1E628EDB7FA0056C57A /* DuckPlayerPreferencesTests.swift */,
1D9FDEC52B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift */,
021EA0822BD6DF1B00772C9A /* TabsPreferencesTests.swift */,
56B33CE02D5F58D20062DBC2 /* VPNPreferencesModelTests.swift */,
);
path = Preferences;
sourceTree = "<group>";
Expand Down Expand Up @@ -12610,6 +12614,7 @@
374EF08429B7575B003D2E87 /* RecentlyClosedCoordinatorTests.swift in Sources */,
3706FDEA293F661700E42796 /* BookmarkNodeTests.swift in Sources */,
3706FDEB293F661700E42796 /* WebsiteDataStoreTests.swift in Sources */,
56B33CE22D5F58D20062DBC2 /* VPNPreferencesModelTests.swift in Sources */,
3706FDEC293F661700E42796 /* TabCollectionViewModelTests.swift in Sources */,
5677A93C2C98414800DA7B0A /* ContextualOnboardingStateMachineTests.swift in Sources */,
3706FDED293F661700E42796 /* EncryptionKeyStoreMock.swift in Sources */,
Expand Down Expand Up @@ -14615,6 +14620,7 @@
CD3301302C89B602009AA127 /* ErrorPageHTMLFactoryTests.swift in Sources */,
4B2975992828285900187C4E /* FirefoxKeyReaderTests.swift in Sources */,
B698E5042908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift in Sources */,
56B33CE12D5F58D20062DBC2 /* VPNPreferencesModelTests.swift in Sources */,
56D145EE29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */,
C11198342C89AEFA00F0272C /* FreemiumDBPFirstProfileSavedNotifierTests.swift in Sources */,
4BB99D0F26FE1A84001E4761 /* ChromiumBookmarksReaderTests.swift in Sources */,
Expand Down Expand Up @@ -15717,8 +15723,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 237.1.0;
kind = revision;
revision = c933d36a84b488150eedce6425ed8c3f5c38ba1f;
};
};
9FF521422BAA8FF300B9819B /* 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",
"state" : {
"revision" : "92f57bfcf15258a360f6df8a48da756491683fe0",
"version" : "237.1.0"
"branch" : "sabrina/risky-sites-protection",
"revision" : "9a6d1f4f0220da9639e72cf81bd40554341f106c"
}
},
{
Expand Down Expand Up @@ -158,8 +158,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "f2f3774fd116a305136b6866e5e7cb7dff39d8f2",
"version" : "3.10.1"
"revision" : "b828ba476ab068f0b00d6b41f92f364961b0f323",
"version" : "3.10.2"
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ extension URL {

static var maliciousSiteProtectionLearnMore = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy/phishing-and-malware-protection/")!

static var dnsBlocklistLearnMore = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/dns-blocklists")!

static var searchSettings: URL {
return URL(string: "https://duckduckgo.com/settings/")!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ extension UserText {

static let vpnDnsServerPickerDefaultTitle = NSLocalizedString("vpn.dns.server.picker.default.title", value: "DuckDuckGo (Recommended)", comment: "Title of the default DNS server option")

static let vpnDnsServerBlockRiskyDomainsToggleTitle = NSLocalizedString("vpn.dns.server.block.risky.domains.toggle.title", value: "Block risky domains", comment: "Name of option the user can opt in where the VPN blocks risky domains")

static let vpnDnsServerBlockRiskyDomainsToggleFooter = NSLocalizedString("vpn.dns.server.block.risky.domains.toggle.footer", value: "Block 150,000+ domains flagged for hosting malware, phishing attacks, and online scams with a DNS-level blocklist.", comment: "Explanation in a footer of option the user can opt in where the VPN blocks risky domains")

static let vpnDnsServerPickerCustomTitle = NSLocalizedString("vpn.dns.server.picker.custom.title", value: "Custom", comment: "Title of the custom DNS server option")

static let vpnDnsServerPickerCustomButtonTitle = NSLocalizedString("vpn.dns.server.picker.custom.button.title", value: "Change…", comment: "Button title of the custom DNS server option")
Expand Down
24 changes: 24 additions & 0 deletions DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -68637,6 +68637,30 @@
}
}
},
"vpn.dns.server.block.risky.domains.toggle.footer" : {
"comment" : "Explanation in a footer of option the user can opt in where the VPN blocks risky domains",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Block 150,000+ domains flagged for hosting malware, phishing attacks, and online scams with a DNS-level blocklist."
}
}
}
},
"vpn.dns.server.block.risky.domains.toggle.title" : {
"comment" : "Name of option the user can opt in where the VPN blocks risky domains",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Block risky domains"
}
}
}
},
"vpn.dns.server.disclaimer" : {
"comment" : "Disclaimer for the custom DNS server option",
"extractionState" : "extracted_with_value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,11 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
options[NetworkProtectionOptionKey.selectedLocation] = NSData(data: data)
}
#endif

if let data = try? JSONEncoder().encode(settings.dnsSettings) {
var dnsSettings = settings.dnsSettings
if settings.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
104 changes: 88 additions & 16 deletions DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import NetworkProtection
import NetworkProtectionIPC
import NetworkProtectionProxy
import NetworkProtectionUI
import PixelKit

final class VPNPreferencesModel: ObservableObject {

Expand All @@ -46,15 +47,8 @@ final class VPNPreferencesModel: ObservableObject {
guard settings.excludeLocalNetworks != excludeLocalNetworks else {
return
}

settings.excludeLocalNetworks = excludeLocalNetworks

Task {
// We need to allow some time for the setting to propagate
// But ultimately this should actually be a user choice
try await Task.sleep(interval: 0.1)
try await vpnXPCClient.command(.restartAdapter)
}
reloadVPN()
}
}

Expand All @@ -78,6 +72,10 @@ final class VPNPreferencesModel: ObservableObject {
featureFlagger.isFeatureOn(.networkProtectionAppExclusions)
}

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

private var isExclusionsFeatureAvailableInBuild: Bool {
proxySettings.proxyAvailable
}
Expand Down Expand Up @@ -114,9 +112,28 @@ final class VPNPreferencesModel: ObservableObject {
}
}

@Published public var dnsSettings: NetworkProtectionDNSSettings = .default
@Published public var isCustomDNSSelected = false
@Published public var customDNSServers: String?
@Published public var dnsSettings: NetworkProtectionDNSSettings
@Published public var isCustomDNSSelected: Bool {
didSet {
if oldValue != isCustomDNSSelected {
updateDNSSettings()
}
}
}
@Published public var customDNSServers: String? {
didSet {
if oldValue != customDNSServers {
updateDNSSettings()
}
}
}
@Published var isBlockRiskyDomainsOn: Bool {
didSet {
if oldValue != isBlockRiskyDomainsOn {
updateDNSSettings()
}
}
}

private let vpnXPCClient: VPNControllerXPCClient
private let settings: VPNSettings
Expand Down Expand Up @@ -150,6 +167,10 @@ final class VPNPreferencesModel: ObservableObject {
showUninstallVPN = defaults.networkProtectionOnboardingStatus != .default
onboardingStatus = defaults.networkProtectionOnboardingStatus
locationItem = VPNLocationPreferenceItemModel(selectedLocation: settings.selectedLocation)
isBlockRiskyDomainsOn = settings.isBlockRiskyDomainsOn
customDNSServers = settings.customDnsServers.joined(separator: ", ")
dnsSettings = settings.dnsSettings
isCustomDNSSelected = settings.dnsSettings.usesCustomDNS

subscribeToAppRoutingRulesChanges()
subscribeToOnboardingStatusChanges(defaults: defaults)
Expand Down Expand Up @@ -232,14 +253,35 @@ final class VPNPreferencesModel: ObservableObject {

private func subscribeToDNSSettingsChanges() {
settings.dnsSettingsPublisher
.assign(to: \.dnsSettings, onWeaklyHeld: self)
.receive(on: DispatchQueue.main)
.sink { [weak self] newDNSSettings in
guard let self = self else { return }
self.dnsSettings = newDNSSettings
if self.isCustomDNSSelected != newDNSSettings.usesCustomDNS {
self.isCustomDNSSelected = newDNSSettings.usesCustomDNS
}
if self.customDNSServers != self.settings.customDnsServers.joined(separator: ", ") {
self.customDNSServers = self.settings.customDnsServers.joined(separator: ", ")
}
if case .ddg(let blockRiskyDomains) = newDNSSettings,
self.isBlockRiskyDomainsOn != blockRiskyDomains {
self.isBlockRiskyDomainsOn = blockRiskyDomains
}
}
.store(in: &cancellables)
isCustomDNSSelected = settings.dnsSettings.usesCustomDNS
customDNSServers = settings.dnsSettings.dnsServersText
}

func reloadVPN() {
Task {
// Allow some time for the change to propagate
try await Task.sleep(interval: 0.1)
try await vpnXPCClient.command(.restartAdapter)
}
}

func resetDNSSettings() {
settings.dnsSettings = .default
settings.dnsSettings = .ddg(blockRiskyDomains: settings.isBlockRiskyDomainsOn)
reloadVPN()
}

@MainActor
Expand Down Expand Up @@ -282,12 +324,42 @@ final class VPNPreferencesModel: ObservableObject {
func manageExcludedSites() {
WindowControllersManager.shared.showVPNDomainExclusions()
}

@MainActor
func openNewTab(with url: URL) {
WindowControllersManager.shared.show(url: url, source: .ui, newTab: true)
}

private func updateDNSSettings() {
// Fire the corresponding pixel events.
if settings.dnsSettings != self.dnsSettings {
if settings.dnsSettings.usesCustomDNS {
PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionDNSUpdateCustom,
frequency: .legacyDailyAndCount)
} else {
PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionDNSUpdateDefault,
frequency: .legacyDailyAndCount)
}
}

if isCustomDNSSelected {
guard let serversText = customDNSServers, !serversText.isEmpty else {
return
}
let servers = serversText.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
settings.dnsSettings = .custom(servers)
} else {
settings.dnsSettings = .ddg(blockRiskyDomains: isBlockRiskyDomainsOn)
}
reloadVPN()
}
}

extension NetworkProtectionDNSSettings {
var dnsServersText: String? {
switch self {
case .default: return nil
case .ddg: return ""
case .custom(let servers): return servers.joined(separator: ", ")
}
}
Expand Down
Loading
Loading