Skip to content

Commit 7d28c9b

Browse files
authored
Added error handling when webcp redirects have browser protocol, Fixes AB#3385536 (#2767)
Issue : There is a recent change made in WebCP side to pass browser protocol redirects for China market urls, 3rd part MDM urls and also the Url to playstore launch for installing CP. This is an unexpected change on their side and we did not have proper handling for these cases. Fix : 1. Added try/catch block around the operations in processWebsiteRequest method. 2. Returning MDM_FLOW as the result instead of CANCELLED result currently being returned when we open link in a browser for webcp urls. 3. Added telemetry for operations in processWebsiteRequest method. 4. Made some minor improvements in AzureActiveDirectoryWebViewClient class to avoid duplicate code for getting broker package name from url. Related broker with just an attribute added : AzureAD/ad-accounts-for-android#3228 Fixes [AB#3385536](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3385536)
1 parent d8106a1 commit 7d28c9b

5 files changed

Lines changed: 267 additions & 27 deletions

File tree

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ vNext
99
- [MINOR] Add client scenario to JwtRequestBody (#2755)
1010
- [MINOR] Awaiting MFA Delegate now automatically returns the AuthMethods to be used when calling MFA Challenge (#2764)
1111
- [MINOR] SDK now handles SMS as strong authentication method #2766
12+
- [MINOR] Added error handling when webcp redirects have browser protocol #2767
1213

1314
Version 22.1.0
1415
----------

common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java

Lines changed: 97 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import java.util.concurrent.TimeUnit;
9393

9494
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.AMAZON_APP_REDIRECT_PREFIX;
95+
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.AZURE_AUTHENTICATOR_APP_PACKAGE_NAME;
9596
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.COMPANY_PORTAL_APP_PACKAGE_NAME;
9697
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.IPPHONE_APP_PACKAGE_NAME;
9798
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.IPPHONE_APP_SHA512_RELEASE_SIGNATURE;
@@ -130,9 +131,9 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient {
130131
private HashMap<String, String> mRequestHeaders;
131132
private String mRequestUrl;
132133
private boolean mAuthUxJavaScriptInterfaceAdded = false;
133-
private boolean mIsWebCpInWebViewFeatureEnabled = false;
134+
private boolean mInWebCpFlow = false;
134135

135-
private String mUtid;
136+
private final String mUtid;
136137

137138
public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
138139
@NonNull final IAuthorizationCompletionCallback completionCallback,
@@ -338,10 +339,10 @@ else if (isRedirectUrl(formattedURL)) {
338339
} else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_ATTACH_PRT_HEADER_WHEN_CROSS_CLOUD) && isCrossCloudRedirect(formattedURL)) {
339340
Logger.info(methodTag,"Navigation contains cross cloud redirect.");
340341
processCrossCloudRedirect(view, url);
341-
} else if (mIsWebCpInWebViewFeatureEnabled && isWebCpEnrollmentUrl(url)) {
342+
} else if (mInWebCpFlow && isWebCpEnrollmentUrl(url)) {
342343
Logger.info(methodTag,"Navigation contains web cp enrollment url.");
343344
processWebCpEnrollmentUrl(view, url);
344-
} else if (mIsWebCpInWebViewFeatureEnabled && isWebCpAuthorizeUrl(url)) {
345+
} else if (mInWebCpFlow && isWebCpAuthorizeUrl(url)) {
345346
processWebCpAuthorize(view, url);
346347
} else if (isDeviceCaRequest(url) && isHttpsScheme(url) && isWebCpInWebviewFeatureEnabled(url)) {
347348
// Special handling for device CA requests due to a corner case in eSTS for webapps/confidential clients, which should be handled by the WebView.
@@ -574,19 +575,78 @@ private void processSwitchBrowserRequest(@NonNull final String url) {
574575
}
575576
}
576577

577-
private void processWebsiteRequest(@NonNull final WebView view, @NonNull final String url) {
578+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
579+
protected void processWebsiteRequest(@NonNull final WebView view, @NonNull final String url) {
578580
final String methodTag = TAG + ":processWebsiteRequest";
579581
view.stopLoading();
582+
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
583+
final Span span = spanContext != null ?
584+
OTelUtility.createSpanFromParent(SpanName.ProcessWebsiteRequest.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessWebsiteRequest.name());
585+
span.setAttribute(AttributeName.is_in_web_cp_flow.name(), mInWebCpFlow);
586+
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
587+
if (isDeviceCaRequest(url)) {
588+
processDeviceCaRequest(view, url);
589+
span.setStatus(StatusCode.OK);
590+
return;
591+
}
580592

581-
if (isDeviceCaRequest(url)) {
582-
processDeviceCaRequest(view, url);
583-
} else {
584-
Logger.info(methodTag, "Not a device CA request. Redirecting to browser.");
585-
openLinkInBrowser(url);
586-
returnResult(RawAuthorizationResult.ResultCode.CANCELLED);
593+
if (isRedirectToPlaystoreToInstallCp(url) && mInWebCpFlow) {
594+
handlePlaystoreLaunchUrlFromWebCp(url);
595+
span.setStatus(StatusCode.OK);
596+
return;
597+
}
598+
599+
// Default case: redirect to browser
600+
handleBrowserRedirect(methodTag, url);
601+
span.setStatus(StatusCode.OK);
602+
} catch (final Throwable throwable) {
603+
Logger.error(methodTag, "Failed to open link in browser.", throwable);
604+
span.recordException(throwable);
605+
span.setStatus(StatusCode.ERROR);
606+
returnError(ErrorStrings.UNEXPECTED_ERROR, "No browser found to open the link.");
607+
} finally {
608+
span.end();
587609
}
588610
}
589611

612+
/**
613+
* Handles the default browser redirect case for website requests.
614+
* @param methodTag The method tag for logging
615+
* @param url The URL to open in browser
616+
*/
617+
private void handleBrowserRedirect(@NonNull final String methodTag, @NonNull final String url) {
618+
Logger.info(methodTag, "Not a device CA request. Redirecting to browser.");
619+
openLinkInBrowser(url);
620+
final RawAuthorizationResult.ResultCode resultCode = mInWebCpFlow
621+
? RawAuthorizationResult.ResultCode.MDM_FLOW
622+
: RawAuthorizationResult.ResultCode.CANCELLED;
623+
624+
Logger.info(methodTag, "Returning result code: " + resultCode);
625+
returnResult(resultCode);
626+
}
627+
628+
// Handles Playstore launch URLs originating from WebCP, specifically for installing the Company Portal app.
629+
private void handlePlaystoreLaunchUrlFromWebCp(@NonNull final String url) {
630+
final String methodTag = TAG + ":handlePlaystoreLaunchUrlFromWebCp";
631+
Logger.info(methodTag, "Handling playstore launch URL from WebCP.");
632+
SpanExtension.current().setAttribute(AttributeName.is_redirect_to_playstore_launch_from_webcp.name(), true);
633+
openLinkInBrowser(url);
634+
returnResult(RawAuthorizationResult.ResultCode.MDM_FLOW);
635+
}
636+
637+
/**
638+
* Checks if it is a redirect to Playstore to install Company Portal app.
639+
* @param url The URL to check.
640+
* @return true if it is a redirect to Playstore to install Company Portal app.
641+
*/
642+
private boolean isRedirectToPlaystoreToInstallCp(@NonNull final String url) {
643+
if (url.contains(PLAY_STORE_INSTALL_APP_PREFIX + COMPANY_PORTAL_APP_PACKAGE_NAME)) {
644+
Logger.info(TAG, "Redirect to Playstore to install Company Portal app.");
645+
return true;
646+
}
647+
return false;
648+
}
649+
590650
/**
591651
* Processed device CA requests detected in the web flow.
592652
* @param view The {@link WebView} instance in which the request originated.
@@ -679,10 +739,11 @@ protected boolean isWebCpInWebviewFeatureEnabled(@NonNull final String originalU
679739
final int waitForFlightsTimeOut = CommonFlightsManager.INSTANCE.getFlightsProvider().getIntValue(CommonFlight.WEB_CP_WAIT_TIMEOUT_FOR_FLIGHTS);
680740
final boolean isWebCpFlightEnabled = CommonFlightsManager.INSTANCE.getFlightsProviderForTenant(homeTenantId, waitForFlightsTimeOut).isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW);
681741
SpanExtension.current().setAttribute(AttributeName.web_cp_flight_get_time.name(), (System.currentTimeMillis() - webCpGetFlightStartTime));
742+
682743
if (isWebCpFlightEnabled) {
683744
// Directly enabled via flight rollout.
684745
Logger.info(methodTag, "WebCP in WebView feature is enabled.");
685-
mIsWebCpInWebViewFeatureEnabled = true;
746+
mInWebCpFlow = true;
686747
return true;
687748
}
688749

@@ -755,12 +816,11 @@ private boolean processPlayStoreURL(@NonNull final WebView view, @NonNull final
755816

756817
view.stopLoading();
757818
if (!(url.startsWith(PLAY_STORE_INSTALL_PREFIX + COMPANY_PORTAL_APP_PACKAGE_NAME))
758-
&& !(url.startsWith(PLAY_STORE_INSTALL_PREFIX + AuthenticationConstants.Broker.AZURE_AUTHENTICATOR_APP_PACKAGE_NAME))) {
819+
&& !(url.startsWith(PLAY_STORE_INSTALL_PREFIX + AZURE_AUTHENTICATOR_APP_PACKAGE_NAME))) {
759820
Logger.info(methodTag, "The URI is either trying to open an unknown application or contains unknown query parameters");
760821
return false;
761822
}
762-
final String appPackageName = (url.contains(COMPANY_PORTAL_APP_PACKAGE_NAME) ?
763-
COMPANY_PORTAL_APP_PACKAGE_NAME : AuthenticationConstants.Broker.AZURE_AUTHENTICATOR_APP_PACKAGE_NAME);
823+
final String appPackageName = getBrokerAppPackageNameFromUrl(url);
764824
Logger.info(methodTag, "Request to open PlayStore to install package : '" + appPackageName + "'");
765825

766826
try {
@@ -779,21 +839,13 @@ private boolean processPlayStoreURL(@NonNull final WebView view, @NonNull final
779839
private boolean processPlayStoreURLForBrokerApps(@NonNull final WebView view, @NonNull final String url) {
780840
final String methodTag = TAG + ":processPlayStoreURL";
781841

782-
final String appPackageName = (url.contains(COMPANY_PORTAL_APP_PACKAGE_NAME) ?
783-
COMPANY_PORTAL_APP_PACKAGE_NAME : AuthenticationConstants.Broker.AZURE_AUTHENTICATOR_APP_PACKAGE_NAME);
842+
final String appPackageName = getBrokerAppPackageNameFromUrl(url);
784843
Logger.info(methodTag, "Request to open PlayStore to install package : '" + appPackageName + "'");
785844

786845
try {
787846
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(PLAY_STORE_INSTALL_APP_PREFIX + appPackageName));
788-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
789847
getActivity().startActivity(intent);
790848
view.stopLoading();
791-
if (appPackageName.equalsIgnoreCase(COMPANY_PORTAL_APP_PACKAGE_NAME) && (mIsWebCpInWebViewFeatureEnabled)) {
792-
// If the flight for webcp is enabled, we will return the result code to the activity to indicate that the MDM flow has started.
793-
// Note that this is only for CP app as we are not aware of any other flows (other than webcp) reaching this code path.
794-
returnResult(RawAuthorizationResult.ResultCode.MDM_FLOW);
795-
}
796-
797849
return true;
798850
} catch (final ActivityNotFoundException e) {
799851
//if GooglePlay is not present on the device.
@@ -839,7 +891,8 @@ private void processAmazonAppUri(@NonNull final String url) {
839891
Logger.info(methodTag, "Sent Intent to launch Amazon app");
840892
}
841893

842-
private void openLinkInBrowser(final String url) {
894+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
895+
protected void openLinkInBrowser(final String url) {
843896
final String methodTag = TAG + ":openLinkInBrowser";
844897
Logger.info(methodTag, "Try to open url link in browser");
845898
final String link = url
@@ -910,7 +963,7 @@ private void processInvalidRedirectUri(@NonNull final WebView view,
910963
final String methodTag = TAG + ":processInvalidRedirectUri";
911964

912965
Logger.error(methodTag, "The RedirectUri is not as expected.", null);
913-
Logger.errorPII(methodTag,String.format("Received %s and expected %s", url, mRedirectUrl), null);
966+
Logger.errorPII(methodTag, String.format("Received %s and expected %s", url, mRedirectUrl), null);
914967
returnError(ErrorStrings.DEVELOPER_REDIRECTURI_INVALID,
915968
String.format("The RedirectUri is not as expected. Received %s and expected %s", url,
916969
mRedirectUrl));
@@ -1138,4 +1191,22 @@ public void finalizeBeforeSendingResult(@NonNull final RawAuthorizationResult re
11381191
((AbstractSmartcardCertBasedAuthChallengeHandler<?>)mCertBasedAuthChallengeHandler).promptSmartcardRemovalForResult(callback);
11391192
}
11401193

1194+
/**
1195+
* Extracts the broker app package name from the given URL.
1196+
* Supports all known broker apps. Returns the first match found.
1197+
* If no known broker app is found, logs a warning and returns an empty string.
1198+
*
1199+
* @param url The URL to inspect.
1200+
* @return The broker app package name, or empty string if not found.
1201+
*/
1202+
private String getBrokerAppPackageNameFromUrl(@NonNull final String url) {
1203+
for (final BrokerData brokerData : BrokerData.getAllBrokers()) {
1204+
if (url.contains(brokerData.getPackageName())) {
1205+
return brokerData.getPackageName();
1206+
}
1207+
}
1208+
Logger.warn(TAG + ":getBrokerAppPackageNameFromUrl", "No known broker app package name found in URL: " + url);
1209+
return "";
1210+
}
1211+
11411212
}

0 commit comments

Comments
 (0)