/* * Copyright 2017-2019 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.mobile.client; import android.Manifest; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.util.Log; import androidx.annotation.AnyThread; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import androidx.browser.customtabs.CustomTabsCallback; import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsServiceConnection; import androidx.browser.customtabs.CustomTabsSession; import androidx.core.content.ContextCompat; import com.amazonaws.AmazonClientException; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSAbstractCognitoIdentityProvider; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.AWSSessionCredentials; import com.amazonaws.auth.AnonymousAWSCredentials; import com.amazonaws.auth.CognitoCachingCredentialsProvider; import com.amazonaws.mobile.auth.core.IdentityManager; import com.amazonaws.mobile.auth.core.SignInStateChangeListener; import com.amazonaws.mobile.auth.core.StartupAuthResultHandler; import com.amazonaws.mobile.auth.core.signin.SignInManager; import com.amazonaws.mobile.auth.core.signin.SignInProvider; import com.amazonaws.mobile.auth.facebook.FacebookButton; import com.amazonaws.mobile.auth.facebook.FacebookSignInProvider; import com.amazonaws.mobile.auth.google.GoogleButton; import com.amazonaws.mobile.auth.google.GoogleSignInProvider; import com.amazonaws.mobile.auth.ui.AuthUIConfiguration; import com.amazonaws.mobile.auth.ui.SignInUI; import com.amazonaws.mobile.auth.userpools.CognitoUserPoolsSignInProvider; import com.amazonaws.mobile.client.internal.InternalCallback; import com.amazonaws.mobile.client.internal.ReturningRunnable; import com.amazonaws.mobile.client.internal.oauth2.AuthorizeResponse; import com.amazonaws.mobile.client.internal.oauth2.OAuth2Client; import com.amazonaws.mobile.client.internal.oauth2.OAuth2Tokens; import com.amazonaws.mobile.client.results.ForgotPasswordResult; import com.amazonaws.mobile.client.results.ForgotPasswordState; import com.amazonaws.mobile.client.results.SignInResult; import com.amazonaws.mobile.client.results.SignInState; import com.amazonaws.mobile.client.results.SignUpResult; import com.amazonaws.mobile.client.results.Tokens; import com.amazonaws.mobile.client.results.UserCodeDeliveryDetails; import com.amazonaws.mobile.config.AWSConfigurable; import com.amazonaws.mobile.config.AWSConfiguration; import com.amazonaws.mobileconnectors.cognitoauth.Auth; import com.amazonaws.mobileconnectors.cognitoauth.AuthUserSession; import com.amazonaws.mobileconnectors.cognitoauth.handlers.AuthHandler; import com.amazonaws.mobileconnectors.cognitoauth.util.ClientConstants; import com.amazonaws.mobileconnectors.cognitoauth.util.Pkce; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoDevice; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserAttributes; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserCodeDeliveryDetails; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserDetails; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserSession; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationDetails; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ChallengeContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.CognitoIdentityProviderContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ForgotPasswordContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.MultiFactorAuthenticationContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.NewPasswordContinuation; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.AuthenticationHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.ForgotPasswordHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.GenericHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.GetDetailsHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.SignUpHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.UpdateAttributesHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.VerificationHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoJWTParser; import com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoPinpointSharedContext; import com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoServiceConstants; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentity; import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentityClient; import com.amazonaws.services.cognitoidentity.model.NotAuthorizedException; import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProvider; import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient; import com.amazonaws.services.cognitoidentityprovider.model.AuthFlowType; import com.amazonaws.services.cognitoidentityprovider.model.GlobalSignOutRequest; import com.amazonaws.services.cognitoidentityprovider.model.InvalidUserPoolConfigurationException; import com.amazonaws.util.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static com.amazonaws.mobile.client.results.SignInState.CUSTOM_CHALLENGE; import static com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoServiceConstants.AUTH_TYPE_INIT_CUSTOM_AUTH; import static com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoServiceConstants.AUTH_TYPE_INIT_USER_PASSWORD; import static com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoServiceConstants.CHLG_TYPE_USER_PASSWORD; /** * The AWSMobileClient provides client APIs and building blocks for developers who want to create * user authentication experiences. This includes declarative methods for performing * authentication actions, a simple “drop-in auth” UI for performing common tasks, automatic * token and credentials management, and state tracking with notifications for performing * workflows in your application when users have authenticated. * * The following demonstrates a simple sample usage inside of MainActivity.java onCreate method. *
     * AWSMobileClient.getInstance().initialize(getApplicationContext(), new Callback<UserStateDetails>() {
 *     public void onResult(UserStateDetails userStateDetails) {
 *         switch (userStateDetails.getUserState()) {
 *             case SIGNED_IN:
 *                 break;
 *             case SIGNED_OUT:
 *                 try {
 *                     AWSMobileClient.getInstance().showSignIn(MainActivity.this);
 *                 } catch (Exception e) {
 *                     Log.e("TAG", "", e);
 *                 }
 *                 break;
 *             default:
 *                 Log.w("Unhandled state see UserState for a list of states");
 *                 break;
 *         }
 *     }
 * })
 * }
 * 
*/ public final class AWSMobileClient implements AWSCredentialsProvider { /** * Log Tag. */ private static final String TAG = AWSMobileClient.class.getSimpleName(); public static final String DEFAULT_USER_AGENT = "AWSMobileClient"; static final String AUTH_KEY = "Auth"; static final String SHARED_PREFERENCES_KEY = "com.amazonaws.mobile.client"; static final String PROVIDER_KEY = "provider"; static final String TOKEN_KEY = "token"; static final String IDENTITY_ID_KEY = "cognitoIdentityId"; static final String SIGN_IN_MODE = "signInMode"; public static final String HOSTED_UI_KEY = "hostedUI"; /// This value is a boolean stored as a String 'true' 'false' static final String FEDERATION_ENABLED_KEY = "isFederationEnabled"; private static final String CUSTOM_ROLE_ARN_KEY = "customRoleArn"; public static final String CHALLENGE_RESPONSE_NEW_PASSWORD_KEY = CognitoServiceConstants.CHLG_RESP_NEW_PASSWORD; public static final String CHALLENGE_RESPONSE_USER_ATTRIBUTES_PREFIX_KEY = CognitoServiceConstants.CHLG_PARAM_USER_ATTRIBUTE_PREFIX; /** * Configuration keys for SignInProviders in awsconfiguration.json. */ private static final String USER_POOLS = "CognitoUserPool"; private static final String FACEBOOK = "FacebookSignIn"; private static final String GOOGLE = "GoogleSignIn"; private static final String GOOGLE_WEBAPP_CONFIG_KEY = "ClientId-WebApp"; /** * Configuration key for Cognito User Pool Custom Endpoint */ private static final String COGNITO_USERPOOL_CUSTOM_ENDPOINT = "Endpoint"; /** * Singleton instance for AWSMobileClient. */ private static volatile AWSMobileClient singleton = null; /** * Map of SDK Client Class and object. */ private final LinkedHashMap, AWSConfigurable> clientMap; /** * AWSConfiguration object that represents the `awsconfiguration.json` file. */ AWSConfiguration awsConfiguration; /** * Federation into this identity pool */ CognitoCachingCredentialsProvider cognitoIdentity; /** * Object that encapuslates the high-level Cognito UserPools client */ CognitoUserPool userpool; String userpoolsLoginKey; Context mContext; Map mFederatedLoginsMap; private UserStateDetails userStateDetails; private Lock mWaitForSignInLock; private volatile CountDownLatch mSignedOutWaitLatch; CognitoUserSession mCognitoUserSession; // Sign-in continuation callbacks given by customer private Callback signInCallback; // Internal tracking continuation private MultiFactorAuthenticationContinuation signInMfaContinuation; private ChallengeContinuation signInChallengeContinuation; private SignInState signInState; // Forgot password continuation callbacks given by customer private Callback forgotPasswordCallback; // Forgot password continuation private ForgotPasswordContinuation forgotPasswordContinuation; // Sign-up user private CognitoUser signUpUser; // Setup TOTP callback given by customer // private Callback totpCallback; // Internal tracking setup TOTP // private String totpSessionToken; // private VerifyMfaContinuation totpContinuation; /** * CredentialsProvider created by the IdentityManager. */ private AWSCredentialsProvider awsCredentialsProvider; /** * Config of SignInProviders: class and permissions. */ private SignInProviderConfig[] signInProviderConfig; /** * Callback for resuming auth session. */ private StartupAuthResultHandler startupAuthResultHandler; /** * Callback for initalizing the SDK with AWSMobileClient. */ private AWSStartupHandler awsStartupHandler; /** * Flag to use default config automatically. * Use the default configuration information if TRUE. */ private boolean mIsLegacyMode; List listeners; private Object showSignInLockObject; private volatile CountDownLatch showSignInWaitLatch; private Object federateWithCognitoIdentityLockObject; private Object initLockObject; KeyValueStore mStore; AWSMobileClientCognitoIdentityProvider provider; DeviceOperations mDeviceOperations; AmazonCognitoIdentityProvider userpoolLL; Auth hostedUI; OAuth2Client mOAuth2Client; String mUserPoolPoolId; String userAgentOverride; enum SignInMode { SIGN_IN("0"), FEDERATED_SIGN_IN("1"), HOSTED_UI("2"), OAUTH2("3"), UNKNOWN("-1"), ; String encode; SignInMode(final String encode) { this.encode = encode; } public String toString() { return encode; } static SignInMode fromString(final String str) { if ("0".equals(str)) { return SIGN_IN; } else if ("1".equals(str)) { return FEDERATED_SIGN_IN; } else if ("2".equals(str)) { return HOSTED_UI; } else if ("3".equals(str)) { return OAUTH2; } return UNKNOWN; } } /** * Flag that indicates if the tokens would be persisted in SharedPreferences. * By default, this is set to true. If set to false, the tokens would be * kept in memory. */ boolean mIsPersistenceEnabled = true; /** * Constructor invoked by getInstance. * * @throws AssertionError when this is called with context more than once. */ private AWSMobileClient() { if (singleton != null) { throw new AssertionError(); } this.clientMap = new LinkedHashMap, AWSConfigurable>(); userpoolsLoginKey = ""; mWaitForSignInLock = new ReentrantLock(); mFederatedLoginsMap = new HashMap(); listeners = new ArrayList(); showSignInLockObject = new Object(); federateWithCognitoIdentityLockObject = new Object(); showSignInWaitLatch = new CountDownLatch(1); initLockObject = new Object(); mStore = new DummyStore(); } /** * Gets the singleton instance of this class. * * @return singleton instance */ public static synchronized AWSMobileClient getInstance() { if (singleton == null) { singleton = new AWSMobileClient(); } return singleton; } /** * For unit testing purposes only so we can force a new instance of * mobile client to be created. * @param forceGetNew True if a new instance of AWSMobileClient should be created. * @return A new instance of AWSMobileClient. */ @VisibleForTesting static synchronized AWSMobileClient getInstance(boolean forceGetNew) { if (forceGetNew) { singleton = null; } return new AWSMobileClient(); } @VisibleForTesting void setUserPool(CognitoUserPool userpool) { this.userpool = userpool; } /** * Retrieve the AWSConfiguration object that represents * the awsconfiguration.json file. * * @return the AWSConfiguration object */ public AWSConfiguration getConfiguration() { return this.awsConfiguration; } @Override public AWSCredentials getCredentials() { if (isLegacyMode()) { return IdentityManager.getDefaultIdentityManager().getCredentialsProvider().getCredentials(); } if (cognitoIdentity == null) { throw new AmazonClientException("Cognito Identity not configured"); } try { if (waitForSignIn()) { Log.d(TAG, "getCredentials: Validated user is signed-in"); } AWSSessionCredentials credentials = cognitoIdentity.getCredentials(); mStore.set(IDENTITY_ID_KEY, cognitoIdentity.getIdentityId()); return credentials; } catch (NotAuthorizedException e) { Log.w(TAG, "getCredentials: Failed to getCredentials from Cognito Identity", e); throw new AmazonClientException("Failed to get credentials from Cognito Identity", e); } catch (Exception e) { throw new AmazonClientException("Failed to get credentials from Cognito Identity", e); } } @Override public void refresh() { if (isLegacyMode()) { IdentityManager.getDefaultIdentityManager().getCredentialsProvider().refresh(); return; } if (cognitoIdentity == null) { throw new AmazonClientException("Cognito Identity not configured"); } cognitoIdentity.refresh(); mStore.set(IDENTITY_ID_KEY, cognitoIdentity.getIdentityId()); } /** * Returns AWSCredentials obtained from Cognito Identity * @return AWSCredentials obtained from Cognito Identity * @throws Exception */ @WorkerThread public AWSCredentials getAWSCredentials() throws Exception { return _getAWSCredentials().await(); } @AnyThread public void getAWSCredentials(final Callback callback) { _getAWSCredentials().async(callback); } private ReturningRunnable _getAWSCredentials() { return new ReturningRunnable() { @Override public AWSCredentials run() { return getCredentials(); } }; } @AnyThread public String getIdentityId() { if (isLegacyMode()) { return IdentityManager.getDefaultIdentityManager().getCachedUserID(); } if (cognitoIdentity == null) { throw new RuntimeException("Cognito Identity not configured"); } final String cachedIdentityId = cognitoIdentity.getCachedIdentityId(); if (cachedIdentityId == null) { return mStore.get(IDENTITY_ID_KEY); } return cachedIdentityId; } boolean isLegacyMode() { return mIsLegacyMode; } @AnyThread public void initialize(final Context context, final Callback callback) { final Context applicationContext = context.getApplicationContext(); initialize(applicationContext, new AWSConfiguration(applicationContext), callback); } @AnyThread public void initialize(final Context context, final AWSConfiguration awsConfig, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_initialize(context, awsConfig, internalCallback)); } CountDownLatch getSignInUILatch() { return showSignInWaitLatch; } protected Runnable _initialize(final Context context, final AWSConfiguration awsConfiguration, final Callback callback) { return new Runnable() { public void run() { synchronized (initLockObject) { if (AWSMobileClient.this.awsConfiguration != null) { callback.onResult(getUserStateDetails(true)); return; } mIsPersistenceEnabled = true; // Default value // Read Persistence key from the awsconfiguration.json and set the flag // appropriately. try { if (awsConfiguration.optJsonObject(AUTH_KEY) != null && awsConfiguration.optJsonObject(AUTH_KEY).has("Persistence")) { mIsPersistenceEnabled = awsConfiguration .optJsonObject(AUTH_KEY) .getBoolean("Persistence"); } } catch (final Exception ex) { // If reading from awsconfiguration.json fails, invoke callback. callback.onError(new RuntimeException("Failed to initialize AWSMobileClient; please check your awsconfiguration.json", ex)); return; } userAgentOverride = awsConfiguration.getUserAgentOverride(); mContext = context.getApplicationContext(); mStore = new AWSMobileClientStore(AWSMobileClient.this); final IdentityManager identityManager = new IdentityManager(mContext); identityManager.enableFederation(false); identityManager.setConfiguration(awsConfiguration); identityManager.setPersistenceEnabled(mIsPersistenceEnabled); IdentityManager.setDefaultIdentityManager(identityManager); registerConfigSignInProviders(awsConfiguration); identityManager.addSignInStateChangeListener(new SignInStateChangeListener() { @Override public void onUserSignedIn() { Log.d(TAG, "onUserSignedIn: Updating user state from drop-in UI"); signInState = SignInState.DONE; com.amazonaws.mobile.auth.core.IdentityProvider currentIdentityProvider = identityManager.getCurrentIdentityProvider(); String token = currentIdentityProvider.getToken(); String providerKey = currentIdentityProvider.getCognitoLoginKey(); federatedSignInWithoutAssigningState(providerKey, token, new Callback() { @Override public void onResult(UserStateDetails result) { Log.d(TAG, "onResult: showSignIn federated"); setUserState(getUserStateDetails(false)); getSignInUILatch().countDown(); } @Override public void onError(Exception e) { Log.w(TAG, "onError: User sign-in had errors from drop-in UI", e); setUserState(getUserStateDetails(false)); getSignInUILatch().countDown(); } }); } @Override public void onUserSignedOut() { Log.d(TAG, "onUserSignedOut: Updating user state from drop-in UI"); setUserState(getUserStateDetails(false)); showSignInWaitLatch.countDown(); } }); if (awsConfiguration.optJsonObject("CredentialsProvider") != null && awsConfiguration.optJsonObject("CredentialsProvider").optJSONObject("CognitoIdentity") != null) { try { JSONObject identityPoolJSON = awsConfiguration.optJsonObject( "CredentialsProvider").getJSONObject("CognitoIdentity").getJSONObject(awsConfiguration.getConfiguration()); final String poolId = identityPoolJSON.getString("PoolId"); final String regionStr = identityPoolJSON.getString("Region"); final ClientConfiguration clientConfig = new ClientConfiguration(); clientConfig.setUserAgent(DEFAULT_USER_AGENT + " " + awsConfiguration.getUserAgent()); if (userAgentOverride != null) { clientConfig.setUserAgentOverride(userAgentOverride); } AmazonCognitoIdentityClient cibClient = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials(), clientConfig); cibClient.setRegion(Region.getRegion(regionStr)); provider = new AWSMobileClientCognitoIdentityProvider( null, poolId, cibClient); cognitoIdentity = new CognitoCachingCredentialsProvider( mContext, provider, Regions.fromName(regionStr)); cognitoIdentity.setPersistenceEnabled(mIsPersistenceEnabled); if (userAgentOverride != null) { cognitoIdentity.setUserAgentOverride(userAgentOverride); } } catch (Exception e) { callback.onError(new RuntimeException("Failed to initialize Cognito Identity; please check your awsconfiguration.json", e)); return; } } final JSONObject userPoolJSON = awsConfiguration.optJsonObject("CognitoUserPool"); if (userPoolJSON != null) { try { mUserPoolPoolId = userPoolJSON.getString("PoolId"); final String clientId = userPoolJSON.getString("AppClientId"); final String clientSecret = userPoolJSON.optString("AppClientSecret"); // only attach user pool to Pinpoint if customer specifies an app ID String pinpointAppId = userPoolJSON.optString("PinpointAppId"); pinpointAppId = pinpointAppId.equals("") ? null : pinpointAppId; final String cognitoUserPoolCustomEndpoint = userPoolJSON.optString(COGNITO_USERPOOL_CUSTOM_ENDPOINT); final ClientConfiguration clientConfig = new ClientConfiguration(); clientConfig.setUserAgent(DEFAULT_USER_AGENT + " " + awsConfiguration.getUserAgent()); if (userAgentOverride != null) { clientConfig.setUserAgentOverride(userAgentOverride); } userpoolLL = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), clientConfig); userpoolLL.setRegion(com.amazonaws.regions.Region.getRegion(Regions.fromName(userPoolJSON.getString("Region")))); userpoolsLoginKey = String.format("cognito-idp.%s.amazonaws.com/%s", userPoolJSON.getString("Region"), userPoolJSON.getString("PoolId")); userpool = new CognitoUserPool(mContext, mUserPoolPoolId, clientId, clientSecret, userpoolLL, pinpointAppId, cognitoUserPoolCustomEndpoint); userpool.setPersistenceEnabled(mIsPersistenceEnabled); mDeviceOperations = new DeviceOperations(AWSMobileClient.this, userpoolLL); } catch (Exception e) { callback.onError(new RuntimeException("Failed to initialize Cognito Userpool; please check your awsconfiguration.json", e)); return; } } JSONObject hostedUIJSON = getHostedUIJSON(awsConfiguration); if (hostedUIJSON != null) { try { // Pre-warm the Custom Tabs based on if (hostedUIJSON.has("TokenURI")) { Log.d(TAG, "initialize: OAuth2 client detected"); mOAuth2Client = new OAuth2Client(mContext, AWSMobileClient.this); mOAuth2Client.setPersistenceEnabled(mIsPersistenceEnabled); mOAuth2Client.setUserAgentOverride(userAgentOverride); } else { _initializeHostedUI(hostedUIJSON); } } catch (Exception e) { callback.onError(new RuntimeException("Failed to initialize OAuth, please check your awsconfiguration.json", e)); } } if (cognitoIdentity == null && userpool == null) { callback.onError(new RuntimeException( "Neither Cognito Identity or Cognito UserPool was used." + " At least one must be present to use AWSMobileClient.")); return; } AWSMobileClient.this.awsConfiguration = awsConfiguration; final UserStateDetails userStateDetails = getUserStateDetails(true); callback.onResult(userStateDetails); setUserState(userStateDetails); } } }; } private void _initializeHostedUI(JSONObject hostedUIJSON) throws JSONException { Log.d(TAG, "initialize: Cognito HostedUI client detected"); final JSONArray scopesJSONArray = hostedUIJSON.getJSONArray("Scopes"); final Set scopes = new HashSet(); for (int i = 0; i < scopesJSONArray.length(); i++) { scopes.add(scopesJSONArray.getString(i)); } if (mUserPoolPoolId == null) { throw new IllegalStateException("User pool Id must be available through user pool setting"); } hostedUI = getHostedUI(hostedUIJSON) .setPersistenceEnabled(mIsPersistenceEnabled) .setAuthHandler(new AuthHandler() { @Override public void onSuccess(AuthUserSession session) { // Ignored because this is used to pre-warm the session } @Override public void onSignout() { // Ignored because this is used to pre-warm the session } @Override public void onFailure(Exception e) { // Ignored because this is used to pre-warm the session } }) .build(); } JSONObject getHostedUIJSONFromJSON() { return getHostedUIJSONFromJSON(this.awsConfiguration); } JSONObject getHostedUIJSONFromJSON(final AWSConfiguration awsConfig) { final JSONObject mobileClientJSON = awsConfig.optJsonObject(AUTH_KEY); if (mobileClientJSON != null && mobileClientJSON.has("OAuth")) { try { JSONObject hostedUIJSONFromJSON = mobileClientJSON.getJSONObject("OAuth"); return hostedUIJSONFromJSON; } catch (Exception e) { Log.w(TAG, "getHostedUIJSONFromJSON: Failed to read config", e); } } return null; } JSONObject getHostedUIJSON() { return getHostedUIJSON(this.awsConfiguration); } JSONObject getHostedUIJSON(final AWSConfiguration awsConfig) { try { JSONObject hostedUIJSONFromJSON = getHostedUIJSONFromJSON(awsConfig); final String hostedUIString = mStore.get(HOSTED_UI_KEY); JSONObject hostedUIJSON = null; try { hostedUIJSON = new JSONObject(hostedUIString); } catch (Exception e) { Log.w(TAG, "Failed to parse HostedUI settings from store", e); } // Since there is no file watcher to keep track of when config file changes, this logic is intended to always check if the config file is different from mstore cache and updates the later accordingly. // If config file cannot be loaded, the mstore data still prevails. if (hostedUIJSONFromJSON != null && (hostedUIJSON == null || hostedUIJSON.toString() != hostedUIJSONFromJSON.toString())) { hostedUIJSON = new JSONObject(hostedUIJSONFromJSON.toString()); mStore.set(HOSTED_UI_KEY, hostedUIJSON.toString()); } return hostedUIJSON; } catch (Exception e) { Log.d(TAG, "getHostedUIJSON: Failed to read config", e); } return null; } Auth.Builder getHostedUI(final JSONObject hostedUIJSON) throws JSONException { final JSONArray scopesJSONArray = hostedUIJSON.getJSONArray("Scopes"); final Set scopes = new HashSet(); for (int i = 0; i < scopesJSONArray.length(); i++) { scopes.add(scopesJSONArray.getString(i)); } return new Auth.Builder() .setApplicationContext(mContext) .setUserPoolId(mUserPoolPoolId) .setAppClientId(hostedUIJSON.getString("AppClientId")) .setAppClientSecret(hostedUIJSON.optString("AppClientSecret", null)) .setAppCognitoWebDomain(hostedUIJSON.getString("WebDomain")) .setSignInRedirect(hostedUIJSON.getString("SignInRedirectURI")) .setSignOutRedirect(hostedUIJSON.getString("SignOutRedirectURI")) .setScopes(scopes) .setAdvancedSecurityDataCollection(false) .setIdentityProvider(hostedUIJSON.optString("IdentityProvider")) .setIdpIdentifier(hostedUIJSON.optString("IdpIdentifier")); } /** * Retrieve a handle to perform device related operations. * This is only available for Cognito User Pools and it must be configured in the client. * * @return a handle for device operations */ @AnyThread public DeviceOperations getDeviceOperations() { if (mDeviceOperations == null) { throw new AmazonClientException("Please check if userpools is configured."); } return mDeviceOperations; } /** * Release the wait for tokens to be refreshed * Doing this fails all pending operations that were * waiting for sign-in. */ @AnyThread public void releaseSignInWait() { if (mSignedOutWaitLatch != null) { mSignedOutWaitLatch.countDown(); } } protected void setUserState(final UserStateDetails details) { final boolean hasChanged = !details.equals(this.userStateDetails); this.userStateDetails = details; if (hasChanged) { synchronized (listeners) { for (final UserStateListener listener : listeners) { new Thread(new Runnable() { @Override public void run() { listener.onUserStateChanged(details); } }).start(); } } } } /** * Uses Android system commands to determine if the device is online. * Requires ACCESS_NETWORK_STATE. * * @param context application context * @return true if permission to access network state is granted and network is connected. */ protected boolean isNetworkAvailable(final Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_NETWORK_STATE); if (hasReadExternalStoragePermission != PackageManager.PERMISSION_GRANTED) { return false; } } try { ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = manager.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { return true; } } catch (Exception e) { Log.w(TAG, "Could not access network state", e); } return false; } boolean isUserpoolsSignedIn() { return userpoolsLoginKey.equals(mStore.get(PROVIDER_KEY)); } /** * Returns the username attribute of the current access token. Note that the value stored in the username * attribute of the access token may vary depending on how sign-in was performed. For example, if the user signed in * with email, the username attribute will have the email address. * @return The username attribute of the current access token. * @see Using the Access Token * from Cognito documentation. */ @AnyThread public String getUsername() { try { if (userpoolsLoginKey.equals(mStore.get(PROVIDER_KEY))) { return userpool.getCurrentUser().getUserId(); } return null; } catch (Exception e) { return null; } } /** * Returns the Cognito User Sub attribute of the current access token. * @return The sub attribute of the current access token. * @see Using the Access Token * from Cognito documentation. */ @AnyThread public String getUserSub() { try { if (userpoolsLoginKey.equals(mStore.get(PROVIDER_KEY))) { String token = mStore.get("token"); return CognitoJWTParser.getClaim(token, "sub"); } return null; } catch (Exception e) { return null; } } /** * Performs a check on the current UserState. When online, this method will attempt to * refresh tokens when they expired. Failing a refresh may cause the UserState to change. * * @return details about the user's state */ @WorkerThread public UserStateDetails currentUserState() { try { return _currentUserState().await(); } catch (Exception e) { throw new RuntimeException("Failed to retrieve user state.", e); } } /** * Performs a check on the current UserState. When online, this method will attempt to * refresh tokens when they expired. Failing a refresh may cause the UserState to change. * * @return details about the user's state */ @AnyThread public void currentUserState(final Callback callback) { _currentUserState().async(callback); } ReturningRunnable _currentUserState() { return new ReturningRunnable() { @Override public UserStateDetails run() throws Exception { return getUserStateDetails(false); } }; } /** * Adds a listener to be notified of state changes. * When encountering SIGNED_OUT_FEDERATED_TOKENS_INVALID * or SIGNED_OUT_USER_POOLS_TOKENS_INVALID * {@link #releaseSignInWait()} or any form of sign-in can be called * to prevent blocking {@link #getCredentials()}, {@link #getTokens()}, or other methods * requiring a sign-in. * @param listener */ @AnyThread public void addUserStateListener(final UserStateListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Removes a listener. This will hold onto references. * Remove when a lifecycle ends to prevent memory leaks. * @param listener * @return true if removed */ @AnyThread public boolean removeUserStateListener(final UserStateListener listener) { synchronized (listeners) { int index = listeners.indexOf(listener); if (index != -1) { this.listeners.remove(index); return true; } return false; } } String getLoginKey() { return userpoolsLoginKey; } /** * A variant of {@link #currentUserState()} that simplifies the output to a boolean. * True if SIGNED_IN, SIGNED_OUT_USER_POOLS_TOKENS_INVALID, SIGNED_OUT_FEDERATED_TOKENS_INVALID. * False if GUEST, SIGNED_OUT. * * @return see above */ @AnyThread public boolean isSignedIn() { final UserStateDetails userStateDetails = getUserStateDetails(true); switch (userStateDetails.getUserState()) { case SIGNED_IN: case SIGNED_OUT_USER_POOLS_TOKENS_INVALID: case SIGNED_OUT_FEDERATED_TOKENS_INVALID: return true; case GUEST: case SIGNED_OUT: return false; default: throw new IllegalStateException("Unknown user state, please report this exception"); } } /** * This method checks the current state of the user. * If the user's session is determined to be expired, then the {@link UserStateListener} will be * notified with @{@link UserState#SIGNED_OUT_USER_POOLS_TOKENS_INVALID} or * @{link UserState#SIGNED_OUT_FEDERATED_TOKENS_INVALID}. * @return true if user is signed-in, false otherwise */ protected boolean waitForSignIn() { UserStateDetails userStateDetails = null; try { mWaitForSignInLock.lock(); mSignedOutWaitLatch = new CountDownLatch(1); userStateDetails = getUserStateDetails(false); Log.d(TAG, "waitForSignIn: userState:" + userStateDetails.getUserState()); switch (userStateDetails.getUserState()) { case SIGNED_IN: setUserState(userStateDetails); return true; case GUEST: case SIGNED_OUT: setUserState(userStateDetails); return false; case SIGNED_OUT_USER_POOLS_TOKENS_INVALID: case SIGNED_OUT_FEDERATED_TOKENS_INVALID: if (userStateDetails.getException() == null || isSignedOutRelatedException(userStateDetails.getException())) { // The service has returned an exception that indicates the user is not authorized // Ask for another sign-in setUserState(userStateDetails); mSignedOutWaitLatch.await(); return getUserStateDetails(false).getUserState().equals(UserState.SIGNED_IN); } else { // The exception is non-conclusive whether the user is not authorized or // there was a network related issue. Throw the exception back to the API call. throw userStateDetails.getException(); } } } catch (Exception e) { throw new AmazonClientException("Operation requires a signed-in state", e); } finally { mWaitForSignInLock.unlock(); } return false; } Map getSignInDetailsMap() { return mStore.get(PROVIDER_KEY, TOKEN_KEY); } String _getCachedIdentityId() { return mStore.get(IDENTITY_ID_KEY); } /** * Has side-effect of attempting to alert developer to try and sign-in user * when required to be signed-in and will mutate the user's state. * * @param offlineCheck true, will determine if the tokens are expired or the credentials are expired and block for refresh * @return the current state of the user */ protected UserStateDetails getUserStateDetails(final boolean offlineCheck) { final Map details = getSignInDetailsMap(); final String providerKey = details.get(PROVIDER_KEY); final String token = details.get(TOKEN_KEY); final String identityId = _getCachedIdentityId(); final boolean federationEnabled = isFederationEnabled(); Log.d(TAG, "Inspecting user state details"); final boolean hasUsefulToken = providerKey != null && token != null; // Offline state detection if (offlineCheck || !isNetworkAvailable(mContext)) { if (hasUsefulToken) { return new UserStateDetails(UserState.SIGNED_IN, details); } else { if (identityId != null) { return new UserStateDetails(UserState.GUEST, details); } else { return new UserStateDetails(UserState.SIGNED_OUT, null); } } } if (getSignInMode().equals(SignInMode.HOSTED_UI)) { if (!federationEnabled || cognitoIdentity == null) { // Do nothing, you are signed-in by having the token Log.d(TAG, String.format("_hostedUISignIn without federation: Putting provider and token in store")); try { Tokens tokens = getHostedUITokens(); String idToken = tokens.getIdToken().getTokenString(); details.put(TOKEN_KEY, idToken); details.put(PROVIDER_KEY, userpoolsLoginKey); mStore.set(details); UserState userState = UserState.SIGNED_IN; final UserStateDetails userStateDetails = new UserStateDetails(userState, details); return userStateDetails; } catch (Exception e) { UserState userState = UserState.SIGNED_OUT_USER_POOLS_TOKENS_INVALID; return new UserStateDetails(userState, null); } } } // Online state detection if (hasUsefulToken && !userpoolsLoginKey.equals(providerKey)) { // TODO enhancement: check if token is expired try { if (!federationEnabled) { // Do nothing, you are signed-in by having the token } else { // Attempt to refresh the token if it matches drop-in UI String refreshedToken = token; final SignInProvider previouslySignedInProvider = SignInManager.getInstance(mContext).getPreviouslySignedInProvider(); if (previouslySignedInProvider != null && providerKey.equals(previouslySignedInProvider.getCognitoLoginKey())) { refreshedToken = previouslySignedInProvider.getToken(); Log.i(TAG, "Token was refreshed using drop-in UI internal mechanism"); } if (refreshedToken == null) { Log.i(TAG, "Token used for federation has become null"); return new UserStateDetails(UserState.SIGNED_OUT_FEDERATED_TOKENS_INVALID, details); } // If token has already been federated if (hasFederatedToken(providerKey, refreshedToken)) { Log.d(TAG, "getUserStateDetails: token already federated just fetch credentials"); if (cognitoIdentity != null) { cognitoIdentity.getCredentials(); } } else { federateWithCognitoIdentity(providerKey, refreshedToken); } } return new UserStateDetails(UserState.SIGNED_IN, details); } catch (Exception e) { Log.w(TAG, "Failed to federate the tokens.", e); UserState userState = UserState.SIGNED_IN; if (isSignedOutRelatedException(e)) { userState = UserState.SIGNED_OUT_FEDERATED_TOKENS_INVALID; } final UserStateDetails userStateDetails = new UserStateDetails(userState, details); userStateDetails.setException(e); return userStateDetails; } } else if (hasUsefulToken && userpool != null) { Tokens tokens = null; String idToken = null; Exception userPoolsException = null; try { tokens = getTokens(false); idToken = tokens.getIdToken().getTokenString(); details.put(TOKEN_KEY, idToken); if (!federationEnabled) { // Do nothing, you are signed-in by having the token } // If token has already been federated else if (hasFederatedToken(providerKey, idToken)) { try { if (cognitoIdentity != null) { cognitoIdentity.getCredentials(); } } catch (Exception e) { Log.w(TAG, "Failed to get or refresh credentials from Cognito Identity", e); } } else { if (cognitoIdentity != null) { federateWithCognitoIdentity(providerKey, idToken); } } } catch (Exception e) { Log.w(TAG, tokens == null ? "Tokens are invalid, please sign-in again." : "Failed to federate the tokens", e); userPoolsException = e; } finally { UserState userState = UserState.SIGNED_IN; if (isSignedOutRelatedException(userPoolsException)) { userState = UserState.SIGNED_OUT_USER_POOLS_TOKENS_INVALID; } final UserStateDetails userStateDetails = new UserStateDetails(userState, details); userStateDetails.setException(userPoolsException); return userStateDetails; } } else { if (cognitoIdentity == null) { return new UserStateDetails(UserState.SIGNED_OUT, details); } else if (identityId != null) { return new UserStateDetails(UserState.GUEST, details); } else { return new UserStateDetails(UserState.SIGNED_OUT, null); } } } boolean isSignedOutRelatedException(final Exception e) { if (e == null) { return false; } if (e instanceof NotAuthorizedException) { return true; } if ("No cached session.".equals(e.getMessage()) && e.getCause() == null) { return true; } return false; } boolean isFederationEnabled() { final String federationEnabledString = mStore.get(FEDERATION_ENABLED_KEY); final boolean federationEnabled; if (federationEnabledString != null) { federationEnabled = federationEnabledString.equals("true"); } else { federationEnabled = true; } return federationEnabled; } SignInMode getSignInMode() { return SignInMode.fromString(mStore.get(SIGN_IN_MODE)); } private boolean hasFederatedToken(final String providerKey, final String token) { if (token == null || token.isEmpty()) { return false; } boolean hasFederatedToken = token.equals(mFederatedLoginsMap.get(providerKey)); Log.d(TAG, "hasFederatedToken: " + hasFederatedToken + " provider: " + providerKey); return hasFederatedToken; } @AnyThread public void signIn(final String username, final String password, final Map validationData, final Callback callback) { signIn(username, password, validationData, Collections.emptyMap(), callback); } @AnyThread public void signIn(final String username, final String password, final Map validationData, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_signIn(username, password, validationData, clientMetadata, null, internalCallback)); } @AnyThread public void signIn(final String username, final String password, final Map validationData, final Map clientMetadata, final AuthFlowType authFlowType, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_signIn(username, password, validationData, clientMetadata, authFlowType, internalCallback)); } @WorkerThread public SignInResult signIn(final String username, final String password, final Map validationData) throws Exception { return signIn(username, password, validationData, Collections.emptyMap()); } @WorkerThread public SignInResult signIn(final String username, final String password, final Map validationData, final Map clientMetadata) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_signIn(username, password, validationData, clientMetadata, null, internalCallback)); } @WorkerThread public SignInResult signIn(final String username, final String password, final Map validationData, final Map clientMetadata, final AuthFlowType authFlowType) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_signIn(username, password, validationData, clientMetadata, authFlowType, internalCallback)); } private Runnable _signIn(final String username, final String password, final Map validationData, final Map clientMetadata, final AuthFlowType authFlowType, final Callback callback) { this.signInCallback = callback; signInState = null; mStore.set(SIGN_IN_MODE, SignInMode.SIGN_IN.toString()); return new Runnable() { @Override public void run() { try { userpool.getUser(username).getSession( clientMetadata, new AuthenticationHandler() { @Override public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) { try { mCognitoUserSession = userSession; signInState = SignInState.DONE; } catch (Exception e) { signInCallback.onError(e); signInCallback = null; } try { if (isFederationEnabled()) { federatedSignInWithoutAssigningState(userpoolsLoginKey, mCognitoUserSession.getIdToken().getJWTToken()); } releaseSignInWait(); } catch (Exception e) { Log.w(TAG, "Failed to federate tokens during sign-in", e); } finally { setUserState(new UserStateDetails(UserState.SIGNED_IN, getSignInDetailsMap())); } signInCallback.onResult(SignInResult.DONE); } @Override public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) { Log.d(TAG, "Sending password."); final HashMap authParameters = new HashMap<>(); // Check if the auth flow type setting is in the configuration. boolean authFlowTypeInConfig = awsConfiguration.optJsonObject(AUTH_KEY) != null && awsConfiguration.optJsonObject(AUTH_KEY).has("authenticationFlowType"); try { String resolvedAuthFlowType = authFlowType != null ? authFlowType.name() : null; if (resolvedAuthFlowType == null && authFlowTypeInConfig) { resolvedAuthFlowType = awsConfiguration.optJsonObject(AUTH_KEY).getString("authenticationFlowType"); } if (resolvedAuthFlowType != null && AUTH_TYPE_INIT_CUSTOM_AUTH.equals(resolvedAuthFlowType)) { // If there's a value in the config and it's CUSTOM_AUTH, we'll // use one of the below constructors depending on what's passed in. if (password != null) { authenticationContinuation.setAuthenticationDetails(new AuthenticationDetails(username, password, authParameters, validationData)); } else { authenticationContinuation.setAuthenticationDetails(new AuthenticationDetails(username, authParameters, validationData)); } } else if (resolvedAuthFlowType != null && AUTH_TYPE_INIT_USER_PASSWORD.equals(resolvedAuthFlowType)) { // If there's a value in the config and it's USER_PASSWORD_AUTH, set the auth type (challenge name) // to be USER_PASSWORD. AuthenticationDetails authenticationDetails = new AuthenticationDetails(username, password, validationData); authenticationDetails.setAuthenticationType(CHLG_TYPE_USER_PASSWORD); authenticationContinuation.setAuthenticationDetails(authenticationDetails); } else { // Otherwise, auth flow is USER_SRP_AUTH and the auth type (challenge name) // will default to PASSWORD_VERIFIER. Log.d(TAG, "Using USER_SRP_AUTH for flow type."); authenticationContinuation.setAuthenticationDetails(new AuthenticationDetails(username, password, validationData)); } } catch (JSONException exception) { Log.w(TAG, "Exception while attempting to read authenticationFlowType from config.", exception); } authenticationContinuation.continueTask(); } @Override public void getMFACode(MultiFactorAuthenticationContinuation continuation) { signInMfaContinuation = continuation; CognitoUserCodeDeliveryDetails parameters = continuation.getParameters(); signInState = SignInState.SMS_MFA; signInCallback.onResult( new SignInResult( SignInState.SMS_MFA, new UserCodeDeliveryDetails( parameters.getDestination(), parameters.getDeliveryMedium(), parameters.getAttributeName() ) ) ); } @Override public void authenticationChallenge(ChallengeContinuation continuation) { try { signInState = SignInState.valueOf(continuation.getChallengeName()); signInChallengeContinuation = continuation; signInCallback.onResult(new SignInResult( signInState, continuation.getParameters())); } catch (IllegalArgumentException e) { signInCallback.onError(e); } } @Override public void onFailure(Exception exception) { signInCallback.onError(exception); } } ); } catch (Exception e) { callback.onError(e); } } }; } @AnyThread public void confirmSignIn(final String signInChallengeResponse, final Callback callback) { confirmSignIn(signInChallengeResponse, Collections.emptyMap(), callback); } @AnyThread public void confirmSignIn(final String signInChallengeResponse, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmSignIn(signInChallengeResponse, clientMetadata, Collections.emptyMap(), internalCallback)); } @AnyThread public void confirmSignIn(final String signInChallengeResponse, final Map clientMetadata, final Map userAttributes, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmSignIn(signInChallengeResponse, clientMetadata, userAttributes, internalCallback)); } @WorkerThread public SignInResult confirmSignIn(final String signInChallengeResponse) throws Exception { return confirmSignIn(signInChallengeResponse, Collections.emptyMap()); } @WorkerThread public SignInResult confirmSignIn(final String signInChallengeResponse, final Map clientMetadata) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignIn(signInChallengeResponse, clientMetadata, Collections.emptyMap(), internalCallback)); } @WorkerThread public SignInResult confirmSignIn(final String signInChallengeResponse, final Map clientMetadata, final Map userAttributes) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignIn(signInChallengeResponse, clientMetadata, userAttributes, internalCallback)); } private Runnable _confirmSignIn(final String signInChallengeResponse, final Map clientMetadata, final Map userAttributes, final Callback callback) { return new Runnable() { @Override public void run() { if (signInState == null) { callback.onError(new IllegalStateException("Cannot call confirmSignIn(String, Callback) " + "without initiating sign-in. This call is used for SMS_MFA and NEW_PASSWORD_REQUIRED " + "sign-in state.")); return; } final CognitoIdentityProviderContinuation detectedContinuation; switch (signInState) { case SMS_MFA: signInMfaContinuation.setMfaCode(signInChallengeResponse); signInMfaContinuation.setClientMetaData(clientMetadata); detectedContinuation = signInMfaContinuation; signInCallback = new InternalCallback(callback); break; case NEW_PASSWORD_REQUIRED: ((NewPasswordContinuation) signInChallengeContinuation) .setPassword(signInChallengeResponse); signInChallengeContinuation.setClientMetaData(clientMetadata); for (final String key : userAttributes.keySet()) { signInChallengeContinuation.setChallengeResponse(CHALLENGE_RESPONSE_USER_ATTRIBUTES_PREFIX_KEY + key, userAttributes.get(key)); } detectedContinuation = signInChallengeContinuation; signInCallback = new InternalCallback(callback); break; case CUSTOM_CHALLENGE: signInChallengeContinuation.setChallengeResponse("ANSWER", signInChallengeResponse); detectedContinuation = signInChallengeContinuation; signInCallback = new InternalCallback(callback); if (clientMetadata != null) { signInChallengeContinuation.setClientMetaData(clientMetadata); } break; case DONE: callback.onError(new IllegalStateException("confirmSignIn called after signIn has succeeded")); return; default: callback.onError(new IllegalStateException("confirmSignIn called on unsupported operation, " + "please file a feature request")); return; } if (detectedContinuation != null) { detectedContinuation.continueTask(); } } }; } /** * The counter part to {@link #signIn}. * Call with the user's response to the sign-in challenge. * * @param signInChallengeResponse obtained from user * @param clientMetaData Meta data for lambda triggers * @param callback callback */ @AnyThread public void confirmSignIn(final Map signInChallengeResponse, final Map clientMetaData, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmSignIn(signInChallengeResponse, internalCallback, clientMetaData)); } /** * The counter part to {@link #signIn}. * Call with the user's response to the sign-in challenge. * * @param signInChallengeResponse obtained from user * @param clientMetaData Meta data for lambda triggers * @return the result containing next steps or done. * @throws Exception */ @WorkerThread public SignInResult confirmSignIn(final Map signInChallengeResponse, final Map clientMetaData) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignIn(signInChallengeResponse, internalCallback, clientMetaData)); } /** * The counter part to {@link #signIn}. * Call with the user's response to the sign-in challenge. * * @param signInChallengeResponse obtained from user * @param callback callback */ @AnyThread public void confirmSignIn(final Map signInChallengeResponse, final Callback callback) { confirmSignIn(signInChallengeResponse, null, callback); } /** * The counter part to {@link #signIn}. * Call with the user's response to the sign-in challenge. * * @param signInChallengeResponse obtained from user * @return the result containing next steps or done. * @throws Exception */ @WorkerThread public SignInResult confirmSignIn(final Map signInChallengeResponse) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignIn(signInChallengeResponse, internalCallback, null)); } private Runnable _confirmSignIn(final Map signInChallengeResponse, final Callback callback, final Map clientMetaData) { return new Runnable() { @Override public void run() { if (signInState == null) { callback.onError(new IllegalStateException("Cannot call confirmSignIn(Map, Callback) " + "without initiating sign-in. This call is used for CUSTOM_CHALLENGE sign-in state.")); return; } final CognitoIdentityProviderContinuation detectedContinuation; switch (signInState) { case SMS_MFA: callback.onError(new IllegalStateException( "Please use confirmSignIn(String, Callback) " + "for SMS_MFA challenges")); case CUSTOM_CHALLENGE: case NEW_PASSWORD_REQUIRED: for (final String key : signInChallengeResponse.keySet()) { signInChallengeContinuation.setChallengeResponse(key, signInChallengeResponse.get(key)); } detectedContinuation = signInChallengeContinuation; signInCallback = new InternalCallback(callback); if (CUSTOM_CHALLENGE.equals(signInState) && clientMetaData != null) { signInChallengeContinuation.setClientMetaData(clientMetaData); } break; case DONE: detectedContinuation = null; callback.onError(new IllegalStateException("confirmSignIn called after signIn has succeeded")); break; default: callback.onError(new IllegalStateException("confirmSignIn called on unsupported operation, " + "please file a feature request")); return; } if (detectedContinuation != null) { detectedContinuation.continueTask(); } } }; } /** * Clears local tokens so that the client is in a signed-out state. */ @AnyThread public void signOut() { mCognitoUserSession = null; if (userpool != null) { userpool.getCurrentUser().signOut(); userpool.getUser().signOut(); } if (cognitoIdentity != null) { cognitoIdentity.clear(); } if (IdentityManager.getDefaultIdentityManager() != null) { IdentityManager.getDefaultIdentityManager().signOut(); } mFederatedLoginsMap.clear(); mStore.clear(); String hostedUIJSON = null; if (awsConfiguration.optJsonObject(AUTH_KEY) != null && awsConfiguration.optJsonObject(AUTH_KEY).has("OAuth")) { try { hostedUIJSON = awsConfiguration.optJsonObject(AUTH_KEY).getJSONObject("OAuth").toString(); } catch (JSONException e) { e.printStackTrace(); } if (hostedUI != null) { hostedUI.signOut(true); } if (mOAuth2Client != null) { mOAuth2Client.signOut(); } } mStore.set(HOSTED_UI_KEY, hostedUIJSON); setUserState(getUserStateDetails(false)); releaseSignInWait(); } /** * Sign-out the user with more options. *
     * {@code
     * SignOutOptions.builder()
     *                  .signOutGlobally(true) // Sign-out user from all sessions across devices
     *                  .build();
     * }
     * 
* @param signOutOptions options */ @WorkerThread public void signOut(final SignOutOptions signOutOptions) throws Exception { _signOut(signOutOptions).await(); } /** * Sign-out the user with more options. *
     * {@code
     * SignOutOptions.builder()
     *                  .signOutGlobally(true) // Sign-out user from all sessions across devices
     *                  .build();
     * }
     * 
* @param signOutOptions options */ @AnyThread public void signOut(final SignOutOptions signOutOptions, final Callback callback) { _signOut(signOutOptions).async(callback); } private ReturningRunnable _signOut(final SignOutOptions signOutOptions) { return new ReturningRunnable() { @Override public Void run() throws Exception { if (signOutOptions.isSignOutGlobally()) { final GlobalSignOutRequest globalSignOutRequest = new GlobalSignOutRequest(); globalSignOutRequest.setAccessToken(getTokens().getAccessToken().getTokenString()); userpoolLL.globalSignOut(globalSignOutRequest); } if (signOutOptions.isInvalidateTokens()) { if (userpool != null) { userpool.getCurrentUser().revokeTokens(); } if (hostedUI != null) { if (signOutOptions.getBrowserPackage() != null) { hostedUI.setBrowserPackage(signOutOptions.getBrowserPackage()); } hostedUI.signOut(); } else if (mOAuth2Client != null) { final CountDownLatch latch = new CountDownLatch(1); final JSONObject hostedUIJSON = getHostedUIJSON(); final String signOutUriString = hostedUIJSON.getString("SignOutURI"); final Uri.Builder uriBuilder = Uri.parse(signOutUriString).buildUpon(); if (getHostedUIJSON().optString("SignOutRedirectURI", null) != null) { uriBuilder.appendQueryParameter("redirect_uri", getHostedUIJSON().getString( "SignOutRedirectURI")); } final JSONObject signOutQueryParametersJSON = hostedUIJSON.getJSONObject( "SignOutQueryParameters"); if (signOutQueryParametersJSON != null) { final Iterator keysIterator = signOutQueryParametersJSON.keys(); while (keysIterator.hasNext()) { String key = keysIterator.next(); uriBuilder.appendQueryParameter(key, signOutQueryParametersJSON.getString(key)); } } final Exception[] signOutError = new Exception[1]; mOAuth2Client.signOut(uriBuilder.build(), new Callback() { @Override public void onResult(Void result) { latch.countDown(); } @Override public void onError(Exception e) { signOutError[0] = e; latch.countDown(); } }); latch.await(); if (signOutError[0] != null) { throw signOutError[0]; } } } signOut(); return null; } }; } /** * Delete the account of the currently logged-in user. * @throws Exception if the user cannot be deleted successfully */ @WorkerThread public void deleteUser() throws Exception { final InternalCallback internalCallback = new InternalCallback<>(); internalCallback.await(_deleteUser(internalCallback)); } /** * Delete the account of the currently logged-in user. * @param callback the callback will be invoked to notify the success or * failure of the deleteUser operation */ @AnyThread public void deleteUser(final Callback callback) { final InternalCallback internalCallback = new InternalCallback<>(callback); internalCallback.async(_deleteUser(internalCallback)); } private Runnable _deleteUser(final Callback callback) { return () -> { if (userpool == null) { callback.onError(new InvalidUserPoolConfigurationException( "A user pool must be configured in order to delete a user." )); } else { CognitoUser currentUser = userpool.getCurrentUser(); currentUser.deleteUserInBackground(new GenericHandler() { @Override public void onSuccess() { signOut(SignOutOptions.builder().signOutGlobally(true).invalidateTokens(true).build(), new Callback() { @Override public void onResult(Void result) { callback.onResult(result); } @Override public void onError(Exception e) { callback.onError(e); } }); } @Override public void onFailure(Exception exception) { callback.onError(exception); } }); } }; } /** * Federate tokens from custom identity providers into Cognito Identity Pool by providing the * logins key and token *

* The logins key can be specified with {@link IdentityProvider#AMAZON#toString()} * * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com * @param token the JWT token vended by the third-party */ @AnyThread public void federatedSignIn(final String providerKey, final String token, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_federatedSignIn(providerKey, token, null, internalCallback, true)); } /** * Federate tokens from custom identity providers into Cognito Identity Pool by providing the * logins key and token *

* The logins key can be specified with {@link IdentityProvider#AMAZON} * * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com * @param token the JWT token vended by the third-party */ @WorkerThread public UserStateDetails federatedSignIn(final String providerKey, final String token) throws Exception { InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_federatedSignIn(providerKey, token, null, internalCallback, true)); } /** * Federate tokens from custom identity providers by providing the * logins key and token *

* The logins key can be specified with {@link IdentityProvider#AMAZON#toString()} * * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com * @param token the JWT token vended by the third-party */ @AnyThread public void federatedSignIn(final String providerKey, final String token, final FederatedSignInOptions options, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_federatedSignIn(providerKey, token, options, internalCallback, true)); } /** * Federate tokens from custom identity providers by providing the * logins key and token *

* The logins key can be specified with {@link IdentityProvider#AMAZON} * * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com * @param token the JWT token vended by the third-party */ @WorkerThread public UserStateDetails federatedSignIn(final String providerKey, final String token, final FederatedSignInOptions options) throws Exception { InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_federatedSignIn(providerKey, token, options, internalCallback, true)); } protected void federatedSignInWithoutAssigningState(final String providerKey, final String token) throws Exception { InternalCallback internalCallback = new InternalCallback(); internalCallback.await(_federatedSignIn(providerKey, token, null, internalCallback, false)); } protected void federatedSignInWithoutAssigningState(final String providerKey, final String token, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_federatedSignIn(providerKey, token, null, internalCallback, false)); } private Runnable _federatedSignIn(final String providerKey, final String token, final FederatedSignInOptions options, final Callback callback, final boolean assignState) { final Map loginsMap = new HashMap(); mStore.set(SIGN_IN_MODE, SignInMode.FEDERATED_SIGN_IN.toString()); try { loginsMap.put(providerKey, token); Log.d(TAG, String.format("_federatedSignIn: Putting provider and token in store")); HashMap details = new HashMap(); details.put(PROVIDER_KEY, providerKey); details.put(TOKEN_KEY, token); details.put(FEDERATION_ENABLED_KEY, "true"); if (IdentityProvider.DEVELOPER.equals(providerKey)) { if (options == null) { callback.onError(new Exception("Developer authenticated identities require the" + "identity id to be specified in FederatedSignInOptions")); } details.put(IDENTITY_ID_KEY, options.getCognitoIdentityId()); } if (options != null && !StringUtils.isBlank(options.getCustomRoleARN())) { details.put(CUSTOM_ROLE_ARN_KEY, options.getCustomRoleARN()); } mStore.set(details); } catch (Exception e) { callback.onError(e); } return new Runnable() { @Override public void run() { try { if (cognitoIdentity == null) { callback.onError(new Exception("Federation is not enabled, " + "please check if you have CognitoIdentity configured.")); return; } if (!token.equals(mFederatedLoginsMap.get(providerKey))) { cognitoIdentity.clear(); cognitoIdentity.setLogins(loginsMap); } UserStateDetails userStateDetails = getUserStateDetails(true); federateWithCognitoIdentity(providerKey, token); callback.onResult(userStateDetails); end(userStateDetails); } catch (final Exception exception) { HashMap details = new HashMap(); details.put(PROVIDER_KEY, null); details.put(TOKEN_KEY, null); details.put(FEDERATION_ENABLED_KEY, null); details.put(IDENTITY_ID_KEY, null); details.put(CUSTOM_ROLE_ARN_KEY, null); mStore.set(details); callback.onError(new RuntimeException("Error in federating the token.", exception)); return; } } private void end(final UserStateDetails details) { if (assignState) { setUserState(details); } } }; } protected void federateWithCognitoIdentity(final String providerKey, final String token) { synchronized (federateWithCognitoIdentityLockObject) { if (!hasFederatedToken(providerKey, token)) { if (IdentityProvider.DEVELOPER.equals(providerKey)) { provider.setDeveloperAuthenticated(mStore.get(IDENTITY_ID_KEY), token); } else { provider.setNotDeveloperAuthenticated(); } final String customRoleArn = mStore.get(CUSTOM_ROLE_ARN_KEY); if (!StringUtils.isBlank(customRoleArn)) { cognitoIdentity.setCustomRoleArn(customRoleArn); } HashMap logins = new HashMap(); logins.put(providerKey, token); cognitoIdentity.setLogins(logins); cognitoIdentity.refresh(); // Ensure cognitoIdentityId and credentials can be retrieved. mStore.set(IDENTITY_ID_KEY, cognitoIdentity.getIdentityId()); mFederatedLoginsMap = cognitoIdentity.getLogins(); } } } /** * Returns the tokens obtained from Cognito Userpools sign-in. * Federated sign-in tokens are not supported. * * @return tokens from Cognito Userpools * @throws Exception when the tokens cannot be retrieved successfully. */ @WorkerThread public Tokens getTokens() throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_getTokens(internalCallback, false)); } /** * Returns the tokens obtained from Cognito Userpools sign-in. * Federated sign-in tokens are not supported. * * @return tokens from Cognito Userpools * @throws Exception when the tokens cannot be retrieved successfully. */ @AnyThread public void getTokens(final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_getTokens(internalCallback, false)); } protected Tokens getTokens(boolean waitForSignIn) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_getTokens(internalCallback, waitForSignIn)); } private Runnable _getTokens(final Callback callback, final boolean waitForSignIn) { return new Runnable() { @Override public void run() { String providerKey = getSignInDetailsMap().get(PROVIDER_KEY); if (providerKey == null) { } else if (!userpoolsLoginKey.equals(providerKey)) { callback.onError(new Exception("getTokens does not support retrieving tokens for federated sign-in")); return; } if (waitForSignIn) { if (!waitForSignIn()) { callback.onError(new Exception("getTokens does not support retrieving tokens while signed-out")); return; } } if (!isUserpoolsSignedIn()) { callback.onError(new Exception("You must be signed-in with Cognito Userpools to be able to use getTokens")); } if (getSignInMode().equals(SignInMode.HOSTED_UI)) { _getHostedUITokens(callback); return; } else if (getSignInMode().equals(SignInMode.OAUTH2)) { callback.onError(new Exception("Tokens are not supported for OAuth2")); return; } try { userpool.getCurrentUser().getSession( Collections.emptyMap(), new AuthenticationHandler() { @Override public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) { try { mCognitoUserSession = userSession; callback.onResult(new Tokens( userSession.getAccessToken().getJWTToken(), userSession.getIdToken().getJWTToken(), userSession.getRefreshToken().getToken() )); } catch (Exception e) { callback.onError(e); } } @Override public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) { signalTokensNotAvailable(null); } @Override public void getMFACode(MultiFactorAuthenticationContinuation continuation) { signalTokensNotAvailable(null); } @Override public void authenticationChallenge(ChallengeContinuation continuation) { signalTokensNotAvailable(null); } @Override public void onFailure(Exception exception) { signalTokensNotAvailable(exception); } private void signalTokensNotAvailable(final Exception e) { Log.w(TAG, "signalTokensNotAvailable"); callback.onError(new Exception("No cached session.", e)); } } ); } catch (Exception e) { callback.onError(e); } } }; } @AnyThread private Tokens getHostedUITokens() throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(() -> _getHostedUITokens(internalCallback)); } private void _getHostedUITokens(final Callback callback) { hostedUI = hostedUI.getCurrentUser(); hostedUI.setAuthHandler(new AuthHandler() { @Override public void onSuccess(AuthUserSession session) { callback.onResult(new Tokens( session.getAccessToken().getJWTToken(), session.getIdToken().getJWTToken(), session.getRefreshToken().getToken() )); } @Override public void onSignout() { callback.onError(new Exception("No cached session.")); } @Override public void onFailure(Exception e) { callback.onError(new Exception("No cached session.", e)); } }); hostedUI.getSessionWithoutWebUI(); } /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. * Call {@link #confirmSignUp(String, String, Callback)} with the necessary next * step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request * @param clientMetadata meta data to be passed to the lambdas invoked by sign up operation. * @param callback callback will be invoked to notify the success or failure of the * SignUp operation */ @AnyThread public void signUp(final String username, final String password, final Map userAttributes, final Map validationData, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_signUp(username, password, userAttributes, validationData, clientMetadata, internalCallback)); } /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. * Call {@link #confirmSignUp(String, String)} with the necessary next step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request * @param clientMetadata meta data to be passed to the lambdas invoked by sign up operation. * @return result of the operation, potentially with next steps * @throws Exception if there is any error generated by the client */ @WorkerThread public SignUpResult signUp(final String username, final String password, final Map userAttributes, final Map clientMetadata, final Map validationData) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_signUp(username, password, userAttributes, validationData, clientMetadata, internalCallback)); } /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. * Call {@link #confirmSignUp(String, String, Callback)} with the necessary next * step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request * @param callback callback will be invoked to notify the success or failure of the * SignUp operation */ @AnyThread public void signUp(final String username, final String password, final Map userAttributes, final Map validationData, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_signUp(username, password, userAttributes, validationData, Collections.emptyMap(), internalCallback)); } /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. * Call {@link #confirmSignUp(String, String)} with the necessary next step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request * @return result of the operation, potentially with next steps * @throws Exception if there is any error generated by the client */ @WorkerThread public SignUpResult signUp(final String username, final String password, final Map userAttributes, final Map validationData) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_signUp(username, password, userAttributes, validationData, Collections.emptyMap(), internalCallback)); } private Runnable _signUp(final String username, final String password, final Map userAttributes, final Map validationData, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { final CognitoUserAttributes cognitoUserAttr = new CognitoUserAttributes(); for (final String key : userAttributes.keySet()) { cognitoUserAttr.addAttribute(key, userAttributes.get(key)); } userpool.signUp(username, password, cognitoUserAttr, validationData, clientMetadata, new SignUpHandler() { @Override public void onSuccess(final CognitoUser user, final com.amazonaws.services.cognitoidentityprovider.model.SignUpResult signUpResult) { signUpUser = user; if (signUpResult == null) { callback.onError(new Exception("SignUpResult received is null")); return; } // When the user is confirmed, Cognito does not send CognitoUserCodeDeliveryDetails // and it appears to be null when the SignUpResult is unmarshalled. Extract the // CognitoUserCodeDeliveryDetails only when the user is not confirmed. if (signUpResult.getCodeDeliveryDetails() == null) { callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(), null, signUpResult.getUserSub())); } else { UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails( signUpResult.getCodeDeliveryDetails().getDestination(), signUpResult.getCodeDeliveryDetails().getDeliveryMedium(), signUpResult.getCodeDeliveryDetails().getAttributeName()); callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(), userCodeDeliveryDetails, signUpResult.getUserSub())); } } @Override public void onFailure(Exception exception) { callback.onError(exception); } }); } }; } /** * Confirm the sign-up request with follow-up information * * @param username username/email address/handle of the user who is signing up * @param signUpChallengeResponse response to the signUp challenge posted * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation. * @param callback the callback will be invoked to notify the success or * failure of the confirmSignUp operation */ @AnyThread public void confirmSignUp(final String username, final String signUpChallengeResponse, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmSignUp(username, signUpChallengeResponse, clientMetadata, internalCallback)); } /** * Confirm the sign-up request with follow-up information * * @param username username of the user who is signing up * @param signUpChallengeResponse response to the signUp challenge posted * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation. */ @WorkerThread public SignUpResult confirmSignUp(final String username, final String signUpChallengeResponse, final Map clientMetadata) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignUp(username, signUpChallengeResponse, clientMetadata, internalCallback)); } /** * Confirm the sign-up request with follow-up information * * @param username username/email address/handle of the user who is signing up * @param signUpChallengeResponse response to the signUp challenge posted * @param callback the callback will be invoked to notify the success or * failure of the confirmSignUp operation */ @AnyThread public void confirmSignUp(final String username, final String signUpChallengeResponse, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmSignUp(username, signUpChallengeResponse, Collections.emptyMap(), internalCallback)); } /** * Confirm the sign-up request with follow-up information * * @param username username of the user who is signing up * @param signUpChallengeResponse response to the signUp challenge posted */ @WorkerThread public SignUpResult confirmSignUp(final String username, final String signUpChallengeResponse) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmSignUp(username, signUpChallengeResponse, Collections.emptyMap(), internalCallback)); } private Runnable _confirmSignUp(final String username, final String signUpChallengeResponse, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { userpool.getUser(username).confirmSignUp(signUpChallengeResponse, false, clientMetadata, new GenericHandler() { @Override public void onSuccess() { callback.onResult(new SignUpResult( true, null, null )); signUpUser = null; } @Override public void onFailure(Exception exception) { callback.onError(exception); } }); } }; } /** * Used when a user has attempted sign-up previously and wants to continue the process. * Note: If the user tries through the normal process with the same username, then it will * fail and this method is required. * * @param username * @param callback */ @AnyThread public void resendSignUp( final String username, final Callback callback) { resendSignUp(username, Collections.emptyMap(), callback); } /** * Used when a user has attempted sign-up previously and wants to continue the process. * Note: If the user tries through the normal process with the same username, then it will * fail and this method is required. * * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * custom workflow. * @param username * @param callback */ @AnyThread public void resendSignUp( final String username, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_resendSignUp(username, clientMetadata, internalCallback)); } /** * Used when a user has attempted sign-up previously and wants to continue the process. * Note: If the user tries through the normal process with the same username, then it will * fail and this method is required. * * @param username */ @WorkerThread public SignUpResult resendSignUp(final String username) throws Exception { return resendSignUp(username, Collections.emptyMap()); } /** * Used when a user has attempted sign-up previously and wants to continue the process. * Note: If the user tries through the normal process with the same username, then it will * fail and this method is required. * * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * custom workflow. * @param username */ @WorkerThread public SignUpResult resendSignUp(final String username, final Map clientMetadata) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_resendSignUp(username, clientMetadata, internalCallback)); } private Runnable _resendSignUp( final String username, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { userpool.getUser(username).resendConfirmationCodeInBackground( clientMetadata, new VerificationHandler() { @Override public void onSuccess(CognitoUserCodeDeliveryDetails verificationCodeDeliveryMedium) { UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails( verificationCodeDeliveryMedium.getDestination(), verificationCodeDeliveryMedium.getDeliveryMedium(), verificationCodeDeliveryMedium.getAttributeName() ); callback.onResult(new SignUpResult( false, userCodeDeliveryDetails, null )); } @Override public void onFailure(Exception exception) { callback.onError(exception); } } ); } }; } /** * Used to reset password if user forgot the old password. * * @param username username of the user trying to reset password. * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation. * @param callback callback will be invoked to notify the success or failure of the * forgot password operation */ @AnyThread public void forgotPassword(final String username, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_forgotPassword(username, clientMetadata, internalCallback)); } /** * Used to reset password if user forgot the old password. * * @param username username of the user trying to reset password. */ @WorkerThread public ForgotPasswordResult forgotPassword(final String username, final Map clientMetadata) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_forgotPassword(username, clientMetadata, internalCallback)); } /** * Used to reset password if user forgot the old password. * * @param username username of the user trying to reset password. * @param callback callback will be invoked to notify the success or failure of the * forgot password operation */ @AnyThread public void forgotPassword(final String username, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_forgotPassword(username, Collections.emptyMap(), internalCallback)); } /** * Used to reset password if user forgot the old password. * * @param username username of the user trying to reset password. */ @WorkerThread public ForgotPasswordResult forgotPassword(final String username) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_forgotPassword(username, Collections.emptyMap(), internalCallback)); } private Runnable _forgotPassword(final String username, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { forgotPasswordCallback = new InternalCallback(callback); userpool.getUser(username).forgotPasswordInBackground(clientMetadata, new ForgotPasswordHandler() { @Override public void onSuccess() { forgotPasswordCallback .onResult(new ForgotPasswordResult(ForgotPasswordState.DONE)); } @Override public void getResetCode(ForgotPasswordContinuation continuation) { forgotPasswordContinuation = continuation; ForgotPasswordResult result = new ForgotPasswordResult(ForgotPasswordState.CONFIRMATION_CODE); CognitoUserCodeDeliveryDetails parameters = continuation.getParameters(); result.setParameters(new UserCodeDeliveryDetails( parameters.getDestination(), parameters.getDeliveryMedium(), parameters.getAttributeName()) ); forgotPasswordCallback.onResult(result); } @Override public void onFailure(Exception exception) { forgotPasswordCallback.onError(exception); } }); } }; } /** * Second method to call after {@link #forgotPassword(String)} to respond to any challenges * that the service may request. * * @param password new password * @param forgotPasswordChallengeResponse response to the forgot password challenge posted * @param clientMetadata metadata to be passed to the lambda invoked by this operation. * @param callback callback will be invoked to notify the success or failure of the * confirm forgot password operation */ @AnyThread public void confirmForgotPassword(final String password, final String forgotPasswordChallengeResponse, final Map clientMetadata, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse, clientMetadata, internalCallback)); } /** * Second method to call after {@link #forgotPassword(String)} to respond to any challenges * that the service may request. * * @param password new password. * @param clientMetadata metadata to be passed to the lambda invoked by this operation. * @param forgotPasswordChallengeResponse response to the forgot password challenge posted. */ @WorkerThread public ForgotPasswordResult confirmForgotPassword(final String password, final Map clientMetadata, final String forgotPasswordChallengeResponse) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmForgotPassword(password, forgotPasswordChallengeResponse, clientMetadata, internalCallback)); } /** * Second method to call after {@link #forgotPassword(String)} to respond to any challenges * that the service may request. * * @param password new password. * @param forgotPasswordChallengeResponse response to the forgot password challenge posted. * @param callback callback will be invoked to notify the success or failure of the * confirm forgot password operation */ @AnyThread @Deprecated public void confirmForgotPassword(final String password, final String forgotPasswordChallengeResponse, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse, Collections.emptyMap(), internalCallback)); } /** * Second method to call after {@link #forgotPassword(String)} to respond to any challenges * that the service may request. * * @param password new password. * @param forgotPasswordChallengeResponse response to the forgot password challenge posted. * @param callback callback will be invoked to notify the success or failure of the * confirm forgot password operation */ @AnyThread public void confirmForgotPassword(final String username, final String password, final String forgotPasswordChallengeResponse, final Callback callback) { final InternalCallback internalCallback = new InternalCallback<>(callback); ForgotPasswordHandler forgotPasswordHandler = new ForgotPasswordHandler() { @Override public void onSuccess() { callback.onResult( new ForgotPasswordResult( ForgotPasswordState.DONE ) ); } @Override public void getResetCode(ForgotPasswordContinuation continuation) { callback.onResult( new ForgotPasswordResult( ForgotPasswordState.CONFIRMATION_CODE ) ); } @Override public void onFailure(Exception exception) { callback.onError(exception); } }; this.forgotPasswordContinuation = new ForgotPasswordContinuation(userpool.getUser(username), null, true, forgotPasswordHandler); internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse, Collections.emptyMap(), internalCallback)); } /** * Second method to call after {@link #forgotPassword(String)} to respond to any challenges * that the service may request. * * @param password new password. * @param forgotPasswordChallengeResponse response to the forgot password challenge posted. */ @WorkerThread public ForgotPasswordResult confirmForgotPassword(final String password, final String forgotPasswordChallengeResponse) throws Exception { final InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_confirmForgotPassword(password, forgotPasswordChallengeResponse, Collections.emptyMap(), internalCallback)); } private Runnable _confirmForgotPassword(final String password, final String forgotPasswordChallengeResponse, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { if (forgotPasswordContinuation == null) { callback.onError(new IllegalStateException("confirmForgotPassword called before initiating forgotPassword")); return; } forgotPasswordContinuation.setPassword(password); forgotPasswordContinuation.setVerificationCode(forgotPasswordChallengeResponse); forgotPasswordContinuation.setClientMetadata(clientMetadata); forgotPasswordCallback = new InternalCallback(callback); forgotPasswordContinuation.continueTask(); } }; } @AnyThread public void changePassword(final String oldPassword, final String newPassword, final Callback callback) { final InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_changePassword(oldPassword, newPassword, internalCallback)); } @WorkerThread public void changePassword(final String oldPassword, final String newPassword) throws Exception { final InternalCallback internalCallback = new InternalCallback(); internalCallback.await(_changePassword(oldPassword, newPassword, internalCallback)); } private Runnable _changePassword(final String oldPassword, final String newPassword, final Callback callback) { return new Runnable() { @Override public void run() { userpool.getCurrentUser().changePassword( oldPassword, newPassword, new GenericHandler() { @Override public void onSuccess() { callback.onResult(null); } @Override public void onFailure(Exception exception) { callback.onError(exception); } } ); } }; } @AnyThread public void getUserAttributes(final Callback> callback) { InternalCallback> internalCallback = new InternalCallback>(callback); internalCallback.async(_getUserAttributes(internalCallback)); } @WorkerThread public Map getUserAttributes() throws Exception { InternalCallback> internalCallback = new InternalCallback>(); return internalCallback.await(_getUserAttributes(internalCallback)); } private Runnable _getUserAttributes(final Callback> callback) { return new Runnable() { @Override public void run() { if (!waitForSignIn()) { callback.onError(new Exception("Operation requires a signed-in state")); return; } userpool.getCurrentUser().getDetails(new GetDetailsHandler() { @Override public void onSuccess(CognitoUserDetails cognitoUserDetails) { callback.onResult(cognitoUserDetails.getAttributes().getAttributes()); } @Override public void onFailure(Exception exception) { callback.onError(exception); } }); } }; } /** * Sends a map of user attributes to update. If an attribute needs to * be verified, then the verification delivery information is returned. * @param userAttributes the attributes i.e. email * @param callback verification delivery information */ @AnyThread public void updateUserAttributes( final Map userAttributes, final Callback> callback) { updateUserAttributes(userAttributes, Collections.emptyMap(), callback); } /** * Sends a map of user attributes to update. If an attribute needs to * be verified, then the verification delivery information is returned. * @param userAttributes the attributes i.e. email * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * custom workflow. * @param callback verification delivery information */ @AnyThread public void updateUserAttributes( final Map userAttributes, final Map clientMetadata, final Callback> callback) { InternalCallback internalCallback = new InternalCallback>(callback); internalCallback.async(_updateUserAttributes(userAttributes, clientMetadata, internalCallback)); } /** * Sends a map of user attributes to update. If an attribute needs to * be verified, then the verification delivery information is returned. * @param userAttributes the attributes i.e. email * @return verification delivery information * @throws Exception */ @WorkerThread public List updateUserAttributes( final Map userAttributes) throws Exception { return updateUserAttributes(userAttributes, Collections.emptyMap()); } /** * Sends a map of user attributes to update. If an attribute needs to * be verified, then the verification delivery information is returned. * @param userAttributes the attributes i.e. email * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * custom workflow. * @return verification delivery information * @throws Exception */ @WorkerThread public List updateUserAttributes( final Map userAttributes, final Map clientMetadata) throws Exception { InternalCallback> internalCallback = new InternalCallback>(); return internalCallback.await(_updateUserAttributes(userAttributes, clientMetadata, internalCallback)); } private Runnable _updateUserAttributes( final Map userAttributes, final Map clientMetadata, final Callback> callback) { return new Runnable() { @Override public void run() { if (!waitForSignIn()) { callback.onError(new Exception("Operation requires a signed-in state")); return; } final CognitoUserAttributes cognitoUserAttributes = new CognitoUserAttributes(); if (userAttributes != null) { for (final String key : userAttributes.keySet()) { cognitoUserAttributes.addAttribute(key, userAttributes.get(key)); } } userpool.getCurrentUser().updateAttributes( cognitoUserAttributes, clientMetadata, new UpdateAttributesHandler() { @Override public void onSuccess(List attributesVerificationList) { final List list = new LinkedList(); for (CognitoUserCodeDeliveryDetails details : attributesVerificationList) { list.add(new UserCodeDeliveryDetails( details.getDestination(), details.getDeliveryMedium(), details.getAttributeName() )); } callback.onResult(list); } @Override public void onFailure(Exception exception) { callback.onError(exception); } } ); } }; } /** * Verify an attribute like email. * @param attributeName i.e. email * @param callback verification delivery information */ @AnyThread public void verifyUserAttribute(final String attributeName, final Callback callback) { verifyUserAttribute(attributeName, Collections.emptyMap(), callback); } /** * Verify an attribute like email. * @param attributeName i.e. email * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * lambda functions triggered by forgot password. * @param callback verification delivery information */ @AnyThread public void verifyUserAttribute(final String attributeName, final Map clientMetadata, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_verifyUserAttribute(attributeName, clientMetadata, internalCallback)); } /** * Verify an attribute like email. * @param attributeName i.e. email * @return verification delivery information * @throws Exception */ @WorkerThread public UserCodeDeliveryDetails verifyUserAttribute(final String attributeName) throws Exception { return verifyUserAttribute(attributeName, Collections.emptyMap()); } /** * Verify an attribute like email. * @param attributeName i.e. email * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for * lambda functions triggered by forgot password. * @return verification delivery information * @throws Exception */ @WorkerThread public UserCodeDeliveryDetails verifyUserAttribute(final String attributeName, final Map clientMetadata) throws Exception { InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_verifyUserAttribute(attributeName, clientMetadata, internalCallback)); } private Runnable _verifyUserAttribute(final String attributeName, final Map clientMetadata, final Callback callback) { return new Runnable() { @Override public void run() { if (!waitForSignIn()) { callback.onError(new Exception("Operation requires a signed-in state")); return; } userpool.getCurrentUser().getAttributeVerificationCodeInBackground( clientMetadata, attributeName, new VerificationHandler() { @Override public void onSuccess(CognitoUserCodeDeliveryDetails verificationCodeDeliveryMedium) { callback.onResult(new UserCodeDeliveryDetails( verificationCodeDeliveryMedium.getDestination(), verificationCodeDeliveryMedium.getDeliveryMedium(), verificationCodeDeliveryMedium.getAttributeName()) ); } @Override public void onFailure(Exception exception) { callback.onError(exception); } } ); } }; } /** * Confirm the attribute with the code provided by user. * @param attributeName i.e. email * @param updateUserAttributeChallengeResponse i.e. 123456 * @param callback callback */ @AnyThread public void confirmUpdateUserAttribute(final String attributeName, final String updateUserAttributeChallengeResponse, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmUserAttribute(attributeName, updateUserAttributeChallengeResponse, internalCallback)); } /** * Confirm the attribute with the code provided by user. * @param attributeName i.e. email * @param updateUserAttributeChallengeResponse i.e. 123456 * @throws Exception */ @WorkerThread public void confirmUpdateUserAttribute(final String attributeName, final String updateUserAttributeChallengeResponse) throws Exception { InternalCallback internalCallback = new InternalCallback(); internalCallback.await(_confirmUserAttribute(attributeName, updateUserAttributeChallengeResponse, internalCallback)); } /** * Confirm the attribute with the code provided by user. * @param attributeName i.e. email * @param updateUserAttributeChallengeResponse i.e. 123456 * @param callback callback */ @AnyThread public void confirmVerifyUserAttribute(final String attributeName, final String updateUserAttributeChallengeResponse, final Callback callback) { InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_confirmUserAttribute(attributeName, updateUserAttributeChallengeResponse, internalCallback)); } /** * Confirm the attribute with the code provided by user. * @param attributeName i.e. email * @param updateUserAttributeChallengeResponse i.e. 123456 * @throws Exception */ @WorkerThread public void confirmVerifyUserAttribute(final String attributeName, final String updateUserAttributeChallengeResponse) throws Exception { InternalCallback internalCallback = new InternalCallback(); internalCallback.await(_confirmUserAttribute(attributeName, updateUserAttributeChallengeResponse, internalCallback)); } private Runnable _confirmUserAttribute(final String attributeName, final String updateUserAttributeChallengeResponse, final Callback callback) { return new Runnable() { @Override public void run() { if (!waitForSignIn()) { callback.onError(new Exception("Operation requires a signed-in state")); return; } userpool.getCurrentUser().verifyAttribute( attributeName, updateUserAttributeChallengeResponse, new GenericHandler() { @Override public void onSuccess() { callback.onResult(null); } @Override public void onFailure(Exception exception) { callback.onError(exception); } } ); } }; } // TODO test code more before release // @Override // public void setupTotp(final Callback callback) { // InternalCallback internalCallback = new InternalCallback(callback); // internalCallback.async(_setupTotp(internalCallback)); // } // // @Override // public SetupTotpResult setupTotp() throws Exception { // InternalCallback internalCallback = new InternalCallback(); // return internalCallback.await(_setupTotp(internalCallback)); // } // // private Runnable _setupTotp(final Callback callback) { // return new Runnable() { // @Override // public void run() { // try { // if (!waitForSignIn()) { // callback.onError(new Exception("Operation requires a signed-in state")); // return; // } // // userpool.getCurrentUser().associateSoftwareToken(null, new RegisterMfaHandler() { // @Override // public void onSuccess(String sessionToken) { // totpSessionToken = sessionToken; // totpCallback // .onResult(new SetupTotpResult(null, sessionToken)); // } // // @Override // public void onVerify(VerifyMfaContinuation continuation) { // totpContinuation = continuation; // totpCallback // .onResult(new SetupTotpResult(SetupTotpState.CONFIRMATION_CODE, null)); // } // // @Override // public void onFailure(Exception exception) { // totpCallback.onError(exception); // } // }); // } catch (Exception e) { // callback.onError(e); // } // } // }; // } // // @Override // public void verifyTotp(final String totpCode, // final String friendlyName, // final Callback callback) { // // InternalCallback internalCallback = new InternalCallback(callback); // internalCallback.async(_setupTotp(internalCallback)); // } // // @Override // public SetupTotpResult verifyTotp(final String totpCode, // final String friendlyName) throws Exception { // // InternalCallback internalCallback = new InternalCallback(); // return internalCallback.await(_verifyTotp(totpCode, friendlyName, internalCallback)); // } // // private Runnable _verifyTotp(final String totpCode, // final String friendlyName, // final Callback callback) { // return new Runnable() { // @Override // public void run() { // if (!waitForSignIn()) { // callback.onError(new Exception("Operation requires a signed-in state")); // return; // } // // try { // userpool.getCurrentUser().verifySoftwareToken( // totpSessionToken, // totpCode, // friendlyName, // new RegisterMfaHandler() { // @Override // public void onSuccess(String sessionToken) { // totpCallback.onResult( // new SetupTotpResult(SetupTotpState.DONE, // sessionToken // ) // ); // } // // @Override // public void onVerify(VerifyMfaContinuation continuation) { // final SetupTotpResult result = new SetupTotpResult( // SetupTotpState.CONFIRMATION_CODE, // null // ); // totpCallback.onResult(result); // } // // @Override // public void onFailure(Exception exception) { // totpCallback.onError(exception); // } // } // ); // } catch (Exception e) { // callback.onError(e); // } // } // }; // } // // public void setPreferredMFA(final MFAOptions mfaOption, final Callback callback) { // InternalCallback internalCallback = new InternalCallback(callback); // internalCallback.async(_setPreferredMFA(mfaOption, internalCallback)); // } // // public void setPreferredMFA(final MFAOptions mfaOption) throws Exception { // InternalCallback internalCallback = new InternalCallback(); // internalCallback.await(_setPreferredMFA(mfaOption, internalCallback)); // } // // private Runnable _setPreferredMFA(final MFAOptions mfaOption, final Callback callback) { // // return new Runnable() { // @Override // public void run() { // if (!waitForSignIn()) { // callback.onError(new Exception("Operation requires a signed-in state")); // return; // } // // try { // CognitoMfaSettings settings = new CognitoMfaSettings(mfaOption.getServiceText()); // settings.setEnabled(true); // settings.setPreferred(true); // List settingsList = new ArrayList(); // settingsList.add(settings); // userpool.getCurrentUser().setUserMfaSettingsInBackground(settingsList, new GenericHandler() { // @Override // public void onSuccess() { // callback.onResult(null); // } // // @Override // public void onFailure(Exception exception) { // callback.onError(exception); // } // }); // } catch (Exception e) { // callback.onError(e); // } // } // }; // } /** * Pass in the Intent from the OAuth 2.0 exchange from {@link #showSignIn(Activity)} * * @param intent * @return true if the intent was accepted. */ @AnyThread public boolean handleAuthResponse(final Intent intent) { if (hostedUI != null) { if (intent != null) { hostedUI.getTokens(intent.getData()); } else { hostedUI.handleFlowCancelled(); } return true; } if (mOAuth2Client != null && intent != null && mOAuth2Client.handleRedirect(intent.getData())) { return true; } return false; } /** * Shows a sign-in UI for user's to sign-in, sign-up, forgot password, create account * @param callingActivity The activity that the sign-in screen will be shown on top of. * @param callback callback with UserStateDetails at end of operation */ @AnyThread public void showSignIn(final Activity callingActivity, final Callback callback) { //SignInUIOptions InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_showSignIn(callingActivity, SignInUIOptions.builder().build(), internalCallback)); } /** * Shows a sign-in UI for user's to sign-in, sign-up, forgot password, create account * @param callingActivity The activity that the sign-in screen will be shown on top of. */ @WorkerThread public UserStateDetails showSignIn(final Activity callingActivity) throws Exception { InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_showSignIn(callingActivity, SignInUIOptions.builder().build(), internalCallback)); } /** * Shows a sign-in UI for user's to sign-in, sign-up, forgot password, create account * @param callingActivity The activity that the sign-in screen will be shown on top of. * @param signInUIOptions Override any default configuration with your preferences. * @param callback callback with UserStateDetails at end of operation */ @AnyThread public void showSignIn(final Activity callingActivity, final SignInUIOptions signInUIOptions, final Callback callback) { //SignInUIOptions InternalCallback internalCallback = new InternalCallback(callback); internalCallback.async(_showSignIn(callingActivity, signInUIOptions, internalCallback)); } /** * Shows a sign-in UI for user's to sign-in, sign-up, forgot password, create account * @param callingActivity The activity that the sign-in screen will be shown on top of. * @param signInUIOptions Override any default configuration with your preferences. */ @WorkerThread public UserStateDetails showSignIn(final Activity callingActivity, final SignInUIOptions signInUIOptions) throws Exception { InternalCallback internalCallback = new InternalCallback(); return internalCallback.await(_showSignIn(callingActivity, signInUIOptions, internalCallback)); } private Runnable _showSignIn(final Activity callingActivity, final SignInUIOptions signInUIOptions, final Callback callback) { if (signInUIOptions.getHostedUIOptions() != null) { final JSONObject hostedUIJSON = getHostedUIJSON(); if (hostedUIJSON == null) { return new Runnable() { @Override public void run() { callback.onError(new Exception("showSignIn called without HostedUI options in awsconfiguration.json")); } }; } if (hostedUIJSON.optString("TokenURI", null) != null) { return _showSignInOAuth2UI(callingActivity, signInUIOptions, callback); } else { return _showSignInHostedUI(callingActivity, signInUIOptions, callback); } } return _showSignInDropInUI(callingActivity, signInUIOptions, callback); } private Runnable _showSignInOAuth2UI(final Activity callingActivity, final SignInUIOptions signInUIOptions, final Callback callback) { return new Runnable() { @Override public void run() { final HostedUIOptions hostedUIOptions = signInUIOptions.getHostedUIOptions(); // Reset settings to JSON JSONObject hostedUIJSON = getHostedUIJSONFromJSON(); if (hostedUIJSON == null) { callback.onError(new Exception("Could not create OAuth configuration object")); } if (hostedUIOptions.getFederationEnabled() != null) { mStore.set(FEDERATION_ENABLED_KEY, hostedUIOptions.getFederationEnabled() ? "true" : "false"); } else { mStore.set(FEDERATION_ENABLED_KEY, "true"); } mStore.set(SIGN_IN_MODE, SignInMode.OAUTH2.toString()); if (isFederationEnabled() && hostedUIOptions.getFederationProviderName() == null) { throw new IllegalArgumentException("OAuth flow requires a federation provider name if federation is enabled."); } if (hostedUIOptions.getSignOutQueryParameters() != null) { try { JSONObject signOutParams = new JSONObject(); for (Map.Entry e : hostedUIOptions.getSignOutQueryParameters().entrySet()) { signOutParams.put(e.getKey(), e.getValue()); } hostedUIJSON.put("SignOutQueryParameters", signOutParams); } catch (JSONException e1) { callback.onError(new Exception("Failed to construct sign-out query parameters", e1)); return; } } if (hostedUIOptions.getTokenQueryParameters() != null) { try { JSONObject tokenParams = new JSONObject(); for (Map.Entry e : hostedUIOptions.getTokenQueryParameters().entrySet()) { tokenParams.put(e.getKey(), e.getValue()); } hostedUIJSON.put("TokenQueryParameters", tokenParams); } catch (JSONException e1) { callback.onError(new Exception("Failed to construct token query parameters", e1)); return; } } mStore.set(HOSTED_UI_KEY, hostedUIJSON.toString()); Uri.Builder authorizeUriBuilder; try { authorizeUriBuilder = Uri.parse(hostedUIJSON.getString("SignInURI")).buildUpon(); if (hostedUIOptions.getSignInQueryParameters() != null) { for (Map.Entry e : hostedUIOptions.getSignInQueryParameters().entrySet()) { authorizeUriBuilder.appendQueryParameter(e.getKey(), e.getValue()); } } authorizeUriBuilder.appendQueryParameter("redirect_uri", hostedUIJSON.getString("SignInRedirectURI")); authorizeUriBuilder.appendQueryParameter("scopes", hostedUIJSON.getJSONArray("Scopes").join(" ")); authorizeUriBuilder.appendQueryParameter("client_id", hostedUIJSON.getString("AppClientId")); } catch (Exception e) { throw new RuntimeException("Failed to construct authorization url for OAuth", e); } Uri.Builder tokensUriBuilder; final Map tokensBody = new HashMap(); try { tokensUriBuilder = Uri.parse(hostedUIJSON.getString("TokenURI")).buildUpon(); if (hostedUIOptions.getTokenQueryParameters() != null) { for (Map.Entry e : hostedUIOptions.getTokenQueryParameters().entrySet()) { tokensUriBuilder.appendQueryParameter(e.getKey(), e.getValue()); } } tokensBody.put("client_id", hostedUIJSON.getString("AppClientId")); tokensBody.put("redirect_uri", hostedUIJSON.getString("SignInRedirectURI")); } catch (Exception e) { throw new RuntimeException("Failed to construct tokens url for OAuth", e); } final Uri tokensUri = tokensUriBuilder.build(); mOAuth2Client.authorize(authorizeUriBuilder.build(), new Callback() { @Override public void onResult(AuthorizeResponse result) { Log.i(TAG, "onResult: OAuth2 callback occurred, exchanging code for token"); mOAuth2Client.requestTokens(tokensUri, new HashMap(), tokensBody, result.getCode(), new Callback() { @Override public void onResult(OAuth2Tokens result) { if (isFederationEnabled()) { federatedSignInWithoutAssigningState( hostedUIOptions.getFederationProviderName(), result.getIdToken(),// TODO verify id token is correct, this would mean OAuth support requires scope openid new Callback() { @Override public void onResult(UserStateDetails result) { final UserStateDetails userStateDetails = getUserStateDetails(false); callback.onResult(userStateDetails); setUserState(userStateDetails); } @Override public void onError(Exception e) { final UserStateDetails userStateDetails = getUserStateDetails(false); callback.onResult(userStateDetails); setUserState(userStateDetails); } }); } else { final UserStateDetails userStateDetails = getUserStateDetails(false); callback.onResult(userStateDetails); setUserState(userStateDetails); } } @Override public void onError(Exception e) { callback.onError(e); } }); } @Override public void onError(Exception e) { callback.onError(e); } }); } }; } private Runnable _showSignInHostedUI(final Activity callingActivity, final SignInUIOptions signInUIOptions, final Callback callback) { return new Runnable() { @Override public void run() { final HostedUIOptions hostedUIOptions = signInUIOptions.getHostedUIOptions(); // Reset settings to JSON JSONObject hostedUIJSON = null; try { hostedUIJSON = new JSONObject(getHostedUIJSONFromJSON().toString()); } catch (JSONException e) { callback.onError(new Exception("Could not create OAuth configuration object", e)); } if (hostedUIOptions.getFederationEnabled() != null) { mStore.set(FEDERATION_ENABLED_KEY, hostedUIOptions.getFederationEnabled() ? "true" : "false"); } else { mStore.set(FEDERATION_ENABLED_KEY, "true"); } if (hostedUIOptions.getSignOutQueryParameters() != null) { try { JSONObject signOutParams = new JSONObject(); for (Map.Entry e : hostedUIOptions.getSignOutQueryParameters().entrySet()) { signOutParams.put(e.getKey(), e.getValue()); } hostedUIJSON.put("SignOutQueryParameters", signOutParams); } catch (JSONException e1) { callback.onError(new Exception("Failed to construct sign-out query parameters", e1)); return; } } if (hostedUIOptions.getTokenQueryParameters() != null) { try { JSONObject tokenParams = new JSONObject(); for (Map.Entry e : hostedUIOptions.getTokenQueryParameters().entrySet()) { tokenParams.put(e.getKey(), e.getValue()); } hostedUIJSON.put("TokenQueryParameters", tokenParams); } catch (JSONException e1) { callback.onError(new Exception("Failed to construct token query parameters", e1)); return; } } mStore.set(HOSTED_UI_KEY, hostedUIJSON.toString()); final HashSet scopes; if (hostedUIOptions.getScopes() != null) { scopes = new HashSet(); Collections.addAll(scopes, hostedUIOptions.getScopes()); } else { scopes = null; } final String identityProvider = hostedUIOptions.getIdentityProvider(); final String idpIdentifier = hostedUIOptions.getIdpIdentifier(); mStore.set(SIGN_IN_MODE, SignInMode.HOSTED_UI.toString()); Auth.Builder hostedUIBuilder = null; try { hostedUIBuilder = getHostedUI(hostedUIJSON); } catch (JSONException e) { throw new RuntimeException("Failed to construct HostedUI from awsconfiguration.json", e); } hostedUIBuilder .setPersistenceEnabled(mIsPersistenceEnabled) .setAuthHandler(new AuthHandler() { boolean hasSucceededOnce = false; @Override public void onSuccess(AuthUserSession session) { Log.d(TAG, "onSuccess: HostedUI signed-in"); hasSucceededOnce = true; if (isFederationEnabled()) { federatedSignInWithoutAssigningState(userpoolsLoginKey, session.getIdToken().getJWTToken(), new Callback() { @Override public void onResult(UserStateDetails result) { Log.d(TAG, "onResult: Federation from the Hosted UI " + "succeeded"); } @Override public void onError(Exception e) { Log.e(TAG, "onError: Federation from the Hosted UI " + "failed", e); } }); } new Thread(new Runnable() { @Override public void run() { final UserStateDetails userStateDetails = getUserStateDetails(false); callback.onResult(userStateDetails); setUserState(userStateDetails); } }).start(); } @Override public void onSignout() { Log.d(TAG, "onSignout: HostedUI signed-out"); } @Override public void onFailure(final Exception e) { if (hasSucceededOnce) { Log.d(TAG, "onFailure: Ignoring failure because HostedUI " + "has signaled success at least once."); return; } new Thread(new Runnable() { @Override public void run() { callback.onError(e); } }).start(); } }); if (scopes != null) { hostedUIBuilder.setScopes(scopes); } if (identityProvider != null) { hostedUIBuilder.setIdentityProvider(identityProvider); } if (idpIdentifier != null) { hostedUIBuilder.setIdpIdentifier(idpIdentifier); } hostedUI = hostedUIBuilder.build(); if (signInUIOptions.getBrowserPackage() != null) { hostedUI.setBrowserPackage(signInUIOptions.getBrowserPackage()); } hostedUI.getSession(callingActivity); } }; } private Runnable _showSignInDropInUI(final Activity callingActivity, final SignInUIOptions signInUIOptions, final Callback callback) { return new Runnable() { @Override public void run() { synchronized (showSignInLockObject) { UserState userState = getUserStateDetails(false).getUserState(); if (UserState.SIGNED_IN.equals(userState)) { callback.onError(new RuntimeException("Called showSignIn while user is already signed-in")); return; } final AuthUIConfiguration.Builder authUIConfigBuilder = new AuthUIConfiguration.Builder() .canCancel(signInUIOptions.canCancel()) .isBackgroundColorFullScreen(false); if (signInUIOptions.getLogo() != null) { authUIConfigBuilder.logoResId(signInUIOptions.getLogo()); } if (signInUIOptions.getBackgroundColor() != null) { authUIConfigBuilder.backgroundColor(signInUIOptions.getBackgroundColor()); } if (isConfigurationKeyPresent(USER_POOLS)) { authUIConfigBuilder.userPools(true); } if (isConfigurationKeyPresent(FACEBOOK)) { authUIConfigBuilder.signInButton(FacebookButton.class); } if (isConfigurationKeyPresent(GOOGLE)) { authUIConfigBuilder.signInButton(GoogleButton.class); } Class nextActivityClass = signInUIOptions.nextActivity() == null ? callingActivity.getClass() : signInUIOptions.nextActivity(); SignInUI signin = (SignInUI) getClient(mContext, SignInUI.class); signin.login(callingActivity, nextActivityClass) .authUIConfiguration(authUIConfigBuilder.build()) .enableFederation(false) .execute(); showSignInWaitLatch = new CountDownLatch(1); try { showSignInWaitLatch.await(); callback.onResult(getUserStateDetails(false)); Log.d(TAG, "run: showSignIn completed"); } catch (InterruptedException e) { callback.onError(e); } } } }; } // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////// /** * Initialize the AWSMobileClient with the parameters passed in * {@link InitializeBuilder} * */ private void initializeWithBuilder(final InitializeBuilder initializeBuilder) { if (initializeBuilder.getAwsConfiguration() != null) { this.awsConfiguration = initializeBuilder.getAwsConfiguration(); } if (initializeBuilder.getSignInProviderConfig() != null) { this.signInProviderConfig = initializeBuilder.getSignInProviderConfig(); } try { fetchCognitoIdentity(initializeBuilder.getContext(), this.startupAuthResultHandler); } catch (final Exception exception) { Log.e(TAG, "Error in initializing the AWSMobileClient. " + "Check if AWS Cloud Config `awsconfiguration.json` is present in the application."); } } /** * Get the AWSConfigurable client if exists, else create one and * add it to the clientMap and return. * * @param context The activity context * @param clientClass SDK Client Class that confirms to the AWSConfigurable interface. */ public AWSConfigurable getClient(final Context context, final Class clientClass) { Log.d(TAG, "Retrieving the client instance for class: " + clientClass); AWSConfigurable client = clientMap.get(clientClass); try { if (client == null) { client = clientClass.newInstance().initialize(context.getApplicationContext(), this.awsConfiguration); clientMap.put(clientClass, client); Log.d(TAG, "Created the new client: " + client.toString()); } } catch (final Exception exception) { Log.e(TAG, "Error occurred in creating and initializing client. " + "Check the context and the clientClass passed in: " + clientClass, exception); } return client; } /** * Fetch the Cognito Identity for the user. * Register the SignProvider with permissions. * Resume any previously signed in auth session and fetch the cognito * federated identity for the user in order to connect to * AWS services. * * @param context The activity context * @param startupAuthResultHandler The callback function for resuming session * @deprecated This method is no longer in use. */ private void fetchCognitoIdentity(final Context context, final StartupAuthResultHandler startupAuthResultHandler) { try { Log.d(TAG, "Fetching the Cognito Identity."); // Create IdentityManager, register the providers and set the permissions. final IdentityManager identityManager = new IdentityManager(context, this.awsConfiguration); IdentityManager.setDefaultIdentityManager(identityManager); if (this.signInProviderConfig == null) { this.registerConfigSignInProviders(this.awsConfiguration); } else { this.registerUserSignInProvidersWithPermissions(); } this.resumeSession((Activity) context, startupAuthResultHandler); } catch (final Exception exception) { Log.e(TAG, "Error occurred in fetching the Cognito Identity " + "and resuming the auth session", exception); } } /** * Register the SignInProvider with their permissions * supplied by the user. */ private void registerUserSignInProvidersWithPermissions() { Log.d(TAG, "Using the SignInProviderConfig supplied by the user."); final IdentityManager identityManager = IdentityManager.getDefaultIdentityManager(); for (final SignInProviderConfig config : signInProviderConfig) { identityManager.addSignInProvider((Class) config.getSignInProviderClass()); if (config.getProviderPermissions() != null) { if (FacebookSignInProvider.class.isInstance(config.getSignInProviderClass())) { FacebookSignInProvider.setPermissions(config.getProviderPermissions()); } if (GoogleSignInProvider.class.isInstance(config.getSignInProviderClass())) { GoogleSignInProvider.setPermissions(config.getProviderPermissions()); } } } } /** * Register the SignInProvider and permissions based on the * AWSConfiguration. */ private void registerConfigSignInProviders(final AWSConfiguration awsConfiguration) { Log.d(TAG, "Using the SignInProviderConfig from `awsconfiguration.json`."); final IdentityManager identityManager = IdentityManager.getDefaultIdentityManager(); try { if (isConfigurationKeyPresent(USER_POOLS, awsConfiguration)) { identityManager.addSignInProvider(CognitoUserPoolsSignInProvider.class); } if (isConfigurationKeyPresent(FACEBOOK, awsConfiguration)) { identityManager.addSignInProvider(FacebookSignInProvider.class); } if (isConfigurationKeyPresent(GOOGLE, awsConfiguration)) { identityManager.addSignInProvider(GoogleSignInProvider.class); } } catch (NoClassDefFoundError exception) { // The above sign in providers are optional dependencies that are required for // drop-in UI to work correctly. Not registering them will still allow users to // sign in via other methods. } } /** * Check if the AWSConfiguration has the specified key. * * @param configurationKey The key for SignIn in AWSConfiguration */ private boolean isConfigurationKeyPresent(final String configurationKey) { return isConfigurationKeyPresent(configurationKey, this.awsConfiguration); } /** * Check if the AWSConfiguration has the specified key. * * @param configurationKey The key for SignIn in AWSConfiguration */ private boolean isConfigurationKeyPresent(final String configurationKey, final AWSConfiguration awsConfig) { try { JSONObject jsonObject = awsConfig.optJsonObject(configurationKey); if (configurationKey.equals(GOOGLE)) { return jsonObject != null && jsonObject.getString(GOOGLE_WEBAPP_CONFIG_KEY) != null; } else { return jsonObject != null; } } catch (final Exception exception) { Log.d(TAG, configurationKey + " not found in `awsconfiguration.json`"); return false; } } /** * Resume any previously signed-in session. * * @param callingActivity The activity context in the app * @param startupAuthResultHandler The Callback function for resuming an auth session */ private void resumeSession(final Activity callingActivity, final StartupAuthResultHandler startupAuthResultHandler) { IdentityManager .getDefaultIdentityManager() .resumeSession(callingActivity, startupAuthResultHandler); } /** * {@code InitializeBuilder} accepts and retrieves * the optional parameters necessary for initializing the * {@link AWSMobileClient} to work on. * * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public class InitializeBuilder { private Context context; private AWSConfiguration awsConfiguration; private SignInProviderConfig[] signInProviderConfig; /** * Constructor that intializes the InitializeBuilder * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public InitializeBuilder() { this.context = null; this.awsConfiguration = null; this.signInProviderConfig = null; } /** * Constructor that intializes the InitializeBuilder * with the context passed in. * * @param context The context object passed in * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public InitializeBuilder(final Context context) { this.context = context; this.awsConfiguration = null; this.signInProviderConfig = null; } /** * Sets the AWSConfiguration object passed in * * @param awsConfiguration The instance of awsconfiguration.json * @return instance of InitializeBuilder * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public InitializeBuilder awsConfiguration(final AWSConfiguration awsConfiguration) { this.awsConfiguration = awsConfiguration; return this; } /** * Sets the list of SignInProviderConfig passed in * * @param providersConfig The SignInProvider class with permissions * @return instance of InitializeBuilder * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public InitializeBuilder signInProviders(final SignInProviderConfig... providersConfig) { this.signInProviderConfig = providersConfig; return this; } /** * Retrieve the instance of AWSConfiguration. * * @return awsConfiguration * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public AWSConfiguration getAwsConfiguration() { return this.awsConfiguration; } /** * Retrieve the instance of SignInProvider class and permissions. * * @return signInProviderConfig * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public SignInProviderConfig[] getSignInProviderConfig() { return this.signInProviderConfig; } /** * Retrieve the context. * * @return context * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public Context getContext() { return this.context; } /** * Initialize the {@link AWSMobileClient} with the parameters passed in. * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public void execute() { initializeWithBuilder(this); } } /** * The wrapper class for SignInProvider class and * the permissions necessary for provider. * * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public class SignInProviderConfig { /** * SignInProvider class. * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated private Class signInProvider; /** * Permissions for the SignInProvider. * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated private String[] providerPermissions; /** * Constructor * * @param signInProvider The class object of the SignInProvider * @param providerPermissions Provider permissions if applicable * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public SignInProviderConfig(final Class signInProvider, final String... providerPermissions) { this.signInProvider = signInProvider; this.providerPermissions = providerPermissions; } /** * Retrieve the SignInProvider class * * @return The SignInProvider class * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public Class getSignInProviderClass() { return this.signInProvider; } /** * Retrieve the provider permissions * * @return the provider permissions * @deprecated Since 2.8.0 This method will be removed in the next minor version. * Please update to use AWSMobileClient using `initialize`. * Please visit https://aws-amplify.github.io for the latest Android documentation. */ @Deprecated public String[] getProviderPermissions() { return this.providerPermissions; } } } /** * A duplicate class of AWSEnhancedCognitoIdentityProvider that provides the ability to * branch into developer authenticated identities. */ class AWSMobileClientCognitoIdentityProvider extends AWSAbstractCognitoIdentityProvider { boolean isDeveloperAuthenticated; /** * An extension of the AbstractCognitoProvider that is used to communicate * with Cognito. * * @param accountId the account id of the developer * @param identityPoolId the identity pool id of the app/user in question * @param cibClient the cib client which will be used to contact the cib * back end */ public AWSMobileClientCognitoIdentityProvider(String accountId, String identityPoolId, AmazonCognitoIdentity cibClient) { super(accountId, identityPoolId, cibClient); } @Override protected String getUserAgent() { return AWSMobileClient.DEFAULT_USER_AGENT; } /** * Internal method that switches the flow of the {@link com.amazonaws.auth.CognitoCredentialsProvider} * to be the developer authenticated flow. * * @param identityId provided by user upstream * @param token provided by user upstream */ void setDeveloperAuthenticated(final String identityId, final String token) { super.setIdentityId(identityId); super.setToken(token); isDeveloperAuthenticated = true; } /** * Internal method that switches the flow of the {@link com.amazonaws.auth.CognitoCredentialsProvider} * to be the Cognito authenticated flow. */ void setNotDeveloperAuthenticated() { isDeveloperAuthenticated = false; } @Override public String getProviderName() { return "Cognito"; } @Override public String refresh() { if (isDeveloperAuthenticated) { // The identity id is already set in the setDeveloperAuthenticated call return this.token; } else { getIdentityId(); return null; } } } class OAuth2Utils { private final Context mContext; private final Uri mSignInRedirectUri; private CustomTabsServiceConnection mCustomTabsServiceConnection; private CustomTabsClient mCustomTabsClient; private CustomTabsSession mCustomTabsSession; private CustomTabsCallback customTabsCallback; private String mState; private String mError; private String mErrorDescription; OAuth2Utils(final Context context, final Uri signInRedirectUri) { this.mContext = context; this.mSignInRedirectUri = signInRedirectUri; customTabsCallback = new CustomTabsCallback(); } void preWarm() { // Warm up custom tabs for faster launch mCustomTabsServiceConnection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(final ComponentName name, final CustomTabsClient client) { mCustomTabsClient = client; mCustomTabsClient.warmup(0L); mCustomTabsSession = mCustomTabsClient.newSession(customTabsCallback); } @Override public void onServiceDisconnected(final ComponentName name) { mCustomTabsClient = null; } }; CustomTabsClient.bindCustomTabsService(mContext, ClientConstants.CHROME_PACKAGE, mCustomTabsServiceConnection); } void authorize(final String webDomain, final String clientId, final Map queryParameterMap) { mState = Pkce.generateRandom(); final Uri.Builder builder = Uri.parse(webDomain).buildUpon(); for (Map.Entry entry : queryParameterMap.entrySet()) { builder.appendQueryParameter(entry.getKey(), entry.getValue()); } if (!queryParameterMap.containsKey("code")) { builder.appendQueryParameter("response_type", "code"); } if (!queryParameterMap.containsKey("client_id")) { if (clientId != null) { builder.appendQueryParameter("client_id", clientId); } else { throw new IllegalArgumentException("Client id must be specified for an authorization request."); } } builder.appendQueryParameter("state", mState); navigate(builder.build()); } /** * Opens the CustomTabs browser to the specified URI * @param uri */ void navigate(Uri uri) { CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(mCustomTabsSession); CustomTabsIntent mCustomTabsIntent = builder.build(); mCustomTabsIntent.intent.setPackage(ClientConstants.CHROME_PACKAGE); mCustomTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); mCustomTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mCustomTabsIntent.launchUrl(mContext, uri); } boolean parse(Uri uri) { // Read the redirect URI for a code if (uri.getScheme().equals(mSignInRedirectUri.getScheme()) && uri.getAuthority().equals(mSignInRedirectUri.getAuthority()) && uri.getPath().equals(mSignInRedirectUri.getPath()) && uri.getQueryParameterNames().containsAll(mSignInRedirectUri.getQueryParameterNames())) { final String code = uri.getQueryParameter("code"); final String state = uri.getQueryParameter("state"); if (!mState.equals(state)) { return false; } mError = uri.getQueryParameter("error"); mErrorDescription = uri.getQueryParameter("error_description"); if (mError != null) { return true; } } return false; } Uri exchangeCode(String uri) { // Call XXXX/token to exchange code for access token return null; } }