mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Logout automatically when pin/biometrics provided wrong too many times
This commit is contained in:
parent
23c9fbf0d5
commit
cf0f3ba454
7 changed files with 77 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -176,7 +176,7 @@ final class PinCodePreferences: NSObject {
|
|||
}
|
||||
|
||||
var isBiometricsSet: Bool {
|
||||
return biometricsEnabled == true
|
||||
return biometricsEnabled == true && canUseBiometricsToUnlock == true
|
||||
}
|
||||
|
||||
func localizedBiometricsName() -> String? {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue