/* * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. * All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.mobileconnectors.cognitoauth.util; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import com.amazonaws.internal.keyvaluestore.AWSKeyValueStore; import com.amazonaws.mobileconnectors.cognitoauth.tokens.AccessToken; import com.amazonaws.mobileconnectors.cognitoauth.tokens.IdToken; import com.amazonaws.mobileconnectors.cognitoauth.tokens.RefreshToken; import com.amazonaws.mobileconnectors.cognitoauth.AuthUserSession; import com.amazonaws.util.StringUtils; import java.security.InvalidParameterException; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; import java.util.Set; /** * Handles tokens caching in the device local storage. *

* Uses SharedPreferences in Android for local storage. *

* Uses {@link SharedPreferences} to cache tokens. */ public final class LocalDataManager { final static String TAG = LocalDataManager.class.getSimpleName(); /** * Returns the last authenticated user on this device. * @param context Required, Android application {@link Context}. * @param clientId Required, User Pool App Domain. * @return the last authenticated user on this device. */ public static String getLastAuthUser(final Context context, final String clientId) { if (context == null || clientId == null) { throw new InvalidParameterException( "Application context, and application domain cannot be null"); } try { SharedPreferences localCache = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, Context.MODE_PRIVATE); String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); return localCache.getString(lastAuthUserKey, null); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return null; } /** * Returns the last authenticated user on this device. * @param context Required, Android application {@link Context}. * @param clientId Required, User Pool App Domain. * @return the last authenticated user on this device. */ public static String getLastAuthUser(AWSKeyValueStore awsKeyValueStore, final Context context, final String clientId) { if (context == null || clientId == null) { throw new InvalidParameterException( "Application context, and application domain cannot be null"); } try { String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); return awsKeyValueStore.get(lastAuthUserKey); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return null; } /** * Returns cached tokens for a user as a {@link AuthUserSession}. * @param context Required: The host application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. * @return {@link AuthUserSession}. */ public static AuthUserSession getCachedSession(final Context context, final String clientId, final String username, final Set scopes) { AuthUserSession session = new AuthUserSession(null, null, null); if (username != null) { if (context == null || clientId == null || clientId.isEmpty()) { throw new InvalidParameterException( "Application context, and application domain cannot be null"); } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); try { SharedPreferences localCache = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, Context.MODE_PRIVATE); Set cachedScopes = localCache.getStringSet(cachedTokenScopes, new HashSet()); // Check if the requested scopes match scopes of the cached tokens. if (!cachedScopes.equals(scopes)) { return session; } // Scopes match, return the cached tokens IdToken idToken = new IdToken(localCache.getString(cachedIdTokenKey, null)); AccessToken accessToken = new AccessToken(localCache.getString(cachedAccessTokenKey, null)); RefreshToken refreshToken = new RefreshToken(localCache.getString(cachedRefreshTokenKey, null)); session = new AuthUserSession(idToken, accessToken, refreshToken); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } } return session; } /** * Returns cached tokens for a user as a {@link AuthUserSession}. * @param context Required: The host application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. * @return {@link AuthUserSession}. */ public static AuthUserSession getCachedSession(final AWSKeyValueStore awsKeyValueStore, final Context context, final String clientId, final String username, final Set scopes) { AuthUserSession session = new AuthUserSession(null, null, null); if (username != null) { if (context == null || clientId == null || clientId.isEmpty()) { throw new InvalidParameterException( "Application context, and application domain cannot be null"); } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); try { String cachedSetString = awsKeyValueStore.get(cachedTokenScopes); Set cachedScopes = setFromString(cachedSetString); // Check if the requested scopes match scopes of the cached tokens. if (!cachedScopes.equals(scopes)) { return session; } // Scopes match, return the cached tokens IdToken idToken = new IdToken(awsKeyValueStore.get(cachedIdTokenKey)); AccessToken accessToken = new AccessToken(awsKeyValueStore.get(cachedAccessTokenKey)); RefreshToken refreshToken = new RefreshToken(awsKeyValueStore.get(cachedRefreshTokenKey)); session = new AuthUserSession(idToken, accessToken, refreshToken); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } } return session; } /** * Caches tokens for a user. * @param context Required: Android application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. * @param session Required: User tokens as {@link AuthUserSession}. */ public static void cacheSession(final Context context, final String clientId, final String username, final AuthUserSession session, final Set scopes) { if (context == null || clientId == null || clientId.isEmpty() || username == null || session == null) { Log.e (TAG, "Application context, and application domain cannot be null"); return; } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenTypeKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_TYPE); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); try { SharedPreferences.Editor localCacheEditor = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, Context.MODE_PRIVATE).edit(); localCacheEditor.putString(cachedTokenTypeKey, ClientConstants.SESSION_TYPE_JWT); localCacheEditor.putString(cachedIdTokenKey, session.getIdToken().getJWTToken()); localCacheEditor.putString(cachedAccessTokenKey, session.getAccessToken().getJWTToken()); localCacheEditor.putString(cachedRefreshTokenKey, session.getRefreshToken().getToken()); if (scopes != null && scopes.size() > 0) { localCacheEditor.putStringSet(cachedTokenScopes, scopes); } localCacheEditor.putString(lastAuthUserKey, username).apply(); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /** * Caches tokens for a user. * @param context Required: Android application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. * @param session Required: User tokens as {@link AuthUserSession}. */ public static void cacheSession(final AWSKeyValueStore awsKeyValueStore, final Context context, final String clientId, final String username, final AuthUserSession session, final Set scopes) { if (context == null || clientId == null || clientId.isEmpty() || username == null || session == null) { Log.e (TAG, "Application context, and application domain cannot be null"); return; } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenTypeKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_TYPE); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); try { awsKeyValueStore.put(cachedTokenTypeKey, ClientConstants.SESSION_TYPE_JWT); awsKeyValueStore.put(cachedIdTokenKey, session.getIdToken().getJWTToken()); awsKeyValueStore.put(cachedAccessTokenKey, session.getAccessToken().getJWTToken()); awsKeyValueStore.put(cachedRefreshTokenKey, session.getRefreshToken().getToken()); if (scopes != null && scopes.size() > 0) { awsKeyValueStore.put(cachedTokenScopes, setToString(scopes)); } awsKeyValueStore.put(lastAuthUserKey, username); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /** * Caches proof key. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @param proofKey Required: The generated proof-key for token exchange. * @param scopes Required: Scopes for the current token request. */ public static void cacheState(final Context context, final String key, final String proofKey, final Set scopes) { try { SharedPreferences.Editor localCacheEditor = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, Context.MODE_PRIVATE).edit(); localCacheEditor.putString(key+ ClientConstants.DOMAIN_QUERY_PARAM_CODE_CHALLENGE, proofKey); localCacheEditor.putStringSet(key+ ClientConstants.DOMAIN_QUERY_PARAM_SCOPES, scopes).apply(); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /** * Caches proof key. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @param proofKey Required: The generated proof-key for token exchange. * @param scopes Required: Scopes for the current token request. */ public static void cacheState(final AWSKeyValueStore awsKeyValueStore, final Context context, final String key, final String proofKey, final Set scopes) { try { awsKeyValueStore.put(key + ClientConstants.DOMAIN_QUERY_PARAM_CODE_CHALLENGE, proofKey); awsKeyValueStore.put(key + ClientConstants.DOMAIN_QUERY_PARAM_SCOPES, setToString(scopes)); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /** * Returns proof-key for current token request. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @return Cached proof-key. */ public static String getCachedProofKey(final Context context, final String key) { try { SharedPreferences localCache = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, 0); return localCache.getString(key+ ClientConstants.DOMAIN_QUERY_PARAM_CODE_CHALLENGE, null); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return null; } /** * Returns proof-key for current token request. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @return Cached proof-key. */ public static String getCachedProofKey(final AWSKeyValueStore awsKeyValueStore, final Context context, final String key) { try { return awsKeyValueStore.get(key + ClientConstants.DOMAIN_QUERY_PARAM_CODE_CHALLENGE); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return null; } /** * Returns scopes for a request. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @return Cached scopes. */ public static Set getCachedScopes(final Context context, final String key) { Set cachedSet = new HashSet(); try { SharedPreferences localCache = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, 0); return localCache.getStringSet(key+ ClientConstants.DOMAIN_QUERY_PARAM_SCOPES, cachedSet); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return cachedSet; } /** * Returns scopes for a request. * @param context Required: The host application {@link Context}. * @param key Required: The mapped key. * @return Cached scopes. */ public static Set getCachedScopes(final AWSKeyValueStore awsKeyValueStore, final Context context, final String key) { Set cachedSet = new HashSet(); try { String cachedSetString = awsKeyValueStore.get(key + ClientConstants.DOMAIN_QUERY_PARAM_SCOPES); return setFromString(cachedSetString); } catch (Exception e) { Log.e(TAG, "Failed to read from SharedPreferences", e); } return cachedSet; } /** * Clears all cached tokens for a user. * @param context Required: The host application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. */ public static void clearCache(final Context context, final String clientId, final String username) { if (username == null) { return; } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenTypeKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_TYPE); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); try { SharedPreferences.Editor localCacheEditor = context.getSharedPreferences(ClientConstants.APP_LOCAL_CACHE, Context.MODE_PRIVATE).edit(); localCacheEditor.remove(cachedIdTokenKey); localCacheEditor.remove(cachedAccessTokenKey); localCacheEditor.remove(cachedRefreshTokenKey); localCacheEditor.remove(cachedTokenTypeKey); localCacheEditor.remove(cachedTokenScopes); localCacheEditor.remove(lastAuthUserKey).apply(); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /** * Clears all cached tokens for a user. * @param context Required: The host application {@link Context}. * @param clientId Required: Cognito App/Client Id. * @param username Required: The username. */ public static void clearCache(final AWSKeyValueStore awsKeyValueStore, final Context context, final String clientId, final String username) { if (username == null) { return; } String cachedIdTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ID); String cachedAccessTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_ACCESS); String cachedRefreshTokenKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_TYPE_REFRESH); String cachedTokenTypeKey = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_TYPE); String cachedTokenScopes = String.format(Locale.US, "%s.%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, username, ClientConstants.TOKEN_KEY_SCOPES); String lastAuthUserKey = String.format(Locale.US, "%s.%s.%s", ClientConstants.APP_LOCAL_CACHE_KEY_PREFIX, clientId, ClientConstants.APP_LAST_AUTH_USER); try { awsKeyValueStore.remove(cachedIdTokenKey); awsKeyValueStore.remove(cachedAccessTokenKey); awsKeyValueStore.remove(cachedRefreshTokenKey); awsKeyValueStore.remove(cachedTokenTypeKey); awsKeyValueStore.remove(cachedTokenScopes); awsKeyValueStore.remove(lastAuthUserKey); } catch (Exception e) { Log.e(TAG, "Failed while writing to SharedPreferences", e); } } /* * Clears all cached tokens for everyone * @param context Required: The host application {@link Context}. */ public static void clearCacheAll(final AWSKeyValueStore awsKeyValueStore) { try { awsKeyValueStore.clear(); } catch (Exception e) { Log.e(TAG, "Failed while clearing data from SharedPreferences", e); } } static String setToString(Set stringSet) { StringBuilder strBuilder = new StringBuilder(); int index = 0; for (String str: stringSet) { strBuilder.append(str); if (index++ < stringSet.size() - 1) { strBuilder.append(","); } } return strBuilder.toString(); } static Set setFromString(String str) { final HashSet stringSet = new HashSet(); if (StringUtils.isBlank(str)) { return stringSet; } String[] stringArray = str.split(","); stringSet.addAll(Arrays.asList(stringArray)); return stringSet; } }