Skip to content

Commit 291afc5

Browse files
authored
feat(AWSMobileClient): Adds option to specify browser for HostedUI (#2152)
Adds the option for a customer to specify a browser other than the default (Chrome) to launch HostedUI when signing in and out. Resolves #539
1 parent 49aa7a8 commit 291afc5

File tree

5 files changed

+153
-17
lines changed

5 files changed

+153
-17
lines changed

aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/Auth.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public final class Auth {
104104
*/
105105
private final String signOutRedirectUri;
106106

107+
/**
108+
* Optional string specifying the browser to open custom tabs. Defaults to Chrome if left null.
109+
*/
110+
private String browserPackage;
111+
107112
/**
108113
* Scopes for the requested tokens.
109114
*/
@@ -144,6 +149,14 @@ public void setPersistenceEnabled(boolean isPersistenceEnabled) {
144149
awsKeyValueStore.setPersistenceEnabled(this.isPersistenceEnabled);
145150
}
146151

152+
/**
153+
* Update the browser package to use for launching a custom tab.
154+
* @param browserPackage the browser package to use for launching a custom tab.
155+
*/
156+
public void setBrowserPackage(String browserPackage) {
157+
this.browserPackage = browserPackage;
158+
}
159+
147160
/**
148161
* Instantiates {@link Auth}.
149162
* <p><b>Note: </b>This SDK not obfuscate the App Secret. When using App Secret in the production
@@ -612,6 +625,13 @@ public String getSignOutRedirectUri() {
612625
return signOutRedirectUri;
613626
}
614627

628+
/**
629+
* @return Optional browser package set for this {@link Auth} instance.
630+
*/
631+
public String getBrowserPackage() {
632+
return browserPackage;
633+
}
634+
615635
/**
616636
* @return Identity Provider set for this {@link Auth} instance.
617637
*/
@@ -652,7 +672,12 @@ public Bundle getCustomTabExtras() {
652672
* @param activity Activity to launch custom tabs from and to listen for the intent completions.
653673
*/
654674
public void getSession(final Activity activity) {
655-
this.user.getSession(true, activity);
675+
if (getBrowserPackage() != null) {
676+
this.user.getSession(true, activity, getBrowserPackage());
677+
} else {
678+
this.user.getSession(true, activity);
679+
}
680+
656681
}
657682

658683
/**
@@ -687,7 +712,11 @@ public void setAuthHandler(final AuthHandler authHandler) {
687712
* </p>
688713
*/
689714
public void signOut() {
690-
this.user.signOut(false);
715+
if (getBrowserPackage() != null) {
716+
this.user.signOut(false, getBrowserPackage());
717+
} else {
718+
this.user.signOut(false);
719+
}
691720
}
692721

693722
/**
@@ -697,7 +726,11 @@ public void signOut() {
697726
* but the session may still be alive from the browser.
698727
*/
699728
public void signOut(final boolean clearLocalTokensOnly) {
700-
this.user.signOut(clearLocalTokensOnly);
729+
if (getBrowserPackage() != null) {
730+
this.user.signOut(clearLocalTokensOnly, getBrowserPackage());
731+
} else {
732+
this.user.signOut(clearLocalTokensOnly);
733+
}
701734
}
702735

703736
/**

aws-android-sdk-cognitoauth/src/main/java/com/amazonaws/mobileconnectors/cognitoauth/AuthClient.java

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public class AuthClient {
6464
*/
6565
public static final int CUSTOM_TABS_ACTIVITY_CODE = 49281;
6666

67+
/**
68+
* Specifies what browser package to default to if one isn't specified.
69+
*/
70+
private static final String DEFAULT_BROWSER_PACKAGE = ClientConstants.CHROME_PACKAGE;
71+
6772
/**
6873
* Android application context.
6974
*/
@@ -162,6 +167,26 @@ protected void setUsername(final String username) {
162167
* This must not be null when showSignInIfExpired is true.
163168
*/
164169
protected void getSession(final boolean showSignInIfExpired, final Activity activity) {
170+
getSession(showSignInIfExpired, activity, DEFAULT_BROWSER_PACKAGE);
171+
}
172+
173+
/**
174+
* Launches user authentication screen and returns a redirect Uri through an {@link Intent}.
175+
* <p>
176+
* Checks for cached, valid tokens and launches the Cognito Web UI if no valid tokens are
177+
* found. This method uses PKCE for authentication. This SDK, therefore, uses code-grant flow
178+
* to authenticate user. The proof-key and a state is generated and its hash is used in added
179+
* as query parameters to create the authentication FQDN.
180+
* The state value set this method is used to temporarily cache the proof-key on the device.
181+
* To exchange the code for tokens, the {@link Auth#getTokens(Uri)} method will use the
182+
* state in the redirect uri to fetch the stored proof-key.
183+
* </p>
184+
* @param showSignInIfExpired true if the web UI should launch when the session is expired
185+
* @param activity The activity to launch the sign in experience from.
186+
* This must not be null when showSignInIfExpired is true.
187+
* @param browserPackage String specifying the browser package to launch the specified url.
188+
*/
189+
protected void getSession(final boolean showSignInIfExpired, final Activity activity, final String browserPackage) {
165190
try {
166191
proofKey = Pkce.generateRandom();
167192
proofKeyHash = Pkce.generateHash(proofKey);
@@ -188,9 +213,10 @@ protected void getSession(final boolean showSignInIfExpired, final Activity acti
188213
pool.getScopes(),
189214
userHandler,
190215
showSignInIfExpired,
216+
browserPackage,
191217
activity);
192218
} else if (showSignInIfExpired) {
193-
launchCognitoAuth(pool.getSignInRedirectUri(), pool.getScopes(), activity);
219+
launchCognitoAuth(pool.getSignInRedirectUri(), pool.getScopes(), activity, browserPackage);
194220
} else {
195221
userHandler.onFailure(new Exception("No cached session"));
196222
}
@@ -211,8 +237,21 @@ protected String getUsername() {
211237
* </p>
212238
*/
213239
public void signOut() {
240+
signOut(DEFAULT_BROWSER_PACKAGE);
241+
}
242+
243+
/**
244+
* Signs-out a user.
245+
* <p>
246+
* Clears cached tokens for the user. Launches the sign-out Cognito web end-point to
247+
* clear all Cognito Auth cookies stored by Chrome.
248+
* </p>
249+
*
250+
* @param browserPackage String specifying the browser package to launch the specified url.
251+
*/
252+
public void signOut(String browserPackage) {
214253
LocalDataManager.clearCache(pool.awsKeyValueStore, context, pool.getAppId(), userId);
215-
launchSignOut(pool.getSignOutRedirectUri());
254+
launchSignOut(pool.getSignOutRedirectUri(), browserPackage);
216255
}
217256

218257
/**
@@ -226,9 +265,24 @@ public void signOut() {
226265
* but the session may still be alive from the browser.
227266
*/
228267
public void signOut(final boolean clearLocalTokensOnly) {
268+
signOut(clearLocalTokensOnly, DEFAULT_BROWSER_PACKAGE);
269+
}
270+
271+
/**
272+
* Signs-out a user.
273+
* <p>
274+
* Clears cached tokens for the user. Launches the sign-out Cognito web end-point to
275+
* clear all Cognito Auth cookies stored by Chrome.
276+
* </p>
277+
*
278+
* @param clearLocalTokensOnly true if signs out the user from the client,
279+
* but the session may still be alive from the browser.
280+
* @param browserPackage String specifying the browser package to launch the specified url.
281+
*/
282+
public void signOut(final boolean clearLocalTokensOnly, final String browserPackage) {
229283
LocalDataManager.clearCache(pool.awsKeyValueStore, context, pool.getAppId(), userId);
230284
if (!clearLocalTokensOnly) {
231-
launchSignOut(pool.getSignOutRedirectUri());
285+
launchSignOut(pool.getSignOutRedirectUri(), browserPackage);
232286
}
233287
}
234288

@@ -385,6 +439,7 @@ public void run() {
385439
* @param tokenScopes Required: A {@link Set<String>} specifying all scopes for the tokens.
386440
* @param callback Required: {@link AuthHandler}.
387441
* @param showSignInIfExpired true if the web UI should launch when the refresh token is expired
442+
* @param browserPackage String specifying the browser package to launch the specified url.
388443
* @param activity The activity to launch the sign in experience from.
389444
* This must not be null if showSignInIfExpired is true.
390445
*/
@@ -393,6 +448,7 @@ private void refreshSession(final AuthUserSession session,
393448
final Set<String> tokenScopes,
394449
final AuthHandler callback,
395450
final boolean showSignInIfExpired,
451+
final String browserPackage,
396452
final Activity activity) {
397453
new Thread(new Runnable() {
398454
final Handler handler = new Handler(context.getMainLooper());
@@ -437,7 +493,7 @@ public void run() {
437493
returnCallback = new Runnable() {
438494
@Override
439495
public void run() {
440-
launchCognitoAuth(redirectUri, tokenScopes, activity);
496+
launchCognitoAuth(redirectUri, tokenScopes, activity, browserPackage);
441497
}
442498
};
443499
} else {
@@ -525,8 +581,13 @@ private Map<String, String> generateTokenRefreshRequest(final String redirectUri
525581
* @param tokenScopes Required: A {@link Set<String>} specifying all scopes for the tokens.
526582
* @param activity The activity to launch the sign in experience from.
527583
* This must not be null if showSignInIfExpired is true.
584+
* @param browserPackage String specifying the browser package to launch the specified url.
528585
*/
529-
private void launchCognitoAuth(final String redirectUri, final Set<String> tokenScopes, final Activity activity) {
586+
private void launchCognitoAuth(
587+
final String redirectUri,
588+
final Set<String> tokenScopes,
589+
final Activity activity,
590+
final String browserPackage) {
530591
// Build the complete web domain to launch the login screen
531592
Uri.Builder builder = new Uri.Builder()
532593
.scheme(ClientConstants.DOMAIN_SCHEME)
@@ -569,36 +630,40 @@ private void launchCognitoAuth(final String redirectUri, final Set<String> token
569630

570631
final Uri fqdn = builder.build();
571632
LocalDataManager.cacheState(pool.awsKeyValueStore, context, state, proofKey, tokenScopes);
572-
launchCustomTabs(fqdn, activity);
633+
launchCustomTabs(fqdn, activity, browserPackage);
573634
}
574635

575636
/**
576637
* Creates the FQDM for Cognito's sign-out endpoint and launches Cognito Auth Web-Domain to
577638
* sign-out.
578639
* @param redirectUri Required: The redirect Uri, which will be launched after authentication.
640+
* @param browserPackage String specifying the browser package to launch the specified url.
579641
*/
580-
private void launchSignOut(final String redirectUri) {
642+
private void launchSignOut(final String redirectUri, final String browserPackage) {
581643
Uri.Builder builder = new Uri.Builder()
582644
.scheme(ClientConstants.DOMAIN_SCHEME)
583645
.authority(pool.getAppWebDomain()).appendPath(ClientConstants.DOMAIN_PATH_SIGN_OUT)
584646
.appendQueryParameter(ClientConstants.DOMAIN_QUERY_PARAM_CLIENT_ID, pool.getAppId())
585647
.appendQueryParameter(ClientConstants.DOMAIN_QUERY_PARAM_LOGOUT_URI, redirectUri);
586648
final Uri fqdn = builder.build();
587-
launchCustomTabsWithoutCallback(fqdn);
649+
launchCustomTabsWithoutCallback(fqdn, browserPackage);
588650
}
589651

590652
/**
591-
* Launches the HostedUI webpage on Chrome Tab.
653+
* Launches the HostedUI webpage on a Custom Tab.
592654
* @param uri Required: {@link Uri}.
593655
* @param activity Activity to launch custom tabs from and which will listen for the intent completion.
656+
* @param browserPackage Optional string specifying the browser package to launch the specified url.
657+
* Defaults to Chrome if null.
594658
*/
595-
private void launchCustomTabs(final Uri uri, final Activity activity) {
659+
private void launchCustomTabs(final Uri uri, final Activity activity, final String browserPackage) {
596660
try {
597661
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(mCustomTabsSession);
598662
mCustomTabsIntent = builder.build();
599663
if(pool.getCustomTabExtras() != null)
600664
mCustomTabsIntent.intent.putExtras(pool.getCustomTabExtras());
601-
mCustomTabsIntent.intent.setPackage(ClientConstants.CHROME_PACKAGE);
665+
mCustomTabsIntent.intent.setPackage(
666+
browserPackage != null ? browserPackage : DEFAULT_BROWSER_PACKAGE);
602667
mCustomTabsIntent.intent.setData(uri);
603668
activity.startActivityForResult(
604669
CustomTabsManagerActivity.createStartIntent(context, mCustomTabsIntent.intent),
@@ -610,16 +675,19 @@ private void launchCustomTabs(final Uri uri, final Activity activity) {
610675
}
611676

612677
/**
613-
* Launches the HostedUI page on Chrome Tab without paying attention to callbacks.
678+
* Launches the HostedUI page on Custom Tab without paying attention to callbacks.
614679
* @param uri Required: {@link Uri}.
680+
* @param browserPackage Optional string specifying the browser package to launch the specified url.
681+
* Defaults to Chrome if null.
615682
*/
616-
private void launchCustomTabsWithoutCallback(final Uri uri) {
683+
private void launchCustomTabsWithoutCallback(final Uri uri, final String browserPackage) {
617684
try {
618685
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(mCustomTabsSession);
619686
mCustomTabsIntent = builder.build();
620687
if(pool.getCustomTabExtras() != null)
621688
mCustomTabsIntent.intent.putExtras(pool.getCustomTabExtras());
622-
mCustomTabsIntent.intent.setPackage(ClientConstants.CHROME_PACKAGE);
689+
mCustomTabsIntent.intent.setPackage(
690+
browserPackage != null ? browserPackage : DEFAULT_BROWSER_PACKAGE);
623691
mCustomTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
624692
mCustomTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
625693
mCustomTabsIntent.launchUrl(context, uri);

aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,6 +1530,9 @@ public Void run() throws Exception {
15301530
}
15311531
if (signOutOptions.isInvalidateTokens()) {
15321532
if (hostedUI != null) {
1533+
if (signOutOptions.getBrowserPackage() != null) {
1534+
hostedUI.setBrowserPackage(signOutOptions.getBrowserPackage());
1535+
}
15331536
hostedUI.signOut();
15341537
} else if (mOAuth2Client != null) {
15351538
final CountDownLatch latch = new CountDownLatch(1);
@@ -3316,6 +3319,9 @@ public void run() {
33163319
hostedUIBuilder.setIdpIdentifier(idpIdentifier);
33173320
}
33183321
hostedUI = hostedUIBuilder.build();
3322+
if (signInUIOptions.getBrowserPackage() != null) {
3323+
hostedUI.setBrowserPackage(signInUIOptions.getBrowserPackage());
3324+
}
33193325
hostedUI.getSession(callingActivity);
33203326
}
33213327
};

aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/SignInUIOptions.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public class SignInUIOptions {
1414
this.builder = builder;
1515
}
1616

17+
public String getBrowserPackage() { return builder.browserPackage; }
18+
1719
public Integer getLogo() {
1820
return builder.logo;
1921
}
@@ -39,6 +41,7 @@ public static Builder builder() {
3941
}
4042

4143
public static class Builder {
44+
private String browserPackage;
4245
private Integer logo;
4346
private Integer backgroundColor;
4447
private boolean canCancel;
@@ -52,6 +55,17 @@ public Builder logo(final Integer logoResourceId) {
5255
return this;
5356
}
5457

58+
/**
59+
* Specify which browser package to use for the sign in operation (e.g. "org.mozilla.firefox").
60+
* Defaults to the Chrome package if not specified.
61+
* @param browserPackage the browser package to use for the sign in operation (e.g. "org.mozilla.firefox").
62+
* @return The builder object.
63+
*/
64+
public Builder browserPackage(final String browserPackage) {
65+
this.browserPackage = browserPackage;
66+
return this;
67+
}
68+
5569
public Builder backgroundColor(final Integer logoResourceId) {
5670
backgroundColor = logoResourceId;
5771
return this;

aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/SignOutOptions.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public boolean isInvalidateTokens() {
1919
return builder.invalidateTokens;
2020
}
2121

22+
public String getBrowserPackage() { return builder.browserPackage; }
23+
2224
/**
2325
* Start creating a SignOutOptions object with this builder.
2426
*
@@ -29,6 +31,7 @@ public static Builder builder() {
2931
}
3032

3133
public static class Builder {
34+
private String browserPackage;
3235
private boolean signOutGlobally;
3336
private boolean invalidateTokens;
3437

@@ -46,6 +49,18 @@ public Builder signOutGlobally(final boolean signOutGlobally) {
4649
return this;
4750
}
4851

52+
/**
53+
* If {@link #invalidateTokens} is true, this can optionally be used to specify which browser package should
54+
* perform the sign out action (e.g. "org.mozilla.firefox"). Defaults to the Chrome package if not specified.
55+
*
56+
* @param browserPackage String specifying the browser to open custom tabs.
57+
* @return Builder object for chaining
58+
*/
59+
public Builder browserPackage(final String browserPackage) {
60+
this.browserPackage = browserPackage;
61+
return this;
62+
}
63+
4964
/**
5065
* When the functionality is available, invalidate tokens on the server by making a call.
5166
* This is currently only supported with OAuth sessions that support this functionality.

0 commit comments

Comments
 (0)