diff --git a/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataModel.swift b/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataModel.swift index 7232cb350..e7d033084 100644 --- a/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataModel.swift +++ b/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataModel.swift @@ -6,6 +6,12 @@ import Foundation public struct AuthenticatorBridgeItemDataModel: Codable, Equatable { // MARK: Properties + /// The domain of the Bitwarden account that owns this item. (e.g. https://vault.bitwarden.com) + public let accountDomain: String? + + /// The email of the Bitwarden account that owns this item. + public let accountEmail: String? + /// Bool indicating if this item is a favorite. public let favorite: Bool @@ -18,6 +24,6 @@ public struct AuthenticatorBridgeItemDataModel: Codable, Equatable { /// The TOTP key used to generate codes. public let totpKey: String? - /// The username of the Bitwarden account that owns this iteam. + /// The username of the item. public let username: String? } diff --git a/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataView.swift b/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataView.swift index 0dd6d3521..54e9ad6bd 100644 --- a/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataView.swift +++ b/AuthenticatorBridgeKit/AuthenticatorBridgeItemDataView.swift @@ -6,6 +6,12 @@ import Foundation public struct AuthenticatorBridgeItemDataView: Codable, Equatable { // MARK: Properties + /// The domain of the Bitwarden account that owns this item. (e.g. https://vault.bitwarden.com) + public let accountDomain: String? + + /// The email of the Bitwarden account that owns this item. + public let accountEmail: String? + /// Bool indicating if this item is a favorite. public let favorite: Bool @@ -18,19 +24,29 @@ public struct AuthenticatorBridgeItemDataView: Codable, Equatable { /// The TOTP key used to generate codes. public let totpKey: String? - /// The username of the Bitwarden account that owns this iteam. + /// The username of the item. public let username: String? /// Initialize an `AuthenticatorBridgeItemDataView` with the values provided. /// /// - Parameters: + /// - accountDomain: The domain of the Bitwarden account that owns this item. + /// - accountEmail: The email of the Bitwarden account that owns this item /// - favorite: Bool indicating if this item is a favorite. /// - id: The unique id of the item. /// - name: The name of the item. /// - totpKey: The TOTP key used to generate codes. - /// - username: The username of the Bitwarden account that owns this item. + /// - username: The username of the item. /// - public init(favorite: Bool, id: String, name: String, totpKey: String?, username: String?) { + public init(accountDomain: String?, + accountEmail: String?, + favorite: Bool, + id: String, + name: String, + totpKey: String?, + username: String?) { + self.accountDomain = accountDomain + self.accountEmail = accountEmail self.favorite = favorite self.id = id self.name = name diff --git a/AuthenticatorBridgeKit/SharedCryptographyService.swift b/AuthenticatorBridgeKit/SharedCryptographyService.swift index 3831046df..c91a51116 100644 --- a/AuthenticatorBridgeKit/SharedCryptographyService.swift +++ b/AuthenticatorBridgeKit/SharedCryptographyService.swift @@ -62,6 +62,8 @@ public class DefaultAuthenticatorCryptographyService: SharedCryptographyService return items.map { item in AuthenticatorBridgeItemDataView( + accountDomain: (try? decrypt(item.accountDomain, withKey: symmetricKey)) ?? "", + accountEmail: (try? decrypt(item.accountEmail, withKey: symmetricKey)) ?? "", favorite: item.favorite, id: item.id, name: (try? decrypt(item.name, withKey: symmetricKey)) ?? "", @@ -79,6 +81,8 @@ public class DefaultAuthenticatorCryptographyService: SharedCryptographyService return items.map { item in AuthenticatorBridgeItemDataModel( + accountDomain: encrypt(item.accountDomain, withKey: symmetricKey) ?? "", + accountEmail: encrypt(item.accountEmail, withKey: symmetricKey) ?? "", favorite: item.favorite, id: item.id, name: encrypt(item.name, withKey: symmetricKey) ?? "", diff --git a/AuthenticatorBridgeKit/Tests/AuthenticatorBridgeItemDataTests.swift b/AuthenticatorBridgeKit/Tests/AuthenticatorBridgeItemDataTests.swift index c9a5d189f..39d6d1eb6 100644 --- a/AuthenticatorBridgeKit/Tests/AuthenticatorBridgeItemDataTests.swift +++ b/AuthenticatorBridgeKit/Tests/AuthenticatorBridgeItemDataTests.swift @@ -49,7 +49,13 @@ final class AuthenticatorBridgeItemDataTests: AuthenticatorBridgeKitTestCase { context: dataStore.persistentContainer.viewContext, userId: "userId", authenticatorItem: AuthenticatorBridgeItemDataModel( - favorite: true, id: "is", name: "name", totpKey: "TOTP Key", username: "username" + accountDomain: "https://vault.example.com", + accountEmail: "test@example.com", + favorite: true, + id: "is", + name: "name", + totpKey: "TOTP Key", + username: "username" ) ) diff --git a/AuthenticatorBridgeKit/Tests/SharedCryptographyServiceTests.swift b/AuthenticatorBridgeKit/Tests/SharedCryptographyServiceTests.swift index 8ae753c88..7596561aa 100644 --- a/AuthenticatorBridgeKit/Tests/SharedCryptographyServiceTests.swift +++ b/AuthenticatorBridgeKit/Tests/SharedCryptographyServiceTests.swift @@ -71,6 +71,16 @@ final class SharedCryptographyServiceTests: AuthenticatorBridgeKitTestCase { // Encrypted values should not remain equal, unless they were `nil` XCTAssertNotEqual(item.name, encryptedItem.name) + if item.accountDomain != nil { + XCTAssertNotEqual(item.accountDomain, encryptedItem.accountDomain) + } else { + XCTAssertNil(encryptedItem.accountDomain) + } + if item.accountEmail != nil { + XCTAssertNotEqual(item.accountEmail, encryptedItem.accountEmail) + } else { + XCTAssertNil(encryptedItem.accountEmail) + } if item.totpKey != nil { XCTAssertNotEqual(item.totpKey, encryptedItem.totpKey) } else { diff --git a/AuthenticatorBridgeKit/Tests/TestHelpers/Fixtures/AuthenticatorBridgeItemDataView+Fixtures.swift b/AuthenticatorBridgeKit/Tests/TestHelpers/Fixtures/AuthenticatorBridgeItemDataView+Fixtures.swift index ec516431a..ad5607716 100644 --- a/AuthenticatorBridgeKit/Tests/TestHelpers/Fixtures/AuthenticatorBridgeItemDataView+Fixtures.swift +++ b/AuthenticatorBridgeKit/Tests/TestHelpers/Fixtures/AuthenticatorBridgeItemDataView+Fixtures.swift @@ -4,6 +4,8 @@ import Foundation extension AuthenticatorBridgeItemDataView { static func fixture( + accountDomain: String? = "", + accountEmail: String? = "", favorite: Bool = false, id: String = UUID().uuidString, name: String = "Name", @@ -11,6 +13,8 @@ extension AuthenticatorBridgeItemDataView { username: String? = nil ) -> AuthenticatorBridgeItemDataView { AuthenticatorBridgeItemDataView( + accountDomain: accountDomain, + accountEmail: accountEmail, favorite: favorite, id: id, name: name, @@ -23,9 +27,12 @@ extension AuthenticatorBridgeItemDataView { [ AuthenticatorBridgeItemDataView.fixture(), AuthenticatorBridgeItemDataView.fixture(favorite: true), + AuthenticatorBridgeItemDataView.fixture(accountDomain: "https://vault.example.com"), + AuthenticatorBridgeItemDataView.fixture(accountEmail: "bw@example.com"), AuthenticatorBridgeItemDataView.fixture(totpKey: "TOTP Key"), AuthenticatorBridgeItemDataView.fixture(username: "Username"), AuthenticatorBridgeItemDataView.fixture(totpKey: "TOTP Key", username: "Username"), + AuthenticatorBridgeItemDataView.fixture(accountEmail: ""), AuthenticatorBridgeItemDataView.fixture(totpKey: ""), AuthenticatorBridgeItemDataView.fixture(username: ""), AuthenticatorBridgeItemDataView.fixture(totpKey: "", username: ""), diff --git a/AuthenticatorBridgeKit/Tests/TestHelpers/MockSharedCryptographyService.swift b/AuthenticatorBridgeKit/Tests/TestHelpers/MockSharedCryptographyService.swift index 49c7ba84a..4e5c5ff33 100644 --- a/AuthenticatorBridgeKit/Tests/TestHelpers/MockSharedCryptographyService.swift +++ b/AuthenticatorBridgeKit/Tests/TestHelpers/MockSharedCryptographyService.swift @@ -7,30 +7,14 @@ class MockSharedCryptographyService: SharedCryptographyService { var decryptCalled = false var encryptCalled = false - func decryptAuthenticatorItemDatas( - _ items: [AuthenticatorBridgeKit.AuthenticatorBridgeItemData] - ) async throws -> [AuthenticatorBridgeKit.AuthenticatorBridgeItemDataView] { - decryptCalled = true - - return items.compactMap { item in - guard let model = item.model else { return nil } - - return AuthenticatorBridgeItemDataView( - favorite: model.favorite, - id: model.id, - name: model.name, - totpKey: model.totpKey, - username: model.username - ) - } - } - func decryptAuthenticatorItems( _ items: [AuthenticatorBridgeItemDataModel] ) async throws -> [AuthenticatorBridgeItemDataView] { decryptCalled = true return items.map { model in AuthenticatorBridgeItemDataView( + accountDomain: model.accountDomain, + accountEmail: model.accountEmail, favorite: model.favorite, id: model.id, name: model.name, @@ -46,6 +30,8 @@ class MockSharedCryptographyService: SharedCryptographyService { encryptCalled = true return items.map { view in AuthenticatorBridgeItemDataModel( + accountDomain: view.accountDomain, + accountEmail: view.accountEmail, favorite: view.favorite, id: view.id, name: view.name, diff --git a/BitwardenShared/Core/Platform/Services/AuthenticatorSyncService.swift b/BitwardenShared/Core/Platform/Services/AuthenticatorSyncService.swift index 0ec5d3c3f..8562b0e10 100644 --- a/BitwardenShared/Core/Platform/Services/AuthenticatorSyncService.swift +++ b/BitwardenShared/Core/Platform/Services/AuthenticatorSyncService.swift @@ -160,16 +160,17 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService { let decryptedCiphers = try await totpCiphers.asyncMap { cipher in try await self.clientService.vault(for: userId).ciphers().decrypt(cipher: cipher) } - let account = try await stateService.getActiveAccount() - let username = account.profile.email + let account = try? await stateService.getAccount(userId: userId) return decryptedCiphers.map { cipher in AuthenticatorBridgeItemDataView( + accountDomain: account?.settings.environmentUrls?.webVaultHost, + accountEmail: account?.profile.email, favorite: false, id: cipher.id ?? UUID().uuidString, name: cipher.name, totpKey: cipher.login?.totp, - username: username + username: cipher.login?.username ) } } diff --git a/BitwardenShared/Core/Platform/Services/AuthenticatorSyncServiceTests.swift b/BitwardenShared/Core/Platform/Services/AuthenticatorSyncServiceTests.swift index 312a8d4d0..4a42563f1 100644 --- a/BitwardenShared/Core/Platform/Services/AuthenticatorSyncServiceTests.swift +++ b/BitwardenShared/Core/Platform/Services/AuthenticatorSyncServiceTests.swift @@ -103,7 +103,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -111,7 +111,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa deletedDate: Date(timeIntervalSinceNow: -10000), id: "Deleted", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -134,7 +134,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -163,7 +163,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa cipherDataStore.cipherSubjectByUserId["1"]?.send([ .fixture( login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -172,11 +172,13 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa waitFor(authBridgeItemService.storedItems["1"]?.first != nil) let item = try XCTUnwrap(authBridgeItemService.storedItems["1"]?.first) + XCTAssertEqual(item.accountDomain, "vault.bitwarden.com") + XCTAssertEqual(item.accountEmail, "user@bitwarden.com") XCTAssertEqual(item.favorite, false) XCTAssertNotNil(item.id) XCTAssertEqual(item.name, "Bitwarden") XCTAssertEqual(item.totpKey, "totp") - XCTAssertEqual(item.username, "user@bitwarden.com") + XCTAssertEqual(item.username, "masked@example.com") } /// Verifies that the AuthSyncService responds to new Ciphers published by converting them into ItemViews and @@ -190,7 +192,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -199,11 +201,13 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa waitFor(authBridgeItemService.storedItems["1"]?.first != nil) let item = try XCTUnwrap(authBridgeItemService.storedItems["1"]?.first) + XCTAssertEqual(item.accountDomain, "vault.bitwarden.com") + XCTAssertEqual(item.accountEmail, "user@bitwarden.com") XCTAssertEqual(item.favorite, false) XCTAssertEqual(item.id, "1234") XCTAssertEqual(item.name, "Bitwarden") XCTAssertEqual(item.totpKey, "totp") - XCTAssertEqual(item.username, "user@bitwarden.com") + XCTAssertEqual(item.username, "masked@example.com") } /// Verifies that the AuthSyncService handles an error when attempting to fetch the accounts to check @@ -343,7 +347,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -360,7 +364,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -392,7 +396,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -416,7 +420,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -424,14 +428,18 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa waitFor(authBridgeItemService.storedItems["1"]?.first != nil) let item = try XCTUnwrap(authBridgeItemService.storedItems["1"]?.first) + XCTAssertEqual(item.accountDomain, "vault.bitwarden.com") + XCTAssertEqual(item.accountEmail, "user@bitwarden.com") XCTAssertEqual(item.favorite, false) XCTAssertEqual(item.id, "1234") XCTAssertEqual(item.name, "Bitwarden") XCTAssertEqual(item.totpKey, "totp") - XCTAssertEqual(item.username, "user@bitwarden.com") + XCTAssertEqual(item.username, "masked@example.com") - await stateService.addAccount(.fixture(profile: .fixture(email: "different@bitwarden.com", - userId: "2"))) + await stateService.addAccount(.fixture( + profile: .fixture(email: "different@bitwarden.com", userId: "2"), + settings: .fixture(environmentUrls: .fixture(webVault: URL(string: "https://vault.example.com"))) + )) stateService.syncToAuthenticatorByUserId["2"] = true vaultTimeoutService.isClientLocked["2"] = false stateService.syncToAuthenticatorSubject.send(("2", true)) @@ -440,7 +448,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "4321", login: .fixture( - username: "different@bitwarden.com", + username: "masked2@example.com", totp: "totp2" ) ), @@ -448,11 +456,13 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa waitFor(authBridgeItemService.storedItems["2"]?.first != nil) let otherItem = try XCTUnwrap(authBridgeItemService.storedItems["2"]?.first) + XCTAssertEqual(otherItem.accountDomain, "vault.example.com") + XCTAssertEqual(otherItem.accountEmail, "different@bitwarden.com") XCTAssertEqual(otherItem.favorite, false) XCTAssertEqual(otherItem.id, "4321") XCTAssertEqual(otherItem.name, "Bitwarden") XCTAssertEqual(otherItem.totpKey, "totp2") - XCTAssertEqual(otherItem.username, "different@bitwarden.com") + XCTAssertEqual(otherItem.username, "masked2@example.com") } /// When the sync is turned on, but the vault is locked, the service should subscribe and wait @@ -473,7 +483,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -482,11 +492,13 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa waitFor(authBridgeItemService.storedItems["1"]?.first != nil) let item = try XCTUnwrap(authBridgeItemService.storedItems["1"]?.first) + XCTAssertEqual(item.accountDomain, "vault.bitwarden.com") + XCTAssertEqual(item.accountEmail, "user@bitwarden.com") XCTAssertEqual(item.favorite, false) XCTAssertEqual(item.id, "1234") XCTAssertEqual(item.name, "Bitwarden") XCTAssertEqual(item.totpKey, "totp") - XCTAssertEqual(item.username, "user@bitwarden.com") + XCTAssertEqual(item.username, "masked@example.com") } /// Verifies that the AuthSyncService stops listening for Cipher updates when the user's vault is locked. @@ -508,7 +520,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -545,7 +557,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ), @@ -585,7 +597,7 @@ final class AuthenticatorSyncServiceTests: BitwardenTestCase { // swiftlint:disa .fixture( id: "1234", login: .fixture( - username: "user@bitwarden.com", + username: "masked@example.com", totp: "totp" ) ),