From c17b87b4f67ddec78e5e6be9276c6f84a7bcd402 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 29 Nov 2023 17:43:09 +0100 Subject: [PATCH] Implement headers support in getCredentials and awaitCredentials (#699) --- .../storage/CredentialsManager.kt | 52 +++++++++++++++ .../storage/SecureCredentialsManager.kt | 66 ++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt index 49150c9a..2efe8537 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt @@ -118,12 +118,37 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting minTtl: Int, parameters: Map, forceRefresh: Boolean + ): Credentials { + return awaitCredentials(scope, minTtl, parameters, mapOf(), forceRefresh) + } + + /** + * Retrieves the credentials from the storage and refresh them if they have already expired. + * It will throw [CredentialsManagerException] if the saved access_token or id_token is null, + * or if the tokens have already expired and the refresh_token is null. + * This is a Coroutine that is exposed only for Kotlin. + * + * @param scope the scope to request for the access token. If null is passed, the previous scope will be kept. + * @param minTtl the minimum time in seconds that the access token should last before expiration. + * @param parameters additional parameters to send in the request to refresh expired credentials. + * @param headers additional headers to send in the request to refresh expired credentials. + * @param forceRefresh this will avoid returning the existing credentials and retrieves a new one even if valid credentials exist. + */ + @JvmSynthetic + @Throws(CredentialsManagerException::class) + public suspend fun awaitCredentials( + scope: String?, + minTtl: Int, + parameters: Map, + headers: Map, + forceRefresh: Boolean ): Credentials { return suspendCancellableCoroutine { continuation -> getCredentials( scope, minTtl, parameters, + headers, forceRefresh, object : Callback { override fun onSuccess(result: Credentials) { @@ -201,6 +226,29 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting parameters: Map, forceRefresh: Boolean, callback: Callback + ) { + getCredentials(scope, minTtl, parameters, mapOf(), forceRefresh, callback) + } + + /** + * Retrieves the credentials from the storage and refresh them if they have already expired. + * It will fail with [CredentialsManagerException] if the saved access_token or id_token is null, + * or if the tokens have already expired and the refresh_token is null. + * + * @param scope the scope to request for the access token. If null is passed, the previous scope will be kept. + * @param minTtl the minimum time in seconds that the access token should last before expiration. + * @param parameters additional parameters to send in the request to refresh expired credentials. + * @param headers additional headers to send in the request to refresh expired credentials. + * @param forceRefresh this will avoid returning the existing credentials and retrieves a new one even if valid credentials exist. + * @param callback the callback that will receive a valid [Credentials] or the [CredentialsManagerException]. + */ + public fun getCredentials( + scope: String?, + minTtl: Int, + parameters: Map, + headers: Map, + forceRefresh: Boolean, + callback: Callback ) { serialExecutor.execute { val accessToken = storage.retrieveString(KEY_ACCESS_TOKEN) @@ -240,6 +288,10 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting request.addParameter("scope", scope) } + for (header in headers) { + request.addHeader(header.key, header.value) + } + try { val fresh = request.execute() val expiresAt = fresh.expiresAt.time diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt index a2a6f60e..de4a1703 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt @@ -146,7 +146,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT return false } if (resultCode == Activity.RESULT_OK) { - continueGetCredentials(scope, minTtl, emptyMap(), forceRefresh, decryptCallback!!) + continueGetCredentials(scope, minTtl, emptyMap(), emptyMap(), forceRefresh, decryptCallback!!) } else { decryptCallback!!.onFailure(CredentialsManagerException("The user didn't pass the authentication challenge.")) decryptCallback = null @@ -281,12 +281,41 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT minTtl: Int, parameters: Map, forceRefresh: Boolean, + ): Credentials { + return awaitCredentials(scope, minTtl, parameters, mapOf(), forceRefresh) + } + + /** + * Tries to obtain the credentials from the Storage. The method will return [Credentials]. + * If something unexpected happens, then [CredentialsManagerException] exception will be thrown. Some devices are not compatible + * at all with the cryptographic implementation and will have [CredentialsManagerException.isDeviceIncompatible] return true. + * This is a Coroutine that is exposed only for Kotlin. + * + * If a LockScreen is setup and [SecureCredentialsManager.requireAuthentication] was called, the user will be asked to authenticate before accessing + * the credentials. Your activity must override the [Activity.onActivityResult] method and call + * [SecureCredentialsManager.checkAuthenticationResult] with the received values. + * + * @param scope the scope to request for the access token. If null is passed, the previous scope will be kept. + * @param minTtl the minimum time in seconds that the access token should last before expiration. + * @param parameters additional parameters to send in the request to refresh expired credentials. + * @param headers additional headers to send in the request to refresh expired credentials. + * @param forceRefresh this will avoid returning the existing credentials and retrieves a new one even if valid credentials exist. + */ + @JvmSynthetic + @Throws(CredentialsManagerException::class) + public suspend fun awaitCredentials( + scope: String?, + minTtl: Int, + parameters: Map, + headers: Map, + forceRefresh: Boolean, ): Credentials { return suspendCancellableCoroutine { continuation -> getCredentials( scope, minTtl, parameters, + headers, forceRefresh, object : Callback { override fun onSuccess(result: Credentials) { @@ -384,6 +413,34 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT parameters: Map, forceRefresh: Boolean, callback: Callback + ) { + getCredentials(scope, minTtl, parameters, mapOf(), forceRefresh, callback) + } + + /** + * Tries to obtain the credentials from the Storage. The callback's [Callback.onSuccess] method will be called with the result. + * If something unexpected happens, the [Callback.onFailure] method will be called with the error. Some devices are not compatible + * at all with the cryptographic implementation and will have [CredentialsManagerException.isDeviceIncompatible] return true. + * + * + * If a LockScreen is setup and [SecureCredentialsManager.requireAuthentication] was called, the user will be asked to authenticate before accessing + * the credentials. Your activity must override the [Activity.onActivityResult] method and call + * [SecureCredentialsManager.checkAuthenticationResult] with the received values. + * + * @param scope the scope to request for the access token. If null is passed, the previous scope will be kept. + * @param minTtl the minimum time in seconds that the access token should last before expiration. + * @param parameters additional parameters to send in the request to refresh expired credentials. + * @param headers additional headers to send in the request to refresh expired credentials. + * @param forceRefresh this will avoid returning the existing credentials and retrieves a new one even if valid credentials exist. + * @param callback the callback to receive the result in. + */ + public fun getCredentials( + scope: String?, + minTtl: Int, + parameters: Map, + headers: Map, + forceRefresh: Boolean, + callback: Callback ) { if (!hasValidCredentials(minTtl.toLong())) { callback.onFailure(CredentialsManagerException("No Credentials were previously set.")) @@ -402,7 +459,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT ?: activity?.startActivityForResult(authIntent, authenticationRequestCode) return } - continueGetCredentials(scope, minTtl, parameters, forceRefresh, callback) + continueGetCredentials(scope, minTtl, parameters, headers, forceRefresh, callback) } /** @@ -451,6 +508,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT scope: String?, minTtl: Int, parameters: Map, + headers: Map, forceRefresh: Boolean, callback: Callback ) { @@ -532,6 +590,10 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT request.addParameter("scope", scope) } + for (header in headers) { + request.addHeader(header.key, header.value) + } + val freshCredentials: Credentials try { val fresh = request.execute()