From ea1dbb9b453a27f8985bd996a51d0d66bb9f48e3 Mon Sep 17 00:00:00 2001 From: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:54:44 +0200 Subject: [PATCH] Fix of the Bitwarden issue (#2646) Task/Issue URL: https://app.asana.com/0/1177771139624306/1207063591659803/f **Description**: Fix of the detection that DuckDuckGo integration is enabled in Bitwarden app. --- DuckDuckGo/Common/Localizables/UserText.swift | 3 + DuckDuckGo/Localizable.xcstrings | 45 ++++++++++++++ .../Bitwarden/Model/BWManager.swift | 4 ++ .../Bitwarden/Model/BWStatus.swift | 1 + .../Services/BWInstallationService.swift | 8 ++- .../Bitwarden/View/ConnectBitwardenView.swift | 60 +++++++++++-------- .../View/ConnectBitwardenViewModel.swift | 8 ++- .../PasswordManagerCoordinator.swift | 2 +- .../View/PreferencesAutofillView.swift | 52 +++++++++++++--- 9 files changed, 146 insertions(+), 37 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 0337f4f9a6..3ca586a824 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -868,6 +868,9 @@ struct UserText { static let bitwardenError = NSLocalizedString("bitwarden.error", value: "Unable to find or connect to Bitwarden", comment: "This message appears when the application is unable to find or connect to Bitwarden, indicating a connection issue.") static let bitwardenNotInstalled = NSLocalizedString("bitwarden.not.installed", value: "Bitwarden app is not installed", comment: "") static let bitwardenOldVersion = NSLocalizedString("bitwarden.old.version", value: "Please update Bitwarden to the latest version", comment: "Message that warns user they need to update their password manager Bitwarden app vesion") + static let bitwardenIncompatible = NSLocalizedString("bitwarden.incompatible", value: "The following Bitwarden versions are incompatible with DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Please revert to an older version by following these steps:", comment: "Message that warns user that specific Bitwarden app vesions are not compatible with this app") + static let bitwardenIncompatibleStep1 = NSLocalizedString("bitwarden.incompatible.step.1", value: "Download v2014.2.1", comment: "First step to downgrade Bitwarden") + static let bitwardenIncompatibleStep2 = NSLocalizedString("bitwarden.incompatible.step.2", value: "2. Open the downloaded DMG file and drag the Bitwarden application to\nthe /Applications folder.", comment: "Second step to downgrade Bitwarden") static let bitwardenIntegrationNotApproved = NSLocalizedString("bitwarden.integration.not.approved", value: "Integration with DuckDuckGo is not approved in Bitwarden app", comment: "While the user tries to connect the DuckDuckGo Browser to password manager Bitwarden This message indicates that the integration with DuckDuckGo has not been approved in the Bitwarden app.") static let bitwardenMissingHandshake = NSLocalizedString("bitwarden.missing.handshake", value: "Missing handshake", comment: "While the user tries to connect the DuckDuckGo Browser to password manager Bitwarden This message indicates a missing handshake (a way for two devices or systems to say hello to each other and agree to communicate or exchange information).") static let bitwardenWaitingForHandshake = NSLocalizedString("bitwarden.waiting.for.handshake", value: "Waiting for the handshake approval in Bitwarden app", comment: "While the user tries to connect the DuckDuckGo Browser to password manager Bitwarden This message indicates the system is waiting for the handshake (a way for two devices or systems to say hello to each other and agree to communicate or exchange information).") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 578c6d0b12..d16b6cc0c6 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -480,6 +480,12 @@ } } } + }, + "1." : { + + }, + "2. Open the downloaded DMG file and drag the Bitwarden application to\nthe /Applications folder." : { + }, "about.app_name" : { "comment" : "Application name to be displayed in the About dialog", @@ -6253,6 +6259,42 @@ } } }, + "bitwarden.incompatible" : { + "comment" : "Message that warns user that specific Bitwarden app vesions are not compatible with this app", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The following Bitwarden versions are incompatible with DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Please revert to an older version by following these steps:" + } + } + } + }, + "bitwarden.incompatible.step.1" : { + "comment" : "First step to downgrade Bitwarden", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Download v2014.2.1" + } + } + } + }, + "bitwarden.incompatible.step.2" : { + "comment" : "Second step to downgrade Bitwarden", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "2. Open the downloaded DMG file and drag the Bitwarden application to\nthe /Applications folder." + } + } + } + }, "bitwarden.install" : { "comment" : "Button to install Bitwarden app", "extractionState" : "extracted_with_value", @@ -13288,6 +13330,9 @@ } } } + }, + "Download v2014.2.1" : { + }, "download.finishing" : { "comment" : "Download being finished information text", diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift index 24be234ef2..f2cbb5486c 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift @@ -82,6 +82,10 @@ final class BWManager: BWManagement, ObservableObject { status = .oldVersion scheduleConnectionAttempt() return + case .incompatible: + status = .incompatible + scheduleConnectionAttempt() + return case .installed: break } diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWStatus.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWStatus.swift index 05b1193940..7f08701240 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWStatus.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWStatus.swift @@ -28,6 +28,7 @@ enum BWStatus: Equatable { // Installed Bitwarden doesn't support the integration case oldVersion + case incompatible // Bitwarden application isn't running case notRunning diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Services/BWInstallationService.swift b/DuckDuckGo/PasswordManager/Bitwarden/Services/BWInstallationService.swift index 7e44aea40d..a1184661bc 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Services/BWInstallationService.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Services/BWInstallationService.swift @@ -23,6 +23,7 @@ enum BWInstallationState { case notInstalled case oldVersion + case incompatible case installed } @@ -41,6 +42,7 @@ final class LocalBitwardenInstallationService: BWInstallationService { static var bundlePath = "/Applications/Bitwarden.app" private lazy var bundleUrl = URL(fileURLWithPath: Self.bundlePath) static var minimumVersion = "2022.10.1" + static var incompatibleVersions = ["2024.3.0", "2024.3.2", "2024.4.0", "2024.4.1"] private lazy var manifestPath: String = { #if DEBUG @@ -75,6 +77,10 @@ final class LocalBitwardenInstallationService: BWInstallationService { return .oldVersion } + guard !Self.incompatibleVersions.contains(version) else { + return .incompatible + } + return .installed } @@ -124,7 +130,7 @@ final class LocalBitwardenInstallationService: BWInstallationService { private func isIntegrationEnabled(in dataFileURL: URL) -> Bool { do { let dataFile = try String(contentsOf: dataFileURL) - return dataFile.range(of: "\"enableDuckDuckGoBrowserIntegration\": true") != nil + return dataFile.range(of: "enableDuckDuckGoBrowserIntegration\": true") != nil } catch { return false } diff --git a/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenView.swift b/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenView.swift index 6373dee683..62ff8c586c 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenView.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenView.swift @@ -94,9 +94,10 @@ struct ConnectBitwardenView: View { @ViewBuilder private func bodyView(for state: ConnectBitwardenViewModel.ViewState) -> some View { switch viewModel.viewState { case .disclaimer: ConnectToBitwardenDisclaimerView() - case .lookingForBitwarden: BitwardenInstallationDetectionView(bitwardenDetected: false, bitwardenNeedsUpdate: false) - case .oldVersion: BitwardenInstallationDetectionView(bitwardenDetected: true, bitwardenNeedsUpdate: true) - case .bitwardenFound: BitwardenInstallationDetectionView(bitwardenDetected: true, bitwardenNeedsUpdate: false) + case .lookingForBitwarden: BitwardenInstallationDetectionView(bitwardenDetected: false, bitwardenNeedsUpdate: false, bitwardenIsIncompatible: false) + case .incompatible: BitwardenInstallationDetectionView(bitwardenDetected: true, bitwardenNeedsUpdate: false, bitwardenIsIncompatible: true) + case .oldVersion: BitwardenInstallationDetectionView(bitwardenDetected: true, bitwardenNeedsUpdate: true, bitwardenIsIncompatible: false) + case .bitwardenFound: BitwardenInstallationDetectionView(bitwardenDetected: true, bitwardenNeedsUpdate: false, bitwardenIsIncompatible: false) case .accessToContainersNotApproved: ConnectToBitwardenView(canConnect: false, canNotAccessSandboxContainers: true) case .waitingForConnectionPermission: ConnectToBitwardenView(canConnect: false, canNotAccessSandboxContainers: false) case .connectToBitwarden: ConnectToBitwardenView(canConnect: true, canNotAccessSandboxContainers: false) @@ -155,37 +156,37 @@ private struct BitwardenInstallationDetectionView: View { let bitwardenDetected: Bool let bitwardenNeedsUpdate: Bool + let bitwardenIsIncompatible: Bool var body: some View { VStack(alignment: .leading, spacing: 10) { - Text(UserText.installBitwarden) - .font(.system(size: 13, weight: .bold)) - - HStack { - NumberedBadge(value: 1) - Text(UserText.installBitwardenInfo) + if !bitwardenIsIncompatible { + Text(UserText.installBitwarden) + .font(.system(size: 13, weight: .bold)) - Spacer() - } - - HStack { - NumberedBadge(value: 2) + HStack { + NumberedBadge(value: 1) + Text(UserText.installBitwardenInfo) + Spacer() + } - Text(UserText.afterBitwardenInstallationInfo) + HStack { + NumberedBadge(value: 2) + Text(UserText.afterBitwardenInstallationInfo) + Spacer() + } - Spacer() + Button(action: { + viewModel.process(action: .openBitwardenProductPage) + }, label: { + Image(.macAppStoreButton) + }) + .buttonStyle(PlainButtonStyle()) + .frame(width: 156, height: 40) } - Button(action: { - viewModel.process(action: .openBitwardenProductPage) - }, label: { - Image(.macAppStoreButton) - }) - .buttonStyle(PlainButtonStyle()) - .frame(width: 156, height: 40) - if bitwardenDetected { if bitwardenNeedsUpdate { HStack { @@ -193,6 +194,17 @@ private struct BitwardenInstallationDetectionView: View { Text(UserText.bitwardenOldVersion) } + } else if bitwardenIsIncompatible { + HStack { + ActivityIndicator(isAnimating: .constant(true), style: .spinning) + + VStack(alignment: .leading) { + Text(UserText.bitwardenIncompatible) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + BitwardenDowngradeInfoView() + } + } } else { HStack { Image(.successCheckmark) diff --git a/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenViewModel.swift b/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenViewModel.swift index bdfc261732..be12ac247d 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenViewModel.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/View/ConnectBitwardenViewModel.swift @@ -36,6 +36,7 @@ final class ConnectBitwardenViewModel: ObservableObject { // Bitwarden installation: case lookingForBitwarden case oldVersion + case incompatible case bitwardenFound // Bitwarden connection: @@ -48,14 +49,14 @@ final class ConnectBitwardenViewModel: ObservableObject { var canContinue: Bool { switch self { - case .lookingForBitwarden, .oldVersion, .waitingForConnectionPermission: return false + case .lookingForBitwarden, .oldVersion, .incompatible, .waitingForConnectionPermission: return false default: return true } } var confirmButtonTitle: String { switch self { - case .disclaimer, .lookingForBitwarden, .oldVersion, .bitwardenFound: return "Next" + case .disclaimer, .lookingForBitwarden, .oldVersion, .incompatible, .bitwardenFound: return "Next" case .waitingForConnectionPermission, .connectToBitwarden: return "Connect" case .accessToContainersNotApproved: return "Open System Settings" case .connectedToBitwarden: return "OK" @@ -106,6 +107,8 @@ final class ConnectBitwardenViewModel: ObservableObject { } case .oldVersion: self.viewState = .oldVersion + case .incompatible: + self.viewState = .incompatible case .notRunning: self.viewState = .waitingForConnectionPermission case .integrationNotApproved: @@ -126,7 +129,6 @@ final class ConnectBitwardenViewModel: ObservableObject { self.viewState = .connectedToBitwarden case .error(error: let error): self.error = error - } } // swiftlint:enable cyclomatic_complexity diff --git a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift index 39c5b222df..98b9e64e1d 100644 --- a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift +++ b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift @@ -91,7 +91,7 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { func askToUnlock(completionHandler: @escaping () -> Void) { switch bitwardenManagement.status { - case .disabled, .notInstalled, .oldVersion, .missingHandshake, .handshakeNotApproved, .error, .accessToContainersNotApproved: + case .disabled, .notInstalled, .oldVersion, .incompatible, .missingHandshake, .handshakeNotApproved, .error, .accessToContainersNotApproved: Task { await WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .autofill) } diff --git a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift index 63cdb989d5..25b1a2cbe2 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift @@ -197,14 +197,18 @@ extension Preferences { .offset(x: Preferences.Const.autoLockWarningOffset) case .notInstalled: BitwardenStatusView(iconType: .warning, - title: UserText.bitwardenNotInstalled, - buttonValue: nil) + title: UserText.bitwardenNotInstalled) .offset(x: Preferences.Const.autoLockWarningOffset) case .oldVersion: BitwardenStatusView(iconType: .warning, - title: UserText.bitwardenOldVersion, - buttonValue: nil) + title: UserText.bitwardenOldVersion) + .offset(x: Preferences.Const.autoLockWarningOffset) + case .incompatible: + BitwardenStatusView(iconType: .warning, + title: UserText.bitwardenIncompatible, + content: AnyView(BitwardenDowngradeInfoView())) .offset(x: Preferences.Const.autoLockWarningOffset) + .offset(x: Preferences.Const.autoLockWarningOffset) case .notRunning: BitwardenStatusView(iconType: .warning, title: UserText.bitwardenPreferencesRun, @@ -273,6 +277,13 @@ extension Preferences { private struct BitwardenStatusView: View { + internal init(iconType: BitwardenStatusView.IconType, title: String, buttonValue: BitwardenStatusView.ButtonValue? = nil, content: AnyView? = nil) { + self.iconType = iconType + self.title = title + self.buttonValue = buttonValue + self.content = content + } + struct ButtonValue { let title: String let action: () -> Void @@ -295,6 +306,7 @@ private struct BitwardenStatusView: View { let iconType: IconType let title: String let buttonValue: ButtonValue? + let content: AnyView? var body: some View { @@ -302,10 +314,15 @@ private struct BitwardenStatusView: View { HStack(alignment: .top) { Image(iconType.imageName) .padding(.top, 2) - Text(title) - .lineLimit(nil) - .fixedSize(horizontal: false, vertical: true) - .padding([.top, .bottom], 2) + VStack(alignment: .leading) { + Text(title) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding([.top, .bottom], 2) + if let content { + content.padding([.top, .bottom], 2) + } + } } .padding([.leading, .trailing], 6) .padding([.top, .bottom], 2) @@ -325,6 +342,25 @@ private struct BitwardenStatusView: View { } +struct BitwardenDowngradeInfoView: View, PreferencesTabOpening { + + var body: some View { + VStack(alignment: .leading) { + VStack(alignment: .leading) { + HStack { + Text("1.") + Button(UserText.bitwardenIncompatibleStep1, action: { + openNewTab(with: URL(string: "https://github.com/bitwarden/clients/releases/download/desktop-v2024.2.1/Bitwarden-2024.2.1-universal.dmg")!) + }).foregroundColor(.accentColor) + } + Text(UserText.bitwardenIncompatibleStep2) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + } + } + } +} + struct ResetNeverPromptSitesSheet: View { @ObservedObject var autofillPreferencesModel: AutofillPreferencesModel