Implemented setting database password in iOS
This commit is contained in:
parent
5c63af15a0
commit
5acb2353c7
|
@ -35,18 +35,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
}
|
||||
|
||||
private func setupBankingUi() -> AuthenticationService {
|
||||
let appDataFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
|
||||
?? Bundle.main.resourceURL?.absoluteString ?? ""
|
||||
|
||||
let persistence = CoreDataBankingPersistence()
|
||||
let authenticationService = AuthenticationService()
|
||||
let authenticationService = AuthenticationService(persistence)
|
||||
self.persistence = persistence
|
||||
|
||||
let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder))
|
||||
|
||||
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, transactionPartySearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
|
||||
|
||||
DependencyInjector.register(dependency: presenter)
|
||||
DependencyInjector.register(dependency: authenticationService)
|
||||
|
||||
return authenticationService
|
||||
|
@ -60,7 +52,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
}
|
||||
|
||||
private func showApplicationMainView(window: UIWindow) {
|
||||
window.rootViewController = UINavigationController(rootViewController: TabBarController())
|
||||
if let persistence = persistence {
|
||||
let appDataFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
|
||||
?? Bundle.main.resourceURL?.absoluteString ?? ""
|
||||
|
||||
let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder))
|
||||
|
||||
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, transactionPartySearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
|
||||
|
||||
DependencyInjector.register(dependency: presenter)
|
||||
|
||||
window.rootViewController = UINavigationController(rootViewController: TabBarController())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import SwiftUI
|
||||
import LocalAuthentication
|
||||
import BankingUiSwift
|
||||
|
||||
|
||||
class AuthenticationService {
|
||||
|
@ -12,13 +13,22 @@ class AuthenticationService {
|
|||
|
||||
private let biometricAuthenticationService = BiometricAuthenticationService()
|
||||
|
||||
private let persistence: IBankingPersistence
|
||||
|
||||
|
||||
init(_ persistence: IBankingPersistence) {
|
||||
self.persistence = persistence
|
||||
|
||||
init() {
|
||||
if let type = readAuthenticationType() {
|
||||
self.authenticationType = type
|
||||
|
||||
if type == .none {
|
||||
openDatabase(false, nil)
|
||||
}
|
||||
}
|
||||
else { // first app run, no authentication type persisted yet -> set to .unprotected
|
||||
removeAppProtection()
|
||||
openDatabase(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +78,41 @@ class AuthenticationService {
|
|||
}
|
||||
|
||||
|
||||
func authenticateUserWithBiometric(_ prompt: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
|
||||
biometricAuthenticationService.authenticate(prompt) { successful, error in
|
||||
var decryptDatabaseResult = false
|
||||
if successful {
|
||||
decryptDatabaseResult = self.openDatabase(true, nil)
|
||||
}
|
||||
|
||||
authenticationResult(successful && decryptDatabaseResult, error)
|
||||
}
|
||||
}
|
||||
|
||||
func authenticateUserWithPassword(_ enteredPassword: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
|
||||
if retrieveLoginPassword() == enteredPassword {
|
||||
let decryptDatabaseResult = openDatabase(false, enteredPassword)
|
||||
authenticationResult(decryptDatabaseResult, nil)
|
||||
}
|
||||
else {
|
||||
authenticationResult(false, "Incorrect password entered".localize())
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func openDatabase(_ useBiometricAuthentication: Bool, _ userLoginPassword: String?) -> Bool {
|
||||
if var databasePassword = readDefaultPassword(useBiometricAuthentication) {
|
||||
if let loginPassword = userLoginPassword {
|
||||
databasePassword = concatPasswords(loginPassword, databasePassword)
|
||||
}
|
||||
|
||||
return persistence.decryptData(password: map(databasePassword))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func setAuthenticationMethodToPassword(_ newLoginPassword: String) {
|
||||
setAuthenticationType(.password)
|
||||
|
||||
|
@ -144,10 +189,10 @@ class AuthenticationService {
|
|||
|
||||
if let newLoginPassword = newLoginPassword {
|
||||
setLoginPassword(newLoginPassword)
|
||||
databasePassword = newLoginPassword + "_" + databasePassword
|
||||
databasePassword = concatPasswords(newLoginPassword, databasePassword)
|
||||
}
|
||||
|
||||
return true
|
||||
return persistence.changePassword(newPassword: map(databasePassword))
|
||||
} catch {
|
||||
NSLog("Could not save default password: \(error)")
|
||||
}
|
||||
|
@ -172,6 +217,18 @@ class AuthenticationService {
|
|||
return nil
|
||||
}
|
||||
|
||||
private func readDefaultPassword(_ useBiometricAuthentication: Bool) -> String? {
|
||||
do {
|
||||
let passwordItem = createDefaultPasswordKeychainItem(useBiometricAuthentication)
|
||||
|
||||
return try passwordItem.readPassword()
|
||||
} catch {
|
||||
NSLog("Could not read default password: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func createDefaultPasswordKeychainItem(_ useBiometricAuthentication: Bool) -> KeychainPasswordItem {
|
||||
var accessControl: SecAccessControl? = nil
|
||||
var context: LAContext? = nil
|
||||
|
@ -237,24 +294,25 @@ class AuthenticationService {
|
|||
}
|
||||
|
||||
|
||||
func authenticateUserWithBiometric(_ prompt: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
|
||||
biometricAuthenticationService.authenticate(prompt, authenticationResult)
|
||||
}
|
||||
|
||||
func authenticateUserWithPassword(_ enteredPassword: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
|
||||
if retrieveLoginPassword() == enteredPassword {
|
||||
authenticationResult(true, nil)
|
||||
}
|
||||
else {
|
||||
authenticationResult(false, "Incorrect password entered".localize())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func generateRandomPassword(_ passwordLength: Int) -> String {
|
||||
let dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789§±!@#$%^&*-_=+;:|/?.>,<"
|
||||
|
||||
return String((0 ..< passwordLength).map{ _ in dictionary.randomElement()! })
|
||||
}
|
||||
|
||||
|
||||
private func concatPasswords(_ loginPassword: String, _ defaultPassword: String) -> String {
|
||||
return loginPassword + "_" + defaultPassword
|
||||
}
|
||||
|
||||
private func map(_ string: String) -> KotlinCharArray {
|
||||
let array = KotlinCharArray(size: Int32(string.count))
|
||||
|
||||
for i in 0 ..< string.count {
|
||||
array.set(index: Int32(i), value: (string as NSString).character(at: i))
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,53 +10,13 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher
|
|||
private let mapper = Mapper()
|
||||
|
||||
|
||||
lazy var persistentContainer: NSPersistentContainer = {
|
||||
/*
|
||||
The persistent container for the application. This implementation
|
||||
creates and returns a container, having loaded the store for the
|
||||
application to it. This property is optional since there are legitimate
|
||||
error conditions that could cause the creation of the store to fail.
|
||||
*/
|
||||
let container = NSPersistentContainer(name: "BankingiOSApp")
|
||||
|
||||
do {
|
||||
let options = [
|
||||
EncryptedStorePassphraseKey : "someKey"
|
||||
]
|
||||
|
||||
let description = try EncryptedStore.makeDescription(options: options, configuration: nil)
|
||||
container.persistentStoreDescriptions = [ description ]
|
||||
}
|
||||
catch {
|
||||
NSLog("Could not initialize encrypted database storage: " + error.localizedDescription)
|
||||
}
|
||||
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
if let error = error as NSError? {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
|
||||
* The device is out of space.
|
||||
* The store could not be migrated to the current model version.
|
||||
Check the error message to determine what the actual problem was.
|
||||
*/
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
return container
|
||||
}()
|
||||
private var persistentContainer: NSPersistentContainer?
|
||||
|
||||
lazy var context: NSManagedObjectContext = {
|
||||
return persistentContainer.viewContext
|
||||
return persistentContainer!.viewContext
|
||||
}()
|
||||
|
||||
func saveContext () {
|
||||
let context = persistentContainer.viewContext
|
||||
if context.hasChanges {
|
||||
do {
|
||||
try context.save()
|
||||
|
@ -71,11 +31,46 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher
|
|||
|
||||
|
||||
func decryptData(password: KotlinCharArray) -> Bool {
|
||||
return true
|
||||
do {
|
||||
let container = NSPersistentContainer(name: "BankingiOSApp")
|
||||
|
||||
let options = [
|
||||
EncryptedStorePassphraseKey : map(password)
|
||||
]
|
||||
|
||||
let description = try EncryptedStore.makeDescription(options: options, configuration: nil)
|
||||
container.persistentStoreDescriptions = [ description ]
|
||||
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
if let error = error as NSError? {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
|
||||
* The device is out of space.
|
||||
* The store could not be migrated to the current model version.
|
||||
Check the error message to determine what the actual problem was.
|
||||
*/
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
self.persistentContainer = container
|
||||
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
NSLog("Could not initialize encrypted database storage: " + error.localizedDescription)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func changePassword(newPassword: KotlinCharArray) -> Bool {
|
||||
if let encryptedStore = persistentContainer.persistentStoreCoordinator.persistentStores.first { $0 is EncryptedStore } as? EncryptedStore {
|
||||
if let encryptedStore = persistentContainer?.persistentStoreCoordinator.persistentStores.first { $0 is EncryptedStore } as? EncryptedStore {
|
||||
do {
|
||||
let result = try encryptedStore.changeDatabasePassphrase(map(newPassword))
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ struct LoginDialog: View {
|
|||
struct LoginDialog_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
LoginDialog(AuthenticationService()) { _ in }
|
||||
LoginDialog(AuthenticationService(CoreDataBankingPersistence())) { _ in }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,10 +43,11 @@ struct ProtectAppSettingsDialog: View {
|
|||
|
||||
init() {
|
||||
let currentAuthenticationType = authenticationService.authenticationType
|
||||
let isBiometricAuthenticationSupported = authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID
|
||||
|
||||
var authenticationTypes = [AuthenticationType]()
|
||||
|
||||
if authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID {
|
||||
if isBiometricAuthenticationSupported {
|
||||
authenticationTypes.append(.biometric)
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,7 @@ struct ProtectAppSettingsDialog: View {
|
|||
self.supportedAuthenticationTypes = authenticationTypes
|
||||
|
||||
|
||||
if currentAuthenticationType == .biometric || currentAuthenticationType != .password {
|
||||
if currentAuthenticationType == .biometric && isBiometricAuthenticationSupported {
|
||||
if authenticationService.deviceSupportsFaceID {
|
||||
_isFaceIDSelected = State(initialValue: true)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue