Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature/biometrical-button] Biometrical Authentication Button #1098

Merged
merged 6 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
Changelog for ownCloud iOS Client [unreleased] (UNRELEASED)
=======================================
The following sections list the changes in ownCloud iOS Client unreleased relevant to
ownCloud admins and users.

[unreleased]: https://github.com/owncloud/ios-app/compare/milestone/11.8.2...master

Summary
-------

* Change - Biometrical Authentication Button: [#1004](https://github.com/owncloud/ios-app/issues/1004)

Details
-------

* Change - Biometrical Authentication Button: [#1004](https://github.com/owncloud/ios-app/issues/1004)

Added biometrical authentication button to provide a fallback for the fileprovider or app, if
the automatically biometrical unlock does not work, or the user cancel the biometrical
authentication flow.

https://github.com/owncloud/ios-app/issues/1004

Changelog for ownCloud iOS Client [11.8.2] (2022-01-17)
=======================================
The following sections list the changes in ownCloud iOS Client 11.8.2 relevant to
Expand Down
5 changes: 5 additions & 0 deletions changelog/unreleased/1004
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Change: Biometrical Authentication Button

Added biometrical authentication button to provide a fallback for the fileprovider or app, if the automatically biometrical unlock does not work, or the user cancel the biometrical authentication flow.

https://github.com/owncloud/ios-app/issues/1004
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
override func prepare(forError error: Error) {
if AppLockManager.supportedOnDevice {
AppLockManager.shared.passwordViewHostViewController = self
AppLockManager.shared.biometricCancelLabel = "Cancel".localized
AppLockManager.shared.cancelAction = { [weak self] in
self?.complete(cancelWith: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil))
}
Expand Down
6 changes: 3 additions & 3 deletions ownCloud/Resources/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "biometrical-faceid.pdf",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"images" : [
{
"filename" : "biometrical.pdf",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.
23 changes: 20 additions & 3 deletions ownCloudAppShared/AppLock/AppLockManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ public class AppLockManager: NSObject {
self.userDefaults.set(newValue, forKey: "applock-locked-until-date")
}
}
private var biometricalAuthenticationSucceeded: Bool {
get {
return userDefaults.bool(forKey: "applock-biometrical-authentication-succeeded")
}
set(newValue) {
self.userDefaults.set(newValue, forKey: "applock-biometrical-authentication-succeeded")
}
}

private let maximumPasscodeAttempts: Int = 3
private let powBaseDelay: Double = 1.5
Expand Down Expand Up @@ -146,7 +154,7 @@ public class AppLockManager: NSObject {
lockscreenOpen = true

// Show biometrical
if !forceShow, !self.shouldDisplayCountdown {
if !forceShow, !self.shouldDisplayCountdown, self.biometricalAuthenticationSucceeded {
showBiometricalAuthenticationInterface(context: context)
}
}
Expand Down Expand Up @@ -187,6 +195,8 @@ public class AppLockManager: NSObject {
private var passcodeControllerByWindow : NSMapTable<ThemeWindow, PasscodeViewController> = NSMapTable.weakToStrongObjects()
private var applockWindowByWindow : NSMapTable<ThemeWindow, AppLockWindow> = NSMapTable.weakToStrongObjects()

open var biometricCancelLabel : String?

open var cancelAction : (() -> Void)?
open var successAction : (() -> Void)?

Expand Down Expand Up @@ -282,7 +292,12 @@ public class AppLockManager: NSObject {
func passwordViewController() -> PasscodeViewController {
var passcodeViewController : PasscodeViewController

passcodeViewController = PasscodeViewController(completionHandler: { (viewController: PasscodeViewController, passcode: String) in
passcodeViewController = PasscodeViewController(biometricalHandler: { (passcodeViewController) in
if !self.shouldDisplayCountdown {
let context = LAContext()
self.showBiometricalAuthenticationInterface(context: context)
}
}, completionHandler: { (viewController: PasscodeViewController, passcode: String) in
self.attemptUnlock(with: passcode, passcodeViewController: viewController)
}, requiredLength: AppLockManager.shared.passcode?.count ?? AppLockSettings.shared.requiredPasscodeDigits)

Expand Down Expand Up @@ -452,7 +467,7 @@ public class AppLockManager: NSObject {
}
}

context.localizedCancelTitle = "Enter code".localized
context.localizedCancelTitle = biometricCancelLabel ?? "Enter code".localized
context.localizedFallbackTitle = ""

self.biometricalAuthenticationInterfaceShown = true
Expand All @@ -461,6 +476,7 @@ public class AppLockManager: NSObject {
self.biometricalAuthenticationInterfaceShown = false

if success {
self.biometricalAuthenticationSucceeded = true
// Fill the passcode dots
OnMainThread {
self.performPasscodeViewControllerUpdates { (passcodeViewController) in
Expand All @@ -478,6 +494,7 @@ public class AppLockManager: NSObject {
self.attemptUnlock(with: self.passcode)
}
} else {
self.biometricalAuthenticationSucceeded = false
if let error = error {
switch error {
case LAError.biometryLockout:
Expand Down
26 changes: 25 additions & 1 deletion ownCloudAppShared/AppLock/PasscodeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
*/

import UIKit
import ownCloudApp
import LocalAuthentication

public typealias PasscodeViewControllerCancelHandler = ((_ passcodeViewController: PasscodeViewController) -> Void)
public typealias PasscodeViewControllerBiometricalHandler = ((_ passcodeViewController: PasscodeViewController) -> Void)
public typealias PasscodeViewControllerCompletionHandler = ((_ passcodeViewController: PasscodeViewController, _ passcode: String) -> Void)

public class PasscodeViewController: UIViewController, Themeable {
Expand All @@ -39,6 +42,8 @@ public class PasscodeViewController: UIViewController, Themeable {
@IBOutlet private var keypadButtons: [ThemeRoundedButton]?
@IBOutlet private var deleteButton: ThemeButton?
@IBOutlet public var cancelButton: ThemeButton?
@IBOutlet public var biometricalButton: ThemeButton?
@IBOutlet public var biometricalImageView: UIImageView?
@IBOutlet public var compactHeightPasscodeTextField: UITextField?

// MARK: - Properties
Expand Down Expand Up @@ -109,6 +114,15 @@ public class PasscodeViewController: UIViewController, Themeable {
}
}

var biometricalButtonHidden: Bool = false {
didSet {
biometricalButton?.isEnabled = biometricalButtonHidden
biometricalButton?.isHidden = !biometricalButtonHidden
biometricalImageView?.isHidden = !biometricalButtonHidden
biometricalImageView?.image = LAContext().biometricsAuthenticationImage()
}
}

var hasCompactHeight: Bool {
if self.traitCollection.verticalSizeClass == .compact {
return true
Expand All @@ -119,11 +133,13 @@ public class PasscodeViewController: UIViewController, Themeable {

// MARK: - Handlers
public var cancelHandler: PasscodeViewControllerCancelHandler?
public var biometricalHandler: PasscodeViewControllerBiometricalHandler?
public var completionHandler: PasscodeViewControllerCompletionHandler?

// MARK: - Init
public init(cancelHandler: PasscodeViewControllerCancelHandler? = nil, completionHandler: @escaping PasscodeViewControllerCompletionHandler, hasCancelButton: Bool = true, keypadButtonsEnabled: Bool = true, requiredLength: Int) {
public init(cancelHandler: PasscodeViewControllerCancelHandler? = nil, biometricalHandler: PasscodeViewControllerBiometricalHandler? = nil, completionHandler: @escaping PasscodeViewControllerCompletionHandler, hasCancelButton: Bool = true, keypadButtonsEnabled: Bool = true, requiredLength: Int) {
self.cancelHandler = cancelHandler
self.biometricalHandler = biometricalHandler
self.completionHandler = completionHandler
self.keypadButtonsEnabled = keypadButtonsEnabled
self.cancelButtonHidden = hasCancelButton
Expand Down Expand Up @@ -157,6 +173,7 @@ public class PasscodeViewController: UIViewController, Themeable {
self.screenBlurringEnabled = { self.screenBlurringEnabled }()
self.errorMessageLabel?.minimumScaleFactor = 0.5
self.errorMessageLabel?.adjustsFontSizeToFitWidth = true
self.biometricalButtonHidden = !(!AppLockSettings.shared.biometricalSecurityEnabled || self.cancelButtonHidden)
updateKeypadButtons()

if #available(iOS 13.4, *) {
Expand All @@ -165,6 +182,7 @@ public class PasscodeViewController: UIViewController, Themeable {
}
PointerEffect.install(on: cancelButton!, effectStyle: .highlight)
PointerEffect.install(on: deleteButton!, effectStyle: .highlight)
PointerEffect.install(on: biometricalButton!, effectStyle: .highlight)
}
}

Expand Down Expand Up @@ -283,6 +301,10 @@ public class PasscodeViewController: UIViewController, Themeable {
cancelHandler?(self)
}

@IBAction func biometricalAction(_ sender: UIButton) {
biometricalHandler?(self)
}

// MARK: - Themeing
public override var preferredStatusBarStyle : UIStatusBarStyle {
if VendorServices.shared.isBranded {
Expand Down Expand Up @@ -311,6 +333,8 @@ public class PasscodeViewController: UIViewController, Themeable {

deleteButton?.themeColorCollection = ThemeColorPairCollection(fromPair: ThemeColorPair(foreground: collection.neutralColors.normal.background, background: .clear))

biometricalImageView?.tintColor = collection.tintColor

cancelButton?.applyThemeCollection(collection, itemStyle: .defaultForItem)
}
}
Expand Down
34 changes: 32 additions & 2 deletions ownCloudAppShared/AppLock/PasscodeViewController.xib
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PasscodeViewController" customModule="ownCloudAppShared" customModuleProvider="target">
<connections>
<outlet property="backgroundBlurView" destination="ZQ4-NE-Hrm" id="GBv-lU-WeS"/>
<outlet property="biometricalButton" destination="M8y-Pc-oda" id="VZj-jp-Ioo"/>
<outlet property="biometricalImageView" destination="uLV-Sc-Alc" id="JbF-51-aGp"/>
<outlet property="cancelButton" destination="g0I-oo-iOZ" id="vc0-3w-JYu"/>
<outlet property="compactHeightPasscodeTextField" destination="SOo-MB-t0E" id="BWm-9n-6ao"/>
<outlet property="deleteButton" destination="F9C-Vg-uCq" id="Dw1-Fd-gaD"/>
Expand Down Expand Up @@ -199,6 +201,21 @@
<action selector="appendDigit:" destination="-1" eventType="touchUpInside" id="fxw-0Q-s08"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="faceid" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="uLV-Sc-Alc">
<rect key="frame" x="8" y="265.5" width="50" height="38.5"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M8y-Pc-oda" customClass="ThemeRoundedButton" customModule="ownCloudAppShared" customModuleProvider="target">
<rect key="frame" x="8" y="255" width="60" height="60"/>
<accessibility key="accessibilityConfiguration" identifier="number0Button"/>
<constraints>
<constraint firstAttribute="width" secondItem="M8y-Pc-oda" secondAttribute="height" multiplier="1:1" id="47x-wi-FWl"/>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="700" id="ZuU-m8-h8R"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="60" id="vWA-oc-OPO"/>
</constraints>
<connections>
<action selector="biometricalAction:" destination="-1" eventType="touchUpInside" id="eXX-Zz-g0B"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F9C-Vg-uCq" customClass="ThemeRoundedButton" customModule="ownCloudAppShared" customModuleProvider="target">
<rect key="frame" x="170" y="255" width="60" height="60"/>
<accessibility key="accessibilityConfiguration" identifier="deleteButton"/>
Expand All @@ -215,22 +232,29 @@
</button>
</subviews>
<constraints>
<constraint firstItem="M8y-Pc-oda" firstAttribute="top" secondItem="fMx-64-bYq" secondAttribute="bottom" constant="25" id="0m2-MP-6Yy"/>
<constraint firstItem="qMI-IV-KI3" firstAttribute="top" secondItem="fMx-64-bYq" secondAttribute="top" id="18A-ZW-jWc"/>
<constraint firstItem="hgh-7z-Fto" firstAttribute="top" secondItem="IVZ-la-esK" secondAttribute="top" id="2eX-x7-uSc"/>
<constraint firstItem="TIO-8z-Ags" firstAttribute="leading" secondItem="M8y-Pc-oda" secondAttribute="trailing" constant="17" id="2mW-yz-103"/>
<constraint firstItem="gBp-H8-606" firstAttribute="top" secondItem="85k-O3-nSF" secondAttribute="top" id="3Tp-Ni-Zn9"/>
<constraint firstItem="hgh-7z-Fto" firstAttribute="leading" secondItem="gBp-H8-606" secondAttribute="leading" id="4eO-HV-3x8"/>
<constraint firstItem="85k-O3-nSF" firstAttribute="top" secondItem="C0s-hA-da3" secondAttribute="top" id="6b4-xi-l10"/>
<constraint firstItem="9xg-xz-Tph" firstAttribute="leading" secondItem="VzK-5I-Ct8" secondAttribute="leading" id="8Dd-LE-Tdo"/>
<constraint firstItem="IVZ-la-esK" firstAttribute="leading" secondItem="85k-O3-nSF" secondAttribute="leading" id="8La-7P-NEs"/>
<constraint firstItem="uLV-Sc-Alc" firstAttribute="bottom" secondItem="M8y-Pc-oda" secondAttribute="bottom" constant="-10" id="A2a-6f-VVQ"/>
<constraint firstItem="TIO-8z-Ags" firstAttribute="leading" secondItem="qMI-IV-KI3" secondAttribute="leading" id="LEo-oa-jZZ"/>
<constraint firstItem="48Q-nf-LTg" firstAttribute="leading" secondItem="gBp-H8-606" secondAttribute="leading" id="LFW-Rw-tRv"/>
<constraint firstItem="hgh-7z-Fto" firstAttribute="top" secondItem="gBp-H8-606" secondAttribute="bottom" constant="25" id="MYC-9Y-80h"/>
<constraint firstItem="qMI-IV-KI3" firstAttribute="leading" secondItem="VzK-5I-Ct8" secondAttribute="leading" id="NHk-xt-rZG"/>
<constraint firstItem="M8y-Pc-oda" firstAttribute="width" secondItem="M8y-Pc-oda" secondAttribute="height" multiplier="1:1" id="NYK-8F-LkF"/>
<constraint firstItem="VzK-5I-Ct8" firstAttribute="top" secondItem="85k-O3-nSF" secondAttribute="top" id="WBr-Xg-iqr"/>
<constraint firstItem="uLV-Sc-Alc" firstAttribute="leading" secondItem="M8y-Pc-oda" secondAttribute="leading" id="ZId-Xc-K0a"/>
<constraint firstItem="uLV-Sc-Alc" firstAttribute="trailing" secondItem="M8y-Pc-oda" secondAttribute="trailing" constant="-10" id="aJW-ss-ZVn"/>
<constraint firstItem="fMx-64-bYq" firstAttribute="leading" secondItem="85k-O3-nSF" secondAttribute="leading" id="aZi-2h-OhS"/>
<constraint firstItem="48Q-nf-LTg" firstAttribute="top" secondItem="hgh-7z-Fto" secondAttribute="bottom" constant="25" id="f3A-Aw-sE9"/>
<constraint firstItem="9xg-xz-Tph" firstAttribute="top" secondItem="IVZ-la-esK" secondAttribute="top" id="jZI-GP-nv9"/>
<constraint firstItem="F9C-Vg-uCq" firstAttribute="top" secondItem="TIO-8z-Ags" secondAttribute="top" id="jch-1Y-paT"/>
<constraint firstItem="uLV-Sc-Alc" firstAttribute="top" secondItem="M8y-Pc-oda" secondAttribute="top" constant="10" id="k3L-AD-mU9"/>
<constraint firstItem="85k-O3-nSF" firstAttribute="leading" secondItem="C0s-hA-da3" secondAttribute="leading" id="k8s-77-W6O"/>
<constraint firstAttribute="bottom" secondItem="F9C-Vg-uCq" secondAttribute="bottom" id="nPA-7A-BV0"/>
<constraint firstItem="F9C-Vg-uCq" firstAttribute="top" secondItem="48Q-nf-LTg" secondAttribute="bottom" constant="25" id="nUX-x9-pef"/>
Expand Down Expand Up @@ -348,6 +372,9 @@
<designable name="IVZ-la-esK">
<size key="intrinsicContentSize" width="60" height="39"/>
</designable>
<designable name="M8y-Pc-oda">
<size key="intrinsicContentSize" width="60" height="39"/>
</designable>
<designable name="TIO-8z-Ags">
<size key="intrinsicContentSize" width="60" height="39"/>
</designable>
Expand All @@ -370,4 +397,7 @@
<size key="intrinsicContentSize" width="60" height="39"/>
</designable>
</designables>
<resources>
<image name="faceid" catalog="system" width="128" height="115"/>
</resources>
</document>