Skip to content

Commit

Permalink
Add VPN reddit cookie workaround (#2851)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/1203137811378537/1207423045670637/f
Tech Design URL:
CC:

Description:

This PR adds the reddit cookie VPN workaround.
  • Loading branch information
samsymons authored Jun 12, 2024
1 parent 0564524 commit be3a02d
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 1 deletion.
6 changes: 6 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,8 @@
4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */; };
4BE344EE2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; };
4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; };
4BE3A6C12C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */; };
4BE3A6C22C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */; };
4BE4005327CF3DC3007D3161 /* SavePaymentMethodPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */; };
4BE4005527CF3F19007D3161 /* SavePaymentMethodViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4005427CF3F19007D3161 /* SavePaymentMethodViewController.swift */; };
4BE41A5E28446EAD00760399 /* BookmarksBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE41A5D28446EAD00760399 /* BookmarksBarViewModel.swift */; };
Expand Down Expand Up @@ -3300,6 +3302,7 @@
4BD57C032AC112DF00B580EE /* SurveyRemoteMessagingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyRemoteMessagingTests.swift; sourceTree = "<group>"; };
4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableScrollView.swift; sourceTree = "<group>"; };
4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModelTests.swift; sourceTree = "<group>"; };
4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNRedditSessionWorkaround.swift; sourceTree = "<group>"; };
4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodPopover.swift; sourceTree = "<group>"; };
4BE4005427CF3F19007D3161 /* SavePaymentMethodViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodViewController.swift; sourceTree = "<group>"; };
4BE41A5D28446EAD00760399 /* BookmarksBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5188,6 +5191,7 @@
EEA3EEAF2B24EB5100E8333A /* VPNLocation */,
7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */,
B6F1B02D2BCE6B47005E863C /* TunnelControllerProvider.swift */,
4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */,
);
path = BothAppTargets;
sourceTree = "<group>";
Expand Down Expand Up @@ -9895,6 +9899,7 @@
3706FB80293F65D500E42796 /* NSAlert+ActiveDownloadsTermination.swift in Sources */,
B677FC552B064A9C0099EB04 /* DataImportViewModel.swift in Sources */,
D64A5FF92AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift in Sources */,
4BE3A6C22C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */,
3707C717294B5D0F00682A9F /* FindInPageTabExtension.swift in Sources */,
3706FB81293F65D500E42796 /* IndexPathExtension.swift in Sources */,
4BF97AD62B43C45800EB4240 /* NetworkProtectionNavBarPopoverManager.swift in Sources */,
Expand Down Expand Up @@ -11113,6 +11118,7 @@
B6C0B23626E732000031CB7F /* DownloadListItem.swift in Sources */,
9F872DA32B90920F00138637 /* BookmarkFolderInfo.swift in Sources */,
4B9DB0232A983B24000927DB /* WaitlistRequest.swift in Sources */,
4BE3A6C12C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */,
B6B1E87E26D5DA0E0062C350 /* DownloadsPopover.swift in Sources */,
85774AFF2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift in Sources */,
4B9292A026670D2A00AD2C21 /* SpacerNode.swift in Sources */,
Expand Down
29 changes: 29 additions & 0 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

#endif

private lazy var vpnRedditSessionWorkaround: VPNRedditSessionWorkaround = {
let ipcClient = TunnelControllerIPCClient()
let statusReporter = DefaultNetworkProtectionStatusReporter(
statusObserver: ipcClient.connectionStatusObserver,
serverInfoObserver: ipcClient.serverInfoObserver,
connectionErrorObserver: ipcClient.connectionErrorObserver,
connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(),
controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications(),
dataVolumeObserver: ipcClient.dataVolumeObserver,
knownFailureObserver: KnownFailureObserverThroughDistributedNotifications()
)

return VPNRedditSessionWorkaround(
accountManager: accountManager,
ipcClient: ipcClient,
statusReporter: statusReporter
)
}()

private var didFinishLaunching = false

#if SPARKLE
Expand Down Expand Up @@ -360,6 +379,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily)
}
}

Task { @MainActor in
await vpnRedditSessionWorkaround.installRedditSessionWorkaround()
}
}

func applicationDidResignActive(_ notification: Notification) {
Task { @MainActor in
await vpnRedditSessionWorkaround.removeRedditSessionWorkaround()
}
}

func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
private func fetchAuthToken() throws -> NSString? {

if let accessToken = try? accessTokenStorage.getAccessToken() {
os_log(.error, log: .networkProtection, "🟢 TunnelController found token: %{public}d", accessToken)
os_log(.error, log: .networkProtection, "🟢 TunnelController found token")
return Self.adaptAccessTokenForVPN(accessToken) as NSString?
}
os_log(.error, log: .networkProtection, "🔴 TunnelController found no token :(")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// VPNRedditSessionWorkaround.swift
//
// 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 NetworkProtection
import NetworkProtectionIPC
import Subscription
import WebKit
import Common

final class VPNRedditSessionWorkaround {

private let accountManager: AccountManaging
private let ipcClient: TunnelControllerIPCClient
private let statusReporter: NetworkProtectionStatusReporter

init(accountManager: AccountManaging,
ipcClient: TunnelControllerIPCClient,
statusReporter: NetworkProtectionStatusReporter) {
self.accountManager = accountManager
self.ipcClient = ipcClient
self.statusReporter = statusReporter
self.statusReporter.forceRefresh()
}

@MainActor
func installRedditSessionWorkaround() async {
let configuration = WKWebViewConfiguration()
await installRedditSessionWorkaround(to: configuration.websiteDataStore.httpCookieStore)
}

@MainActor
func removeRedditSessionWorkaround() async {
let configuration = WKWebViewConfiguration()
await removeRedditSessionWorkaround(from: configuration.websiteDataStore.httpCookieStore)
}

@MainActor
func installRedditSessionWorkaround(to cookieStore: WKHTTPCookieStore) async {
guard accountManager.isUserAuthenticated,
statusReporter.statusObserver.recentValue.isConnected,
let redditSessionCookie = HTTPCookie.emptyRedditSession else {
return
}

let cookies = await cookieStore.allCookies()
var requiresRedditSessionCookie = true
for cookie in cookies {
if cookie.domain == redditSessionCookie.domain,
cookie.name == redditSessionCookie.name {
// Avoid adding the cookie if one already exists
requiresRedditSessionCookie = false
break
}
}

if requiresRedditSessionCookie {
os_log(.error, log: .networkProtection, "Installing VPN cookie workaround...")
await cookieStore.setCookie(redditSessionCookie)
os_log(.error, log: .networkProtection, "Installed VPN cookie workaround")
}
}

func removeRedditSessionWorkaround(from cookieStore: WKHTTPCookieStore) async {
guard let redditSessionCookie = HTTPCookie.emptyRedditSession else {
return
}

let cookies = await cookieStore.allCookies()
for cookie in cookies {
if cookie.domain == redditSessionCookie.domain, cookie.name == redditSessionCookie.name {
if cookie.value == redditSessionCookie.value {
os_log(.error, log: .networkProtection, "Removing VPN cookie workaround")
await cookieStore.deleteCookie(cookie)
os_log(.error, log: .networkProtection, "Removed VPN cookie workaround")
}

break
}
}
}

}

private extension HTTPCookie {

static var emptyRedditSession: HTTPCookie? {
return HTTPCookie(properties: [
.domain: ".reddit.com",
.path: "/",
.name: "reddit_session",
.value: "",
.secure: "TRUE"
])
}

}

private extension ConnectionStatus {

var isConnected: Bool {
switch self {
case .connected: return true
default: return false
}
}

}

0 comments on commit be3a02d

Please sign in to comment.