diff --git a/NevisExampleApp/Model/Operation.swift b/NevisExampleApp/Model/Operation.swift index f178813..325b0c4 100644 --- a/NevisExampleApp/Model/Operation.swift +++ b/NevisExampleApp/Model/Operation.swift @@ -32,6 +32,9 @@ public enum Operation { /// Device information change operation. case deviceInformationChange + /// Local data operation. + case localData + /// Unknown operation. case unknown @@ -54,6 +57,8 @@ public enum Operation { return L10n.Operation.Pinchange.title case .deviceInformationChange: return L10n.Operation.DeviceInformationChange.title + case .localData: + return L10n.Operation.LocalData.title case .unknown: return String() } diff --git a/NevisExampleApp/Resources/en.lproj/Localizable.strings b/NevisExampleApp/Resources/en.lproj/Localizable.strings index 92cde05..df16624 100644 --- a/NevisExampleApp/Resources/en.lproj/Localizable.strings +++ b/NevisExampleApp/Resources/en.lproj/Localizable.strings @@ -11,6 +11,7 @@ "home_pin_change_button" = "PIN change"; "home_change_device_information_button" = "Change Device Information"; "home_auth_cloud_api_registration_button" = "Auth Cloud Api Registration"; +"home_delete_authenticators_button" = "Delete Authenticators"; "home_separator" = "Identity Suite only"; "home_in_band_registration_button" = "In-Band Register"; @@ -76,7 +77,7 @@ "logging_title" = "Pull to see log"; // Operation -"operation_success_title" = "%@ successful!"; +"operation_success_title" = "%@ succeeded!"; "operation_failed_title" = "%@ failed!"; "operation_init_client_title" = "Client initialization"; "operation_payload_decode_title" = "Payload decode"; @@ -86,6 +87,7 @@ "operation_deregistration_title" = "Deregistration"; "operation_pin_change_title" = "PIN change"; "operation_device_information_change_title" = "Device information change"; +"operation_local_data_title" = "Local data operation"; // Error "error_generic_title" = "Failure"; diff --git a/NevisExampleApp/Screens/Home/HomePresenter.swift b/NevisExampleApp/Screens/Home/HomePresenter.swift index 854e79d..e2e0054 100644 --- a/NevisExampleApp/Screens/Home/HomePresenter.swift +++ b/NevisExampleApp/Screens/Home/HomePresenter.swift @@ -122,7 +122,13 @@ extension HomePresenter { /// Starts an In-Band Authentication operation. func authenticate() { - let accounts = mobileAuthenticationClient?.localData.accounts ?? [any Account]() + guard let accounts = mobileAuthenticationClient?.localData.accounts, !accounts.isEmpty else { + logger.log("Accounts not found.", color: .red) + let operationError = OperationError(operation: .authentication, + underlyingError: AppError.accountsNotFound) + return errorHandlerChain.handle(error: operationError) + } + let parameter: SelectAccountParameter = .select(accounts: accounts, operation: .authentication, handler: nil, @@ -157,22 +163,30 @@ extension HomePresenter { /// Starts PIN changing. func changePin() { + // find enrolled accounts + guard let accounts = mobileAuthenticationClient?.localData.accounts, !accounts.isEmpty else { + let operationError = OperationError(operation: .pinChange, + underlyingError: AppError.accountsNotFound) + return errorHandlerChain.handle(error: operationError) + } + // find PIN authenticator guard let authenticators = mobileAuthenticationClient?.localData.authenticators else { - return errorHandlerChain.handle(error: AppError.pinAuthenticatorNotFound) + let operationError = OperationError(operation: .pinChange, + underlyingError: AppError.pinAuthenticatorNotFound) + return errorHandlerChain.handle(error: operationError) } guard let pinAuthenticator = authenticators.filter({ $0.aaid == AuthenticatorAaid.Pin.rawValue }).first else { - return errorHandlerChain.handle(error: AppError.pinAuthenticatorNotFound) + let operationError = OperationError(operation: .pinChange, + underlyingError: AppError.pinAuthenticatorNotFound) + return errorHandlerChain.handle(error: operationError) } guard let enrollment = pinAuthenticator.userEnrollment as? SdkUserEnrollment else { - return errorHandlerChain.handle(error: AppError.pinAuthenticatorNotFound) - } - - // find enrolled accounts - guard let accounts = mobileAuthenticationClient?.localData.accounts else { - return errorHandlerChain.handle(error: AppError.accountsNotFound) + let operationError = OperationError(operation: .pinChange, + underlyingError: AppError.pinAuthenticatorNotFound) + return errorHandlerChain.handle(error: operationError) } let eligibleAccounts = accounts.filter { account in @@ -183,7 +197,9 @@ extension HomePresenter { switch eligibleAccounts.count { case 0: - errorHandlerChain.handle(error: AppError.accountsNotFound) + let operationError = OperationError(operation: .pinChange, + underlyingError: AppError.accountsNotFound) + return errorHandlerChain.handle(error: operationError) case 1: // do PIN change automatically doPinChange(for: eligibleAccounts.first!.username) @@ -202,7 +218,9 @@ extension HomePresenter { let deviceInformation = mobileAuthenticationClient?.localData.deviceInformation guard let deviceInformation else { logger.log("Device information not found.", color: .red) - return errorHandlerChain.handle(error: AppError.deviceInformationNotFound) + let operationError = OperationError(operation: .deviceInformationChange, + underlyingError: AppError.deviceInformationNotFound) + return errorHandlerChain.handle(error: operationError) } let parameter: ChangeDeviceInformationParameter = .change(deviceInformation: deviceInformation) @@ -214,6 +232,29 @@ extension HomePresenter { appCoordinator.navigateToAuthCloudApiRegistration() } + /// Deletes local authenticators. + func deleteLocalAuthenticators() { + // find enrolled accounts + guard let accounts = mobileAuthenticationClient?.localData.accounts, !accounts.isEmpty else { + logger.log("Accounts not found.", color: .red) + let operationError = OperationError(operation: .localData, + underlyingError: AppError.accountsNotFound) + return errorHandlerChain.handle(error: operationError) + } + + doDeleteAuthenticators(of: accounts.map(\.username)) { result in + switch result { + case .success: + self.logger.log("Delete authenticators succeeded.", color: .green) + self.appCoordinator.navigateToResult(with: .success(operation: .localData)) + case let .failure(error): + self.logger.log("Delete authenticators failed.", color: .red) + let operationError = OperationError(operation: .localData, underlyingError: error) + self.errorHandlerChain.handle(error: operationError) + } + } + } + /// Starts In-Band registration operation. func register() { appCoordinator.navigateToUsernamePasswordLogin() @@ -268,4 +309,45 @@ private extension HomePresenter { } .execute() } + + /// Deletes all local authenticators of all accounts. + /// + /// - Parameters: + /// - usernames: The usernames of the enrolled accounts. + /// - handler: The code need to be executed after deletion. + func doDeleteAuthenticators(of usernames: [Username], completion handler: @escaping (Result<(), Error>) -> ()) { + var remainingUsernames = usernames + guard let username = remainingUsernames.popLast() else { + return handler(.success) + } + + doDeleteAuthenticators(of: username) { result in + if case let .failure(error) = result { + return handler(.failure(error)) + } + self.logger.log("Delete authenticators succeeded for user \(username).", color: .green) + self.doDeleteAuthenticators(of: remainingUsernames, completion: handler) + } + } + + /// Deletes all local authenticators of an account. + /// + /// - Parameters: + /// - usernames: The username of the enrolled account. + /// - handler: The code need to be executed after deletion. + func doDeleteAuthenticators(of username: Username, completion handler: @escaping (Result<(), Error>) -> ()) { + DispatchQueue.global().async { + do { + try self.mobileAuthenticationClient?.localData.deleteAuthenticator(username: username, aaid: nil) + DispatchQueue.main.async { + handler(.success) + } + } + catch { + DispatchQueue.main.async { + handler(.failure(error)) + } + } + } + } } diff --git a/NevisExampleApp/Screens/Home/HomeScreen.swift b/NevisExampleApp/Screens/Home/HomeScreen.swift index b66389c..6df7882 100644 --- a/NevisExampleApp/Screens/Home/HomeScreen.swift +++ b/NevisExampleApp/Screens/Home/HomeScreen.swift @@ -35,6 +35,9 @@ final class HomeScreen: BaseScreen, Screen { /// The Auth Cloud Api Register button. private let authCloudApiRegisterButton = OutlinedButton(title: L10n.Home.authCloudApiRegistration) + /// The Delete Authenticators button. + private let deleteAuthenticatorsButton = OutlinedButton(title: L10n.Home.deleteAuthenticators) + /// The separator label. private let separatorLabel = NSLabel(text: L10n.Home.separator, style: .normal) @@ -102,6 +105,7 @@ private extension HomeScreen { setupPinChangeButton() setupChangeDeviceInformationButton() setupAuthCloudApiRegisterButton() + setupDeleteAuthenticatorsButton() setupSeparatorLabel() setupInBandRegisterButton() } @@ -166,6 +170,14 @@ private extension HomeScreen { } } + func setupDeleteAuthenticatorsButton() { + deleteAuthenticatorsButton.do { + addItemToBottom($0, spacing: 16) + $0.setHeight(with: 40) + $0.addTarget(self, action: #selector(deleteAuthenticators), for: .touchUpInside) + } + } + func setupSeparatorLabel() { separatorLabel.do { addItemToBottom($0, spacing: 8) @@ -216,6 +228,11 @@ private extension HomeScreen { presenter.authCloudApiRegister() } + @objc + func deleteAuthenticators() { + presenter.deleteLocalAuthenticators() + } + @objc func register() { presenter.register() diff --git a/NevisExampleApp/Utility/Localization/Strings.swift b/NevisExampleApp/Utility/Localization/Strings.swift index 9cdc89c..bc7fcec 100644 --- a/NevisExampleApp/Utility/Localization/Strings.swift +++ b/NevisExampleApp/Utility/Localization/Strings.swift @@ -39,8 +39,10 @@ enum L10n { static let changePin = L10n.tr("home_pin_change_button") /// Change device information button: "Change Device Information" static let changeDeviceInformation = L10n.tr("home_change_device_information_button") - /// Auth cloud api registration button: "Auth Cloud Api Registration" + /// Auth Cloud Api registration button: "Auth Cloud Api Registration" static let authCloudApiRegistration = L10n.tr("home_auth_cloud_api_registration_button") + /// Delete authenticators button: "Delete Authenticators" + static let deleteAuthenticators = L10n.tr("home_delete_authenticators_button") /// Screen description: "Identity Suite only" static let separator = L10n.tr("home_separator") /// In-band registration button: "In-Band Register" @@ -289,9 +291,14 @@ enum L10n { static let title = L10n.tr("operation_device_information_change_title") } + enum LocalData { + /// Operation title: "Local data operation" + static let title = L10n.tr("operation_local_data_title") + } + /// Successful operation related localized strings. enum Success { - /// Title: "%@ successful!" + /// Title: "%@ succeeded!" /// /// - parameter operation: The current operation. /// - returns: The localized string.