mirror of
https://github.com/twofas/2fas-ios.git
synced 2024-11-25 03:40:26 +01:00
TF-1453 Working MDM functionality
This commit is contained in:
parent
c7780859a8
commit
3642d80870
@ -248,6 +248,9 @@ public final class InteractorFactory {
|
||||
}
|
||||
|
||||
public func mdmInteractor() -> MDMInteracting {
|
||||
MDMInteractor(mainRepository: MainRepositoryImpl.shared)
|
||||
MDMInteractor(
|
||||
mainRepository: MainRepositoryImpl.shared,
|
||||
pairingInteractor: pairingWebExtensionInteractor()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ public protocol MDMInteracting: AnyObject {
|
||||
var isBackupBlocked: Bool { get }
|
||||
var isBiometryBlocked: Bool { get }
|
||||
var isBrowserExtensionBlocked: Bool { get }
|
||||
var isLockoutAttemptsChangeBlocked: Bool { get }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { get }
|
||||
var isPasscodeRequried: Bool { get }
|
||||
var shouldSetPasscode: Bool { get }
|
||||
|
||||
func apply()
|
||||
}
|
||||
@ -50,6 +54,22 @@ extension MDMInteractor: MDMInteracting {
|
||||
mainRepository.mdmIsBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var isLockoutAttemptsChangeBlocked: Bool {
|
||||
mainRepository.mdmLockoutAttepts != nil
|
||||
}
|
||||
|
||||
var isLockoutBlockTimeChangeBlocked: Bool {
|
||||
mainRepository.mdmLockoutBlockTime != nil
|
||||
}
|
||||
|
||||
var isPasscodeRequried: Bool {
|
||||
mainRepository.mdmIsPasscodeRequried
|
||||
}
|
||||
|
||||
var shouldSetPasscode: Bool {
|
||||
isPasscodeRequried && !mainRepository.isPINSet
|
||||
}
|
||||
|
||||
func apply() {
|
||||
if isBackupBlocked && mainRepository.isCloudBackupConnected {
|
||||
mainRepository.clearBackup()
|
||||
@ -62,5 +82,13 @@ extension MDMInteractor: MDMInteracting {
|
||||
if isBrowserExtensionBlocked && pairingInteractor.hasActiveBrowserExtension {
|
||||
pairingInteractor.disableExtension(completion: { _ in })
|
||||
}
|
||||
|
||||
if let lockoutAttepts = mainRepository.mdmLockoutAttepts {
|
||||
mainRepository.setAppLockAttempts(lockoutAttepts)
|
||||
}
|
||||
|
||||
if let blockTime = mainRepository.mdmLockoutBlockTime {
|
||||
mainRepository.setAppLockBlockTime(blockTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ public enum PairingWebExtensionError: Error {
|
||||
case noPublicKey
|
||||
case serverError
|
||||
case noInternet
|
||||
case blocked
|
||||
}
|
||||
|
||||
public enum UnparingWebExtensionError: Error {
|
||||
@ -82,6 +83,11 @@ extension PairingWebExtensionInteractor: PairingWebExtensionInteracting {
|
||||
|
||||
func pair(with extensionID: ExtensionID, completion: @escaping (Result<Void, PairingWebExtensionError>) -> Void) {
|
||||
Log("PairingWebExtensionInteractor - pair. extensionID: \(extensionID)", module: .interactor)
|
||||
guard !mainRepository.mdmIsBrowserExtensionBlocked else {
|
||||
Log("PairingWebExtensionInteractor - pair. Error: blocked!", module: .interactor)
|
||||
completion(.failure(.blocked))
|
||||
return
|
||||
}
|
||||
guard !mainRepository.listAllPairedExtensions().map({ $0.extensionID }).contains(extensionID) else {
|
||||
Log("PairingWebExtensionInteractor - failure. Already paired", module: .interactor)
|
||||
completion(.failure(.alreadyPaired))
|
||||
|
@ -23,4 +23,7 @@ protocol MDMRepository: AnyObject {
|
||||
var isBackupBlocked: Bool { get }
|
||||
var isBiometryBlocked: Bool { get }
|
||||
var isBrowserExtensionBlocked: Bool { get }
|
||||
var lockoutAttepts: AppLockAttempts? { get }
|
||||
var lockoutBlockTime: AppLockBlockTime? { get }
|
||||
var isPasscodeRequried: Bool { get }
|
||||
}
|
||||
|
@ -19,10 +19,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
//option that passcode is required
|
||||
//preset lockout settings
|
||||
//toggle/hide browser extension
|
||||
|
||||
final class MDMRepositoryImpl {
|
||||
private let mdmKey = "com.apple.configuration.managed"
|
||||
private let userDefaults = UserDefaults.standard
|
||||
@ -33,11 +29,15 @@ final class MDMRepositoryImpl {
|
||||
case isBackupBlocked = "blockBackup"
|
||||
case isBiometryBlocked = "blockBiometry"
|
||||
case isBrowserExtensionBlocked = "blockBrowserExtension"
|
||||
case lockoutAttempts = "lockoutAttempts"
|
||||
case lockoutBlockTime = "lockoutBlockTime"
|
||||
case isPasscodeRequried = "requirePasscode"
|
||||
}
|
||||
|
||||
private let isBackupBlockedDefaultValue = false
|
||||
private let isBiometryBlockedDefaultValue = false
|
||||
private let isBrowserExtensionBlockedDefaultValue = false
|
||||
private let isPasscodeRequriedDefaultValue = false
|
||||
|
||||
init() {
|
||||
reload()
|
||||
@ -69,4 +69,36 @@ extension MDMRepositoryImpl: MDMRepository {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var lockoutAttepts: AppLockAttempts? {
|
||||
guard let value = settings[SettingsKeys.lockoutAttempts.rawValue] as? Int else {
|
||||
return nil
|
||||
}
|
||||
switch value {
|
||||
case 0: return .noLimit
|
||||
case 3: return .try3
|
||||
case 5: return .try5
|
||||
case 10: return .try10
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var lockoutBlockTime: AppLockBlockTime? {
|
||||
guard let value = settings[SettingsKeys.lockoutBlockTime.rawValue] as? Int else {
|
||||
return nil
|
||||
}
|
||||
switch value {
|
||||
case 3: return .min3
|
||||
case 5: return .min5
|
||||
case 10: return .min10
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var isPasscodeRequried: Bool {
|
||||
guard let value = settings[SettingsKeys.isPasscodeRequried.rawValue] as? Bool else {
|
||||
return isPasscodeRequriedDefaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
@ -476,4 +476,7 @@ protocol MainRepository: AnyObject {
|
||||
var mdmIsBackupBlocked: Bool { get }
|
||||
var mdmIsBiometryBlocked: Bool { get }
|
||||
var mdmIsBrowserExtensionBlocked: Bool { get }
|
||||
var mdmLockoutAttepts: AppLockAttempts? { get }
|
||||
var mdmLockoutBlockTime: AppLockBlockTime? { get }
|
||||
var mdmIsPasscodeRequried: Bool { get }
|
||||
}
|
||||
|
@ -31,4 +31,16 @@ extension MainRepositoryImpl {
|
||||
var mdmIsBrowserExtensionBlocked: Bool {
|
||||
mdmRepository.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var mdmLockoutAttepts: AppLockAttempts? {
|
||||
mdmRepository.lockoutAttepts
|
||||
}
|
||||
|
||||
var mdmLockoutBlockTime: AppLockBlockTime? {
|
||||
mdmRepository.lockoutBlockTime
|
||||
}
|
||||
|
||||
var mdmIsPasscodeRequried: Bool {
|
||||
mdmRepository.isPasscodeRequried
|
||||
}
|
||||
}
|
||||
|
@ -671,6 +671,7 @@
|
||||
C2BBD1D42B8113A9009A91FB /* TwoFASWidgetInline.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BBD1D22B81127C009A91FB /* TwoFASWidgetInline.swift */; };
|
||||
C2BBD1D72B812117009A91FB /* TwoFASWidgetCircular.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BBD1D52B8120F4009A91FB /* TwoFASWidgetCircular.swift */; };
|
||||
C2BBD2382B8130B0009A91FB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C2BBD2332B81308C009A91FB /* Localizable.strings */; };
|
||||
C2BC3FC92B94E70F004C4BA0 /* NewPINNavigationFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */; };
|
||||
C2BD82B2236619E800FBD69A /* BackupAreaWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD82B1236619E800FBD69A /* BackupAreaWarningView.swift */; };
|
||||
C2BD85DA2640290E0087D087 /* IntroductionFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD85D92640290E0087D087 /* IntroductionFlowController.swift */; };
|
||||
C2BD85DC26402A910087D087 /* IntroductionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD85DB26402A910087D087 /* IntroductionContainerView.swift */; };
|
||||
@ -2291,6 +2292,7 @@
|
||||
C2BBD24C2B813162009A91FB /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = TwoFAS/Other/uk.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BBD24D2B813162009A91FB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = TwoFAS/Other/de.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BBD24E2B813162009A91FB /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = TwoFAS/Other/pl.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPINNavigationFlowController.swift; sourceTree = "<group>"; };
|
||||
C2BD82B1236619E800FBD69A /* BackupAreaWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAreaWarningView.swift; sourceTree = "<group>"; };
|
||||
C2BD85D92640290E0087D087 /* IntroductionFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionFlowController.swift; sourceTree = "<group>"; };
|
||||
C2BD85DB26402A910087D087 /* IntroductionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionContainerView.swift; sourceTree = "<group>"; };
|
||||
@ -5647,6 +5649,7 @@
|
||||
C2A7067C276D1A5F00885D79 /* Flow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */,
|
||||
C2A7067D276D1A7500885D79 /* NewPINFlowController.swift */,
|
||||
C2997AA82454D767006D5943 /* SelectPINLengthController.swift */,
|
||||
);
|
||||
@ -9024,6 +9027,7 @@
|
||||
C2B1208F29D76BD10020281E /* AppearancePresenter+Menu.swift in Sources */,
|
||||
C2A7945127F9016D00E5C641 /* BrowserExtensionEditNameViewController.swift in Sources */,
|
||||
C242B4D12A9BD950005CC1BC /* AddingServiceAdvancedSectionDividerView.swift in Sources */,
|
||||
C2BC3FC92B94E70F004C4BA0 /* NewPINNavigationFlowController.swift in Sources */,
|
||||
C21B3A1427AF0BC9005C603B /* ColorPickerFlowController.swift in Sources */,
|
||||
C2B39E5929F5BD7800EC31F6 /* TokensCategory.swift in Sources */,
|
||||
C26BCE01281010A000CA6A9A /* SelectServiceModuleInteractor.swift in Sources */,
|
||||
|
@ -50,6 +50,12 @@ extension UIViewController {
|
||||
definesPresentationContext = true
|
||||
}
|
||||
|
||||
func configureAsFullscreenModal() {
|
||||
modalPresentationStyle = .fullScreen
|
||||
isModalInPresentation = true
|
||||
definesPresentationContext = true
|
||||
}
|
||||
|
||||
func setCustomLeftBackButton() {
|
||||
navigationItem.leftBarButtonItem = createCustomLeftBackButton()
|
||||
}
|
||||
|
@ -131,7 +131,8 @@ final class ModuleInteractorFactory {
|
||||
|
||||
func appLockModuleInteractor() -> AppLockModuleInteracting {
|
||||
AppLockModuleInteractor(
|
||||
appLockInteractor: InteractorFactory.shared.appLockStateInteractor()
|
||||
appLockInteractor: InteractorFactory.shared.appLockStateInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -143,8 +144,8 @@ final class ModuleInteractorFactory {
|
||||
)
|
||||
}
|
||||
|
||||
func newPINModuleInteractor() -> NewPINModuleInteracting {
|
||||
NewPINModuleInteractor()
|
||||
func newPINModuleInteractor(lockNavigation: Bool) -> NewPINModuleInteracting {
|
||||
NewPINModuleInteractor(lockNavigation: lockNavigation)
|
||||
}
|
||||
|
||||
func trashModuleInteractor() -> TrashModuleInteracting {
|
||||
@ -334,7 +335,9 @@ final class ModuleInteractorFactory {
|
||||
newVersionInteractor: InteractorFactory.shared.newVersionInteractor(),
|
||||
networkStatusInteractor: InteractorFactory.shared.networkStatusInteractor(),
|
||||
appInfoInteractor: InteractorFactory.shared.appInfoInteractor(),
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor()
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor(),
|
||||
protectionInteractor: InteractorFactory.shared.protectionInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ protocol MainFlowControlling: AnyObject {
|
||||
func toSecretSyncError(_ serviceName: String)
|
||||
func toOpenFileImport(url: URL)
|
||||
func toSetupSplit()
|
||||
func toSetPIN()
|
||||
|
||||
// MARK: - App update
|
||||
func toShowNewVersionAlert(for appStoreURL: URL, skip: @escaping Callback)
|
||||
@ -137,6 +138,12 @@ extension MainFlowController: MainFlowControlling {
|
||||
|
||||
viewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - MDM requriments
|
||||
|
||||
func toSetPIN() {
|
||||
NewPINNavigationFlowController.present(on: viewController, parent: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension MainFlowController: ImporterOpenFileHeadlessFlowControllerParent {
|
||||
@ -201,3 +208,12 @@ extension MainFlowController: MainSplitFlowControllerParent {
|
||||
viewController.presenter.handleSwitchToExternalImport()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainFlowController: NewPINNavigationFlowControllerParent {
|
||||
func pinGathered(with PIN: String, pinType: PINType) {
|
||||
viewController.presenter.handleSavePIN(PIN, pinType: pinType)
|
||||
viewController.dismiss(animated: true) { [weak viewController] in
|
||||
viewController?.presenter.handleViewIsVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,15 @@ protocol MainModuleInteracting: AnyObject {
|
||||
var secretSyncError: ((String) -> Void)? { get set }
|
||||
var isAppLocked: Bool { get }
|
||||
var isBrowserExtensionAllowed: Bool { get }
|
||||
var shouldSetPasscode: Bool { get }
|
||||
|
||||
func applyMDMRules()
|
||||
func initialize()
|
||||
func checkForImport() -> URL?
|
||||
func clearImportedFileURL()
|
||||
|
||||
func savePIN(_ PIN: String, ofType pinType: PINType)
|
||||
|
||||
// MARK: - New app version
|
||||
func checkForNewAppVersion(completion: @escaping (URL?) -> Void)
|
||||
func skipAppVersion()
|
||||
@ -45,6 +49,10 @@ final class MainModuleInteractor {
|
||||
!mdmInteractor.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var shouldSetPasscode: Bool {
|
||||
mdmInteractor.shouldSetPasscode
|
||||
}
|
||||
|
||||
private let logUploadingInteractor: LogUploadingInteracting
|
||||
private let cloudBackupStateInteractor: CloudBackupStateInteracting
|
||||
private let fileInteractor: FileInteracting
|
||||
@ -53,6 +61,7 @@ final class MainModuleInteractor {
|
||||
private let appInfoInteractor: AppInfoInteracting
|
||||
private let rootInteractor: RootInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
private let protectionInteractor: ProtectionInteracting
|
||||
|
||||
init(
|
||||
logUploadingInteractor: LogUploadingInteracting,
|
||||
@ -63,7 +72,8 @@ final class MainModuleInteractor {
|
||||
networkStatusInteractor: NetworkStatusInteracting,
|
||||
appInfoInteractor: AppInfoInteracting,
|
||||
rootInteractor: RootInteracting,
|
||||
mdmInteractor: MDMInteracting
|
||||
mdmInteractor: MDMInteracting,
|
||||
protectionInteractor: ProtectionInteracting
|
||||
) {
|
||||
self.logUploadingInteractor = logUploadingInteractor
|
||||
self.cloudBackupStateInteractor = cloudBackupStateInteractor
|
||||
@ -73,6 +83,7 @@ final class MainModuleInteractor {
|
||||
self.appInfoInteractor = appInfoInteractor
|
||||
self.rootInteractor = rootInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
self.protectionInteractor = protectionInteractor
|
||||
|
||||
cloudBackupStateInteractor.secretSyncError = { [weak self] in self?.secretSyncError?($0) }
|
||||
}
|
||||
@ -93,6 +104,14 @@ extension MainModuleInteractor: MainModuleInteracting {
|
||||
fileInteractor.markAsHandled()
|
||||
}
|
||||
|
||||
func applyMDMRules() {
|
||||
mdmInteractor.apply()
|
||||
}
|
||||
|
||||
func savePIN(_ PIN: String, ofType pinType: PINType) {
|
||||
protectionInteractor.savePIN(PIN, typeOfPIN: pinType)
|
||||
}
|
||||
|
||||
// MARK: - New app version
|
||||
|
||||
func checkForNewAppVersion(completion: @escaping (URL?) -> Void) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Data
|
||||
|
||||
final class MainPresenter {
|
||||
weak var view: MainViewControlling?
|
||||
@ -82,13 +83,20 @@ final class MainPresenter {
|
||||
func handleViewIsVisible() {
|
||||
viewIsVisible()
|
||||
}
|
||||
|
||||
func handleSavePIN(_ PIN: String, pinType: PINType) {
|
||||
interactor.savePIN(PIN, ofType: pinType)
|
||||
}
|
||||
}
|
||||
|
||||
private extension MainPresenter {
|
||||
func viewIsVisible() {
|
||||
guard !interactor.isAppLocked && !handlingViewIsVisible else { return }
|
||||
handlingViewIsVisible = true
|
||||
if let url = interactor.checkForImport() {
|
||||
interactor.applyMDMRules()
|
||||
if interactor.shouldSetPasscode {
|
||||
flowController.toSetPIN()
|
||||
} else if let url = interactor.checkForImport() {
|
||||
flowController.toOpenFileImport(url: url)
|
||||
interactor.clearImportedFileURL()
|
||||
handlingViewIsVisible = false
|
||||
|
@ -21,6 +21,9 @@ import Foundation
|
||||
import Data
|
||||
|
||||
protocol AppLockModuleInteracting: AnyObject {
|
||||
var isLockoutAttemptsChangeBlocked: Bool { get }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { get }
|
||||
|
||||
var selectedAttempts: AppLockAttempts { get }
|
||||
var selectedBlockTime: AppLockBlockTime { get }
|
||||
|
||||
@ -30,15 +33,19 @@ protocol AppLockModuleInteracting: AnyObject {
|
||||
|
||||
final class AppLockModuleInteractor {
|
||||
private let appLockInteractor: AppLockStateInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
|
||||
init(appLockInteractor: AppLockStateInteracting) {
|
||||
init(appLockInteractor: AppLockStateInteracting, mdmInteractor: MDMInteracting) {
|
||||
self.appLockInteractor = appLockInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
}
|
||||
}
|
||||
|
||||
extension AppLockModuleInteractor: AppLockModuleInteracting {
|
||||
var selectedAttempts: AppLockAttempts { appLockInteractor.appLockAttempts }
|
||||
var selectedBlockTime: AppLockBlockTime { appLockInteractor.appLockBlockTime }
|
||||
var isLockoutAttemptsChangeBlocked: Bool { mdmInteractor.isLockoutAttemptsChangeBlocked }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { mdmInteractor.isLockoutBlockTimeChangeBlocked }
|
||||
|
||||
func setAttempts(_ value: AppLockAttempts) {
|
||||
appLockInteractor.setAppLockAttempts(value)
|
||||
|
@ -34,4 +34,5 @@ struct AppLockMenuSection: TableViewSection {
|
||||
struct AppLockMenuCell: Hashable {
|
||||
let title: String
|
||||
let checkmark: Bool
|
||||
let disabled: Bool
|
||||
}
|
||||
|
@ -27,7 +27,11 @@ extension AppLockPresenter {
|
||||
title: T.Settings.tooManyAttemptsHeader,
|
||||
cells:
|
||||
AppLockAttempts.allCases.map {
|
||||
AppLockMenuCell(title: $0.localized, checkmark: selectedAttempt == $0)
|
||||
AppLockMenuCell(
|
||||
title: $0.localized,
|
||||
checkmark: selectedAttempt == $0,
|
||||
disabled: interactor.isLockoutAttemptsChangeBlocked
|
||||
)
|
||||
},
|
||||
footer: T.Settings.howManyAttemptsFooter
|
||||
)
|
||||
@ -37,7 +41,11 @@ extension AppLockPresenter {
|
||||
title: T.Settings.blockFor,
|
||||
cells:
|
||||
AppLockBlockTime.allCases.map {
|
||||
AppLockMenuCell(title: $0.localized, checkmark: selectedBlockTime == $0)
|
||||
AppLockMenuCell(
|
||||
title: $0.localized,
|
||||
checkmark: selectedBlockTime == $0,
|
||||
disabled: interactor.isLockoutBlockTimeChangeBlocked
|
||||
)
|
||||
}
|
||||
)
|
||||
var menu: [AppLockMenuSection] = [
|
||||
|
@ -37,10 +37,14 @@ final class AppLockPresenter {
|
||||
|
||||
func handleSelection(at indexPath: IndexPath) {
|
||||
if indexPath.section == 0 {
|
||||
guard let value = AppLockAttempts.allCases[safe: indexPath.row] else { return }
|
||||
guard let value = AppLockAttempts.allCases[
|
||||
safe: indexPath.row
|
||||
], !interactor.isLockoutAttemptsChangeBlocked else { return }
|
||||
interactor.setAttempts(value)
|
||||
} else {
|
||||
guard let value = AppLockBlockTime.allCases[safe: indexPath.row] else { return }
|
||||
guard let value = AppLockBlockTime.allCases[
|
||||
safe: indexPath.row
|
||||
], !interactor.isLockoutBlockTimeChangeBlocked else { return }
|
||||
interactor.setBlockTime(value)
|
||||
}
|
||||
reload()
|
||||
|
@ -132,6 +132,9 @@ extension AppLockViewController {
|
||||
cell.update(icon: nil, title: data.title, kind: accessory)
|
||||
cell.tintColor = Theme.Colors.Fill.theme
|
||||
cell.selectionStyle = .none
|
||||
if data.disabled {
|
||||
cell.disable()
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
@ -103,13 +103,14 @@ extension AppSecurityFlowController: AppSecurityFlowControlling {
|
||||
on: navi,
|
||||
parent: self,
|
||||
action: newAction,
|
||||
step: .second(PIN: PIN, pinType: typeOfPIN)
|
||||
step: .second(PIN: PIN, pinType: typeOfPIN),
|
||||
lockNavigation: false
|
||||
)
|
||||
}
|
||||
|
||||
func toCreatePIN(pinType: PINType) {
|
||||
let navi = navigationControllerForModal()
|
||||
NewPINFlowController.setRoot(in: navi, parent: self, pinType: pinType)
|
||||
NewPINFlowController.setRoot(in: navi, parent: self, pinType: pinType, lockNavigation: false)
|
||||
viewController.present(navi, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@ -164,7 +165,13 @@ extension AppSecurityFlowController: VerifyPINFlowControllerParent {
|
||||
dismiss()
|
||||
case .change(let currentPINType):
|
||||
guard let navi = viewController.presentedViewController as? UINavigationController else { return }
|
||||
NewPINFlowController.push(on: navi, parent: self, action: .change, step: .first(pinType: currentPINType))
|
||||
NewPINFlowController.push(
|
||||
on: navi,
|
||||
parent: self,
|
||||
action: .change,
|
||||
step: .first(pinType: currentPINType),
|
||||
lockNavigation: false
|
||||
)
|
||||
case .authorize:
|
||||
viewController.presenter.handleInitialAutorization()
|
||||
guard let vc = viewController.children.first(where: { $0 is VerifyPINViewController }) else { return }
|
||||
@ -190,7 +197,7 @@ extension AppSecurityFlowController: NewPINFlowControllerParent {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
func pingGathered(
|
||||
func pinGathered(
|
||||
with PIN: String,
|
||||
pinType: PINType,
|
||||
action: NewPINFlowController.Action,
|
||||
|
@ -27,6 +27,7 @@ protocol AppSecurityModuleInteracting: AnyObject {
|
||||
var isBiometryEnabled: Bool { get }
|
||||
var isBiometryAllowed: Bool { get }
|
||||
var currentPINType: PINType { get }
|
||||
var isPasscodeRequried: Bool { get }
|
||||
|
||||
func toggleBiometry()
|
||||
|
||||
@ -62,6 +63,7 @@ extension AppSecurityModuleInteractor: AppSecurityModuleInteracting {
|
||||
var isBiometryEnabled: Bool { protectionInteractor.isBiometryEnabled }
|
||||
var isBiometryAllowed: Bool { !mdmInteractor.isBiometryBlocked }
|
||||
var currentPINType: PINType { protectionInteractor.pinType ?? .digits4 }
|
||||
var isPasscodeRequried: Bool { mdmInteractor.isPasscodeRequried }
|
||||
|
||||
func toggleBiometry() {
|
||||
guard protectionInteractor.isBiometryAvailable else { return }
|
||||
|
@ -35,6 +35,7 @@ struct AppSecurityMenuCell: Hashable {
|
||||
struct Toggle: Hashable {
|
||||
let kind: ToggleKind
|
||||
let isOn: Bool
|
||||
let isBlocked: Bool
|
||||
}
|
||||
|
||||
// swiftlint:disable discouraged_none_name
|
||||
|
@ -23,9 +23,10 @@ import Data
|
||||
extension AppSecurityPresenter {
|
||||
func buildMenu() -> [AppSecurityMenuSection] {
|
||||
let isPINset = interactor.isPINSet
|
||||
|
||||
let PINcell = AppSecurityMenuCell(
|
||||
title: T.Settings.pinCode,
|
||||
accessory: .toggle(toggle: .init(kind: .PIN, isOn: isPINset))
|
||||
accessory: .toggle(toggle: .init(kind: .PIN, isOn: isPINset, isBlocked: interactor.isPasscodeRequried))
|
||||
)
|
||||
|
||||
guard isPINset else {
|
||||
@ -57,7 +58,7 @@ extension AppSecurityPresenter {
|
||||
let section = AppSecurityMenuSection(title: T.Settings.biometricAuthentication, cells: [
|
||||
AppSecurityMenuCell(
|
||||
title: biometryType.localized,
|
||||
accessory: .toggle(toggle: .init(kind: .biometry, isOn: isBiometryEnabled))
|
||||
accessory: .toggle(toggle: .init(kind: .biometry, isOn: isBiometryEnabled, isBlocked: false))
|
||||
)
|
||||
])
|
||||
switch biometryType {
|
||||
|
@ -151,6 +151,12 @@ extension AppSecurityViewController {
|
||||
} else {
|
||||
cell.selectionStyle = .none
|
||||
}
|
||||
|
||||
let accessory = data.accessory
|
||||
if case let .toggle(toggle) = accessory, toggle.isBlocked {
|
||||
cell.disable()
|
||||
cell.disableToggle()
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
@ -194,7 +200,7 @@ extension AppSecurityViewController {
|
||||
case .arrow: return .arrow
|
||||
case .info(let text): return .infoArrow(text: text)
|
||||
case .toggle(let toggle): return .toggle(isEnabled: toggle.isOn) { [weak tableView, weak self] calledCell, _ in
|
||||
guard let indexPath = tableView?.indexPath(for: calledCell) else { return }
|
||||
guard let indexPath = tableView?.indexPath(for: calledCell), !toggle.isBlocked else { return }
|
||||
self?.presenter.handleToggle(for: indexPath)
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ extension SettingsMenuPresenter {
|
||||
|
||||
menu.append(contentsOf: [
|
||||
backup,
|
||||
security,
|
||||
security
|
||||
])
|
||||
|
||||
if interactor.isBrowserExtensionAllowed {
|
||||
|
@ -22,7 +22,7 @@ import Data
|
||||
|
||||
protocol NewPINFlowControllerParent: AnyObject {
|
||||
func hideNewPIN()
|
||||
func pingGathered(
|
||||
func pinGathered(
|
||||
with PIN: String,
|
||||
pinType: PINType,
|
||||
action: NewPINFlowController.Action,
|
||||
@ -52,14 +52,15 @@ final class NewPINFlowController: FlowController {
|
||||
static func setRoot(
|
||||
in navigationController: UINavigationController,
|
||||
parent: NewPINFlowControllerParent,
|
||||
pinType: PINType
|
||||
pinType: PINType,
|
||||
lockNavigation: Bool
|
||||
) {
|
||||
let view = NewPINViewController()
|
||||
let flowController = NewPINFlowController(viewController: view)
|
||||
flowController.parent = parent
|
||||
flowController.action = .create
|
||||
flowController.step = .first(pinType: pinType)
|
||||
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor()
|
||||
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor(lockNavigation: lockNavigation)
|
||||
interactor.selectedPINType = pinType
|
||||
|
||||
let presenter = NewPINPresenter(
|
||||
@ -80,7 +81,8 @@ final class NewPINFlowController: FlowController {
|
||||
on navigationController: UINavigationController,
|
||||
parent: NewPINFlowControllerParent,
|
||||
action: Action,
|
||||
step: Step
|
||||
step: Step,
|
||||
lockNavigation: Bool
|
||||
) {
|
||||
let view = NewPINViewController()
|
||||
let flowController = NewPINFlowController(viewController: view)
|
||||
@ -101,7 +103,7 @@ final class NewPINFlowController: FlowController {
|
||||
selectedPINType = pinType
|
||||
}
|
||||
|
||||
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor()
|
||||
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor(lockNavigation: lockNavigation)
|
||||
interactor.selectedPIN = selectedPIN
|
||||
interactor.selectedPINType = selectedPINType
|
||||
|
||||
@ -130,7 +132,7 @@ extension NewPINFlowController: NewPINFlowControlling {
|
||||
|
||||
func toPINGathered(with PIN: String, pinType: PINType) {
|
||||
guard let action, let step else { return }
|
||||
parent?.pingGathered(with: PIN, pinType: pinType, action: action, step: step)
|
||||
parent?.pinGathered(with: PIN, pinType: pinType, action: action, step: step)
|
||||
}
|
||||
|
||||
func toChangePINType() {
|
||||
|
@ -0,0 +1,76 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Data
|
||||
|
||||
protocol NewPINNavigationFlowControllerParent: AnyObject {
|
||||
func pinGathered(
|
||||
with PIN: String,
|
||||
pinType: PINType
|
||||
)
|
||||
}
|
||||
|
||||
protocol NewPINNavigationFlowControlling: AnyObject {}
|
||||
|
||||
final class NewPINNavigationFlowController: NavigationFlowController {
|
||||
private weak var parent: NewPINNavigationFlowControllerParent?
|
||||
|
||||
static func present(
|
||||
on viewController: UIViewController,
|
||||
parent: NewPINNavigationFlowControllerParent
|
||||
) {
|
||||
let flowController = NewPINNavigationFlowController()
|
||||
flowController.parent = parent
|
||||
|
||||
let navi = CommonNavigationControllerFlow(flowController: flowController)
|
||||
navi.configureAsFullscreenModal()
|
||||
|
||||
flowController.navigationController = navi
|
||||
|
||||
NewPINFlowController.setRoot(in: navi, parent: flowController, pinType: .digits6, lockNavigation: true)
|
||||
|
||||
viewController.present(navi, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension NewPINNavigationFlowController: NewPINNavigationFlowControlling {}
|
||||
|
||||
extension NewPINNavigationFlowController: NewPINFlowControllerParent {
|
||||
func hideNewPIN() {}
|
||||
func pinGathered(
|
||||
with PIN: String,
|
||||
pinType: PINType,
|
||||
action: NewPINFlowController.Action,
|
||||
step: NewPINFlowController.Step
|
||||
) {
|
||||
switch step {
|
||||
case .first:
|
||||
NewPINFlowController.push(
|
||||
on: navigationController,
|
||||
parent: self,
|
||||
action: .create,
|
||||
step: .second(PIN: PIN, pinType: pinType),
|
||||
lockNavigation: true
|
||||
)
|
||||
case .second(let PIN, let pinType):
|
||||
parent?.pinGathered(with: PIN, pinType: pinType)
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ protocol NewPINModuleInteracting: AnyObject {
|
||||
var selectedPIN: String? { get set }
|
||||
var selectedPINType: PINType? { get set }
|
||||
|
||||
var lockNavigation: Bool { get }
|
||||
|
||||
var pinType: PINType { get }
|
||||
|
||||
func validatePIN(_ PIN: String) -> Bool
|
||||
@ -31,8 +33,15 @@ protocol NewPINModuleInteracting: AnyObject {
|
||||
}
|
||||
|
||||
final class NewPINModuleInteractor {
|
||||
let lockNavigation: Bool
|
||||
var selectedPIN: String?
|
||||
var selectedPINType: PINType?
|
||||
|
||||
init(lockNavigation: Bool, selectedPIN: String? = nil, selectedPINType: PINType? = nil) {
|
||||
self.lockNavigation = lockNavigation
|
||||
self.selectedPIN = selectedPIN
|
||||
self.selectedPINType = selectedPINType
|
||||
}
|
||||
}
|
||||
|
||||
extension NewPINModuleInteractor: NewPINModuleInteracting {
|
||||
|
@ -37,6 +37,12 @@ final class NewPINPresenter: PINKeyboardPresenter {
|
||||
codeLength = interactor.pinType.digits
|
||||
}
|
||||
|
||||
func viewDidLoad() {
|
||||
if !interactor.lockNavigation {
|
||||
view?.showCancelButton()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear() {
|
||||
super.viewWillAppear()
|
||||
|
||||
|
@ -21,6 +21,7 @@ import UIKit
|
||||
|
||||
protocol NewPINViewControlling: AnyObject {
|
||||
func setTitle(_ title: String)
|
||||
func showCancelButton()
|
||||
}
|
||||
|
||||
final class NewPINViewController: PINKeyboardViewController {
|
||||
@ -28,13 +29,8 @@ final class NewPINViewController: PINKeyboardViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
title: T.Commons.cancel,
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(cancelAction)
|
||||
)
|
||||
navigationItem.backButtonDisplayMode = .minimal
|
||||
presenter.viewDidLoad()
|
||||
}
|
||||
|
||||
override func deleteButtonTap() {
|
||||
@ -69,4 +65,13 @@ extension NewPINViewController: NewPINViewControlling {
|
||||
func setTitle(_ title: String) {
|
||||
self.title = title
|
||||
}
|
||||
|
||||
func showCancelButton() {
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
title: T.Commons.cancel,
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(cancelAction)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user