Implement native iPhone entry editor
This commit is contained in:
@@ -585,18 +585,36 @@ fileprivate struct FfiConverterString: FfiConverter {
|
|||||||
|
|
||||||
public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
|
public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
|
||||||
|
|
||||||
|
func addEntryEditorField(editor: UInt64, kind: MobileEntryEditorFieldKind, name: String?, value: String) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
|
func beginCreateEntry(directory: String, name: String, passphrase: String?) throws -> MobileEntryEditorSession
|
||||||
|
|
||||||
|
func beginEntryEditor(path: String) throws -> MobileEntryEditorSession
|
||||||
|
|
||||||
func cancel() throws
|
func cancel() throws
|
||||||
|
|
||||||
func copyEntryField(path: String, field: UInt64) throws -> MobileEntryCopy
|
func copyEntryField(path: String, field: UInt64) throws -> MobileEntryCopy
|
||||||
|
|
||||||
|
func discardEntryEditor(editor: UInt64) throws
|
||||||
|
|
||||||
|
func entryEditor(editor: UInt64) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
func entryPage(path: String) throws -> MobileEntryPage
|
func entryPage(path: String) throws -> MobileEntryPage
|
||||||
|
|
||||||
|
func generateEntryEditorPassword(editor: UInt64, length: UInt32?, noSymbols: Bool) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
func manualLock() throws
|
func manualLock() throws
|
||||||
|
|
||||||
|
func removeEntryEditorField(editor: UInt64, field: UInt64) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
|
func reorderEntryEditorField(editor: UInt64, field: UInt64, index: UInt32) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
func replaceEntryField(path: String, field: UInt64, value: String) throws -> MobileEntryPage
|
func replaceEntryField(path: String, field: UInt64, value: String) throws -> MobileEntryPage
|
||||||
|
|
||||||
func revealEntryField(path: String, field: UInt64) throws -> String
|
func revealEntryField(path: String, field: UInt64) throws -> String
|
||||||
|
|
||||||
|
func saveEntryEditor(editor: UInt64, fields: [MobileEntryEditorInput]) throws -> MobileEntryPage
|
||||||
|
|
||||||
func setBiometricUnlock(enabled: Bool) throws -> MobileAuthenticationState
|
func setBiometricUnlock(enabled: Bool) throws -> MobileAuthenticationState
|
||||||
|
|
||||||
func state() throws -> MobileAuthenticationState
|
func state() throws -> MobileAuthenticationState
|
||||||
@@ -605,6 +623,8 @@ public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
|
|||||||
|
|
||||||
func unlockEntry(path: String, passphrase: String?) throws -> MobileAuthenticationState
|
func unlockEntry(path: String, passphrase: String?) throws -> MobileAuthenticationState
|
||||||
|
|
||||||
|
func updateEntryEditor(editor: UInt64, fields: [MobileEntryEditorInput]) throws -> MobileEntryEditorPage
|
||||||
|
|
||||||
}
|
}
|
||||||
open class MobileAuthentication: MobileAuthenticationProtocol, @unchecked Sendable {
|
open class MobileAuthentication: MobileAuthenticationProtocol, @unchecked Sendable {
|
||||||
fileprivate let handle: UInt64
|
fileprivate let handle: UInt64
|
||||||
@@ -659,6 +679,41 @@ open class MobileAuthentication: MobileAuthenticationProtocol, @unchecked Sendab
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
open func addEntryEditorField(editor: UInt64, kind: MobileEntryEditorFieldKind, name: String?, value: String)throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_add_entry_editor_field(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterTypeMobileEntryEditorFieldKind_lower(kind),
|
||||||
|
FfiConverterOptionString.lower(name),
|
||||||
|
FfiConverterString.lower(value),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open func beginCreateEntry(directory: String, name: String, passphrase: String?)throws -> MobileEntryEditorSession {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorSession_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_begin_create_entry(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterString.lower(directory),
|
||||||
|
FfiConverterString.lower(name),
|
||||||
|
FfiConverterOptionString.lower(passphrase),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open func beginEntryEditor(path: String)throws -> MobileEntryEditorSession {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorSession_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_begin_entry_editor(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterString.lower(path),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func cancel()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
open func cancel()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_cancel(
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_cancel(
|
||||||
@@ -678,6 +733,25 @@ open func copyEntryField(path: String, field: UInt64)throws -> MobileEntryCopy
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func discardEntryEditor(editor: UInt64)throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_discard_entry_editor(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),uniffiCallStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func entryEditor(editor: UInt64)throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_entry_editor(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func entryPage(path: String)throws -> MobileEntryPage {
|
open func entryPage(path: String)throws -> MobileEntryPage {
|
||||||
return try FfiConverterTypeMobileEntryPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
return try FfiConverterTypeMobileEntryPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
@@ -688,6 +762,18 @@ open func entryPage(path: String)throws -> MobileEntryPage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func generateEntryEditorPassword(editor: UInt64, length: UInt32?, noSymbols: Bool)throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_generate_entry_editor_password(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterOptionUInt32.lower(length),
|
||||||
|
FfiConverterBool.lower(noSymbols),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func manualLock()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
open func manualLock()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(
|
||||||
@@ -696,6 +782,29 @@ open func manualLock()throws {try rustCallWithError(FfiConverterTypeMobileAuth
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func removeEntryEditorField(editor: UInt64, field: UInt64)throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_remove_entry_editor_field(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterUInt64.lower(field),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open func reorderEntryEditorField(editor: UInt64, field: UInt64, index: UInt32)throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_reorder_entry_editor_field(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterUInt64.lower(field),
|
||||||
|
FfiConverterUInt32.lower(index),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func replaceEntryField(path: String, field: UInt64, value: String)throws -> MobileEntryPage {
|
open func replaceEntryField(path: String, field: UInt64, value: String)throws -> MobileEntryPage {
|
||||||
return try FfiConverterTypeMobileEntryPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
return try FfiConverterTypeMobileEntryPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
@@ -719,6 +828,17 @@ open func revealEntryField(path: String, field: UInt64)throws -> String {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func saveEntryEditor(editor: UInt64, fields: [MobileEntryEditorInput])throws -> MobileEntryPage {
|
||||||
|
return try FfiConverterTypeMobileEntryPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_save_entry_editor(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterSequenceTypeMobileEntryEditorInput.lower(fields),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func setBiometricUnlock(enabled: Bool)throws -> MobileAuthenticationState {
|
open func setBiometricUnlock(enabled: Bool)throws -> MobileAuthenticationState {
|
||||||
return try FfiConverterTypeMobileAuthenticationState_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
return try FfiConverterTypeMobileAuthenticationState_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
@@ -757,6 +877,17 @@ open func unlockEntry(path: String, passphrase: String?)throws -> MobileAuthent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func updateEntryEditor(editor: UInt64, fields: [MobileEntryEditorInput])throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobileauthentication_update_entry_editor(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterUInt64.lower(editor),
|
||||||
|
FfiConverterSequenceTypeMobileEntryEditorInput.lower(fields),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1239,6 +1370,290 @@ public func FfiConverterTypeMobileEntryCopy_lower(_ value: MobileEntryCopy) -> R
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct MobileEntryEditorField: Equatable, Hashable {
|
||||||
|
public var id: UInt64
|
||||||
|
public var kind: MobileEntryEditorFieldKind
|
||||||
|
public var name: String?
|
||||||
|
public var label: String
|
||||||
|
public var systemImage: String
|
||||||
|
public var value: String?
|
||||||
|
public var maskedValue: String
|
||||||
|
public var diagnostic: String?
|
||||||
|
public var sensitive: Bool
|
||||||
|
public var multiline: Bool
|
||||||
|
public var nameEditable: Bool
|
||||||
|
public var removable: Bool
|
||||||
|
public var reorderable: Bool
|
||||||
|
|
||||||
|
// Default memberwise initializers are never public by default, so we
|
||||||
|
// declare one manually.
|
||||||
|
public init(id: UInt64, kind: MobileEntryEditorFieldKind, name: String?, label: String, systemImage: String, value: String?, maskedValue: String, diagnostic: String?, sensitive: Bool, multiline: Bool, nameEditable: Bool, removable: Bool, reorderable: Bool) {
|
||||||
|
self.id = id
|
||||||
|
self.kind = kind
|
||||||
|
self.name = name
|
||||||
|
self.label = label
|
||||||
|
self.systemImage = systemImage
|
||||||
|
self.value = value
|
||||||
|
self.maskedValue = maskedValue
|
||||||
|
self.diagnostic = diagnostic
|
||||||
|
self.sensitive = sensitive
|
||||||
|
self.multiline = multiline
|
||||||
|
self.nameEditable = nameEditable
|
||||||
|
self.removable = removable
|
||||||
|
self.reorderable = reorderable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileEntryEditorField: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileEntryEditorField: FfiConverterRustBuffer {
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileEntryEditorField {
|
||||||
|
return
|
||||||
|
try MobileEntryEditorField(
|
||||||
|
id: FfiConverterUInt64.read(from: &buf),
|
||||||
|
kind: FfiConverterTypeMobileEntryEditorFieldKind.read(from: &buf),
|
||||||
|
name: FfiConverterOptionString.read(from: &buf),
|
||||||
|
label: FfiConverterString.read(from: &buf),
|
||||||
|
systemImage: FfiConverterString.read(from: &buf),
|
||||||
|
value: FfiConverterOptionString.read(from: &buf),
|
||||||
|
maskedValue: FfiConverterString.read(from: &buf),
|
||||||
|
diagnostic: FfiConverterOptionString.read(from: &buf),
|
||||||
|
sensitive: FfiConverterBool.read(from: &buf),
|
||||||
|
multiline: FfiConverterBool.read(from: &buf),
|
||||||
|
nameEditable: FfiConverterBool.read(from: &buf),
|
||||||
|
removable: FfiConverterBool.read(from: &buf),
|
||||||
|
reorderable: FfiConverterBool.read(from: &buf)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileEntryEditorField, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterUInt64.write(value.id, into: &buf)
|
||||||
|
FfiConverterTypeMobileEntryEditorFieldKind.write(value.kind, into: &buf)
|
||||||
|
FfiConverterOptionString.write(value.name, into: &buf)
|
||||||
|
FfiConverterString.write(value.label, into: &buf)
|
||||||
|
FfiConverterString.write(value.systemImage, into: &buf)
|
||||||
|
FfiConverterOptionString.write(value.value, into: &buf)
|
||||||
|
FfiConverterString.write(value.maskedValue, into: &buf)
|
||||||
|
FfiConverterOptionString.write(value.diagnostic, into: &buf)
|
||||||
|
FfiConverterBool.write(value.sensitive, into: &buf)
|
||||||
|
FfiConverterBool.write(value.multiline, into: &buf)
|
||||||
|
FfiConverterBool.write(value.nameEditable, into: &buf)
|
||||||
|
FfiConverterBool.write(value.removable, into: &buf)
|
||||||
|
FfiConverterBool.write(value.reorderable, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorField_lift(_ buf: RustBuffer) throws -> MobileEntryEditorField {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorField.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorField_lower(_ value: MobileEntryEditorField) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileEntryEditorField.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct MobileEntryEditorInput: Equatable, Hashable {
|
||||||
|
public var id: UInt64
|
||||||
|
public var name: String?
|
||||||
|
public var value: String?
|
||||||
|
|
||||||
|
// Default memberwise initializers are never public by default, so we
|
||||||
|
// declare one manually.
|
||||||
|
public init(id: UInt64, name: String?, value: String?) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileEntryEditorInput: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileEntryEditorInput: FfiConverterRustBuffer {
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileEntryEditorInput {
|
||||||
|
return
|
||||||
|
try MobileEntryEditorInput(
|
||||||
|
id: FfiConverterUInt64.read(from: &buf),
|
||||||
|
name: FfiConverterOptionString.read(from: &buf),
|
||||||
|
value: FfiConverterOptionString.read(from: &buf)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileEntryEditorInput, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterUInt64.write(value.id, into: &buf)
|
||||||
|
FfiConverterOptionString.write(value.name, into: &buf)
|
||||||
|
FfiConverterOptionString.write(value.value, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorInput_lift(_ buf: RustBuffer) throws -> MobileEntryEditorInput {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorInput.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorInput_lower(_ value: MobileEntryEditorInput) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileEntryEditorInput.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct MobileEntryEditorPage: Equatable, Hashable {
|
||||||
|
public var path: String
|
||||||
|
public var title: String
|
||||||
|
public var fields: [MobileEntryEditorField]
|
||||||
|
public var dirty: Bool
|
||||||
|
public var creating: Bool
|
||||||
|
public var defaultPasswordLength: UInt32
|
||||||
|
public var maximumPasswordLength: UInt32
|
||||||
|
|
||||||
|
// Default memberwise initializers are never public by default, so we
|
||||||
|
// declare one manually.
|
||||||
|
public init(path: String, title: String, fields: [MobileEntryEditorField], dirty: Bool, creating: Bool, defaultPasswordLength: UInt32, maximumPasswordLength: UInt32) {
|
||||||
|
self.path = path
|
||||||
|
self.title = title
|
||||||
|
self.fields = fields
|
||||||
|
self.dirty = dirty
|
||||||
|
self.creating = creating
|
||||||
|
self.defaultPasswordLength = defaultPasswordLength
|
||||||
|
self.maximumPasswordLength = maximumPasswordLength
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileEntryEditorPage: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileEntryEditorPage: FfiConverterRustBuffer {
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileEntryEditorPage {
|
||||||
|
return
|
||||||
|
try MobileEntryEditorPage(
|
||||||
|
path: FfiConverterString.read(from: &buf),
|
||||||
|
title: FfiConverterString.read(from: &buf),
|
||||||
|
fields: FfiConverterSequenceTypeMobileEntryEditorField.read(from: &buf),
|
||||||
|
dirty: FfiConverterBool.read(from: &buf),
|
||||||
|
creating: FfiConverterBool.read(from: &buf),
|
||||||
|
defaultPasswordLength: FfiConverterUInt32.read(from: &buf),
|
||||||
|
maximumPasswordLength: FfiConverterUInt32.read(from: &buf)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileEntryEditorPage, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterString.write(value.path, into: &buf)
|
||||||
|
FfiConverterString.write(value.title, into: &buf)
|
||||||
|
FfiConverterSequenceTypeMobileEntryEditorField.write(value.fields, into: &buf)
|
||||||
|
FfiConverterBool.write(value.dirty, into: &buf)
|
||||||
|
FfiConverterBool.write(value.creating, into: &buf)
|
||||||
|
FfiConverterUInt32.write(value.defaultPasswordLength, into: &buf)
|
||||||
|
FfiConverterUInt32.write(value.maximumPasswordLength, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorPage_lift(_ buf: RustBuffer) throws -> MobileEntryEditorPage {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorPage.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorPage_lower(_ value: MobileEntryEditorPage) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileEntryEditorPage.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct MobileEntryEditorSession: Equatable, Hashable {
|
||||||
|
public var id: UInt64
|
||||||
|
public var page: MobileEntryEditorPage
|
||||||
|
|
||||||
|
// Default memberwise initializers are never public by default, so we
|
||||||
|
// declare one manually.
|
||||||
|
public init(id: UInt64, page: MobileEntryEditorPage) {
|
||||||
|
self.id = id
|
||||||
|
self.page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileEntryEditorSession: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileEntryEditorSession: FfiConverterRustBuffer {
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileEntryEditorSession {
|
||||||
|
return
|
||||||
|
try MobileEntryEditorSession(
|
||||||
|
id: FfiConverterUInt64.read(from: &buf),
|
||||||
|
page: FfiConverterTypeMobileEntryEditorPage.read(from: &buf)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileEntryEditorSession, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterUInt64.write(value.id, into: &buf)
|
||||||
|
FfiConverterTypeMobileEntryEditorPage.write(value.page, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorSession_lift(_ buf: RustBuffer) throws -> MobileEntryEditorSession {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorSession.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorSession_lower(_ value: MobileEntryEditorSession) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileEntryEditorSession.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public struct MobileEntryField: Equatable, Hashable {
|
public struct MobileEntryField: Equatable, Hashable {
|
||||||
public var id: UInt64
|
public var id: UInt64
|
||||||
public var label: String
|
public var label: String
|
||||||
@@ -2279,6 +2694,7 @@ public enum MobileAuthenticationErrorKind: Equatable, Hashable {
|
|||||||
case entry
|
case entry
|
||||||
case secureStorage
|
case secureStorage
|
||||||
case expired
|
case expired
|
||||||
|
case conflict
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2318,6 +2734,8 @@ public struct FfiConverterTypeMobileAuthenticationErrorKind: FfiConverterRustBuf
|
|||||||
|
|
||||||
case 9: return .expired
|
case 9: return .expired
|
||||||
|
|
||||||
|
case 10: return .conflict
|
||||||
|
|
||||||
default: throw UniffiInternalError.unexpectedEnumCase
|
default: throw UniffiInternalError.unexpectedEnumCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2361,6 +2779,10 @@ public struct FfiConverterTypeMobileAuthenticationErrorKind: FfiConverterRustBuf
|
|||||||
case .expired:
|
case .expired:
|
||||||
writeInt(&buf, Int32(9))
|
writeInt(&buf, Int32(9))
|
||||||
|
|
||||||
|
|
||||||
|
case .conflict:
|
||||||
|
writeInt(&buf, Int32(10))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2462,6 +2884,86 @@ public func FfiConverterTypeMobileAuthenticationFfiError_lower(_ value: MobileAu
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public enum MobileEntryEditorFieldKind: Equatable, Hashable {
|
||||||
|
|
||||||
|
case password
|
||||||
|
case field
|
||||||
|
case note
|
||||||
|
case otpUri
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileEntryEditorFieldKind: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileEntryEditorFieldKind: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = MobileEntryEditorFieldKind
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileEntryEditorFieldKind {
|
||||||
|
let variant: Int32 = try readInt(&buf)
|
||||||
|
switch variant {
|
||||||
|
|
||||||
|
case 1: return .password
|
||||||
|
|
||||||
|
case 2: return .field
|
||||||
|
|
||||||
|
case 3: return .note
|
||||||
|
|
||||||
|
case 4: return .otpUri
|
||||||
|
|
||||||
|
default: throw UniffiInternalError.unexpectedEnumCase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileEntryEditorFieldKind, into buf: inout [UInt8]) {
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
|
||||||
|
case .password:
|
||||||
|
writeInt(&buf, Int32(1))
|
||||||
|
|
||||||
|
|
||||||
|
case .field:
|
||||||
|
writeInt(&buf, Int32(2))
|
||||||
|
|
||||||
|
|
||||||
|
case .note:
|
||||||
|
writeInt(&buf, Int32(3))
|
||||||
|
|
||||||
|
|
||||||
|
case .otpUri:
|
||||||
|
writeInt(&buf, Int32(4))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorFieldKind_lift(_ buf: RustBuffer) throws -> MobileEntryEditorFieldKind {
|
||||||
|
return try FfiConverterTypeMobileEntryEditorFieldKind.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileEntryEditorFieldKind_lower(_ value: MobileEntryEditorFieldKind) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileEntryEditorFieldKind.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public enum MobileEntrySectionKind: Equatable, Hashable {
|
public enum MobileEntrySectionKind: Equatable, Hashable {
|
||||||
|
|
||||||
case password
|
case password
|
||||||
@@ -3809,6 +4311,30 @@ public func FfiConverterTypeMobileTab_lower(_ value: MobileTab) -> RustBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = UInt32?
|
||||||
|
|
||||||
|
public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
|
||||||
|
guard let value = value else {
|
||||||
|
writeInt(&buf, Int8(0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeInt(&buf, Int8(1))
|
||||||
|
FfiConverterUInt32.write(value, into: &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
|
||||||
|
switch try readInt(&buf) as Int8 {
|
||||||
|
case 0: return nil
|
||||||
|
case 1: return try FfiConverterUInt32.read(from: &buf)
|
||||||
|
default: throw UniffiInternalError.unexpectedOptionalTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if swift(>=5.8)
|
#if swift(>=5.8)
|
||||||
@_documentation(visibility: private)
|
@_documentation(visibility: private)
|
||||||
#endif
|
#endif
|
||||||
@@ -3906,6 +4432,56 @@ fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
fileprivate struct FfiConverterSequenceTypeMobileEntryEditorField: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = [MobileEntryEditorField]
|
||||||
|
|
||||||
|
public static func write(_ value: [MobileEntryEditorField], into buf: inout [UInt8]) {
|
||||||
|
let len = Int32(value.count)
|
||||||
|
writeInt(&buf, len)
|
||||||
|
for item in value {
|
||||||
|
FfiConverterTypeMobileEntryEditorField.write(item, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [MobileEntryEditorField] {
|
||||||
|
let len: Int32 = try readInt(&buf)
|
||||||
|
var seq = [MobileEntryEditorField]()
|
||||||
|
seq.reserveCapacity(Int(len))
|
||||||
|
for _ in 0 ..< len {
|
||||||
|
seq.append(try FfiConverterTypeMobileEntryEditorField.read(from: &buf))
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
fileprivate struct FfiConverterSequenceTypeMobileEntryEditorInput: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = [MobileEntryEditorInput]
|
||||||
|
|
||||||
|
public static func write(_ value: [MobileEntryEditorInput], into buf: inout [UInt8]) {
|
||||||
|
let len = Int32(value.count)
|
||||||
|
writeInt(&buf, len)
|
||||||
|
for item in value {
|
||||||
|
FfiConverterTypeMobileEntryEditorInput.write(item, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [MobileEntryEditorInput] {
|
||||||
|
let len: Int32 = try readInt(&buf)
|
||||||
|
var seq = [MobileEntryEditorInput]()
|
||||||
|
seq.reserveCapacity(Int(len))
|
||||||
|
for _ in 0 ..< len {
|
||||||
|
seq.append(try FfiConverterTypeMobileEntryEditorInput.read(from: &buf))
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if swift(>=5.8)
|
#if swift(>=5.8)
|
||||||
@_documentation(visibility: private)
|
@_documentation(visibility: private)
|
||||||
#endif
|
#endif
|
||||||
@@ -4193,24 +4769,51 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab() != 65280) {
|
if (uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab() != 65280) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_add_entry_editor_field() != 58790) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_begin_create_entry() != 21567) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_begin_entry_editor() != 63281) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_cancel() != 6512) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_cancel() != 6512) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_copy_entry_field() != 17773) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_copy_entry_field() != 17773) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_discard_entry_editor() != 28195) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_editor() != 65179) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_page() != 56594) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_page() != 56594) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_generate_entry_editor_password() != 34289) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock() != 57220) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock() != 57220) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_remove_entry_editor_field() != 12238) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_reorder_entry_editor_field() != 62124) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_replace_entry_field() != 15315) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_replace_entry_field() != 15315) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_reveal_entry_field() != 13396) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_reveal_entry_field() != 13396) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_save_entry_editor() != 30971) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biometric_unlock() != 9486) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biometric_unlock() != 9486) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
@@ -4223,6 +4826,9 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_unlock_entry() != 28139) {
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_unlock_entry() != 28139) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_update_entry_editor() != 30139) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cached() != 32436) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cached() != 32436) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,6 +253,21 @@ uint64_t uniffi_ironstorage_apple_fn_clone_mobileauthentication(uint64_t handle,
|
|||||||
void uniffi_ironstorage_apple_fn_free_mobileauthentication(uint64_t handle, RustCallStatus *_Nonnull out_status
|
void uniffi_ironstorage_apple_fn_free_mobileauthentication(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ADD_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ADD_ENTRY_EDITOR_FIELD
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_add_entry_editor_field(uint64_t ptr, uint64_t editor, RustBuffer kind, RustBuffer name, RustBuffer value, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_BEGIN_CREATE_ENTRY
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_BEGIN_CREATE_ENTRY
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_begin_create_entry(uint64_t ptr, RustBuffer directory, RustBuffer name, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_BEGIN_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_BEGIN_ENTRY_EDITOR
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_begin_entry_editor(uint64_t ptr, RustBuffer path, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_CANCEL
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_CANCEL
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_CANCEL
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_CANCEL
|
||||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
void uniffi_ironstorage_apple_fn_method_mobileauthentication_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
@@ -263,16 +278,41 @@ void uniffi_ironstorage_apple_fn_method_mobileauthentication_cancel(uint64_t ptr
|
|||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_copy_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_copy_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_DISCARD_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_DISCARD_ENTRY_EDITOR
|
||||||
|
void uniffi_ironstorage_apple_fn_method_mobileauthentication_discard_entry_editor(uint64_t ptr, uint64_t editor, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_entry_editor(uint64_t ptr, uint64_t editor, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_entry_page(uint64_t ptr, RustBuffer path, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_entry_page(uint64_t ptr, RustBuffer path, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_GENERATE_ENTRY_EDITOR_PASSWORD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_GENERATE_ENTRY_EDITOR_PASSWORD
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_generate_entry_editor_password(uint64_t ptr, uint64_t editor, RustBuffer length, int8_t no_symbols, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
||||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
void uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_remove_entry_editor_field(uint64_t ptr, uint64_t editor, uint64_t field, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REORDER_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REORDER_ENTRY_EDITOR_FIELD
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_reorder_entry_editor_field(uint64_t ptr, uint64_t editor, uint64_t field, uint32_t index, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_replace_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustBuffer value, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_replace_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustBuffer value, RustCallStatus *_Nonnull out_status
|
||||||
@@ -283,6 +323,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_replace_entry
|
|||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_reveal_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_reveal_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_save_entry_editor(uint64_t ptr, uint64_t editor, RustBuffer fields, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_biometric_unlock(uint64_t ptr, int8_t enabled, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_biometric_unlock(uint64_t ptr, int8_t enabled, RustCallStatus *_Nonnull out_status
|
||||||
@@ -303,6 +348,11 @@ void uniffi_ironstorage_apple_fn_method_mobileauthentication_touch_user_activity
|
|||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_unlock_entry(uint64_t ptr, RustBuffer path, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_unlock_entry(uint64_t ptr, RustBuffer path, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_UPDATE_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_UPDATE_ENTRY_EDITOR
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_update_entry_editor(uint64_t ptr, uint64_t editor, RustBuffer fields, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEHOMEOPERATION
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEHOMEOPERATION
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEHOMEOPERATION
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEHOMEOPERATION
|
||||||
uint64_t uniffi_ironstorage_apple_fn_clone_mobilehomeoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
|
uint64_t uniffi_ironstorage_apple_fn_clone_mobilehomeoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
@@ -734,6 +784,24 @@ uint16_t uniffi_ironstorage_apple_checksum_func_replace_configured_mobile_applic
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab(void
|
uint16_t uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ADD_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ADD_ENTRY_EDITOR_FIELD
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_add_entry_editor_field(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_BEGIN_CREATE_ENTRY
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_BEGIN_CREATE_ENTRY
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_begin_create_entry(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_BEGIN_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_BEGIN_ENTRY_EDITOR
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_begin_entry_editor(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_CANCEL
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_CANCEL
|
||||||
@@ -746,18 +814,48 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_cancel(vo
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_ENTRY_FIELD
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_ENTRY_FIELD
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_copy_entry_field(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_copy_entry_field(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_DISCARD_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_DISCARD_ENTRY_EDITOR
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_discard_entry_editor(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_editor(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_PAGE
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_page(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_page(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_GENERATE_ENTRY_EDITOR_PASSWORD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_GENERATE_ENTRY_EDITOR_PASSWORD
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_generate_entry_editor_password(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_remove_entry_editor_field(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REORDER_ENTRY_EDITOR_FIELD
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REORDER_ENTRY_EDITOR_FIELD
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_reorder_entry_editor_field(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
|
||||||
@@ -770,6 +868,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_replace_e
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_reveal_entry_field(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_reveal_entry_field(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_save_entry_editor(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||||
@@ -794,6 +898,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_touch_use
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_UNLOCK_ENTRY
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_UNLOCK_ENTRY
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_unlock_entry(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_unlock_entry(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_UPDATE_ENTRY_EDITOR
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_UPDATE_ENTRY_EDITOR
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_update_entry_editor(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_CACHED
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_CACHED
|
||||||
|
|||||||
@@ -896,6 +896,7 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
private var shellPage: MobilePage?
|
private var shellPage: MobilePage?
|
||||||
private var directoryPage: MobilePasswordPage?
|
private var directoryPage: MobilePasswordPage?
|
||||||
private var loadTask: Task<Void, Never>?
|
private var loadTask: Task<Void, Never>?
|
||||||
|
private var createTask: Task<Void, Never>?
|
||||||
private var loadGeneration = 0
|
private var loadGeneration = 0
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@@ -943,6 +944,7 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
loadTask?.cancel()
|
loadTask?.cancel()
|
||||||
|
createTask?.cancel()
|
||||||
NotificationCenter.default.removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,6 +1022,100 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func createRequested() {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "New Password",
|
||||||
|
message: "Enter a name for the password entry in this folder.",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.placeholder = "Entry Name"
|
||||||
|
field.autocapitalizationType = .words
|
||||||
|
field.clearButtonMode = .whileEditing
|
||||||
|
field.returnKeyType = .next
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "Create", style: .default) { [weak self, weak alert] _ in
|
||||||
|
guard let name = alert?.textFields?.first?.text, !name.isEmpty else { return }
|
||||||
|
self?.beginCreate(name: name, passphrase: nil)
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginCreate(name: String, passphrase: String?) {
|
||||||
|
guard let authentication else {
|
||||||
|
presentAuthenticationFailure(.unavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
createTask?.cancel()
|
||||||
|
let directory = path ?? ""
|
||||||
|
createTask = Task { [weak self] in
|
||||||
|
let result = await Task.detached(priority: .userInitiated) {
|
||||||
|
do {
|
||||||
|
return Result<MobileEntryEditorSession, AuthenticationFailure>.success(
|
||||||
|
try authentication.beginCreateEntry(
|
||||||
|
directory: directory,
|
||||||
|
name: name,
|
||||||
|
passphrase: passphrase
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch let error as MobileAuthenticationFfiError {
|
||||||
|
return .failure(AuthenticationFailure(error))
|
||||||
|
} catch {
|
||||||
|
return .failure(.unexpected)
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
guard !Task.isCancelled, let self else { return }
|
||||||
|
switch result {
|
||||||
|
case let .success(session):
|
||||||
|
presentEditor(session, focusedField: nil)
|
||||||
|
case let .failure(failure)
|
||||||
|
where passphrase == nil
|
||||||
|
&& (failure.kind == .passphraseRequired
|
||||||
|
|| failure.kind == .biometryUnavailable):
|
||||||
|
promptForCreationPassphrase(name: name, message: failure.detail)
|
||||||
|
case let .failure(failure):
|
||||||
|
presentAuthenticationFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func promptForCreationPassphrase(name: String, message: String) {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "GPG Key Passphrase",
|
||||||
|
message: message,
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.isSecureTextEntry = true
|
||||||
|
field.textContentType = .password
|
||||||
|
field.placeholder = "Passphrase"
|
||||||
|
field.returnKeyType = .go
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "Unlock", style: .default) { [weak self, weak alert] _ in
|
||||||
|
guard let value = alert?.textFields?.first?.text, !value.isEmpty else { return }
|
||||||
|
alert?.textFields?.first?.text = nil
|
||||||
|
self?.beginCreate(name: name, passphrase: value)
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentEditor(_ session: MobileEntryEditorSession, focusedField: UInt64?) {
|
||||||
|
guard let authentication else { return }
|
||||||
|
let editor = MobileEntryEditorViewController(
|
||||||
|
authentication: authentication,
|
||||||
|
session: session,
|
||||||
|
focusedField: focusedField
|
||||||
|
) { [weak self] _ in
|
||||||
|
NotificationCenter.default.post(name: .ironStorageLocalStoreDidChange, object: nil)
|
||||||
|
self?.reload()
|
||||||
|
}
|
||||||
|
let navigation = UINavigationController(rootViewController: editor)
|
||||||
|
navigation.modalPresentationStyle = .formSheet
|
||||||
|
present(navigation, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
private func reload() {
|
private func reload() {
|
||||||
loadGeneration += 1
|
loadGeneration += 1
|
||||||
let generation = loadGeneration
|
let generation = loadGeneration
|
||||||
@@ -1063,6 +1159,7 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
title = page.title
|
title = page.title
|
||||||
guard page.state == .ready else {
|
guard page.state == .ready else {
|
||||||
directoryPage = nil
|
directoryPage = nil
|
||||||
|
navigationItem.rightBarButtonItem = nil
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
refreshControl?.endRefreshing()
|
refreshControl?.endRefreshing()
|
||||||
var configuration = page.state == .loading
|
var configuration = page.state == .loading
|
||||||
@@ -1088,6 +1185,12 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
case let .success(page):
|
case let .success(page):
|
||||||
directoryPage = page
|
directoryPage = page
|
||||||
title = page.title
|
title = page.title
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||||
|
barButtonSystemItem: .add,
|
||||||
|
target: self,
|
||||||
|
action: #selector(createRequested)
|
||||||
|
)
|
||||||
|
navigationItem.rightBarButtonItem?.accessibilityLabel = "Create password entry"
|
||||||
contentUnavailableConfiguration = nil
|
contentUnavailableConfiguration = nil
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
if page.rows.isEmpty {
|
if page.rows.isEmpty {
|
||||||
@@ -1101,6 +1204,7 @@ private final class PasswordDirectoryViewController: UITableViewController, Mobi
|
|||||||
tableView.selectRow(at: IndexPath(row: row, section: 0), animated: false, scrollPosition: .none)
|
tableView.selectRow(at: IndexPath(row: row, section: 0), animated: false, scrollPosition: .none)
|
||||||
}
|
}
|
||||||
case let .failure(failure):
|
case let .failure(failure):
|
||||||
|
navigationItem.rightBarButtonItem = nil
|
||||||
if failure.kind == .directoryMissing, path != nil {
|
if failure.kind == .directoryMissing, path != nil {
|
||||||
UIAccessibility.post(
|
UIAccessibility.post(
|
||||||
notification: .announcement,
|
notification: .announcement,
|
||||||
@@ -1436,32 +1540,15 @@ private final class LockedPasswordViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func edit(_ field: MobileEntryField) {
|
private func edit(_ field: MobileEntryField) {
|
||||||
performFieldAction(field) { authentication, path in
|
|
||||||
try authentication.revealEntryField(path: path, field: field.id)
|
|
||||||
} success: { [weak self] value in
|
|
||||||
guard let self else { return }
|
|
||||||
let editor = MobileEntryEditorViewController(field: field, value: value) {
|
|
||||||
[weak self] updated in self?.save(updated, for: field)
|
|
||||||
}
|
|
||||||
present(UINavigationController(rootViewController: editor), animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func save(_ value: String, for field: MobileEntryField) {
|
|
||||||
guard let authentication else { return }
|
guard let authentication else { return }
|
||||||
entryTask?.cancel()
|
entryTask?.cancel()
|
||||||
showLoading(title: "Saving Password")
|
|
||||||
let path = entry.path
|
let path = entry.path
|
||||||
entryTask = Task { [weak self] in
|
entryTask = Task { [weak self] in
|
||||||
let result = await Task.detached(priority: .userInitiated) {
|
let result = await Task.detached(priority: .userInitiated) {
|
||||||
do {
|
do {
|
||||||
try authentication.touchUserActivity()
|
try authentication.touchUserActivity()
|
||||||
return Result<MobileEntryPage, AuthenticationFailure>.success(
|
return Result<MobileEntryEditorSession, AuthenticationFailure>.success(
|
||||||
try authentication.replaceEntryField(
|
try authentication.beginEntryEditor(path: path)
|
||||||
path: path,
|
|
||||||
field: field.id,
|
|
||||||
value: value
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} catch let error as MobileAuthenticationFfiError {
|
} catch let error as MobileAuthenticationFfiError {
|
||||||
return .failure(AuthenticationFailure(error))
|
return .failure(AuthenticationFailure(error))
|
||||||
@@ -1471,13 +1558,22 @@ private final class LockedPasswordViewController: UITableViewController {
|
|||||||
}.value
|
}.value
|
||||||
guard !Task.isCancelled, let self else { return }
|
guard !Task.isCancelled, let self else { return }
|
||||||
switch result {
|
switch result {
|
||||||
case let .success(page):
|
case let .success(session):
|
||||||
self.page = page
|
let editor = MobileEntryEditorViewController(
|
||||||
revealedValues.removeValue(forKey: field.id)
|
authentication: authentication,
|
||||||
contentUnavailableConfiguration = nil
|
session: session,
|
||||||
tableView.reloadData()
|
focusedField: field.id
|
||||||
NotificationCenter.default.post(name: .ironStorageLocalStoreDidChange, object: nil)
|
) { [weak self] _ in
|
||||||
showFeedback("\(field.label) saved")
|
NotificationCenter.default.post(
|
||||||
|
name: .ironStorageLocalStoreDidChange,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
self?.revealedValues.removeAll(keepingCapacity: false)
|
||||||
|
self?.loadEntry()
|
||||||
|
}
|
||||||
|
let navigation = UINavigationController(rootViewController: editor)
|
||||||
|
navigation.modalPresentationStyle = .formSheet
|
||||||
|
present(navigation, animated: true)
|
||||||
case let .failure(failure):
|
case let .failure(failure):
|
||||||
handle(failure)
|
handle(failure)
|
||||||
}
|
}
|
||||||
@@ -1742,17 +1838,37 @@ private final class MobileEntryFieldCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private final class MobileEntryEditorViewController: UIViewController {
|
private final class MobileEntryEditorViewController: UITableViewController,
|
||||||
private let field: MobileEntryField
|
UIAdaptivePresentationControllerDelegate
|
||||||
private let valueView = UITextView()
|
{
|
||||||
private let save: (String) -> Void
|
private struct FieldState {
|
||||||
|
var name: String?
|
||||||
|
var value: String?
|
||||||
|
}
|
||||||
|
|
||||||
init(field: MobileEntryField, value: String, save: @escaping (String) -> Void) {
|
private let authentication: MobileAuthentication
|
||||||
self.field = field
|
private var editorID: UInt64
|
||||||
self.save = save
|
private var page: MobileEntryEditorPage
|
||||||
super.init(nibName: nil, bundle: nil)
|
private let focusedField: UInt64?
|
||||||
valueView.text = value
|
private let saved: (String) -> Void
|
||||||
title = "Edit \(field.label)"
|
private var states: [UInt64: FieldState] = [:]
|
||||||
|
private var localDirty = false
|
||||||
|
private var focused = false
|
||||||
|
private var operationTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
init(
|
||||||
|
authentication: MobileAuthentication,
|
||||||
|
session: MobileEntryEditorSession,
|
||||||
|
focusedField: UInt64?,
|
||||||
|
saved: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
self.authentication = authentication
|
||||||
|
editorID = session.id
|
||||||
|
page = session.page
|
||||||
|
self.focusedField = focusedField
|
||||||
|
self.saved = saved
|
||||||
|
super.init(style: .insetGrouped)
|
||||||
|
resetStates()
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
@@ -1760,34 +1876,590 @@ private final class MobileEntryEditorViewController: UIViewController {
|
|||||||
fatalError("init(coder:) is not supported")
|
fatalError("init(coder:) is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
operationTask?.cancel()
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
view.backgroundColor = .systemGroupedBackground
|
title = page.creating ? "New \(page.title)" : "Edit \(page.title)"
|
||||||
valueView.font = .preferredFont(forTextStyle: .body)
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
valueView.adjustsFontForContentSizeCategory = true
|
tableView.register(MobileEntryEditorCell.self, forCellReuseIdentifier: "EditorField")
|
||||||
valueView.autocorrectionType = field.sensitive ? .no : .default
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
valueView.autocapitalizationType = field.sensitive ? .none : .sentences
|
tableView.estimatedRowHeight = 120
|
||||||
valueView.translatesAutoresizingMaskIntoConstraints = false
|
tableView.setEditing(true, animated: false)
|
||||||
view.addSubview(valueView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
valueView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
|
||||||
valueView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
|
|
||||||
valueView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
|
|
||||||
valueView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -16),
|
|
||||||
])
|
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||||
systemItem: .cancel,
|
systemItem: .cancel,
|
||||||
primaryAction: UIAction { [weak self] _ in self?.dismiss(animated: true) }
|
primaryAction: UIAction { [weak self] _ in self?.cancelRequested() }
|
||||||
)
|
)
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||||
systemItem: .save,
|
systemItem: .save,
|
||||||
primaryAction: UIAction { [weak self] _ in
|
primaryAction: UIAction { [weak self] _ in self?.saveRequested() }
|
||||||
guard let self else { return }
|
|
||||||
let value = valueView.text ?? ""
|
|
||||||
dismiss(animated: true) { self.save(value) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
valueView.becomeFirstResponder()
|
let add = UIBarButtonItem(
|
||||||
|
systemItem: .add,
|
||||||
|
primaryAction: UIAction { [weak self] _ in self?.addRequested() }
|
||||||
|
)
|
||||||
|
add.accessibilityLabel = "Add entry field"
|
||||||
|
let generate = UIBarButtonItem(
|
||||||
|
title: "Generate Password",
|
||||||
|
image: UIImage(systemName: "wand.and.stars"),
|
||||||
|
primaryAction: UIAction { [weak self] _ in self?.generateRequested() }
|
||||||
|
)
|
||||||
|
toolbarItems = [
|
||||||
|
add,
|
||||||
|
UIBarButtonItem(systemItem: .flexibleSpace),
|
||||||
|
generate,
|
||||||
|
]
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(authenticationDidChange),
|
||||||
|
name: .ironStorageAuthenticationDidChange,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
navigationController?.setToolbarHidden(false, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
navigationController?.presentationController?.delegate = self
|
||||||
|
guard !focused, let focusedField,
|
||||||
|
let index = page.fields.firstIndex(where: { $0.id == focusedField })
|
||||||
|
else { return }
|
||||||
|
focused = true
|
||||||
|
let path = IndexPath(row: index, section: 0)
|
||||||
|
tableView.scrollToRow(at: path, at: .middle, animated: true)
|
||||||
|
(tableView.cellForRow(at: path) as? MobileEntryEditorCell)?.focusValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func numberOfSections(in tableView: UITableView) -> Int { 1 }
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
page.fields.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
titleForHeaderInSection section: Int
|
||||||
|
) -> String? {
|
||||||
|
"Entry Fields"
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
titleForFooterInSection section: Int
|
||||||
|
) -> String? {
|
||||||
|
"Drag the reorder controls to preserve the exact field order saved by Rust."
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
cellForRowAt indexPath: IndexPath
|
||||||
|
) -> UITableViewCell {
|
||||||
|
guard let cell = tableView.dequeueReusableCell(
|
||||||
|
withIdentifier: "EditorField",
|
||||||
|
for: indexPath
|
||||||
|
) as? MobileEntryEditorCell else { return UITableViewCell() }
|
||||||
|
let field = page.fields[indexPath.row]
|
||||||
|
let state = states[field.id] ?? FieldState(name: field.name, value: field.value)
|
||||||
|
cell.configure(field: field, name: state.name, value: state.value) { [weak self] name, value in
|
||||||
|
self?.states[field.id] = FieldState(name: name, value: value)
|
||||||
|
self?.localDirty = true
|
||||||
|
}
|
||||||
|
cell.showsReorderControl = field.reorderable
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
|
page.fields[indexPath.row].removable
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||||
|
page.fields[indexPath.row].reorderable
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
editingStyleForRowAt indexPath: IndexPath
|
||||||
|
) -> UITableViewCell.EditingStyle {
|
||||||
|
page.fields[indexPath.row].removable ? .delete : .none
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
commit editingStyle: UITableViewCell.EditingStyle,
|
||||||
|
forRowAt indexPath: IndexPath
|
||||||
|
) {
|
||||||
|
guard editingStyle == .delete else { return }
|
||||||
|
do {
|
||||||
|
try synchronizeDraft()
|
||||||
|
page = try authentication.removeEntryEditorField(
|
||||||
|
editor: editorID,
|
||||||
|
field: page.fields[indexPath.row].id
|
||||||
|
)
|
||||||
|
resetStates()
|
||||||
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||||
|
} catch {
|
||||||
|
presentEditorFailure(error)
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
moveRowAt sourceIndexPath: IndexPath,
|
||||||
|
to destinationIndexPath: IndexPath
|
||||||
|
) {
|
||||||
|
let field = page.fields[sourceIndexPath.row]
|
||||||
|
do {
|
||||||
|
try synchronizeDraft()
|
||||||
|
page = try authentication.reorderEntryEditorField(
|
||||||
|
editor: editorID,
|
||||||
|
field: field.id,
|
||||||
|
index: UInt32(destinationIndexPath.row)
|
||||||
|
)
|
||||||
|
resetStates()
|
||||||
|
tableView.reloadData()
|
||||||
|
} catch {
|
||||||
|
presentEditorFailure(error)
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
||||||
|
guard isDirty else {
|
||||||
|
try? authentication.discardEntryEditor(editor: editorID)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentationControllerDidAttemptToDismiss(
|
||||||
|
_ presentationController: UIPresentationController
|
||||||
|
) {
|
||||||
|
confirmDiscard()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func authenticationDidChange() {
|
||||||
|
guard (try? authentication.state().unlocked) != true else { return }
|
||||||
|
states = states.mapValues { _ in FieldState(name: nil, value: nil) }
|
||||||
|
tableView.visibleCells
|
||||||
|
.compactMap { $0 as? MobileEntryEditorCell }
|
||||||
|
.forEach { $0.clearSensitiveContent() }
|
||||||
|
dismiss(animated: true)
|
||||||
|
UIAccessibility.post(notification: .announcement, argument: "Entry editor locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isDirty: Bool { localDirty || page.dirty }
|
||||||
|
|
||||||
|
private func resetStates() {
|
||||||
|
states = Dictionary(uniqueKeysWithValues: page.fields.map {
|
||||||
|
($0.id, FieldState(name: $0.name, value: $0.value))
|
||||||
|
})
|
||||||
|
localDirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func inputs() -> [MobileEntryEditorInput] {
|
||||||
|
page.fields.map { field in
|
||||||
|
let state = states[field.id] ?? FieldState(name: field.name, value: field.value)
|
||||||
|
return MobileEntryEditorInput(id: field.id, name: state.name, value: state.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func synchronizeDraft() throws {
|
||||||
|
view.endEditing(true)
|
||||||
|
try authentication.touchUserActivity()
|
||||||
|
page = try authentication.updateEntryEditor(editor: editorID, fields: inputs())
|
||||||
|
resetStates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cancelRequested() {
|
||||||
|
if isDirty {
|
||||||
|
confirmDiscard()
|
||||||
|
} else {
|
||||||
|
discardAndDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func confirmDiscard() {
|
||||||
|
let sheet = UIAlertController(
|
||||||
|
title: "Discard Changes?",
|
||||||
|
message: "Unsaved entry changes will be lost.",
|
||||||
|
preferredStyle: .actionSheet
|
||||||
|
)
|
||||||
|
sheet.addAction(UIAlertAction(title: "Discard Changes", style: .destructive) {
|
||||||
|
[weak self] _ in self?.discardAndDismiss()
|
||||||
|
})
|
||||||
|
sheet.addAction(UIAlertAction(title: "Keep Editing", style: .cancel))
|
||||||
|
sheet.popoverPresentationController?.barButtonItem = navigationItem.leftBarButtonItem
|
||||||
|
present(sheet, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func discardAndDismiss() {
|
||||||
|
try? authentication.discardEntryEditor(editor: editorID)
|
||||||
|
states.removeAll(keepingCapacity: false)
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addRequested() {
|
||||||
|
let sheet = UIAlertController(title: "Add Field", message: nil, preferredStyle: .actionSheet)
|
||||||
|
sheet.addAction(UIAlertAction(title: "Named Field", style: .default) { [weak self] _ in
|
||||||
|
self?.promptForNamedField()
|
||||||
|
})
|
||||||
|
sheet.addAction(UIAlertAction(title: "Notes", style: .default) { [weak self] _ in
|
||||||
|
self?.addField(kind: .note, name: nil, value: "")
|
||||||
|
})
|
||||||
|
sheet.addAction(UIAlertAction(title: "OTP URI", style: .default) { [weak self] _ in
|
||||||
|
self?.promptForOtpUri()
|
||||||
|
})
|
||||||
|
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
sheet.popoverPresentationController?.barButtonItem = toolbarItems?.first
|
||||||
|
present(sheet, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func promptForNamedField() {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "Named Field",
|
||||||
|
message: "Enter a field name. You can enter its value in the form.",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.placeholder = "Field Name"
|
||||||
|
field.autocapitalizationType = .none
|
||||||
|
field.autocorrectionType = .no
|
||||||
|
field.clearButtonMode = .whileEditing
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "Add", style: .default) { [weak self, weak alert] _ in
|
||||||
|
guard let name = alert?.textFields?.first?.text, !name.isEmpty else { return }
|
||||||
|
self?.addField(kind: .field, name: name, value: "")
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func promptForOtpUri() {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "OTP URI",
|
||||||
|
message: "Enter the complete otpauth URI.",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.placeholder = "otpauth://…"
|
||||||
|
field.keyboardType = .URL
|
||||||
|
field.autocapitalizationType = .none
|
||||||
|
field.autocorrectionType = .no
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "Add", style: .default) { [weak self, weak alert] _ in
|
||||||
|
guard let value = alert?.textFields?.first?.text, !value.isEmpty else { return }
|
||||||
|
self?.addField(kind: .otpUri, name: nil, value: value)
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addField(kind: MobileEntryEditorFieldKind, name: String?, value: String) {
|
||||||
|
do {
|
||||||
|
try synchronizeDraft()
|
||||||
|
page = try authentication.addEntryEditorField(
|
||||||
|
editor: editorID,
|
||||||
|
kind: kind,
|
||||||
|
name: name,
|
||||||
|
value: value
|
||||||
|
)
|
||||||
|
resetStates()
|
||||||
|
tableView.reloadData()
|
||||||
|
if !page.fields.isEmpty {
|
||||||
|
let path = IndexPath(row: page.fields.count - 1, section: 0)
|
||||||
|
tableView.scrollToRow(at: path, at: .bottom, animated: true)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
presentEditorFailure(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateRequested() {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "Generate Password",
|
||||||
|
message: "Choose a validated length from 1 through \(page.maximumPasswordLength).",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { [defaultLength = page.defaultPasswordLength] field in
|
||||||
|
field.text = String(defaultLength)
|
||||||
|
field.keyboardType = .numberPad
|
||||||
|
field.clearButtonMode = .whileEditing
|
||||||
|
field.accessibilityLabel = "Password length"
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "With Symbols", style: .default) { [weak self, weak alert] _ in
|
||||||
|
self?.generate(from: alert, noSymbols: false)
|
||||||
|
})
|
||||||
|
alert.addAction(UIAlertAction(title: "Letters & Numbers", style: .default) {
|
||||||
|
[weak self, weak alert] _ in self?.generate(from: alert, noSymbols: true)
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generate(from alert: UIAlertController?, noSymbols: Bool) {
|
||||||
|
guard let text = alert?.textFields?.first?.text,
|
||||||
|
let length = UInt32(text)
|
||||||
|
else {
|
||||||
|
let failure = UIAlertController(
|
||||||
|
title: "Invalid Password Length",
|
||||||
|
message: "Enter a whole number from 1 through \(page.maximumPasswordLength).",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
failure.addAction(UIAlertAction(title: "OK", style: .default))
|
||||||
|
present(failure, animated: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try synchronizeDraft()
|
||||||
|
page = try authentication.generateEntryEditorPassword(
|
||||||
|
editor: editorID,
|
||||||
|
length: length,
|
||||||
|
noSymbols: noSymbols
|
||||||
|
)
|
||||||
|
resetStates()
|
||||||
|
tableView.reloadData()
|
||||||
|
UIAccessibility.post(notification: .announcement, argument: "Password generated")
|
||||||
|
} catch {
|
||||||
|
presentEditorFailure(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveRequested() {
|
||||||
|
view.endEditing(true)
|
||||||
|
let fields = inputs()
|
||||||
|
let authentication = authentication
|
||||||
|
let editorID = editorID
|
||||||
|
navigationItem.leftBarButtonItem?.isEnabled = false
|
||||||
|
navigationItem.rightBarButtonItem?.isEnabled = false
|
||||||
|
operationTask?.cancel()
|
||||||
|
operationTask = Task { [weak self] in
|
||||||
|
let result = await Task.detached(priority: .userInitiated) {
|
||||||
|
do {
|
||||||
|
try authentication.touchUserActivity()
|
||||||
|
return Result<MobileEntryPage, AuthenticationFailure>.success(
|
||||||
|
try authentication.saveEntryEditor(editor: editorID, fields: fields)
|
||||||
|
)
|
||||||
|
} catch let error as MobileAuthenticationFfiError {
|
||||||
|
return .failure(AuthenticationFailure(error))
|
||||||
|
} catch {
|
||||||
|
return .failure(.unexpected)
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
guard !Task.isCancelled, let self else { return }
|
||||||
|
navigationItem.leftBarButtonItem?.isEnabled = true
|
||||||
|
navigationItem.rightBarButtonItem?.isEnabled = true
|
||||||
|
switch result {
|
||||||
|
case let .success(savedPage):
|
||||||
|
states.removeAll(keepingCapacity: false)
|
||||||
|
dismiss(animated: true) { self.saved(savedPage.id) }
|
||||||
|
case let .failure(failure):
|
||||||
|
presentSaveFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentSaveFailure(_ failure: AuthenticationFailure) {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: failure.title,
|
||||||
|
message: failure.detail,
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
if failure.kind == .conflict {
|
||||||
|
alert.addAction(UIAlertAction(title: "Reload Latest", style: .destructive) {
|
||||||
|
[weak self] _ in self?.reloadLatest()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Keep Editing", style: .cancel))
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadLatest() {
|
||||||
|
let path = page.path
|
||||||
|
try? authentication.discardEntryEditor(editor: editorID)
|
||||||
|
do {
|
||||||
|
let session = try authentication.beginEntryEditor(path: path)
|
||||||
|
editorID = session.id
|
||||||
|
page = session.page
|
||||||
|
resetStates()
|
||||||
|
tableView.reloadData()
|
||||||
|
} catch {
|
||||||
|
presentEditorFailure(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentEditorFailure(_ error: Error) {
|
||||||
|
let failure = (error as? MobileAuthenticationFfiError).map(AuthenticationFailure.init)
|
||||||
|
?? .unexpected
|
||||||
|
presentAuthenticationFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private final class MobileEntryEditorCell: UITableViewCell, UITextViewDelegate {
|
||||||
|
private let iconView = UIImageView()
|
||||||
|
private let labelView = UILabel()
|
||||||
|
private let nameField = UITextField()
|
||||||
|
private let valueField = UITextField()
|
||||||
|
private let valueView = UITextView()
|
||||||
|
private let diagnosticView = UILabel()
|
||||||
|
private let revealButton = UIButton(type: .system)
|
||||||
|
private var changed: ((String?, String?) -> Void)?
|
||||||
|
private var currentName: String?
|
||||||
|
private var currentValue: String?
|
||||||
|
private var sensitive = false
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
selectionStyle = .none
|
||||||
|
iconView.tintColor = .secondaryLabel
|
||||||
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
labelView.font = .preferredFont(forTextStyle: .caption1)
|
||||||
|
labelView.textColor = .secondaryLabel
|
||||||
|
labelView.adjustsFontForContentSizeCategory = true
|
||||||
|
for field in [nameField, valueField] {
|
||||||
|
field.font = .preferredFont(forTextStyle: .body)
|
||||||
|
field.adjustsFontForContentSizeCategory = true
|
||||||
|
field.clearButtonMode = .whileEditing
|
||||||
|
field.addTarget(self, action: #selector(textFieldChanged(_:)), for: .editingChanged)
|
||||||
|
}
|
||||||
|
nameField.placeholder = "Field Name"
|
||||||
|
nameField.autocapitalizationType = .none
|
||||||
|
nameField.autocorrectionType = .no
|
||||||
|
valueView.font = .preferredFont(forTextStyle: .body)
|
||||||
|
valueView.adjustsFontForContentSizeCategory = true
|
||||||
|
valueView.backgroundColor = .clear
|
||||||
|
valueView.isScrollEnabled = false
|
||||||
|
valueView.textContainerInset = .zero
|
||||||
|
valueView.textContainer.lineFragmentPadding = 0
|
||||||
|
valueView.delegate = self
|
||||||
|
diagnosticView.font = .preferredFont(forTextStyle: .footnote)
|
||||||
|
diagnosticView.textColor = .systemOrange
|
||||||
|
diagnosticView.adjustsFontForContentSizeCategory = true
|
||||||
|
diagnosticView.numberOfLines = 0
|
||||||
|
revealButton.configuration = .plain()
|
||||||
|
revealButton.configuration?.image = UIImage(systemName: "eye")
|
||||||
|
revealButton.addAction(UIAction { [weak self] _ in self?.toggleReveal() }, for: .touchUpInside)
|
||||||
|
let heading = UIStackView(arrangedSubviews: [labelView, nameField])
|
||||||
|
heading.axis = .vertical
|
||||||
|
heading.spacing = 2
|
||||||
|
let body = UIStackView(arrangedSubviews: [heading, valueField, valueView, diagnosticView])
|
||||||
|
body.axis = .vertical
|
||||||
|
body.spacing = 6
|
||||||
|
let row = UIStackView(arrangedSubviews: [iconView, body, revealButton])
|
||||||
|
row.alignment = .top
|
||||||
|
row.spacing = 12
|
||||||
|
row.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.addSubview(row)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
iconView.widthAnchor.constraint(equalToConstant: 24),
|
||||||
|
iconView.heightAnchor.constraint(equalToConstant: 24),
|
||||||
|
revealButton.widthAnchor.constraint(equalToConstant: 44),
|
||||||
|
revealButton.heightAnchor.constraint(equalToConstant: 44),
|
||||||
|
valueView.heightAnchor.constraint(greaterThanOrEqualToConstant: 72),
|
||||||
|
row.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||||
|
row.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||||
|
row.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
|
||||||
|
row.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(
|
||||||
|
field: MobileEntryEditorField,
|
||||||
|
name: String?,
|
||||||
|
value: String?,
|
||||||
|
changed: @escaping (String?, String?) -> Void
|
||||||
|
) {
|
||||||
|
self.changed = changed
|
||||||
|
currentName = name
|
||||||
|
currentValue = value
|
||||||
|
sensitive = field.sensitive
|
||||||
|
iconView.image = UIImage(systemName: field.systemImage)
|
||||||
|
labelView.text = field.label
|
||||||
|
nameField.text = name
|
||||||
|
nameField.isHidden = !field.nameEditable
|
||||||
|
labelView.isHidden = field.nameEditable
|
||||||
|
let multiline = field.multiline || field.kind == .note
|
||||||
|
valueField.isHidden = multiline
|
||||||
|
valueView.isHidden = !multiline
|
||||||
|
valueField.text = value ?? field.maskedValue
|
||||||
|
valueView.text = value ?? field.maskedValue
|
||||||
|
let editable = value != nil
|
||||||
|
nameField.isEnabled = editable
|
||||||
|
valueField.isEnabled = editable
|
||||||
|
valueView.isEditable = editable
|
||||||
|
valueField.isSecureTextEntry = field.sensitive
|
||||||
|
valueField.textContentType = field.kind == .password ? .password : nil
|
||||||
|
valueField.keyboardType = field.kind == .otpUri ? .URL : .default
|
||||||
|
valueField.autocapitalizationType = field.sensitive ? .none : .sentences
|
||||||
|
valueField.autocorrectionType = field.sensitive ? .no : .default
|
||||||
|
valueView.autocapitalizationType = .sentences
|
||||||
|
diagnosticView.text = field.diagnostic
|
||||||
|
diagnosticView.isHidden = field.diagnostic == nil
|
||||||
|
revealButton.isHidden = !field.sensitive
|
||||||
|
revealButton.accessibilityLabel = "Reveal \(field.label)"
|
||||||
|
nameField.accessibilityLabel = "\(field.label) name"
|
||||||
|
valueField.accessibilityLabel = field.label
|
||||||
|
valueView.accessibilityLabel = field.label
|
||||||
|
}
|
||||||
|
|
||||||
|
func focusValue() {
|
||||||
|
if valueView.isHidden {
|
||||||
|
valueField.becomeFirstResponder()
|
||||||
|
} else {
|
||||||
|
valueView.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSensitiveContent() {
|
||||||
|
currentValue = nil
|
||||||
|
valueField.text = nil
|
||||||
|
valueView.text = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
changed = nil
|
||||||
|
currentName = nil
|
||||||
|
currentValue = nil
|
||||||
|
valueField.isSecureTextEntry = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func textFieldChanged(_ sender: UITextField) {
|
||||||
|
if sender === nameField {
|
||||||
|
currentName = sender.text
|
||||||
|
} else {
|
||||||
|
currentValue = sender.text
|
||||||
|
}
|
||||||
|
changed?(currentName, currentValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
|
currentValue = textView.text
|
||||||
|
changed?(currentName, currentValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toggleReveal() {
|
||||||
|
guard sensitive else { return }
|
||||||
|
valueField.isSecureTextEntry.toggle()
|
||||||
|
revealButton.configuration?.image = UIImage(
|
||||||
|
systemName: valueField.isSecureTextEntry ? "eye" : "eye.slash"
|
||||||
|
)
|
||||||
|
revealButton.accessibilityLabel = valueField.isSecureTextEntry
|
||||||
|
? "Reveal field"
|
||||||
|
: "Hide field"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ use ironstorage::{
|
|||||||
MobileEntryCopy as StorageEntryCopy,
|
MobileEntryCopy as StorageEntryCopy,
|
||||||
},
|
},
|
||||||
mobile_entry::{
|
mobile_entry::{
|
||||||
MobileEntryPage as StorageEntryPage, MobileEntrySectionKind as StorageEntrySectionKind,
|
MobileEntryEditorFieldKind as StorageEntryEditorFieldKind,
|
||||||
|
MobileEntryEditorInput as StorageEntryEditorInput,
|
||||||
|
MobileEntryEditorPage as StorageEntryEditorPage,
|
||||||
|
MobileEntryEditorSession as StorageEntryEditorSession, MobileEntryPage as StorageEntryPage,
|
||||||
|
MobileEntrySectionKind as StorageEntrySectionKind,
|
||||||
},
|
},
|
||||||
mobile_home::{
|
mobile_home::{
|
||||||
self, MobileHomeChangeKind as StorageHomeChangeKind,
|
self, MobileHomeChangeKind as StorageHomeChangeKind,
|
||||||
@@ -511,6 +515,7 @@ pub enum MobileAuthenticationErrorKind {
|
|||||||
Entry,
|
Entry,
|
||||||
SecureStorage,
|
SecureStorage,
|
||||||
Expired,
|
Expired,
|
||||||
|
Conflict,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
|
impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
|
||||||
@@ -525,6 +530,7 @@ impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
|
|||||||
StorageAuthenticationErrorKind::Entry => Self::Entry,
|
StorageAuthenticationErrorKind::Entry => Self::Entry,
|
||||||
StorageAuthenticationErrorKind::SecureStorage => Self::SecureStorage,
|
StorageAuthenticationErrorKind::SecureStorage => Self::SecureStorage,
|
||||||
StorageAuthenticationErrorKind::Expired => Self::Expired,
|
StorageAuthenticationErrorKind::Expired => Self::Expired,
|
||||||
|
StorageAuthenticationErrorKind::Conflict => Self::Conflict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,6 +630,124 @@ pub struct MobileEntryCopy {
|
|||||||
pub timeout_seconds: u64,
|
pub timeout_seconds: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||||
|
pub enum MobileEntryEditorFieldKind {
|
||||||
|
Password,
|
||||||
|
Field,
|
||||||
|
Note,
|
||||||
|
OtpUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageEntryEditorFieldKind> for MobileEntryEditorFieldKind {
|
||||||
|
fn from(kind: StorageEntryEditorFieldKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
StorageEntryEditorFieldKind::Password => Self::Password,
|
||||||
|
StorageEntryEditorFieldKind::Field => Self::Field,
|
||||||
|
StorageEntryEditorFieldKind::Note => Self::Note,
|
||||||
|
StorageEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MobileEntryEditorFieldKind> for StorageEntryEditorFieldKind {
|
||||||
|
fn from(kind: MobileEntryEditorFieldKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
MobileEntryEditorFieldKind::Password => Self::Password,
|
||||||
|
MobileEntryEditorFieldKind::Field => Self::Field,
|
||||||
|
MobileEntryEditorFieldKind::Note => Self::Note,
|
||||||
|
MobileEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MobileEntryEditorField {
|
||||||
|
pub id: u64,
|
||||||
|
pub kind: MobileEntryEditorFieldKind,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub label: String,
|
||||||
|
pub system_image: String,
|
||||||
|
pub value: Option<String>,
|
||||||
|
pub masked_value: String,
|
||||||
|
pub diagnostic: Option<String>,
|
||||||
|
pub sensitive: bool,
|
||||||
|
pub multiline: bool,
|
||||||
|
pub name_editable: bool,
|
||||||
|
pub removable: bool,
|
||||||
|
pub reorderable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MobileEntryEditorPage {
|
||||||
|
pub path: String,
|
||||||
|
pub title: String,
|
||||||
|
pub fields: Vec<MobileEntryEditorField>,
|
||||||
|
pub dirty: bool,
|
||||||
|
pub creating: bool,
|
||||||
|
pub default_password_length: u32,
|
||||||
|
pub maximum_password_length: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&StorageEntryEditorPage> for MobileEntryEditorPage {
|
||||||
|
fn from(page: &StorageEntryEditorPage) -> Self {
|
||||||
|
Self {
|
||||||
|
path: page.path().to_owned(),
|
||||||
|
title: page.title().to_owned(),
|
||||||
|
fields: page
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.map(|field| MobileEntryEditorField {
|
||||||
|
id: field.id(),
|
||||||
|
kind: field.kind().into(),
|
||||||
|
name: field.name().map(str::to_owned),
|
||||||
|
label: field.label().to_owned(),
|
||||||
|
system_image: field.system_image().to_owned(),
|
||||||
|
value: field.value().map(str::to_owned),
|
||||||
|
masked_value: field.masked_value().to_owned(),
|
||||||
|
diagnostic: field.diagnostic().map(str::to_owned),
|
||||||
|
sensitive: field.sensitive(),
|
||||||
|
multiline: field.multiline(),
|
||||||
|
name_editable: field.name_editable(),
|
||||||
|
removable: field.removable(),
|
||||||
|
reorderable: field.reorderable(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
dirty: page.dirty(),
|
||||||
|
creating: page.creating(),
|
||||||
|
default_password_length: page.default_password_length(),
|
||||||
|
maximum_password_length: page.maximum_password_length(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MobileEntryEditorSession {
|
||||||
|
pub id: u64,
|
||||||
|
pub page: MobileEntryEditorPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageEntryEditorSession> for MobileEntryEditorSession {
|
||||||
|
fn from(session: StorageEntryEditorSession) -> Self {
|
||||||
|
Self {
|
||||||
|
id: session.id(),
|
||||||
|
page: session.page().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MobileEntryEditorInput {
|
||||||
|
pub id: u64,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MobileEntryEditorInput> for StorageEntryEditorInput {
|
||||||
|
fn from(input: MobileEntryEditorInput) -> Self {
|
||||||
|
Self::new(input.id, input.name, input.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<StorageEntryCopy> for MobileEntryCopy {
|
impl From<StorageEntryCopy> for MobileEntryCopy {
|
||||||
fn from(copy: StorageEntryCopy) -> Self {
|
fn from(copy: StorageEntryCopy) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -760,6 +884,119 @@ impl MobileAuthentication {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn begin_entry_editor(
|
||||||
|
&self,
|
||||||
|
path: String,
|
||||||
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.begin_entry_editor(&path)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_create_entry(
|
||||||
|
&self,
|
||||||
|
directory: String,
|
||||||
|
name: String,
|
||||||
|
passphrase: Option<String>,
|
||||||
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.begin_create_entry(
|
||||||
|
&directory,
|
||||||
|
&name,
|
||||||
|
passphrase
|
||||||
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.entry_editor(editor)
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
fields: Vec<MobileEntryEditorInput>,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.update_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
kind: MobileEntryEditorFieldKind,
|
||||||
|
name: Option<String>,
|
||||||
|
value: String,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.add_entry_editor_field(editor, kind.into(), name, value)
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
field: u64,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.remove_entry_editor_field(editor, field)
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reorder_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
field: u64,
|
||||||
|
index: u32,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.reorder_entry_editor_field(editor, field, index as usize)
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_entry_editor_password(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
length: Option<u32>,
|
||||||
|
no_symbols: bool,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.generate_entry_editor_password(editor, length, no_symbols)
|
||||||
|
.map(|page| (&page).into())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
fields: Vec<MobileEntryEditorInput>,
|
||||||
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.save_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_entry_editor(&self, editor: u64) -> Result<(), MobileAuthenticationFfiError> {
|
||||||
|
self.authentication
|
||||||
|
.discard_entry_editor(editor)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationFfiError> {
|
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationFfiError> {
|
||||||
self.authentication.manual_lock().map_err(Into::into)
|
self.authentication.manual_lock().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,6 +320,10 @@ impl EntryDocument {
|
|||||||
self.conflict_token
|
self.conflict_token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_modified(&self) -> bool {
|
||||||
|
self.modified
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add(
|
pub fn add(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Shared mobile authentication state; Swift only supplies input and presents results.
|
//! Shared mobile authentication state; Swift only supplies input and presents results.
|
||||||
|
|
||||||
use std::{error::Error, fmt, sync::Mutex};
|
use std::{collections::BTreeMap, error::Error, fmt, sync::Mutex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
authentication::{
|
authentication::{
|
||||||
@@ -10,9 +10,17 @@ use crate::{
|
|||||||
crypto::{CryptoError, KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
crypto::{CryptoError, KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||||
document::{DocumentError, EntryDocument, EntryDocumentService, EntryFieldId},
|
document::{DocumentError, EntryDocument, EntryDocumentService, EntryFieldId},
|
||||||
git::{AutomaticEntryCommitter, GitIdentity},
|
git::{AutomaticEntryCommitter, GitIdentity},
|
||||||
mobile_entry::{MobileEntryPage, MobileEntryValueError, field_value},
|
mobile_entry::{
|
||||||
repository::{EntryPath, Repository, RepositoryError, SecretBytes},
|
MobileEntryDraft, MobileEntryEditorError, MobileEntryEditorFieldKind,
|
||||||
|
MobileEntryEditorInput, MobileEntryEditorPage, MobileEntryEditorSession, MobileEntryPage,
|
||||||
|
MobileEntryValueError, field_value,
|
||||||
|
},
|
||||||
|
recipient::RecipientPolicyManager,
|
||||||
|
repository::{
|
||||||
|
DirectoryPath, EncryptedEntry, EntryPath, Repository, RepositoryError, SecretBytes,
|
||||||
|
},
|
||||||
secret_store::{SecretProtectionPolicy, SecretStoreError},
|
secret_store::{SecretProtectionPolicy, SecretStoreError},
|
||||||
|
write::{VaultWriter, WriteError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -26,6 +34,7 @@ pub enum MobileAuthenticationErrorKind {
|
|||||||
Entry,
|
Entry,
|
||||||
SecureStorage,
|
SecureStorage,
|
||||||
Expired,
|
Expired,
|
||||||
|
Conflict,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -150,6 +159,8 @@ struct ActiveMobileLease {
|
|||||||
struct MobileAuthenticationStatus {
|
struct MobileAuthenticationStatus {
|
||||||
biometric_unlock_enabled: bool,
|
biometric_unlock_enabled: bool,
|
||||||
active: Option<ActiveMobileLease>,
|
active: Option<ActiveMobileLease>,
|
||||||
|
next_editor_id: u64,
|
||||||
|
editors: BTreeMap<u64, MobileEntryDraft>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
||||||
@@ -181,6 +192,8 @@ impl MobileAuthentication {
|
|||||||
status: Mutex::new(MobileAuthenticationStatus {
|
status: Mutex::new(MobileAuthenticationStatus {
|
||||||
biometric_unlock_enabled: config.biometric_unlock_enabled(),
|
biometric_unlock_enabled: config.biometric_unlock_enabled(),
|
||||||
active: None,
|
active: None,
|
||||||
|
next_editor_id: 0,
|
||||||
|
editors: BTreeMap::new(),
|
||||||
}),
|
}),
|
||||||
config,
|
config,
|
||||||
repository,
|
repository,
|
||||||
@@ -197,17 +210,26 @@ impl MobileAuthentication {
|
|||||||
) -> Result<MobileAuthenticationState, MobileAuthenticationError> {
|
) -> Result<MobileAuthenticationState, MobileAuthenticationError> {
|
||||||
let entry = EntryPath::parse(path).map_err(entry_error)?;
|
let entry = EntryPath::parse(path).map_err(entry_error)?;
|
||||||
let ciphertext = self.repository.read_entry(&entry).map_err(entry_error)?;
|
let ciphertext = self.repository.read_entry(&entry).map_err(entry_error)?;
|
||||||
let candidates = self.keys.decrypting_keys(&ciphertext).map_err(key_error)?;
|
self.unlock_ciphertext(&ciphertext, passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unlock_ciphertext(
|
||||||
|
&self,
|
||||||
|
ciphertext: &EncryptedEntry,
|
||||||
|
passphrase: Option<SecretBytes>,
|
||||||
|
) -> Result<MobileAuthenticationState, MobileAuthenticationError> {
|
||||||
|
let candidates = self.keys.decrypting_keys(ciphertext).map_err(key_error)?;
|
||||||
|
|
||||||
if let Some(active) = self.take_active()? {
|
if let Some(active) = self.take_active()? {
|
||||||
let mut provider = KeyOnlyProvider::new(active.handle.clone(), &active.key);
|
let mut provider = KeyOnlyProvider::new(active.handle.clone(), &active.key);
|
||||||
if self.keys.decrypt(&ciphertext, &mut provider).is_ok() {
|
if self.keys.decrypt(ciphertext, &mut provider).is_ok() {
|
||||||
self.restore_active(active)?;
|
self.restore_active(active)?;
|
||||||
return self.state();
|
return self.state();
|
||||||
}
|
}
|
||||||
self.session
|
self.session
|
||||||
.manual_lock()
|
.manual_lock()
|
||||||
.map_err(MobileAuthenticationError::authentication)?;
|
.map_err(MobileAuthenticationError::authentication)?;
|
||||||
|
self.status()?.editors.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let biometric_enabled = self.status()?.biometric_unlock_enabled;
|
let biometric_enabled = self.status()?.biometric_unlock_enabled;
|
||||||
@@ -220,7 +242,7 @@ impl MobileAuthentication {
|
|||||||
.authenticate_with_passphrase(key, candidate)
|
.authenticate_with_passphrase(key, candidate)
|
||||||
.map_err(MobileAuthenticationError::authentication)?;
|
.map_err(MobileAuthenticationError::authentication)?;
|
||||||
let mut provider = KeyOnlyProvider::new(handle.clone(), key);
|
let mut provider = KeyOnlyProvider::new(handle.clone(), key);
|
||||||
match self.keys.decrypt(&ciphertext, &mut provider) {
|
match self.keys.decrypt(ciphertext, &mut provider) {
|
||||||
Ok(plaintext) => {
|
Ok(plaintext) => {
|
||||||
drop(plaintext);
|
drop(plaintext);
|
||||||
if biometric_enabled {
|
if biometric_enabled {
|
||||||
@@ -257,7 +279,7 @@ impl MobileAuthentication {
|
|||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
let mut provider = KeyOnlyProvider::new(handle.clone(), key);
|
let mut provider = KeyOnlyProvider::new(handle.clone(), key);
|
||||||
if let Ok(plaintext) = self.keys.decrypt(&ciphertext, &mut provider) {
|
if let Ok(plaintext) = self.keys.decrypt(ciphertext, &mut provider) {
|
||||||
drop(plaintext);
|
drop(plaintext);
|
||||||
self.set_active(handle, key.clone())?;
|
self.set_active(handle, key.clone())?;
|
||||||
return self.state();
|
return self.state();
|
||||||
@@ -312,6 +334,7 @@ impl MobileAuthentication {
|
|||||||
let mut status = self.status()?;
|
let mut status = self.status()?;
|
||||||
status.biometric_unlock_enabled = false;
|
status.biometric_unlock_enabled = false;
|
||||||
status.active = None;
|
status.active = None;
|
||||||
|
status.editors.clear();
|
||||||
}
|
}
|
||||||
self.state()
|
self.state()
|
||||||
}
|
}
|
||||||
@@ -324,6 +347,7 @@ impl MobileAuthentication {
|
|||||||
let mut status = self.status()?;
|
let mut status = self.status()?;
|
||||||
if remaining.is_none() {
|
if remaining.is_none() {
|
||||||
status.active = None;
|
status.active = None;
|
||||||
|
status.editors.clear();
|
||||||
}
|
}
|
||||||
Ok(MobileAuthenticationState {
|
Ok(MobileAuthenticationState {
|
||||||
unlocked: status.active.is_some(),
|
unlocked: status.active.is_some(),
|
||||||
@@ -391,11 +415,191 @@ impl MobileAuthentication {
|
|||||||
Ok(MobileEntryPage::from_document(&document))
|
Ok(MobileEntryPage::from_document(&document))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn begin_entry_editor(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
||||||
|
let document = self.open_active_document(path)?;
|
||||||
|
self.store_editor(MobileEntryDraft::new(document, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_create_entry(
|
||||||
|
&self,
|
||||||
|
directory: &str,
|
||||||
|
name: &str,
|
||||||
|
passphrase: Option<SecretBytes>,
|
||||||
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
||||||
|
let directory = DirectoryPath::parse(directory).map_err(entry_error)?;
|
||||||
|
let leaf = EntryPath::parse(name).map_err(entry_error)?;
|
||||||
|
if leaf
|
||||||
|
.as_path()
|
||||||
|
.parent()
|
||||||
|
.is_some_and(|parent| !parent.as_os_str().is_empty())
|
||||||
|
{
|
||||||
|
return Err(entry_detail(
|
||||||
|
"Entry Name Is Invalid",
|
||||||
|
"enter a name without a folder separator",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let path =
|
||||||
|
EntryPath::parse(directory.as_path().join(leaf.as_path())).map_err(entry_error)?;
|
||||||
|
let path_text = path.to_string();
|
||||||
|
if VaultWriter::new(&self.repository, &self.keys)
|
||||||
|
.entry_exists(&path_text)
|
||||||
|
.map_err(|error| entry_detail("Password Entry Is Unavailable", error))?
|
||||||
|
{
|
||||||
|
return Err(entry_detail(
|
||||||
|
"Password Already Exists",
|
||||||
|
"Choose a different entry name.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let recipients = RecipientPolicyManager::new(&self.repository, &self.keys)
|
||||||
|
.resolve_for_entry(&path, None)
|
||||||
|
.map_err(|error| entry_detail("Recipients Are Unavailable", error))?;
|
||||||
|
let probe = self
|
||||||
|
.keys
|
||||||
|
.encrypt(
|
||||||
|
SecretBytes::new(b"IronStorage mobile entry creation".to_vec()),
|
||||||
|
recipients.recipients(),
|
||||||
|
)
|
||||||
|
.map_err(key_error)?;
|
||||||
|
self.unlock_ciphertext(&probe, passphrase)?;
|
||||||
|
let mut document = self.open_active_document(&path_text)?;
|
||||||
|
document
|
||||||
|
.add(
|
||||||
|
0,
|
||||||
|
crate::document::EntryFieldDraft::line(Vec::new()).map_err(document_error)?,
|
||||||
|
)
|
||||||
|
.map_err(document_error)?;
|
||||||
|
self.store_editor(MobileEntryDraft::new(document, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
self.status()?
|
||||||
|
.editors
|
||||||
|
.get(&editor)
|
||||||
|
.map(MobileEntryDraft::page)
|
||||||
|
.ok_or_else(editor_missing)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
fields: Vec<MobileEntryEditorInput>,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||||
|
draft.apply(fields).map_err(editor_error)?;
|
||||||
|
Ok(draft.page())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
kind: MobileEntryEditorFieldKind,
|
||||||
|
name: Option<String>,
|
||||||
|
value: String,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||||
|
draft.add(kind, name, value).map_err(editor_error)?;
|
||||||
|
Ok(draft.page())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
field: u64,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||||
|
draft.remove(field).map_err(editor_error)?;
|
||||||
|
Ok(draft.page())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reorder_entry_editor_field(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
field: u64,
|
||||||
|
index: usize,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||||
|
draft.reorder(field, index).map_err(editor_error)?;
|
||||||
|
Ok(draft.page())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_entry_editor_password(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
length: Option<u32>,
|
||||||
|
no_symbols: bool,
|
||||||
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||||
|
draft
|
||||||
|
.generate_password(length, no_symbols)
|
||||||
|
.map_err(editor_error)?;
|
||||||
|
Ok(draft.page())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_entry_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
fields: Vec<MobileEntryEditorInput>,
|
||||||
|
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_active()?;
|
||||||
|
let mut draft = self
|
||||||
|
.status()?
|
||||||
|
.editors
|
||||||
|
.remove(&editor)
|
||||||
|
.ok_or_else(editor_missing)?;
|
||||||
|
if let Err(error) = draft.apply(fields) {
|
||||||
|
self.restore_editor(editor, draft)?;
|
||||||
|
return Err(editor_error(error));
|
||||||
|
}
|
||||||
|
let path = draft.document().path().to_string();
|
||||||
|
let mut committer = match AutomaticEntryCommitter::for_entry(
|
||||||
|
&self.repository,
|
||||||
|
&path,
|
||||||
|
GitIdentity::ironstorage(),
|
||||||
|
) {
|
||||||
|
Ok(committer) => committer,
|
||||||
|
Err(error) => {
|
||||||
|
self.restore_editor(editor, draft)?;
|
||||||
|
return Err(entry_detail("Password Entry Could Not Be Saved", error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(error) = EntryDocumentService::new(&self.repository, &self.keys)
|
||||||
|
.save_recoverable(draft.document(), None, &mut committer)
|
||||||
|
{
|
||||||
|
self.restore_editor(editor, draft)?;
|
||||||
|
return Err(document_error(error));
|
||||||
|
}
|
||||||
|
Ok(MobileEntryPage::from_document(draft.document()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_entry_editor(&self, editor: u64) -> Result<(), MobileAuthenticationError> {
|
||||||
|
self.status()?.editors.remove(&editor);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationError> {
|
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationError> {
|
||||||
self.session
|
self.session
|
||||||
.manual_lock()
|
.manual_lock()
|
||||||
.map_err(MobileAuthenticationError::authentication)?;
|
.map_err(MobileAuthenticationError::authentication)?;
|
||||||
self.status()?.active = None;
|
let mut status = self.status()?;
|
||||||
|
status.active = None;
|
||||||
|
status.editors.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +607,9 @@ impl MobileAuthentication {
|
|||||||
self.session
|
self.session
|
||||||
.cancel()
|
.cancel()
|
||||||
.map_err(MobileAuthenticationError::authentication)?;
|
.map_err(MobileAuthenticationError::authentication)?;
|
||||||
self.status()?.active = None;
|
let mut status = self.status()?;
|
||||||
|
status.active = None;
|
||||||
|
status.editors.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +653,48 @@ impl MobileAuthentication {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_active(&self) -> Result<(), MobileAuthenticationError> {
|
||||||
|
let handle = self
|
||||||
|
.status()?
|
||||||
|
.active
|
||||||
|
.as_ref()
|
||||||
|
.map(|active| active.handle.clone())
|
||||||
|
.ok_or_else(locked_error)?;
|
||||||
|
if let Err(error) = handle.ensure_active() {
|
||||||
|
let mut status = self.status()?;
|
||||||
|
status.active = None;
|
||||||
|
status.editors.clear();
|
||||||
|
return Err(MobileAuthenticationError::authentication(error));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_editor(
|
||||||
|
&self,
|
||||||
|
draft: MobileEntryDraft,
|
||||||
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
||||||
|
let mut status = self.status()?;
|
||||||
|
let id = status.next_editor_id;
|
||||||
|
status.next_editor_id = status.next_editor_id.checked_add(1).ok_or_else(|| {
|
||||||
|
entry_detail(
|
||||||
|
"Entry Editor Is Unavailable",
|
||||||
|
"entry editor identifiers are exhausted",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let page = draft.page();
|
||||||
|
status.editors.insert(id, draft);
|
||||||
|
Ok(MobileEntryEditorSession::new(id, page))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_editor(
|
||||||
|
&self,
|
||||||
|
editor: u64,
|
||||||
|
draft: MobileEntryDraft,
|
||||||
|
) -> Result<(), MobileAuthenticationError> {
|
||||||
|
self.status()?.editors.insert(editor, draft);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn open_active_document(&self, path: &str) -> Result<EntryDocument, MobileAuthenticationError> {
|
fn open_active_document(&self, path: &str) -> Result<EntryDocument, MobileAuthenticationError> {
|
||||||
let (handle, key) = {
|
let (handle, key) = {
|
||||||
let status = self.status()?;
|
let status = self.status()?;
|
||||||
@@ -519,13 +767,32 @@ fn locked_error() -> MobileAuthenticationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn document_error(error: DocumentError) -> MobileAuthenticationError {
|
fn document_error(error: DocumentError) -> MobileAuthenticationError {
|
||||||
entry_detail("Password Entry Is Unavailable", error)
|
let kind = if matches!(
|
||||||
|
&error,
|
||||||
|
DocumentError::Write(WriteError::ConcurrentModification { .. })
|
||||||
|
) {
|
||||||
|
MobileAuthenticationErrorKind::Conflict
|
||||||
|
} else {
|
||||||
|
MobileAuthenticationErrorKind::Entry
|
||||||
|
};
|
||||||
|
MobileAuthenticationError::new(kind, "Password Entry Could Not Be Saved", error.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_error(error: MobileEntryValueError) -> MobileAuthenticationError {
|
fn value_error(error: MobileEntryValueError) -> MobileAuthenticationError {
|
||||||
entry_detail("Field Value Is Unavailable", error)
|
entry_detail("Field Value Is Unavailable", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn editor_error(error: MobileEntryEditorError) -> MobileAuthenticationError {
|
||||||
|
entry_detail("Entry Draft Is Invalid", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor_missing() -> MobileAuthenticationError {
|
||||||
|
entry_detail(
|
||||||
|
"Entry Draft Is Unavailable",
|
||||||
|
"the entry draft was discarded or locked",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn entry_detail(title: &str, error: impl fmt::Display) -> MobileAuthenticationError {
|
fn entry_detail(title: &str, error: impl fmt::Display) -> MobileAuthenticationError {
|
||||||
MobileAuthenticationError::new(
|
MobileAuthenticationError::new(
|
||||||
MobileAuthenticationErrorKind::Entry,
|
MobileAuthenticationErrorKind::Entry,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
//! Storage-owned projection of lossless entry documents for native mobile viewers.
|
//! Storage-owned projection of lossless entry documents for native mobile viewers.
|
||||||
|
|
||||||
use std::{error::Error, fmt};
|
use std::{collections::BTreeSet, error::Error, fmt, num::NonZeroUsize};
|
||||||
|
|
||||||
use crate::document::{
|
use crate::document::{
|
||||||
DocumentError, EntryDocument, EntryField, EntryFieldDiagnostic, EntryFieldId, EntryFieldKind,
|
DocumentError, EntryDocument, EntryField, EntryFieldDiagnostic, EntryFieldDraft, EntryFieldId,
|
||||||
EntrySensitivity,
|
EntryFieldKind, EntrySensitivity,
|
||||||
};
|
};
|
||||||
|
use crate::generate::{GenerateError, GeneratorConfig, MAX_PASSWORD_LENGTH};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum MobileEntrySectionKind {
|
pub enum MobileEntrySectionKind {
|
||||||
@@ -104,6 +105,352 @@ pub struct MobileEntryPage {
|
|||||||
sections: Vec<MobileEntrySection>,
|
sections: Vec<MobileEntrySection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MobileEntryEditorFieldKind {
|
||||||
|
Password,
|
||||||
|
Field,
|
||||||
|
Note,
|
||||||
|
OtpUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MobileEntryEditorField {
|
||||||
|
id: u64,
|
||||||
|
kind: MobileEntryEditorFieldKind,
|
||||||
|
name: Option<String>,
|
||||||
|
label: String,
|
||||||
|
system_image: String,
|
||||||
|
value: Option<String>,
|
||||||
|
masked_value: String,
|
||||||
|
diagnostic: Option<String>,
|
||||||
|
sensitive: bool,
|
||||||
|
multiline: bool,
|
||||||
|
name_editable: bool,
|
||||||
|
removable: bool,
|
||||||
|
reorderable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileEntryEditorField {
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> MobileEntryEditorFieldKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<&str> {
|
||||||
|
self.name.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(&self) -> &str {
|
||||||
|
&self.label
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_image(&self) -> &str {
|
||||||
|
&self.system_image
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<&str> {
|
||||||
|
self.value.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn masked_value(&self) -> &str {
|
||||||
|
&self.masked_value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diagnostic(&self) -> Option<&str> {
|
||||||
|
self.diagnostic.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sensitive(&self) -> bool {
|
||||||
|
self.sensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiline(&self) -> bool {
|
||||||
|
self.multiline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name_editable(&self) -> bool {
|
||||||
|
self.name_editable
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removable(&self) -> bool {
|
||||||
|
self.removable
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reorderable(&self) -> bool {
|
||||||
|
self.reorderable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MobileEntryEditorPage {
|
||||||
|
path: String,
|
||||||
|
title: String,
|
||||||
|
fields: Vec<MobileEntryEditorField>,
|
||||||
|
dirty: bool,
|
||||||
|
creating: bool,
|
||||||
|
default_password_length: u32,
|
||||||
|
maximum_password_length: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MobileEntryEditorSession {
|
||||||
|
id: u64,
|
||||||
|
page: MobileEntryEditorPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileEntryEditorSession {
|
||||||
|
pub fn new(id: u64, page: MobileEntryEditorPage) -> Self {
|
||||||
|
Self { id, page }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page(&self) -> &MobileEntryEditorPage {
|
||||||
|
&self.page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileEntryEditorPage {
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
&self.title
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields(&self) -> &[MobileEntryEditorField] {
|
||||||
|
&self.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn creating(&self) -> bool {
|
||||||
|
self.creating
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_password_length(&self) -> u32 {
|
||||||
|
self.default_password_length
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maximum_password_length(&self) -> u32 {
|
||||||
|
self.maximum_password_length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MobileEntryEditorInput {
|
||||||
|
id: u64,
|
||||||
|
name: Option<String>,
|
||||||
|
value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileEntryEditorInput {
|
||||||
|
pub fn new(id: u64, name: Option<String>, value: Option<String>) -> Self {
|
||||||
|
Self { id, name, value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MobileEntryDraft {
|
||||||
|
document: EntryDocument,
|
||||||
|
creating: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileEntryDraft {
|
||||||
|
pub fn new(document: EntryDocument, creating: bool) -> Self {
|
||||||
|
Self { document, creating }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page(&self) -> MobileEntryEditorPage {
|
||||||
|
let fields = self
|
||||||
|
.document
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, field)| editor_field(index, field))
|
||||||
|
.collect();
|
||||||
|
MobileEntryEditorPage {
|
||||||
|
path: self.document.path().to_string(),
|
||||||
|
title: self
|
||||||
|
.document
|
||||||
|
.path()
|
||||||
|
.as_path()
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or("Password")
|
||||||
|
.to_owned(),
|
||||||
|
fields,
|
||||||
|
dirty: self.document.is_modified(),
|
||||||
|
creating: self.creating,
|
||||||
|
default_password_length: GeneratorConfig::pass_defaults().default_length().get() as u32,
|
||||||
|
maximum_password_length: MAX_PASSWORD_LENGTH as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(
|
||||||
|
&mut self,
|
||||||
|
inputs: Vec<MobileEntryEditorInput>,
|
||||||
|
) -> Result<(), MobileEntryEditorError> {
|
||||||
|
if inputs.len() != self.document.fields().len() {
|
||||||
|
return Err(MobileEntryEditorError::FieldSetChanged);
|
||||||
|
}
|
||||||
|
let mut seen = BTreeSet::new();
|
||||||
|
let mut replacements = Vec::with_capacity(inputs.len());
|
||||||
|
for input in inputs {
|
||||||
|
if !seen.insert(input.id) {
|
||||||
|
return Err(MobileEntryEditorError::FieldSetChanged);
|
||||||
|
}
|
||||||
|
let id = EntryFieldId::from_value(input.id);
|
||||||
|
let field = self
|
||||||
|
.document
|
||||||
|
.field(id)
|
||||||
|
.ok_or(MobileEntryEditorError::FieldSetChanged)?;
|
||||||
|
if let Some(value) = input.value {
|
||||||
|
replacements.push((id, editor_draft(field, input.name, value)?));
|
||||||
|
} else if field.metadata().diagnostic() != Some(EntryFieldDiagnostic::NonUtf8Value) {
|
||||||
|
return Err(MobileEntryEditorError::NonUtf8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (id, replacement) in replacements {
|
||||||
|
self.document.update(id, replacement)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(
|
||||||
|
&mut self,
|
||||||
|
kind: MobileEntryEditorFieldKind,
|
||||||
|
name: Option<String>,
|
||||||
|
value: String,
|
||||||
|
) -> Result<(), MobileEntryEditorError> {
|
||||||
|
let draft = match kind {
|
||||||
|
MobileEntryEditorFieldKind::Password => {
|
||||||
|
return Err(MobileEntryEditorError::ProtectedPasswordField);
|
||||||
|
}
|
||||||
|
MobileEntryEditorFieldKind::Field => EntryFieldDraft::field(
|
||||||
|
name.ok_or(MobileEntryEditorError::MissingFieldName)?,
|
||||||
|
value.into_bytes(),
|
||||||
|
)?,
|
||||||
|
MobileEntryEditorFieldKind::Note => {
|
||||||
|
EntryFieldDraft::field("notes", value.into_bytes())?
|
||||||
|
}
|
||||||
|
MobileEntryEditorFieldKind::OtpUri => EntryFieldDraft::otp_uri(value.into_bytes())?,
|
||||||
|
};
|
||||||
|
self.document.add(self.document.fields().len(), draft)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, id: u64) -> Result<(), MobileEntryEditorError> {
|
||||||
|
let id = EntryFieldId::from_value(id);
|
||||||
|
if self
|
||||||
|
.document
|
||||||
|
.fields()
|
||||||
|
.first()
|
||||||
|
.is_some_and(|field| field.id() == id)
|
||||||
|
{
|
||||||
|
return Err(MobileEntryEditorError::ProtectedPasswordField);
|
||||||
|
}
|
||||||
|
self.document.remove(id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reorder(&mut self, id: u64, index: usize) -> Result<(), MobileEntryEditorError> {
|
||||||
|
let id = EntryFieldId::from_value(id);
|
||||||
|
if index == 0
|
||||||
|
|| self
|
||||||
|
.document
|
||||||
|
.fields()
|
||||||
|
.first()
|
||||||
|
.is_some_and(|field| field.id() == id)
|
||||||
|
{
|
||||||
|
return Err(MobileEntryEditorError::ProtectedPasswordField);
|
||||||
|
}
|
||||||
|
self.document.reorder(id, index)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_password(
|
||||||
|
&mut self,
|
||||||
|
length: Option<u32>,
|
||||||
|
no_symbols: bool,
|
||||||
|
) -> Result<(), MobileEntryEditorError> {
|
||||||
|
let length = length
|
||||||
|
.map(|length| {
|
||||||
|
usize::try_from(length)
|
||||||
|
.ok()
|
||||||
|
.and_then(NonZeroUsize::new)
|
||||||
|
.ok_or(MobileEntryEditorError::Generate(
|
||||||
|
GenerateError::InvalidLength,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let generated = GeneratorConfig::pass_defaults().generate_secret(length, no_symbols)?;
|
||||||
|
let value = generated.expose().to_vec();
|
||||||
|
if let Some(password) = self
|
||||||
|
.document
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.find(|field| field.metadata().kind() == EntryFieldKind::Password)
|
||||||
|
{
|
||||||
|
self.document
|
||||||
|
.update(password.id(), EntryFieldDraft::line(value)?)?;
|
||||||
|
} else {
|
||||||
|
self.document.add(0, EntryFieldDraft::line(value)?)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn document(&self) -> &EntryDocument {
|
||||||
|
&self.document
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MobileEntryEditorError {
|
||||||
|
Document(DocumentError),
|
||||||
|
Generate(GenerateError),
|
||||||
|
FieldSetChanged,
|
||||||
|
MissingFieldName,
|
||||||
|
ProtectedPasswordField,
|
||||||
|
NonUtf8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MobileEntryEditorError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Document(error) => error.fmt(formatter),
|
||||||
|
Self::Generate(error) => error.fmt(formatter),
|
||||||
|
Self::FieldSetChanged => formatter.write_str("the entry fields changed while editing"),
|
||||||
|
Self::MissingFieldName => formatter.write_str("a named field requires a name"),
|
||||||
|
Self::ProtectedPasswordField => {
|
||||||
|
formatter.write_str("the first password field cannot be removed or moved")
|
||||||
|
}
|
||||||
|
Self::NonUtf8 => formatter.write_str("the entry field is not valid UTF-8"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for MobileEntryEditorError {}
|
||||||
|
|
||||||
|
impl From<DocumentError> for MobileEntryEditorError {
|
||||||
|
fn from(error: DocumentError) -> Self {
|
||||||
|
Self::Document(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GenerateError> for MobileEntryEditorError {
|
||||||
|
fn from(error: GenerateError) -> Self {
|
||||||
|
Self::Generate(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MobileEntryPage {
|
impl MobileEntryPage {
|
||||||
pub fn from_document(document: &EntryDocument) -> Self {
|
pub fn from_document(document: &EntryDocument) -> Self {
|
||||||
let title = document
|
let title = document
|
||||||
@@ -213,12 +560,71 @@ fn mobile_field(field: &EntryField) -> MobileEntryField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn editor_field(index: usize, field: &EntryField) -> MobileEntryEditorField {
|
||||||
|
let metadata = field.metadata();
|
||||||
|
let kind = if is_named_note(field) {
|
||||||
|
MobileEntryEditorFieldKind::Note
|
||||||
|
} else {
|
||||||
|
match metadata.kind() {
|
||||||
|
EntryFieldKind::Password => MobileEntryEditorFieldKind::Password,
|
||||||
|
EntryFieldKind::OtpUri => MobileEntryEditorFieldKind::OtpUri,
|
||||||
|
EntryFieldKind::Note => MobileEntryEditorFieldKind::Note,
|
||||||
|
EntryFieldKind::Username
|
||||||
|
| EntryFieldKind::Email
|
||||||
|
| EntryFieldKind::Url
|
||||||
|
| EntryFieldKind::Field => MobileEntryEditorFieldKind::Field,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sensitive = matches!(
|
||||||
|
kind,
|
||||||
|
MobileEntryEditorFieldKind::Password | MobileEntryEditorFieldKind::OtpUri
|
||||||
|
);
|
||||||
|
MobileEntryEditorField {
|
||||||
|
id: field.id().value(),
|
||||||
|
kind,
|
||||||
|
name: metadata.name().map(str::to_owned),
|
||||||
|
label: label(field),
|
||||||
|
system_image: system_image(metadata.kind()).to_owned(),
|
||||||
|
value: String::from_utf8(field.value().to_vec()).ok(),
|
||||||
|
masked_value: if metadata.sensitivity() == EntrySensitivity::Empty {
|
||||||
|
"Empty"
|
||||||
|
} else if sensitive {
|
||||||
|
"Hidden"
|
||||||
|
} else {
|
||||||
|
"Unavailable"
|
||||||
|
}
|
||||||
|
.to_owned(),
|
||||||
|
diagnostic: metadata.diagnostic().map(diagnostic),
|
||||||
|
sensitive,
|
||||||
|
multiline: matches!(kind, MobileEntryEditorFieldKind::Note)
|
||||||
|
|| field.value().contains(&b'\n'),
|
||||||
|
name_editable: matches!(kind, MobileEntryEditorFieldKind::Field),
|
||||||
|
removable: index > 0,
|
||||||
|
reorderable: index > 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor_draft(
|
||||||
|
field: &EntryField,
|
||||||
|
name: Option<String>,
|
||||||
|
value: String,
|
||||||
|
) -> Result<EntryFieldDraft, MobileEntryEditorError> {
|
||||||
|
Ok(match field.metadata().kind() {
|
||||||
|
EntryFieldKind::Password => EntryFieldDraft::line(value.into_bytes())?,
|
||||||
|
EntryFieldKind::OtpUri => EntryFieldDraft::otp_uri(value.into_bytes())?,
|
||||||
|
EntryFieldKind::Note => EntryFieldDraft::multiline(value.into_bytes()),
|
||||||
|
EntryFieldKind::Username
|
||||||
|
| EntryFieldKind::Email
|
||||||
|
| EntryFieldKind::Url
|
||||||
|
| EntryFieldKind::Field => EntryFieldDraft::field(
|
||||||
|
name.ok_or(MobileEntryEditorError::MissingFieldName)?,
|
||||||
|
value.into_bytes(),
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn section_kind(field: &EntryField) -> MobileEntrySectionKind {
|
fn section_kind(field: &EntryField) -> MobileEntrySectionKind {
|
||||||
if field.metadata().name().is_some_and(|name| {
|
if is_named_note(field) {
|
||||||
["comment", "comments", "note", "notes"]
|
|
||||||
.iter()
|
|
||||||
.any(|candidate| name.eq_ignore_ascii_case(candidate))
|
|
||||||
}) {
|
|
||||||
return MobileEntrySectionKind::Notes;
|
return MobileEntrySectionKind::Notes;
|
||||||
}
|
}
|
||||||
match field.metadata().kind() {
|
match field.metadata().kind() {
|
||||||
@@ -232,6 +638,14 @@ fn section_kind(field: &EntryField) -> MobileEntrySectionKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_named_note(field: &EntryField) -> bool {
|
||||||
|
field.metadata().name().is_some_and(|name| {
|
||||||
|
["comment", "comments", "note", "notes"]
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| name.eq_ignore_ascii_case(candidate))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn label(field: &EntryField) -> String {
|
fn label(field: &EntryField) -> String {
|
||||||
if field.metadata().kind() == EntryFieldKind::Password {
|
if field.metadata().kind() == EntryFieldKind::Password {
|
||||||
return "Password".to_owned();
|
return "Password".to_owned();
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use ironstorage::{
|
|||||||
document::{
|
document::{
|
||||||
DocumentError, EntryDocumentService, EntryFieldDraft, EntryFieldKind, EntrySensitivity,
|
DocumentError, EntryDocumentService, EntryFieldDraft, EntryFieldKind, EntrySensitivity,
|
||||||
},
|
},
|
||||||
mobile_entry::{MobileEntryPage, MobileEntrySectionKind, field_value},
|
mobile_entry::{
|
||||||
|
MobileEntryDraft, MobileEntryEditorError, MobileEntryEditorFieldKind,
|
||||||
|
MobileEntryEditorInput, MobileEntryPage, MobileEntrySectionKind, field_value,
|
||||||
|
},
|
||||||
recipient::RecipientPolicyManager,
|
recipient::RecipientPolicyManager,
|
||||||
repository::{EntryPath, Repository, SecretBytes},
|
repository::{EntryPath, Repository, SecretBytes},
|
||||||
write::{EntryCommit, EntryCommitError, EntryCommitter, WriteError},
|
write::{EntryCommit, EntryCommitError, EntryCommitter, WriteError},
|
||||||
@@ -624,6 +627,133 @@ fn atomic_save_creates_updates_and_rejects_stale_documents() -> TestResult {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mobile_editor_creates_edits_generates_and_preserves_dynamic_fields() -> TestResult {
|
||||||
|
let fixture = FixtureSet::load()?;
|
||||||
|
let store = fixture.materialize_store("basic")?;
|
||||||
|
let repository = Repository::open(store.path())?;
|
||||||
|
let keys = KeyStore::load(fixture.path("keys"))?;
|
||||||
|
let mut secrets = FixtureSecrets::all(&fixture);
|
||||||
|
write_plaintext(
|
||||||
|
&repository,
|
||||||
|
&keys,
|
||||||
|
"documents/mobile-editor",
|
||||||
|
b"old password\nlogin: alice\ncomments: first\nsecond\nnote: repeated one\nnote: repeated two\nbinary: \xff\n",
|
||||||
|
)?;
|
||||||
|
let service = EntryDocumentService::new(&repository, &keys);
|
||||||
|
let document = service.open("documents/mobile-editor", &mut secrets)?;
|
||||||
|
let mut draft = MobileEntryDraft::new(document, false);
|
||||||
|
let page = draft.page();
|
||||||
|
assert!(!page.dirty());
|
||||||
|
assert!(!page.creating());
|
||||||
|
assert_eq!(page.default_password_length(), 25);
|
||||||
|
assert_eq!(page.fields().len(), 6);
|
||||||
|
assert!(page.fields()[2].multiline());
|
||||||
|
assert_eq!(page.fields()[2].value(), Some("first\nsecond"));
|
||||||
|
assert!(page.fields()[5].value().is_none());
|
||||||
|
|
||||||
|
let password = page.fields()[0].id();
|
||||||
|
assert!(matches!(
|
||||||
|
draft.remove(password),
|
||||||
|
Err(MobileEntryEditorError::ProtectedPasswordField)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
draft.reorder(password, 1),
|
||||||
|
Err(MobileEntryEditorError::ProtectedPasswordField)
|
||||||
|
));
|
||||||
|
assert!(draft.generate_password(Some(0), false).is_err());
|
||||||
|
|
||||||
|
let repeated_one = page.fields()[3].id();
|
||||||
|
let inputs = page
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let (name, value) = if field.label() == "Login" {
|
||||||
|
(Some("username".to_owned()), Some("bob".to_owned()))
|
||||||
|
} else if field.label() == "Comments" {
|
||||||
|
(
|
||||||
|
field.name().map(str::to_owned),
|
||||||
|
Some("heading\nline: preserved\n\nend".to_owned()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
field.name().map(str::to_owned),
|
||||||
|
field.value().map(str::to_owned),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
MobileEntryEditorInput::new(field.id(), name, value)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
draft.apply(inputs)?;
|
||||||
|
draft.add(
|
||||||
|
MobileEntryEditorFieldKind::Field,
|
||||||
|
Some("tags".to_owned()),
|
||||||
|
"work, mobile".to_owned(),
|
||||||
|
)?;
|
||||||
|
draft.add(
|
||||||
|
MobileEntryEditorFieldKind::Note,
|
||||||
|
None,
|
||||||
|
"open note\nsecond line".to_owned(),
|
||||||
|
)?;
|
||||||
|
draft.remove(repeated_one)?;
|
||||||
|
let open_note = draft.page().fields().last().expect("open note").id();
|
||||||
|
draft.reorder(open_note, 2)?;
|
||||||
|
draft.generate_password(Some(32), true)?;
|
||||||
|
|
||||||
|
let edited = draft.page();
|
||||||
|
assert!(edited.dirty());
|
||||||
|
let password = edited.fields().first().expect("password").value().unwrap();
|
||||||
|
assert_eq!(password.len(), 32);
|
||||||
|
assert!(
|
||||||
|
password
|
||||||
|
.chars()
|
||||||
|
.all(|character| character.is_ascii_alphanumeric())
|
||||||
|
);
|
||||||
|
assert_eq!(edited.fields()[2].value(), Some("open note\nsecond line"));
|
||||||
|
|
||||||
|
let mut committer = Committer::default();
|
||||||
|
service.save_recoverable(draft.document(), None, &mut committer)?;
|
||||||
|
let reopened = service.open("documents/mobile-editor", &mut secrets)?;
|
||||||
|
assert_eq!(
|
||||||
|
reopened
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.filter(|field| field.metadata().name() == Some("note"))
|
||||||
|
.count(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
reopened.fields().iter().any(|field| {
|
||||||
|
field.metadata().name() == Some("username") && field.value() == b"bob"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert!(reopened.fields().iter().any(|field| {
|
||||||
|
field.metadata().name() == Some("comments")
|
||||||
|
&& field.value() == b"heading\nline: preserved\n\nend"
|
||||||
|
}));
|
||||||
|
assert!(reopened.fields().iter().any(|field| {
|
||||||
|
field.metadata().name() == Some("tags") && field.value() == b"work, mobile"
|
||||||
|
}));
|
||||||
|
assert!(
|
||||||
|
reopened
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.any(|field| { field.metadata().name() == Some("binary") && field.value() == b"\xff" })
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut created = service.open("documents/cancelled-mobile", &mut secrets)?;
|
||||||
|
created.add(0, EntryFieldDraft::line(Vec::new())?)?;
|
||||||
|
let created = MobileEntryDraft::new(created, true);
|
||||||
|
assert!(created.page().creating());
|
||||||
|
assert!(created.page().dirty());
|
||||||
|
drop(created);
|
||||||
|
assert!(matches!(
|
||||||
|
repository.read_entry(&EntryPath::parse("documents/cancelled-mobile")?),
|
||||||
|
Err(ironstorage::repository::RepositoryError::NotFound { .. })
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_compatibility_entry_round_trips_as_a_document() -> TestResult {
|
fn every_compatibility_entry_round_trips_as_a_document() -> TestResult {
|
||||||
let fixture = FixtureSet::load()?;
|
let fixture = FixtureSet::load()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user