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
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: MickeyMoz <sebastian@mozilla.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
- Loading branch information
1 parent
e7ad21b
commit b172382
Showing
11 changed files
with
369 additions
and
0 deletions.
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]. | ||
*/ | ||
internal 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. | ||
*/ | ||
internal 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() | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
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,50 @@ | ||
/* 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.biometric.BiometricManager | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import mozilla.components.support.test.mock | ||
import mozilla.components.support.test.robolectric.testContext | ||
import mozilla.components.support.test.whenever | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
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`() { | ||
assertFalse(testContext.canUseBiometricFeature()) | ||
} | ||
|
||
@Test | ||
fun `isHardwareAvailable is true based on AuthenticationStatus`() { | ||
val manager: BiometricManager = mock { | ||
whenever(canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) | ||
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS) | ||
.thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE) | ||
.thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) | ||
} | ||
|
||
assertTrue(BiometricUtils.isHardwareAvailable(manager)) | ||
assertFalse(BiometricUtils.isHardwareAvailable(manager)) | ||
assertFalse(BiometricUtils.isHardwareAvailable(manager)) | ||
} | ||
|
||
@Test | ||
fun `isEnrolled is true based on AuthenticationStatus`() { | ||
val manager: BiometricManager = mock { | ||
whenever(canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) | ||
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS) | ||
} | ||
assertTrue(BiometricUtils.isEnrolled(manager)) | ||
} | ||
} |
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