Logout automatically when pin/biometrics provided wrong too many times

This commit is contained in:
ismailgulek 2020-09-29 16:57:46 +03:00
parent 23c9fbf0d5
commit cf0f3ba454
7 changed files with 77 additions and 13 deletions

View file

@ -176,9 +176,15 @@ final class BuildSettings: NSObject {
/// Maximum number of allowed pin failures when unlocking, before force logging out the user. Defaults to `3`
static let maxAllowedNumberOfPinFailures: Int = 3
/// Maximum number of allowed biometrics failures when unlocking, before fallbacking the user to the pin. Defaults to `5`
/// Maximum number of allowed biometrics failures when unlocking, before fallbacking the user to the pin if set or logging out the user. Defaults to `5`
static let maxAllowedNumberOfBiometricsFailures: Int = 5
/// Indicates should the app log out the user when number of PIN failures reaches `maxAllowedNumberOfPinFailures`. Defaults to `false`
static let logOutUserWhenPINFailuresExceeded: Bool = false
/// Indicates should the app log out the user when number of biometrics failures reaches `maxAllowedNumberOfBiometricsFailures`. Defaults to `false`
static let logOutUserWhenBiometricsFailuresExceeded: Bool = false
// MARK: - General Settings Screen
static let settingsScreenShowUserFirstName: Bool = false

View file

@ -43,6 +43,11 @@ class LocalAuthenticationService: NSObject {
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
}
/// Whether the application currently showing the biometrics setup or unlock dialog.
/// Showing biometrics dialog will cause the app to resign active.
/// This property can be used in order to distinguish real resignations and biometrics case.
static var isShowingBiometrics: Bool = false
var shouldShowPinCode: Bool {
if !pinCodePreferences.isPinSet && !pinCodePreferences.isBiometricsSet {
return false
@ -56,6 +61,13 @@ class LocalAuthenticationService: NSObject {
return (systemUptime - appLastActiveTime) > pinCodePreferences.graceTimeInSeconds
}
var shouldShowInactiveScreen: Bool {
if !isProtectionSet {
return false
}
return !LocalAuthenticationService.isShowingBiometrics
}
var isProtectionSet: Bool {
return pinCodePreferences.isPinSet || pinCodePreferences.isBiometricsSet
}
@ -64,4 +76,14 @@ class LocalAuthenticationService: NSObject {
appLastActiveTime = systemUptime
}
func shouldLogOutUser() -> Bool {
if BuildSettings.logOutUserWhenPINFailuresExceeded && pinCodePreferences.numberOfPinFailures >= pinCodePreferences.maxAllowedNumberOfPinFailures {
return true
}
if BuildSettings.logOutUserWhenBiometricsFailuresExceeded && pinCodePreferences.numberOfBiometricsFailures >= pinCodePreferences.maxAllowedNumberOfBiometricsFailures {
return true
}
return false
}
}

View file

@ -528,7 +528,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
wrongBackupVersionAlert = nil;
}
if ([self.localAuthenticationService isProtectionSet])
if ([self.localAuthenticationService shouldShowInactiveScreen])
{
if (self.setPinCoordinatorBridgePresenter)
{
@ -540,11 +540,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
self.setPinCoordinatorBridgePresenter.delegate = self;
[self.setPinCoordinatorBridgePresenter presentIn:self.window];
}
else
{
[self.setPinCoordinatorBridgePresenter dismiss];
self.setPinCoordinatorBridgePresenter = nil;
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application

View file

@ -41,6 +41,7 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
weak var viewDelegate: EnterPinCodeViewModelViewDelegate?
weak var coordinatorDelegate: EnterPinCodeViewModelCoordinatorDelegate?
private let pinCodePreferences: PinCodePreferences
private let localAuthenticationService: LocalAuthenticationService
// MARK: - Setup
@ -49,6 +50,7 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
self.originalViewMode = viewMode
self.viewMode = viewMode
self.pinCodePreferences = pinCodePreferences
self.localAuthenticationService = LocalAuthenticationService(pinCodePreferences: pinCodePreferences)
}
// MARK: - Public
@ -141,6 +143,12 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
if currentPin != pinCodePreferences.pin {
// no match
numberOfFailuresDuringEnterPIN += 1
pinCodePreferences.numberOfPinFailures += 1
if viewMode == .unlock && localAuthenticationService.shouldLogOutUser() {
// log out user
self.coordinatorDelegate?.enterPinCodeViewModelDidCompleteWithReset(self)
return
}
if numberOfFailuresDuringEnterPIN < pinCodePreferences.allowedNumberOfTrialsBeforeAlert {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPin)
@ -153,6 +161,9 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
}
} else {
// match
// we can use biometrics anymore, if set
pinCodePreferences.canUseBiometricsToUnlock = nil
pinCodePreferences.resetCounters()
// complete with a little delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.coordinatorDelegate?.enterPinCodeViewModelDidComplete(self)

View file

@ -176,7 +176,7 @@ final class PinCodePreferences: NSObject {
}
var isBiometricsSet: Bool {
return biometricsEnabled == true
return biometricsEnabled == true && canUseBiometricsToUnlock == true
}
func localizedBiometricsName() -> String? {

View file

@ -53,8 +53,7 @@ final class SetPinCoordinator: SetPinCoordinatorType {
private func getRootCoordinator() -> Coordinator & Presentable {
switch viewMode {
case .unlock:
let canUseBiometricsToUnlock = pinCodePreferences.canUseBiometricsToUnlock ?? true
if pinCodePreferences.isBiometricsSet && canUseBiometricsToUnlock {
if pinCodePreferences.isBiometricsSet {
return createSetupBiometricsCoordinator()
} else {
return createEnterPinCodeCoordinator()
@ -139,6 +138,7 @@ extension SetPinCoordinator: EnterPinCodeCoordinatorDelegate {
func enterPinCodeCoordinatorDidCompleteWithReset(_ coordinator: EnterPinCodeCoordinatorType) {
self.delegate?.setPinCoordinatorDidCompleteWithReset(self)
pinCodePreferences.reset()
}
func enterPinCodeCoordinator(_ coordinator: EnterPinCodeCoordinatorType, didCompleteWithPin pin: String) {
@ -171,7 +171,13 @@ extension SetPinCoordinator: SetupBiometricsCoordinatorDelegate {
}
func setupBiometricsCoordinatorDidCompleteWithReset(_ coordinator: SetupBiometricsCoordinatorType) {
self.delegate?.setPinCoordinatorDidCompleteWithReset(self)
if viewMode == .unlock && pinCodePreferences.isPinSet {
// and user also has set a pin, so fallback to it
setRootCoordinator(createEnterPinCodeCoordinator())
} else {
// cascade rest
self.delegate?.setPinCoordinatorDidCompleteWithReset(self)
}
}
func setupBiometricsCoordinatorDidCancel(_ coordinator: SetupBiometricsCoordinatorType) {

View file

@ -28,6 +28,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType {
private let session: MXSession?
private let viewMode: SetPinCoordinatorViewMode
private let pinCodePreferences: PinCodePreferences
private let localAuthenticationService: LocalAuthenticationService
// MARK: Public
@ -40,6 +41,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType {
self.session = session
self.viewMode = viewMode
self.pinCodePreferences = pinCodePreferences
self.localAuthenticationService = LocalAuthenticationService(pinCodePreferences: pinCodePreferences)
}
deinit {
@ -74,26 +76,47 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType {
// MARK: - Private
private func enableDisableBiometrics() {
LocalAuthenticationService.isShowingBiometrics = true
LAContext().evaluatePolicy(.deviceOwnerAuthentication, localizedReason: VectorL10n.biometricsUsageReason) { (success, error) in
if success {
// complete after a little delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.pinCodePreferences.canUseBiometricsToUnlock = nil
self.pinCodePreferences.resetCounters()
self.coordinatorDelegate?.setupBiometricsViewModelDidComplete(self)
LocalAuthenticationService.isShowingBiometrics = false
}
} else {
LocalAuthenticationService.isShowingBiometrics = false
}
}
}
private func unlockWithBiometrics() {
LocalAuthenticationService.isShowingBiometrics = true
LAContext().evaluatePolicy(.deviceOwnerAuthentication, localizedReason: VectorL10n.biometricsUsageReason) { (success, error) in
if success {
// complete after a little delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.pinCodePreferences.canUseBiometricsToUnlock = nil
self.pinCodePreferences.resetCounters()
self.coordinatorDelegate?.setupBiometricsViewModelDidComplete(self)
LocalAuthenticationService.isShowingBiometrics = false
}
} else {
if let error = error as NSError?, error.code == LAError.Code.userCancel.rawValue || error.code == LAError.Code.userFallback.rawValue {
self.userCancelledUnlockWithBiometrics()
if let error = error as NSError? {
self.pinCodePreferences.numberOfBiometricsFailures += 1
if self.localAuthenticationService.shouldLogOutUser() {
// biometrics can't be used until further unlock with pin or a new log in
self.pinCodePreferences.canUseBiometricsToUnlock = false
DispatchQueue.main.async {
self.coordinatorDelegate?.setupBiometricsViewModelDidCompleteWithReset(self)
LocalAuthenticationService.isShowingBiometrics = false
}
} else if error.code == LAError.Code.userCancel.rawValue || error.code == LAError.Code.userFallback.rawValue {
self.userCancelledUnlockWithBiometrics()
LocalAuthenticationService.isShowingBiometrics = false
}
}
}
}
@ -101,6 +124,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType {
private func userCancelledUnlockWithBiometrics() {
if pinCodePreferences.isPinSet {
self.pinCodePreferences.canUseBiometricsToUnlock = false
// cascade this cancellation, coordinator should take care of it
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.coordinatorDelegate?.setupBiometricsViewModelDidCancel(self)