@@ -6,6 +6,7 @@ import com.auth0.android.authentication.AuthenticationAPIClient
66import com.auth0.android.authentication.AuthenticationException
77import com.auth0.android.callback.Callback
88import com.auth0.android.result.Credentials
9+ import com.auth0.android.result.SSOCredentials
910import kotlinx.coroutines.suspendCancellableCoroutine
1011import java.util.*
1112import java.util.concurrent.Executor
@@ -53,6 +54,85 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
5354 storage.store(LEGACY_KEY_CACHE_EXPIRES_AT , credentials.expiresAt.time)
5455 }
5556
57+
58+ /* *
59+ * Stores the given [SSOCredentials] refresh token in the storage.
60+ * This method must be called if the SSOCredentials are obtained by directly invoking [AuthenticationAPIClient.fetchSessionToken] api and
61+ * [rotating refresh token](https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation) are enabled for
62+ * the client. Method will silently return ,if the passed credentials has no refresh token.
63+ *
64+ * @param ssoCredentials the credentials to save in the storage.
65+ */
66+ override fun saveSsoCredentials (ssoCredentials : SSOCredentials ) {
67+ if (ssoCredentials.refreshToken.isNullOrEmpty())
68+ return // No refresh token to save
69+ serialExecutor.execute {
70+ val existingRefreshToken = storage.retrieveString(KEY_REFRESH_TOKEN )
71+ // Checking if the existing one needs to be replaced with the new one
72+ if (ssoCredentials.refreshToken == existingRefreshToken)
73+ return @execute
74+ storage.store(KEY_REFRESH_TOKEN , ssoCredentials.refreshToken)
75+ }
76+ }
77+
78+ /* *
79+ * Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
80+ * if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
81+ * if a new one is issued
82+ */
83+ override fun getSsoCredentials (callback : Callback <SSOCredentials , CredentialsManagerException >) {
84+ serialExecutor.execute {
85+ val refreshToken = storage.retrieveString(KEY_REFRESH_TOKEN )
86+ if (refreshToken.isNullOrEmpty()) {
87+ callback.onFailure(CredentialsManagerException .NO_REFRESH_TOKEN )
88+ return @execute
89+ }
90+
91+ try {
92+ val sessionCredentials =
93+ authenticationClient.fetchSessionToken(refreshToken)
94+ .execute()
95+ saveSsoCredentials(sessionCredentials)
96+ callback.onSuccess(sessionCredentials)
97+ } catch (error: AuthenticationException ) {
98+ val exception = when {
99+ error.isRefreshTokenDeleted ||
100+ error.isInvalidRefreshToken -> CredentialsManagerException .Code .RENEW_FAILED
101+
102+ error.isNetworkError -> CredentialsManagerException .Code .NO_NETWORK
103+ else -> CredentialsManagerException .Code .API_ERROR
104+ }
105+ callback.onFailure(
106+ CredentialsManagerException (
107+ exception,
108+ error
109+ )
110+ )
111+ }
112+ }
113+ }
114+
115+ /* *
116+ * Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
117+ * if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
118+ * if a new one is issued
119+ */
120+ @JvmSynthetic
121+ @Throws(CredentialsManagerException ::class )
122+ override suspend fun awaitSsoCredentials (): SSOCredentials {
123+ return suspendCancellableCoroutine { continuation ->
124+ getSsoCredentials(object : Callback <SSOCredentials , CredentialsManagerException > {
125+ override fun onSuccess (result : SSOCredentials ) {
126+ continuation.resume(result)
127+ }
128+
129+ override fun onFailure (error : CredentialsManagerException ) {
130+ continuation.resumeWithException(error)
131+ }
132+ })
133+ }
134+ }
135+
56136 /* *
57137 * Retrieves the credentials from the storage and refresh them if they have already expired.
58138 * It will throw [CredentialsManagerException] if the saved access_token or id_token is null,
@@ -144,8 +224,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
144224 forceRefresh : Boolean
145225 ): Credentials {
146226 return suspendCancellableCoroutine { continuation ->
147- getCredentials(
148- scope,
227+ getCredentials(scope,
149228 minTtl,
150229 parameters,
151230 headers,
@@ -299,8 +378,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
299378 if (willAccessTokenExpire) {
300379 val tokenLifetime = (expiresAt - currentTimeInMillis - minTtl * 1000 ) / - 1000
301380 val wrongTtlException = CredentialsManagerException (
302- CredentialsManagerException .Code .LARGE_MIN_TTL ,
303- String .format(
381+ CredentialsManagerException .Code .LARGE_MIN_TTL , String .format(
304382 Locale .getDefault(),
305383 " The lifetime of the renewed Access Token (%d) is less than the minTTL requested (%d). Increase the 'Token Expiration' setting of your Auth0 API in the dashboard, or request a lower minTTL." ,
306384 tokenLifetime,
@@ -326,15 +404,14 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
326404 callback.onSuccess(credentials)
327405 } catch (error: AuthenticationException ) {
328406 val exception = when {
329- error.isRefreshTokenDeleted ||
330- error.isInvalidRefreshToken -> CredentialsManagerException . Code . RENEW_FAILED
407+ error.isRefreshTokenDeleted || error.isInvalidRefreshToken -> CredentialsManagerException . Code . RENEW_FAILED
408+
331409 error.isNetworkError -> CredentialsManagerException .Code .NO_NETWORK
332410 else -> CredentialsManagerException .Code .API_ERROR
333411 }
334412 callback.onFailure(
335413 CredentialsManagerException (
336- exception,
337- error
414+ exception, error
338415 )
339416 )
340417 }
@@ -364,8 +441,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
364441 val emptyCredentials =
365442 TextUtils .isEmpty(accessToken) && TextUtils .isEmpty(idToken) || expiresAt == null
366443 return ! (emptyCredentials || willExpire(
367- expiresAt!! ,
368- minTtl
444+ expiresAt!! , minTtl
369445 ) && refreshToken == null )
370446 }
371447
0 commit comments