Skip to content

Commit

Permalink
[PM-13902] Update Bitwarden SDK for new SSH Key type (#1090)
Browse files Browse the repository at this point in the history
  • Loading branch information
fedemkr authored Oct 30, 2024
1 parent 17ae8bc commit f588bd5
Show file tree
Hide file tree
Showing 20 changed files with 403 additions and 2 deletions.
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
87 changes: 87 additions & 0 deletions BitwardenShared/Core/Vault/Extensions/BitwardenSdkVaultTests.swift
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

0 comments on commit f588bd5

Please sign in to comment.