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 {
|
private func setupBankingUi() -> AuthenticationService {
|
||||||
let appDataFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
|
|
||||||
?? Bundle.main.resourceURL?.absoluteString ?? ""
|
|
||||||
|
|
||||||
let persistence = CoreDataBankingPersistence()
|
let persistence = CoreDataBankingPersistence()
|
||||||
let authenticationService = AuthenticationService()
|
let authenticationService = AuthenticationService(persistence)
|
||||||
self.persistence = 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)
|
DependencyInjector.register(dependency: authenticationService)
|
||||||
|
|
||||||
return authenticationService
|
return authenticationService
|
||||||
|
@ -60,7 +52,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showApplicationMainView(window: UIWindow) {
|
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 SwiftUI
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
import BankingUiSwift
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationService {
|
class AuthenticationService {
|
||||||
|
@ -12,13 +13,22 @@ class AuthenticationService {
|
||||||
|
|
||||||
private let biometricAuthenticationService = BiometricAuthenticationService()
|
private let biometricAuthenticationService = BiometricAuthenticationService()
|
||||||
|
|
||||||
|
private let persistence: IBankingPersistence
|
||||||
|
|
||||||
init() {
|
|
||||||
|
init(_ persistence: IBankingPersistence) {
|
||||||
|
self.persistence = persistence
|
||||||
|
|
||||||
if let type = readAuthenticationType() {
|
if let type = readAuthenticationType() {
|
||||||
self.authenticationType = type
|
self.authenticationType = type
|
||||||
|
|
||||||
|
if type == .none {
|
||||||
|
openDatabase(false, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else { // first app run, no authentication type persisted yet -> set to .unprotected
|
else { // first app run, no authentication type persisted yet -> set to .unprotected
|
||||||
removeAppProtection()
|
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) {
|
func setAuthenticationMethodToPassword(_ newLoginPassword: String) {
|
||||||
setAuthenticationType(.password)
|
setAuthenticationType(.password)
|
||||||
|
|
||||||
|
@ -144,10 +189,10 @@ class AuthenticationService {
|
||||||
|
|
||||||
if let newLoginPassword = newLoginPassword {
|
if let newLoginPassword = newLoginPassword {
|
||||||
setLoginPassword(newLoginPassword)
|
setLoginPassword(newLoginPassword)
|
||||||
databasePassword = newLoginPassword + "_" + databasePassword
|
databasePassword = concatPasswords(newLoginPassword, databasePassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return persistence.changePassword(newPassword: map(databasePassword))
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("Could not save default password: \(error)")
|
NSLog("Could not save default password: \(error)")
|
||||||
}
|
}
|
||||||
|
@ -172,6 +217,18 @@ class AuthenticationService {
|
||||||
return nil
|
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 {
|
private func createDefaultPasswordKeychainItem(_ useBiometricAuthentication: Bool) -> KeychainPasswordItem {
|
||||||
var accessControl: SecAccessControl? = nil
|
var accessControl: SecAccessControl? = nil
|
||||||
var context: LAContext? = 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 {
|
private func generateRandomPassword(_ passwordLength: Int) -> String {
|
||||||
let dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789§±!@#$%^&*-_=+;:|/?.>,<"
|
let dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789§±!@#$%^&*-_=+;:|/?.>,<"
|
||||||
|
|
||||||
return String((0 ..< passwordLength).map{ _ in dictionary.randomElement()! })
|
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()
|
private let mapper = Mapper()
|
||||||
|
|
||||||
|
|
||||||
lazy var persistentContainer: NSPersistentContainer = {
|
private 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
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var context: NSManagedObjectContext = {
|
lazy var context: NSManagedObjectContext = {
|
||||||
return persistentContainer.viewContext
|
return persistentContainer!.viewContext
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func saveContext () {
|
func saveContext () {
|
||||||
let context = persistentContainer.viewContext
|
|
||||||
if context.hasChanges {
|
if context.hasChanges {
|
||||||
do {
|
do {
|
||||||
try context.save()
|
try context.save()
|
||||||
|
@ -71,11 +31,46 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher
|
||||||
|
|
||||||
|
|
||||||
func decryptData(password: KotlinCharArray) -> Bool {
|
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 {
|
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 {
|
do {
|
||||||
let result = try encryptedStore.changeDatabasePassphrase(map(newPassword))
|
let result = try encryptedStore.changeDatabasePassphrase(map(newPassword))
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ struct LoginDialog: View {
|
||||||
struct LoginDialog_Previews: PreviewProvider {
|
struct LoginDialog_Previews: PreviewProvider {
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
LoginDialog(AuthenticationService()) { _ in }
|
LoginDialog(AuthenticationService(CoreDataBankingPersistence())) { _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,11 @@ struct ProtectAppSettingsDialog: View {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let currentAuthenticationType = authenticationService.authenticationType
|
let currentAuthenticationType = authenticationService.authenticationType
|
||||||
|
let isBiometricAuthenticationSupported = authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID
|
||||||
|
|
||||||
var authenticationTypes = [AuthenticationType]()
|
var authenticationTypes = [AuthenticationType]()
|
||||||
|
|
||||||
if authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID {
|
if isBiometricAuthenticationSupported {
|
||||||
authenticationTypes.append(.biometric)
|
authenticationTypes.append(.biometric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ struct ProtectAppSettingsDialog: View {
|
||||||
self.supportedAuthenticationTypes = authenticationTypes
|
self.supportedAuthenticationTypes = authenticationTypes
|
||||||
|
|
||||||
|
|
||||||
if currentAuthenticationType == .biometric || currentAuthenticationType != .password {
|
if currentAuthenticationType == .biometric && isBiometricAuthenticationSupported {
|
||||||
if authenticationService.deviceSupportsFaceID {
|
if authenticationService.deviceSupportsFaceID {
|
||||||
_isFaceIDSelected = State(initialValue: true)
|
_isFaceIDSelected = State(initialValue: true)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue