Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #7657 - #7656: Local Authentication Changes and Bug Fixes (#7682)
Browse files Browse the repository at this point in the history
  • Loading branch information
soner-yuksel committed Jul 6, 2023
1 parent 29c4fb0 commit 36dbfe1
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 42 deletions.
19 changes: 12 additions & 7 deletions Sources/Brave/Frontend/Login/LoginAuthViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ import Preferences
import BraveUI
import Shared
import UIKit
import LocalAuthentication
import Combine

class LoginAuthViewController: UITableViewController {

private let windowProtection: WindowProtection?
private var localAuthObservers = Set<AnyCancellable>()

// MARK: Lifecycle

init(windowProtection: WindowProtection?, requiresAuthentication: Bool = false) {
self.windowProtection = windowProtection
self.requiresAuthentication = requiresAuthentication
super.init(nibName: nil, bundle: nil)

windowProtection?.isCancellable = true
}

required init?(coder: NSCoder) {
Expand All @@ -29,8 +34,8 @@ class LoginAuthViewController: UITableViewController {
super.viewWillAppear(animated)

if requiresAuthentication, Preferences.Privacy.lockWithPasscode.value {
askForAuthentication() { [weak self] success in
if !success {
askForAuthentication() { [weak self] success, error in
if !success, error != .userCancel {
self?.navigationController?.popViewController(animated: true)
}
}
Expand Down Expand Up @@ -58,19 +63,19 @@ class LoginAuthViewController: UITableViewController {

// MARK: Internal

func askForAuthentication(completion: ((Bool) -> Void)? = nil) {
func askForAuthentication(completion: ((Bool, LAError.Code?) -> Void)? = nil) {
guard let windowProtection = windowProtection else {
completion?(false)
completion?(false, nil)
return
}

if !windowProtection.isPassCodeAvailable {
showSetPasscodeError() {
completion?(false)
completion?(false, .passcodeNotSet)
}
} else {
windowProtection.presentAuthenticationForViewController(determineLockWithPasscode: false) { status in
completion?(status)
windowProtection.presentAuthenticationForViewController(determineLockWithPasscode: false) { status, error in
completion?(status, error)
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions Sources/Brave/Frontend/Login/LoginInfoViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftKeychainWrapper
import BraveCore
import UIKit
import BraveUI
import Combine

class LoginInfoViewController: LoginAuthViewController {

Expand Down Expand Up @@ -70,6 +71,8 @@ class LoginInfoViewController: LoginAuthViewController {
return dateFormatter.string(from: credentials.dateCreated ?? Date())
}

private var localAuthObservers = Set<AnyCancellable>()

// MARK: Lifecycle

init(passwordAPI: BravePasswordAPI, credentials: PasswordForm, windowProtection: WindowProtection?) {
Expand Down Expand Up @@ -287,7 +290,7 @@ extension LoginInfoViewController {
extension LoginInfoViewController {

@objc private func edit() {
askForAuthentication() { [weak self] status in
askForAuthentication() { [weak self] status, _ in
guard let self = self, status else { return }

self.isEditingFieldData = true
Expand Down Expand Up @@ -410,7 +413,7 @@ extension LoginInfoViewController: LoginInfoTableViewCellDelegate {
}

func didSelectReveal(_ cell: LoginInfoTableViewCell, completion: ((Bool) -> Void)?) {
askForAuthentication() { status in
askForAuthentication() { status, _ in
completion?(status)
}
}
Expand All @@ -425,7 +428,7 @@ extension LoginInfoViewController: LoginInfoTableViewCellDelegate {
}

if authenticationRequired {
askForAuthentication() { status in
askForAuthentication() { status, _ in
if status {
addPasswordToPasteBoardWithExpiry()
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/Brave/Frontend/Login/LoginListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Storage
import Data
import BraveCore
import Favicon
import Combine

class LoginListViewController: LoginAuthViewController {

Expand Down Expand Up @@ -42,6 +43,8 @@ class LoginListViewController: LoginAuthViewController {
private let emptyStateOverlayView = EmptyStateOverlayView(
overlayDetails: EmptyOverlayStateDetails(title: Strings.Login.loginListEmptyScreenTitle))

private var localAuthObservers = Set<AnyCancellable>()

// MARK: Lifecycle

init(passwordAPI: BravePasswordAPI, windowProtection: WindowProtection?) {
Expand All @@ -62,6 +65,11 @@ class LoginListViewController: LoginAuthViewController {
self.fetchLoginInfo()
}
})

windowProtection?.cancelPressed
.sink { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
}.store(in: &localAuthObservers)
}

required init?(coder: NSCoder) {
Expand Down
72 changes: 56 additions & 16 deletions Sources/Brave/Frontend/Passcode/WindowProtection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,29 @@ public class WindowProtection {
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
let lockImageView = UIImageView(image: UIImage(named: "browser-lock-icon", in: .module, compatibleWith: nil)!)
let unlockButton = FilledActionButton(type: .system).then {
$0.setTitle("Unlock", for: .normal)
$0.setTitle(Strings.unlockButtonTitle, for: .normal)
$0.titleLabel?.font = .preferredFont(forTextStyle: .headline)
$0.titleLabel?.adjustsFontForContentSizeCategory = true
$0.backgroundColor = .braveBlurpleTint
$0.isHidden = true
}

let cancelButton = ActionButton(type: .system).then {
$0.setTitle(Strings.cancelButtonTitle, for: .normal)
$0.titleLabel?.font = .preferredFont(forTextStyle: .headline)
$0.titleLabel?.adjustsFontForContentSizeCategory = true
$0.setTitle(Strings.cancelButtonTitle, for: .normal)
$0.tintColor = .braveLabel
$0.isHidden = true
}

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(backgroundView)
view.addSubview(lockImageView)
view.addSubview(unlockButton)
view.addSubview(cancelButton)
backgroundView.snp.makeConstraints {
$0.edges.equalTo(view)
}
Expand All @@ -40,12 +50,20 @@ public class WindowProtection {
}
unlockButton.snp.makeConstraints {
$0.leading.greaterThanOrEqualToSuperview().offset(20)
$0.trailing.lessThanOrEqualToSuperview().offset(20)
$0.trailing.lessThanOrEqualToSuperview().offset(-20)
$0.centerX.equalToSuperview()
$0.height.greaterThanOrEqualTo(44)
$0.width.greaterThanOrEqualTo(230)
$0.top.equalTo(lockImageView.snp.bottom).offset(60)
}
cancelButton.snp.makeConstraints {
$0.leading.greaterThanOrEqualToSuperview().offset(20)
$0.trailing.lessThanOrEqualToSuperview().offset(-20)
$0.centerX.equalToSuperview()
$0.height.greaterThanOrEqualTo(44)
$0.width.greaterThanOrEqualTo(230)
$0.top.equalTo(unlockButton.snp.bottom).offset(15)
}
}
}

Expand Down Expand Up @@ -77,6 +95,20 @@ public class WindowProtection {
return true
}

var isCancellable: Bool = false {
didSet {
if oldValue != isCancellable {
lockedViewController.cancelButton.isHidden = !isCancellable
}
}
}

private let onCancelPressed = PassthroughSubject<Void, Never>()

var cancelPressed: AnyPublisher<Void, Never> {
onCancelPressed.eraseToAnyPublisher()
}

public init?(window: UIWindow) {
guard let scene = window.windowScene else { return nil }
protectedWindow = window
Expand All @@ -86,7 +118,8 @@ public class WindowProtection {
passcodeWindow.rootViewController = lockedViewController

lockedViewController.unlockButton.addTarget(self, action: #selector(tappedUnlock), for: .touchUpInside)

lockedViewController.cancelButton.addTarget(self, action: #selector(tappedCancel), for: .touchUpInside)

NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
.sink(receiveValue: { [weak self] _ in
guard let self = self else { return }
Expand All @@ -113,36 +146,41 @@ public class WindowProtection {
@objc private func tappedUnlock() {
presentLocalAuthentication()
}

@objc private func tappedCancel() {
onCancelPressed.send(())
isVisible = false
}

private func updateVisibleStatusForForeground(_ determineLockWithPasscode: Bool = true, completion: ((Bool) -> Void)? = nil) {
private func updateVisibleStatusForForeground(_ determineLockWithPasscode: Bool = true, completion: ((Bool, LAError.Code?) -> Void)? = nil) {
var error: NSError?
if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error),
(error as? LAError)?.code == .passcodeNotSet {
// User no longer has a passcode set so we can't evaluate the auth policy
isVisible = false
completion?(false)
completion?(false, .passcodeNotSet)
return
}

if determineLockWithPasscode {
let isLocked = Preferences.Privacy.lockWithPasscode.value
isVisible = isLocked
if isLocked {
presentLocalAuthentication() { status in
completion?(status)
presentLocalAuthentication() { status, error in
completion?(status, error)
}
}
} else {
isVisible = true
presentLocalAuthentication() { status in
completion?(status)
presentLocalAuthentication() { status, error in
completion?(status, error)
}
}
}

private func presentLocalAuthentication(completion: ((Bool) -> Void)? = nil) {
private func presentLocalAuthentication(completion: ((Bool, LAError.Code?) -> Void)? = nil) {
if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
completion?(false)
completion?(false, .passcodeNotSet)
return
}

Expand All @@ -158,11 +196,13 @@ public class WindowProtection {
completion: { [self] _ in
isVisible = false
lockedViewController.view.alpha = 1.0
completion?(true)
completion?(true, nil)
})
} else {
lockedViewController.unlockButton.isHidden = false
completion?(false)

let errorPolicy = error as? LAError
completion?(false, errorPolicy?.code)

if let error = error {
Logger.module.error("Failed to unlock browser using local authentication: \(error.localizedDescription)")
Expand All @@ -172,14 +212,14 @@ public class WindowProtection {
}
}

func presentAuthenticationForViewController(determineLockWithPasscode: Bool = true, completion: ((Bool) -> Void)? = nil) {
func presentAuthenticationForViewController(determineLockWithPasscode: Bool = true, completion: ((Bool, LAError.Code?) -> Void)? = nil) {
if isVisible {
return
}

context = LAContext()
updateVisibleStatusForForeground(determineLockWithPasscode) { status in
completion?(status)
updateVisibleStatusForForeground(determineLockWithPasscode) { status, error in
completion?(status, error)
}
}
}
43 changes: 30 additions & 13 deletions Sources/Brave/Frontend/Sync/SyncViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,34 @@ import UIKit
import Shared
import BraveShared
import Data
import LocalAuthentication
import Combine

class SyncViewController: UIViewController {

let windowProtection: WindowProtection?
private let requiresAuthentication: Bool
private let isModallyPresented: Bool
private var localAuthObservers = Set<AnyCancellable>()

// MARK: Lifecycle

init(windowProtection: WindowProtection? = nil, requiresAuthentication: Bool = false, isModallyPresented: Bool = false) {
init(windowProtection: WindowProtection? = nil,
requiresAuthentication: Bool = false,
isAuthenticationCancellable: Bool = true,
isModallyPresented: Bool = false) {
self.windowProtection = windowProtection
self.requiresAuthentication = requiresAuthentication
self.isModallyPresented = isModallyPresented

super.init(nibName: nil, bundle: nil)

windowProtection?.isCancellable = isAuthenticationCancellable

windowProtection?.cancelPressed
.sink { [weak self] _ in
self?.dismissSyncController()
}.store(in: &localAuthObservers)
}

required init?(coder: NSCoder) {
Expand All @@ -33,15 +46,11 @@ class SyncViewController: UIViewController {
super.viewWillAppear(animated)

if requiresAuthentication {
askForAuthentication() { [weak self] success in
askForAuthentication() { [weak self] success, error in
guard let self else { return }

if !success {
if isModallyPresented {
self.dismiss(animated: true)
} else {
self.navigationController?.popViewController(animated: true)
}
if !success, error != .userCancel {
self.dismissSyncController()
}
}
}
Expand All @@ -60,24 +69,32 @@ class SyncViewController: UIViewController {

/// A method to ask biometric authentication to user
/// - Parameter completion: block returning authentication status
func askForAuthentication(completion: ((Bool) -> Void)? = nil) {
func askForAuthentication(completion: ((Bool, LAError.Code?) -> Void)? = nil) {
guard let windowProtection = windowProtection else {
completion?(false)
completion?(false, nil)
return
}

if !windowProtection.isPassCodeAvailable {
showSetPasscodeError() {
completion?(false)
completion?(false, LAError.passcodeNotSet)
}
} else {
windowProtection.presentAuthenticationForViewController(
determineLockWithPasscode: false) { status in
completion?(status)
determineLockWithPasscode: false) { status, error in
completion?(status, error)
}
}
}

private func dismissSyncController() {
if isModallyPresented {
self.dismiss(animated: true)
} else {
self.navigationController?.popViewController(animated: true)
}
}

/// An alert presenter for passcode error to warn user to setup passcode to use feature
/// - Parameter completion: block after Ok button is pressed
private func showSetPasscodeError(completion: @escaping (() -> Void)) {
Expand Down
Loading

0 comments on commit 36dbfe1

Please sign in to comment.