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

[PM-13902] Update Bitwarden SDK for new SSH Key type #1090

Merged
merged 6 commits into from
Oct 30, 2024
Merged
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
25 changes: 25 additions & 0 deletions BitwardenShared/Core/Vault/Extensions/BitwardenSdk+Vault.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extension CipherDetailsResponseModel {
reprompt: BitwardenShared.CipherRepromptType(type: cipher.reprompt),
revisionDate: cipher.revisionDate,
secureNote: cipher.secureNote.map(CipherSecureNoteModel.init),
sshKey: cipher.sshKey.map(CipherSSHKeyModel.init),
type: BitwardenShared.CipherType(type: cipher.type),
viewPassword: cipher.viewPassword
)
Expand Down Expand Up @@ -192,6 +193,16 @@ extension CipherSecureNoteModel {
}
}

extension CipherSSHKeyModel {
init(sshKey: BitwardenSdk.SshKey) {
self.init(
keyFingerprint: sshKey.fingerprint,
privateKey: sshKey.privateKey,
publicKey: sshKey.publicKey
)
}
}

extension CipherType {
init(type: BitwardenSdk.CipherType) {
switch type {
Expand All @@ -203,6 +214,8 @@ extension CipherType {
self = .login
case .secureNote:
self = .secureNote
case .sshKey:
self = .sshKey
}
}
}
Expand Down Expand Up @@ -300,6 +313,7 @@ extension BitwardenSdk.Cipher {
identity: model.identity.map(Identity.init),
card: model.card.map(Card.init),
secureNote: model.secureNote.map(SecureNote.init),
sshKey: model.sshKey.map(SshKey.init),
favorite: model.favorite,
reprompt: BitwardenSdk.CipherRepromptType(model.reprompt),
organizationUseTotp: model.organizationUseTotp,
Expand Down Expand Up @@ -347,6 +361,7 @@ extension BitwardenSdk.CipherView: Identifiable {
identity: nil,
card: nil,
secureNote: nil,
sshKey: nil,
favorite: false,
reprompt: .none,
organizationUseTotp: false,
Expand Down Expand Up @@ -528,6 +543,16 @@ extension BitwardenSdk.SecureNoteType {
}
}

extension BitwardenSdk.SshKey {
init(sshKeyModel model: CipherSSHKeyModel) {
self.init(
privateKey: model.privateKey,
publicKey: model.publicKey,
fingerprint: model.keyFingerprint
)
}
}

extension BitwardenSdk.UriMatchType {
init(type: UriMatchType) {
switch type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,91 @@ import XCTest

@testable import BitwardenShared

// MARK: - Cipher

class BitwardenSdkVaultCipherTests: BitwardenTestCase {
// MARK: Tests

/// `init(responseModel:)` inits the correct Cipher from CipherDetailsResponseModel with `.sshKey` type.
func test_init_fromCipherDetailsResponseModelWithSSHKey() {
let responseModel = CipherDetailsResponseModel.fixture(
id: "1",
sshKey: .fixture(),
type: .sshKey
)
let cipher = Cipher(responseModel: responseModel)
XCTAssertEqual(cipher.id, responseModel.id)
XCTAssertEqual(Int(cipher.type.rawValue), responseModel.type.rawValue)
XCTAssertEqual(cipher.sshKey?.publicKey, responseModel.sshKey?.publicKey)
XCTAssertEqual(cipher.sshKey?.privateKey, responseModel.sshKey?.privateKey)
XCTAssertEqual(cipher.sshKey?.fingerprint, responseModel.sshKey?.keyFingerprint)
}
}

// MARK: - CipherDetailsResponseModel

class BitwardenSdkVaultCipherDetailsResponseModelTests: BitwardenTestCase { // swiftlint:disable:this type_name
// MARK: Tests

/// `init(cipher:)` Inits a cipher details response model from an SDK cipher without id throws.
func test_init_fromSdkNoIdThrows() throws {
let cipher = Cipher.fixture(
id: nil
)
XCTAssertThrowsError(try CipherDetailsResponseModel(cipher: cipher))
}

/// `init(cipher:)` Inits a cipher details response model from an SDK cipher that is an SSH key.
func test_init_fromSdkCipherSSHKey() throws {
let cipher = Cipher.fixture(
id: "1",
sshKey: .fixture(),
type: .sshKey
)
let responseModel = try CipherDetailsResponseModel(cipher: cipher)
XCTAssertEqual(responseModel.id, cipher.id)
XCTAssertEqual(responseModel.sshKey?.privateKey, cipher.sshKey?.privateKey)
XCTAssertEqual(responseModel.sshKey?.publicKey, cipher.sshKey?.publicKey)
XCTAssertEqual(responseModel.sshKey?.keyFingerprint, cipher.sshKey?.fingerprint)
}
}

// MARK: - CipherSSHKeyModel

class BitwardenSdkVaultCipherSSHKeyModelTests: BitwardenTestCase {
// MARK: Tests

/// `init(sshKey:)` Inits cipher SSH key model from the SDK one.
func test_init_fromSdkSSHKey() {
let model = CipherSSHKeyModel(
sshKey: .init(
privateKey: "privateKey",
publicKey: "publicKey",
fingerprint: "fingerprint"
)
)

XCTAssertEqual(model.privateKey, "privateKey")
XCTAssertEqual(model.publicKey, "publicKey")
XCTAssertEqual(model.keyFingerprint, "fingerprint")
}
}

// MARK: - CipherType

class BitwardenSdkVaultCipherTypeTests: BitwardenTestCase {
// MARK: Tests

/// `init(type:)` initializes the cipher type based on the SDK cipher type.
func test_init_bySdkCipherType() {
XCTAssertEqual(CipherType(type: .login), .login)
XCTAssertEqual(CipherType(type: .card), .card)
XCTAssertEqual(CipherType(type: .identity), .identity)
XCTAssertEqual(CipherType(type: .secureNote), .secureNote)
XCTAssertEqual(CipherType(type: .sshKey), .sshKey)
}
}

// MARK: - CipherView

class CipherViewTests: BitwardenTestCase {
Expand Down Expand Up @@ -57,6 +142,7 @@ class CipherViewTests: BitwardenTestCase {
identity: nil,
card: nil,
secureNote: nil,
sshKey: nil,
favorite: false,
reprompt: .none,
organizationUseTotp: false,
Expand Down Expand Up @@ -105,6 +191,7 @@ class CipherViewTests: BitwardenTestCase {
identity: nil,
card: nil,
secureNote: nil,
sshKey: nil,
favorite: false,
reprompt: .none,
organizationUseTotp: false,
Expand Down
14 changes: 14 additions & 0 deletions BitwardenShared/Core/Vault/Models/API/CipherSSHKeyModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// API model for an SSH key.
///
struct CipherSSHKeyModel: Codable, Equatable {
// MARK: Properties

/// The key fingerprint of the SSH key.
let keyFingerprint: String?

/// The private key of the SSH key.
let privateKey: String?

/// The public key of the SSH key.
let publicKey: String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

@testable import BitwardenShared

extension CipherSSHKeyModel {
static func fixture(
keyFingerprint: String? = "keyFingerprint",
privateKey: String? = "privateKey",
publicKey: String? = "publicKey"
) -> CipherSSHKeyModel {
self.init(
keyFingerprint: keyFingerprint,
privateKey: privateKey,
publicKey: publicKey
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ struct CipherDetailsResponseModel: JSONResponse, Equatable {
/// Secure note data if the cipher is a secure note.
let secureNote: CipherSecureNoteModel?

/// SSH key if the `type` is `.sshKey`.
let sshKey: CipherSSHKeyModel?

/// The type of the cipher.
let type: CipherType

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extension CipherDetailsResponseModel {
reprompt: CipherRepromptType = .none,
revisionDate: Date = Date(),
secureNote: CipherSecureNoteModel? = nil,
sshKey: CipherSSHKeyModel? = nil,
type: CipherType = .login,
viewPassword: Bool = false
) -> CipherDetailsResponseModel {
Expand All @@ -50,6 +51,7 @@ extension CipherDetailsResponseModel {
reprompt: reprompt,
revisionDate: revisionDate,
secureNote: secureNote,
sshKey: sshKey,
type: type,
viewPassword: viewPassword
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class CipherAPIServiceTests: XCTestCase { // swiftlint:disable:this type_body_le
reprompt: .none,
revisionDate: Date(timeIntervalSince1970: 1_691_656_425.345),
secureNote: nil,
sshKey: nil,
type: .login,
viewPassword: true
)
Expand Down Expand Up @@ -121,6 +122,7 @@ class CipherAPIServiceTests: XCTestCase { // swiftlint:disable:this type_body_le
reprompt: .none,
revisionDate: Date(timeIntervalSince1970: 1_691_656_425.345),
secureNote: nil,
sshKey: nil,
type: .login,
viewPassword: true
)
Expand Down Expand Up @@ -248,6 +250,7 @@ class CipherAPIServiceTests: XCTestCase { // swiftlint:disable:this type_body_le
reprompt: .none,
revisionDate: Date(timeIntervalSince1970: 1_691_656_425.345),
secureNote: nil,
sshKey: nil,
type: .login,
viewPassword: true
),
Expand Down Expand Up @@ -300,6 +303,7 @@ class CipherAPIServiceTests: XCTestCase { // swiftlint:disable:this type_body_le
reprompt: .none,
revisionDate: Date(timeIntervalSince1970: 1_691_656_425.345),
secureNote: nil,
sshKey: nil,
type: .login,
viewPassword: true
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ extension CipherListViewType {
self = .login(hasFido2: !(cipher.login?.fido2Credentials?.isEmpty ?? true), totp: cipher.login?.totp)
case .secureNote:
self = .secureNote
case .sshKey:
self = .sshKey
}
}
}
Expand All @@ -108,6 +110,7 @@ extension Cipher {
identity: cipherView.identity.map(Identity.init),
card: cipherView.card.map(Card.init),
secureNote: cipherView.secureNote.map(SecureNote.init),
sshKey: cipherView.sshKey.map(SshKey.init),
favorite: cipherView.favorite,
reprompt: cipherView.reprompt,
organizationUseTotp: cipherView.organizationUseTotp,
Expand Down Expand Up @@ -139,6 +142,7 @@ extension CipherView {
identity: cipher.identity.map(IdentityView.init),
card: cipher.card.map(CardView.init),
secureNote: cipher.secureNote.map(SecureNoteView.init),
sshKey: cipher.sshKey.map(SshKeyView.init),
favorite: cipher.favorite,
reprompt: cipher.reprompt,
organizationUseTotp: cipher.organizationUseTotp,
Expand Down Expand Up @@ -481,4 +485,24 @@ extension Send {
expirationDate: sendView.expirationDate
)
}
}

extension SshKey {
init(sshKeyView: SshKeyView) {
self.init(
privateKey: sshKeyView.privateKey,
publicKey: sshKeyView.publicKey,
fingerprint: sshKeyView.fingerprint
)
}
}

extension SshKeyView {
init(sshKey: SshKey) {
self.init(
privateKey: sshKey.privateKey,
publicKey: sshKey.publicKey,
fingerprint: sshKey.fingerprint
)
}
} // swiftlint:disable:this file_length
Original file line number Diff line number Diff line change
Expand Up @@ -1055,3 +1055,6 @@
"SomethingWentWrong" = "Something went wrong";
"UnableToMoveTheSelectedItemPleaseTryAgain" = "Unable to move the selected item. Please try again.";
"Done" = "Done";
"CopyPublicKey" = "Copy public key";
"CopyPrivateKey" = "Copy private key";
"CopyFingerprint" = "Copy fingerprint";
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ extension Alert {
try await tapAction(title: Localizations.cancel)
}

/// Simulates a user interaction with the alert action that is in the specified index and matches the title.
/// - Parameters:
/// - byIndex: The index to get the alert action.
/// - withTitle: The title of the alert action to trigger.
/// - alertTextFields: `AlertTextField` list to execute the action
/// - Throws: Throws an `AlertError` if the alert action cannot be found.
func tapAction(
byIndex: Int,
withTitle: String,
_ alertTextFields: [AlertTextField]? = nil
) async throws {
let alertAction = alertActions[byIndex]
guard alertAction.title == withTitle else {
throw AlertError.alertActionNotFound(title: withTitle)
}
await alertAction.handler?(alertAction, alertTextFields ?? self.alertTextFields)
}

/// Simulates a user interaction with the alert action that matches the provided title.
///
/// - Parameters:
Expand Down
37 changes: 36 additions & 1 deletion BitwardenShared/UI/Vault/Extensions/Alert+Vault.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ extension Alert {
///
/// - Returns: An alert presenting the user with options to select an attachment type.
@MainActor
static func moreOptions( // swiftlint:disable:this function_body_length function_parameter_count
static func moreOptions( // swiftlint:disable:this function_body_length function_parameter_count cyclomatic_complexity line_length
canCopyTotp: Bool,
cipherView: CipherView,
hasMasterPassword: Bool,
Expand Down Expand Up @@ -263,6 +263,41 @@ extension Alert {
))
})
}
case .sshKey:
if let publicKey = cipherView.sshKey?.publicKey {
alertActions.append(AlertAction(title: Localizations.copyPublicKey, style: .default) { _, _ in
await action(.copy(
toast: Localizations.publicKey,
value: publicKey,
requiresMasterPasswordReprompt: false,
logEvent: nil,
cipherId: cipherView.id
))
})
}
if let privateKey = cipherView.sshKey?.privateKey,
cipherView.viewPassword {
alertActions.append(AlertAction(title: Localizations.copyPrivateKey, style: .default) { _, _ in
await action(.copy(
toast: Localizations.privateKey,
value: privateKey,
requiresMasterPasswordReprompt: cipherView.reprompt == .password && hasMasterPassword,
logEvent: nil,
cipherId: cipherView.id
))
})
}
if let fingerprint = cipherView.sshKey?.fingerprint {
alertActions.append(AlertAction(title: Localizations.copyFingerprint, style: .default) { _, _ in
await action(.copy(
toast: Localizations.fingerprint,
value: fingerprint,
requiresMasterPasswordReprompt: false,
logEvent: nil,
cipherId: cipherView.id
))
})
}
}

// Return the alert.
Expand Down
Loading
Loading