-
Notifications
You must be signed in to change notification settings - Fork 140
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
Improve authenticated flow of the Credentials Manager #519
Changes from all commits
28aaa28
0895e13
fa675fc
71b5c68
17f14b3
13dda39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,12 @@ import android.os.Build | |
import android.text.TextUtils | ||
import android.util.Base64 | ||
import android.util.Log | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.result.ActivityResultLauncher | ||
import androidx.activity.result.contract.ActivityResultContracts | ||
import androidx.annotation.IntRange | ||
import androidx.annotation.VisibleForTesting | ||
import androidx.lifecycle.Lifecycle | ||
import com.auth0.android.authentication.AuthenticationAPIClient | ||
import com.auth0.android.authentication.AuthenticationException | ||
import com.auth0.android.callback.AuthenticationCallback | ||
|
@@ -36,6 +40,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT | |
private var authenticateBeforeDecrypt: Boolean | ||
private var authenticationRequestCode = -1 | ||
private var activity: Activity? = null | ||
private var activityResultContract: ActivityResultLauncher<Intent>? = null | ||
|
||
//State for retrying operations | ||
private var decryptCallback: Callback<Credentials, CredentialsManagerException>? = null | ||
|
@@ -63,12 +68,13 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT | |
|
||
/** | ||
* Require the user to authenticate using the configured LockScreen before accessing the credentials. | ||
* This feature is disabled by default and will only work if the device is running on Android version 21 or up and if the user | ||
* has configured a secure LockScreen (PIN, Pattern, Password or Fingerprint). | ||
* This method MUST be called in [Activity.onCreate]. This feature is disabled by default and will | ||
* only work if the user has configured a secure LockScreen (PIN, Pattern, Password or Fingerprint). | ||
* | ||
* | ||
* The activity passed as first argument here must override the [Activity.onActivityResult] method and | ||
* call [SecureCredentialsManager.checkAuthenticationResult] with the received parameters. | ||
* If the activity passed as first argument is a subclass of ComponentActivity, the authentication result | ||
* will be handled internally using "Activity Results API". Otherwise, your activity must override the | ||
* [Activity.onActivityResult] method and call [SecureCredentialsManager.checkAuthenticationResult] with | ||
* the received parameters. | ||
* | ||
* @param activity a valid activity context. Will be used in the authentication request to launch a LockScreen intent. | ||
* @param requestCode the request code to use in the authentication request. Must be a value between 1 and 255. | ||
|
@@ -84,25 +90,42 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT | |
): Boolean { | ||
require(!(requestCode < 1 || requestCode > 255)) { "Request code must be a value between 1 and 255." } | ||
val kManager = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager | ||
authIntent = | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) kManager.createConfirmDeviceCredentialIntent( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the min SDK is already 21, no reason to keep these checks around |
||
title, | ||
description | ||
) else null | ||
authIntent = kManager.createConfirmDeviceCredentialIntent(title, description) | ||
authenticateBeforeDecrypt = | ||
((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && kManager.isDeviceSecure | ||
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && kManager.isKeyguardSecure) | ||
((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && kManager.isDeviceSecure || kManager.isKeyguardSecure) | ||
&& authIntent != null) | ||
if (authenticateBeforeDecrypt) { | ||
this.activity = activity | ||
authenticationRequestCode = requestCode | ||
|
||
/* | ||
* https://developer.android.com/training/basics/intents/result#register | ||
* Docs say it's safe to call "registerForActivityResult" BEFORE the activity is created. In practice, | ||
* when that's not the case, a RuntimeException is thrown. The lifecycle state check below is meant to | ||
* prevent that exception while still falling back to the old "startActivityForResult" flow. That's in | ||
* case devs are invoking this method in places other than the Activity's "OnCreate" method. | ||
*/ | ||
if (activity is ComponentActivity && !activity.lifecycle.currentState.isAtLeast( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the context is a subclass of |
||
Lifecycle.State.STARTED | ||
) | ||
) { | ||
activityResultContract = | ||
activity.registerForActivityResult( | ||
ActivityResultContracts.StartActivityForResult(), | ||
activity.activityResultRegistry | ||
) { | ||
checkAuthenticationResult(authenticationRequestCode, it.resultCode) | ||
} | ||
} else { | ||
this.activity = activity | ||
} | ||
} | ||
return authenticateBeforeDecrypt | ||
} | ||
|
||
/** | ||
* Checks the result after showing the LockScreen to the user. | ||
* Must be called from the [Activity.onActivityResult] method with the received parameters. | ||
* Called internally when your activity is a subclass of ComponentActivity (using Activity Results API). | ||
* It's safe to call this method even if [SecureCredentialsManager.requireAuthentication] was unsuccessful. | ||
* | ||
* @param requestCode the request code received in the onActivityResult call. | ||
|
@@ -239,7 +262,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT | |
decryptCallback = callback | ||
this.scope = scope | ||
this.minTtl = minTtl | ||
activity!!.startActivityForResult(authIntent, authenticationRequestCode) | ||
activityResultContract?.launch(authIntent) | ||
?: activity?.startActivityForResult(authIntent, authenticationRequestCode) | ||
return | ||
} | ||
continueGetCredentials(scope, minTtl, parameters, callback) | ||
|
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.
It's OK if they don't. I'm defaulting to the old scenario anyways, but it's better to lead them to use the new way for future compatibility & easier deprecation reasons.