9292import java .util .concurrent .TimeUnit ;
9393
9494import 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 ;
9596import static com .microsoft .identity .common .adal .internal .AuthenticationConstants .Broker .COMPANY_PORTAL_APP_PACKAGE_NAME ;
9697import static com .microsoft .identity .common .adal .internal .AuthenticationConstants .Broker .IPPHONE_APP_PACKAGE_NAME ;
9798import 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