This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 473
For #12289 Add lib-auth
for authentication using biometrics or PIN.
#12291
Merged
Changes from 9 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a1404bf
Biometric prompt feature
MickeyMoz 40d95a2
For #12289 Implement the common part from Biometric in ac
iorgamgabriel 4feb1cf
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel 7b5f8d5
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel 81806fb
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel 185f4ef
Merge remote-tracking branch 'origin/main' into 12289
iorgamgabriel 489dbda
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel 2181e0c
Merge remote-tracking branch 'origin/12289' into 12289
iorgamgabriel 74e7c09
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel ae7d53e
For #12289 Add lib-auth for authentication using biometrics or PIN.
iorgamgabriel e14785a
Merge remote-tracking branch 'origin/12289' into 12289
iorgamgabriel e440c9c
Merge remote-tracking branch 'origin/main' into 12289
iorgamgabriel 722e5d0
Merge branch 'main' into 12289
mergify[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
apply plugin: 'com.android.library' | ||
apply plugin: 'kotlin-android' | ||
|
||
android { | ||
compileSdkVersion config.compileSdkVersion | ||
|
||
defaultConfig { | ||
minSdkVersion config.minSdkVersion | ||
targetSdkVersion config.targetSdkVersion | ||
} | ||
|
||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation project(':support-base') | ||
implementation Dependencies.androidx_biometric | ||
|
||
testImplementation project(':support-test') | ||
testImplementation Dependencies.androidx_test_core | ||
testImplementation Dependencies.androidx_test_junit | ||
testImplementation Dependencies.testing_robolectric | ||
testImplementation Dependencies.testing_mockito | ||
} | ||
apply from: '../../../publish.gradle' | ||
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Add project specific ProGuard rules here. | ||
# You can control the set of applied configuration files using the | ||
# proguardFiles setting in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} | ||
|
||
# Uncomment this to preserve the line number information for | ||
# debugging stack traces. | ||
#-keepattributes SourceFile,LineNumberTable | ||
|
||
# If you keep the line number information, uncomment this to | ||
# hide the original source file name. | ||
#-renamesourcefileattribute SourceFile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest package="mozilla.components.lib.auth"> | ||
|
||
</manifest> |
29 changes: 29 additions & 0 deletions
29
components/lib/auth/src/main/java/mozilla/components/lib/auth/AuthenticationDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.lib.auth | ||
|
||
/** | ||
* Callbacks for BiometricPrompt Authentication | ||
*/ | ||
interface AuthenticationDelegate { | ||
|
||
/** | ||
* Called when a biometric (e.g. fingerprint, face, etc.) | ||
* is presented but not recognized as belonging to the user. | ||
*/ | ||
fun onAuthFailure() | ||
|
||
/** | ||
* Called when a biometric (e.g. fingerprint, face, etc.) is recognized, | ||
* indicating that the user has successfully authenticated. | ||
*/ | ||
fun onAuthSuccess() | ||
|
||
/** | ||
* Called when an unrecoverable error has been encountered and authentication has stopped. | ||
* @param errorText A human-readable error string that can be shown on an UI | ||
*/ | ||
fun onAuthError(errorText: String) | ||
} |
78 changes: 78 additions & 0 deletions
78
components/lib/auth/src/main/java/mozilla/components/lib/auth/BiometricPromptAuth.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.lib.auth | ||
|
||
import android.content.Context | ||
import androidx.annotation.VisibleForTesting | ||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK | ||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL | ||
import androidx.biometric.BiometricPrompt | ||
import androidx.core.content.ContextCompat | ||
import androidx.fragment.app.Fragment | ||
import mozilla.components.support.base.feature.LifecycleAwareFeature | ||
import mozilla.components.support.base.log.logger.Logger | ||
|
||
/** | ||
* A [LifecycleAwareFeature] for the Android Biometric API to prompt for user authentication. | ||
* The prompt also requests support for the device PIN as a fallback authentication mechanism. | ||
* | ||
* @param context Android context. | ||
* @param fragment The fragment on which this feature will live. | ||
* @param authenticationDelegate Callbacks for BiometricPrompt. | ||
*/ | ||
class BiometricPromptAuth( | ||
private val context: Context, | ||
private val fragment: Fragment, | ||
private val authenticationDelegate: AuthenticationDelegate | ||
) : LifecycleAwareFeature { | ||
private val logger = Logger(javaClass.simpleName) | ||
|
||
@VisibleForTesting | ||
internal var biometricPrompt: BiometricPrompt? = null | ||
|
||
override fun start() { | ||
val executor = ContextCompat.getMainExecutor(context) | ||
biometricPrompt = BiometricPrompt(fragment, executor, PromptCallback()) | ||
} | ||
|
||
override fun stop() { | ||
biometricPrompt = null | ||
} | ||
|
||
/** | ||
* Requests the user for biometric authentication. | ||
* | ||
* @param title Adds a title for the authentication prompt. | ||
* @param subtitle Adds a subtitle for the authentication prompt. | ||
*/ | ||
fun requestAuthentication( | ||
title: String, | ||
subtitle: String = "" | ||
) { | ||
val promptInfo: BiometricPrompt.PromptInfo = BiometricPrompt.PromptInfo.Builder() | ||
.setAllowedAuthenticators(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) | ||
.setTitle(title) | ||
.setSubtitle(subtitle) | ||
.build() | ||
biometricPrompt?.authenticate(promptInfo) | ||
} | ||
|
||
internal inner class PromptCallback : BiometricPrompt.AuthenticationCallback() { | ||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { | ||
logger.error("onAuthenticationError: errorMessage $errString errorCode=$errorCode") | ||
authenticationDelegate.onAuthError(errString.toString()) | ||
} | ||
|
||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { | ||
logger.debug("onAuthenticationSucceeded") | ||
authenticationDelegate.onAuthSuccess() | ||
} | ||
|
||
override fun onAuthenticationFailed() { | ||
logger.error("onAuthenticationFailed") | ||
authenticationDelegate.onAuthFailure() | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
components/lib/auth/src/main/java/mozilla/components/lib/auth/BiometricUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.lib.auth | ||
|
||
import android.content.Context | ||
import android.os.Build | ||
import androidx.biometric.BiometricManager | ||
|
||
/** | ||
* Utility class for BiometricPromptAuth | ||
*/ | ||
|
||
fun Context.canUseBiometricFeature(): Boolean { | ||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||
val manager = BiometricManager.from(this) | ||
return BiometricUtils.canUseFeature(manager) | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
internal object BiometricUtils { | ||
|
||
/** | ||
* Checks if the appropriate SDK version and hardware capabilities are met to use the feature. | ||
*/ | ||
internal fun canUseFeature(manager: BiometricManager): Boolean { | ||
return isHardwareAvailable(manager) && isEnrolled(manager) | ||
} | ||
|
||
/** | ||
* Checks if the hardware requirements are met for using the [BiometricManager]. | ||
*/ | ||
private fun isHardwareAvailable(biometricManager: BiometricManager): Boolean { | ||
val status = | ||
biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) | ||
return status != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE && | ||
status != BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE | ||
} | ||
|
||
/** | ||
* Checks if the user can use the [BiometricManager] and is therefore enrolled. | ||
*/ | ||
private fun isEnrolled(biometricManager: BiometricManager): Boolean { | ||
val status = | ||
biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) | ||
return status == BiometricManager.BIOMETRIC_SUCCESS | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
components/lib/auth/src/test/java/mozilla/components/lib/auth/BiometricPromptAuthTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.lib.auth | ||
|
||
import androidx.biometric.BiometricPrompt | ||
import androidx.fragment.app.Fragment | ||
import mozilla.components.support.test.any | ||
import mozilla.components.support.test.mock | ||
import mozilla.components.support.test.robolectric.createAddedTestFragment | ||
import mozilla.components.support.test.robolectric.testContext | ||
import org.junit.Assert.assertNotNull | ||
import org.junit.Assert.assertNull | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mockito.Mockito.verify | ||
import org.robolectric.RobolectricTestRunner | ||
|
||
@RunWith(RobolectricTestRunner::class) | ||
class BiometricPromptAuthTest { | ||
|
||
private lateinit var biometricPromptAuth: BiometricPromptAuth | ||
private lateinit var fragment: Fragment | ||
|
||
@Before | ||
fun setup() { | ||
fragment = createAddedTestFragment { Fragment() } | ||
biometricPromptAuth = BiometricPromptAuth( | ||
testContext, | ||
fragment, | ||
object : AuthenticationDelegate { | ||
override fun onAuthFailure() { | ||
} | ||
|
||
override fun onAuthSuccess() { | ||
} | ||
|
||
override fun onAuthError(errorText: String) { | ||
} | ||
} | ||
) | ||
} | ||
|
||
@Test | ||
fun `prompt is created and destroyed on start and stop`() { | ||
assertNull(biometricPromptAuth.biometricPrompt) | ||
|
||
biometricPromptAuth.start() | ||
|
||
assertNotNull(biometricPromptAuth.biometricPrompt) | ||
|
||
biometricPromptAuth.stop() | ||
|
||
assertNull(biometricPromptAuth.biometricPrompt) | ||
} | ||
|
||
@Test | ||
fun `requestAuthentication invokes biometric prompt`() { | ||
val prompt: BiometricPrompt = mock() | ||
|
||
biometricPromptAuth.biometricPrompt = prompt | ||
|
||
biometricPromptAuth.requestAuthentication("title", "subtitle") | ||
|
||
verify(prompt).authenticate(any()) | ||
} | ||
|
||
@Test | ||
fun `promptCallback fires feature callbacks`() { | ||
val authenticationDelegate: AuthenticationDelegate = mock() | ||
val feature = BiometricPromptAuth(testContext, fragment, authenticationDelegate) | ||
val callback = feature.PromptCallback() | ||
val prompt = BiometricPrompt(fragment, callback) | ||
|
||
feature.biometricPrompt = prompt | ||
|
||
callback.onAuthenticationError(BiometricPrompt.ERROR_CANCELED, "") | ||
|
||
verify(authenticationDelegate).onAuthError("") | ||
|
||
callback.onAuthenticationFailed() | ||
|
||
verify(authenticationDelegate).onAuthFailure() | ||
|
||
callback.onAuthenticationSucceeded(mock()) | ||
|
||
verify(authenticationDelegate).onAuthSuccess() | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
components/lib/auth/src/test/java/mozilla/components/lib/auth/BiometricUtilsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.lib.auth | ||
|
||
import android.os.Build | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import mozilla.components.support.test.robolectric.testContext | ||
import org.junit.Assert | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.robolectric.annotation.Config | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
class BiometricUtilsTest { | ||
|
||
@Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) | ||
@Test | ||
fun `canUseFeature checks for SDK compatible`() { | ||
Assert.assertFalse(testContext.canUseBiometricFeature()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, maybe I wasn't clear: you can still write tests for these easily I think, but you don't have to make the utils class public.
So the methods can be internal:
And the tests can take a mocked biometric manager to write the tests for those methods:
The test you added for
canUseFeature
is good enough coverage for that method.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done