diff --git a/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/AuthClient.java b/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/AuthClient.java index 171be894bd..072fc33258 100644 --- a/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/AuthClient.java +++ b/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/AuthClient.java @@ -21,6 +21,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Handler; import androidx.browser.customtabs.CustomTabsClient; @@ -28,9 +31,11 @@ import androidx.browser.customtabs.CustomTabsServiceConnection; import androidx.browser.customtabs.CustomTabsSession; import android.text.TextUtils; +import android.util.Log; import com.amazonaws.cognito.clientcontext.data.UserContextDataProvider; import com.amazonaws.mobileconnectors.cognitoauth.activities.CustomTabsManagerActivity; +import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthClientException; import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthInvalidGrantException; import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthNavigationException; import com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthServiceException; @@ -44,8 +49,11 @@ import java.net.URL; import java.security.InvalidParameterException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Local client for {@link Auth}. @@ -64,6 +72,21 @@ public class AuthClient { */ public static final int CUSTOM_TABS_ACTIVITY_CODE = 49281; + /** + * Namespace for logging client activities + */ + private static final String TAG = AuthClient.class.getSimpleName(); + + /** + * Name of redirect activity in charge of handling auth responses. + */ + private static final String REDIRECT_ACTIVITY_NAME = "HostedUIRedirectActivity"; + + /** + * Default timeout duration for auth redirects. + */ + private static final long REDIRECT_TIMEOUT_SECONDS = 10; + /** * Specifies what browser package to default to if one isn't specified. */ @@ -104,12 +127,20 @@ public class AuthClient { */ private AuthHandler userHandler; + /** + * Remembers whether redirect activity was found in manifest or not. + */ + private boolean isRedirectActivityDeclared; + + // - Chrome Custom Tabs Controls private CustomTabsClient mCustomTabsClient; private CustomTabsSession mCustomTabsSession; private CustomTabsIntent mCustomTabsIntent; private CustomTabsServiceConnection mCustomTabsServiceConnection; + private CountDownLatch cookiesCleared; + /** * Constructs {@link AuthClient} with no user name. * @param context Required: The android application {@link Context}. @@ -129,6 +160,7 @@ protected AuthClient(final Context context, final Auth pool, final String userna this.context = context; this.pool = pool; this.userId = username; + this.isRedirectActivityDeclared = false; preWarmChrome(); } @@ -250,8 +282,7 @@ public void signOut() { * @param browserPackage String specifying the browser package to launch the specified url. */ public void signOut(String browserPackage) { - LocalDataManager.clearCache(pool.awsKeyValueStore, context, pool.getAppId(), userId); - launchSignOut(pool.getSignOutRedirectUri(), browserPackage); + signOut(false, browserPackage); } /** @@ -271,8 +302,8 @@ public void signOut(final boolean clearLocalTokensOnly) { /** * Signs-out a user. *

- * Clears cached tokens for the user. Launches the sign-out Cognito web end-point to - * clear all Cognito Auth cookies stored by Chrome. + * Launches the sign-out Cognito web end-point to clear all Cognito Auth cookies stored + * by Chrome. Cached tokens will be deleted if sign-out redirect is completed. *

* * @param clearLocalTokensOnly true if signs out the user from the client, @@ -280,9 +311,33 @@ public void signOut(final boolean clearLocalTokensOnly) { * @param browserPackage String specifying the browser package to launch the specified url. */ public void signOut(final boolean clearLocalTokensOnly, final String browserPackage) { - LocalDataManager.clearCache(pool.awsKeyValueStore, context, pool.getAppId(), userId); if (!clearLocalTokensOnly) { + endSession(browserPackage); + } + + // Delete local cache + LocalDataManager.clearCache(pool.awsKeyValueStore, context, pool.getAppId(), userId); + } + + /** + * Ends current browser session. + * @param browserPackage browser package to launch sign-out endpoint from. + * @throws AuthClientException if sign-out redirect fails to resolve. + */ + private void endSession(final String browserPackage) throws AuthClientException { + boolean redirectReceived; + try { + cookiesCleared = new CountDownLatch(1); launchSignOut(pool.getSignOutRedirectUri(), browserPackage); + if (!isRedirectActivityDeclared()) { + cookiesCleared.countDown(); + } + redirectReceived = cookiesCleared.await(REDIRECT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new AuthNavigationException("User cancelled sign-out."); + } + if (!redirectReceived) { + throw new AuthServiceException("Timed out while waiting for sign-out redirect response."); } } @@ -415,6 +470,11 @@ public void run() { } } } else { + if (cookiesCleared != null) { + cookiesCleared.countDown(); + Log.d(TAG, "Sign-out was successful."); + } + // User sign-out. returnCallback = new Runnable() { @Override @@ -572,7 +632,7 @@ private Map generateTokenRefreshRequest(final String redirectUri if (userContextData != null) { httpBodyParams.put(ClientConstants.DOMAIN_QUERY_PARAM_USERCONTEXTDATA, userContextData); } - return httpBodyParams; + return httpBodyParams; } /** @@ -671,9 +731,9 @@ private void launchCustomTabs(final Uri uri, final Activity activity, final Stri CUSTOM_TABS_ACTIVITY_CODE ); } else { - context.startActivity( - CustomTabsManagerActivity.createStartIntent(context, mCustomTabsIntent.intent) - ); + Intent startIntent = CustomTabsManagerActivity.createStartIntent(context, mCustomTabsIntent.intent); + startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY); + context.startActivity(startIntent); } } catch (final Exception e) { userHandler.onFailure(e); @@ -708,4 +768,36 @@ public void onServiceDisconnected(final ComponentName name) { } }; } + + // Inspects context to determine whether HostedUIRedirectActivity is declared in + // customer's AndroidManifest.xml. + private boolean isRedirectActivityDeclared() { + // If the activity was found at least once, then don't bother searching again. + if (isRedirectActivityDeclared) { + return true; + } + if (context == null) { + Log.w(TAG, "Context is null. Failed to inspect packages."); + return false; + } + try { + List packages = context.getPackageManager() + .getInstalledPackages(PackageManager.GET_ACTIVITIES); + for (PackageInfo packageInfo : packages) { + if (packageInfo.activities == null) { + continue; + } + for (ActivityInfo activityInfo : packageInfo.activities) { + if (activityInfo.name.contains(REDIRECT_ACTIVITY_NAME)) { + isRedirectActivityDeclared = true; + return true; + } + } + } + Log.w(TAG, REDIRECT_ACTIVITY_NAME + " is not declared in AndroidManifest."); + } catch (Exception error) { + Log.w(TAG, "Failed to inspect packages."); + } + return false; + } } diff --git a/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsRedirectActivity.java b/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsRedirectActivity.java index 50d13ea510..52233c18e2 100644 --- a/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsRedirectActivity.java +++ b/aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/activities/CustomTabsRedirectActivity.java @@ -3,6 +3,12 @@ import android.app.Activity; import android.os.Bundle; +/** + * Handles auth redirect for sign-in and sign-out. + * + * If cognitoauth module is being used in conjunction with AWSMobileClient, then use + * com.amazonaws.mobile.client.activities.HostedUIRedirectActivity instead of this. + */ public final class CustomTabsRedirectActivity extends Activity { @Override public void onCreate(Bundle savedInstanceBundle) { diff --git a/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/activities/HostedUIRedirectActivity.java b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/activities/HostedUIRedirectActivity.java new file mode 100644 index 0000000000..a15ee277a5 --- /dev/null +++ b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/activities/HostedUIRedirectActivity.java @@ -0,0 +1,28 @@ +package com.amazonaws.mobile.client.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +import com.amazonaws.mobile.client.AWSMobileClient; +import com.amazonaws.mobileconnectors.cognitoauth.activities.CustomTabsManagerActivity; + +/** + * Handles auth redirect for sign-in and sign-out. + */ +public final class HostedUIRedirectActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceBundle) { + super.onCreate(savedInstanceBundle); + startActivity(CustomTabsManagerActivity.createResponseHandlingIntent( + this, getIntent().getData())); + } + + @Override + public void onResume() { + super.onResume(); + Log.d("AuthClient", "Handling auth redirect response"); + AWSMobileClient.getInstance().handleAuthResponse(getIntent()); + finish(); + } +}