diff --git a/changelog.txt b/changelog.txt index 4ee084308b..7829e11fe2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,7 @@ Version 21.3.0 - [MINOR] changes accommodating the WrappedKeyAlgoIdentifier module (#2667) - [MINOR] Fixing the sign in screens when edge to edge is enabled (#2665) - [MINOR] Native auth: Make native auth MFA feature more backward compatible(#2669) +- [MINOR] Showing webcp flow in webview (#2673) Version 21.2.0 ---------- diff --git a/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java b/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java index 1ec8ee73d6..7acfaeb766 100644 --- a/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java +++ b/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java @@ -1267,6 +1267,16 @@ public static String computeMaxHostBrokerProtocol() { */ public static final String WEBCP_LAUNCH_COMPANY_PORTAL_URL = BROWSER_EXT_WEB_CP + "enrollment"; + /** + * Redirect URL from WebCP that should launch the enrollment flow. + */ + public static final String WEBCP_ENROLLMENT_URL = "https://enterprise.google.com/android/enroll"; + + /** + * WebCP clientId. + */ + public static final String WEBCP_CLIENT_ID = "74bcdadc-2fdc-4bb3-8459-76d06952a0e9"; + /** * A query param indicating that this is an intune device CA link. */ diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java index 5d65984a1f..94dd76afa5 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java @@ -55,7 +55,7 @@ import com.microsoft.identity.common.internal.ui.webview.certbasedauth.AbstractSmartcardCertBasedAuthChallengeHandler; import com.microsoft.identity.common.internal.ui.webview.certbasedauth.AbstractCertBasedAuthChallengeHandler; import com.microsoft.identity.common.internal.ui.webview.certbasedauth.CertBasedAuthFactory; -import com.microsoft.identity.common.internal.ui.webview.challengehandlers.CrossCloudChallengeHandler; +import com.microsoft.identity.common.internal.ui.webview.challengehandlers.ReAttachPrtHeaderHandler; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserChallenge; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserRequestHandler; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.NonceRedirectHandler; @@ -81,12 +81,14 @@ import com.microsoft.identity.common.logging.Logger; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.Principal; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.AMAZON_APP_REDIRECT_PREFIX; import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.COMPANY_PORTAL_APP_PACKAGE_NAME; @@ -115,6 +117,8 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient { public static final String ERROR = "error"; public static final String ERROR_DESCRIPTION = "error_description"; private static final String DEVICE_CERT_ISSUER = "CN=MS-Organization-Access"; + // 3 secs wait for the intent to be launched and the current flow is killed for smooth transition. + private static final int THREAD_SLEEP_FOR_INTENT_LAUNCH_MS = 3; private final String mRedirectUrl; private final CertBasedAuthFactory mCertBasedAuthFactory; private AbstractCertBasedAuthChallengeHandler mCertBasedAuthChallengeHandler; @@ -246,7 +250,6 @@ private boolean handleUrl(final WebView view, final String url) { mAuthUxJavaScriptInterfaceAdded = false; } - try { if (isPkeyAuthUrl(formattedURL)) { Logger.info(methodTag,"WebView detected request for pkeyauth challenge."); @@ -324,12 +327,15 @@ else if (isRedirectUrl(formattedURL)) { processHeaderForwardingRequiredUri(view, url); } else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_ATTACH_PRT_HEADER_WHEN_CROSS_CLOUD) && isCrossCloudRedirect(formattedURL)) { Logger.info(methodTag,"Navigation contains cross cloud redirect."); - processCloudRedirectAndPrtHeader(view, url); - } - else { + processCrossCloudRedirect(view, url); + } else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW) && isWebCpEnrollmentUrl(url)) { + Logger.info(methodTag,"Navigation contains web cp enrollment url."); + processWebCpEnrollmentUrl(view, url); + } else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW) && isWebCpAuthorizeUrl(url)) { + processWebCpAuthorize(view, url); + } else { Logger.info(methodTag,"This maybe a valid URI, but no special handling for this mentioned URI, hence deferring to WebView for loading."); processInvalidUrl(url); - return false; } } catch (final ClientException exception) { @@ -447,6 +453,53 @@ private boolean isAmazonAppRedirect(@NonNull final String url) { return url.startsWith(AMAZON_APP_REDIRECT_PREFIX); } + private boolean isWebCpEnrollmentUrl(@NonNull final String url) { + return url.startsWith(AuthenticationConstants.Broker.WEBCP_ENROLLMENT_URL); + } + + private boolean isWebCpAuthorizeUrl(@NonNull final String url) { + // URL should be for authorize request for webcp. + final String methodTag = TAG + ":isWebCpAuthorizeUrl"; + try { + final URI uri = new URI(url); + final String host = uri.getHost(); + final String path = uri.getPath(); + + if (host == null || path == null) { + Logger.info(methodTag, "URL missing host or path"); + return false; + } + + if (!AzureActiveDirectory.isValidCloudHost(new URL(url))) { + Logger.info(methodTag, "URL host is not a valid Azure cloud host"); + return false; + } + + if (!path.contains("/authorize")) { + Logger.info(methodTag, "URL path does not contain /authorize"); + return false; + } + + final Map queryParams = StringExtensions.getUrlParameters(url); + final String clientId = queryParams.get(AuthenticationConstants.OAuth2.CLIENT_ID); + + if (StringUtil.isNullOrEmpty(clientId)) { + Logger.info(methodTag, "Authorize URL does not contain client_id"); + return false; + } + + final boolean isWebCpClient = AuthenticationConstants.Broker.WEBCP_CLIENT_ID.equalsIgnoreCase(clientId); + Logger.info(methodTag, isWebCpClient + ? "WebCP authorize URL contains valid WebCP client_id." + : "Authorize URL contains different client_id."); + return isWebCpClient; + + } catch (final URISyntaxException | MalformedURLException e) { + Logger.info(methodTag, "Invalid URL: " + e.getMessage()); + return false; + } + } + private boolean isHeaderForwardingRequiredUri(@NonNull final String url) { // MSAL makes MSA requests first to login.microsoftonline.com, and then gets redirected to login.live.com. // This drops all the headers, which can have credentials useful for SSO and correlationIds useful for @@ -498,20 +551,16 @@ private void processSwitchBrowserRequest(@NonNull final String url) { private void processWebsiteRequest(@NonNull final WebView view, @NonNull final String url) { final String methodTag = TAG + ":processWebsiteRequest"; - view.stopLoading(); - if (url.contains(AuthenticationConstants.Broker.BROWSER_DEVICE_CA_URL_QUERY_STRING_PARAMETER)) { + if (isDeviceCaRequest(url)) { Logger.info(methodTag, "This is a device CA request."); - final PackageHelper packageHelper = new PackageHelper(getActivity().getPackageManager()); - - // If CP is installed, redirect to CP. - // TODO: Until we get a signal from eSTS that CP is the MDM app, we cannot assume that. - // CP is currently working on this. - // Until that comes, we'll only handle this in ipphone. - if (packageHelper.isPackageInstalledAndEnabled(IPPHONE_APP_PACKAGE_NAME) && - IPPHONE_APP_SIGNATURE.equals(packageHelper.getSha1SignatureForPackage(IPPHONE_APP_PACKAGE_NAME)) && - packageHelper.isPackageInstalledAndEnabled(COMPANY_PORTAL_APP_PACKAGE_NAME)) { + + if (shouldLaunchCompanyPortal()) { + // If CP is installed, redirect to CP. + // TODO: Until we get a signal from eSTS that CP is the MDM app, we cannot assume that. + // CP is currently working on this. + // Until that comes, we'll only handle this in ipphone. try { launchCompanyPortal(); return; @@ -520,14 +569,66 @@ private void processWebsiteRequest(@NonNull final WebView view, @NonNull final S } } - // Otherwise, launch in Browser. + loadDeviceCaUrl(url, view); + } else { + Logger.info(methodTag, "Not a device CA request. Redirecting to browser."); openLinkInBrowser(url); + returnResult(RawAuthorizationResult.ResultCode.CANCELLED); + } + } + + private boolean isDeviceCaRequest(@NonNull final String url) { + return url.contains(AuthenticationConstants.Broker.BROWSER_DEVICE_CA_URL_QUERY_STRING_PARAMETER); + } + + // Decides whether to launch the Company Portal app based on the presence of the IPPhone app and its signature. + private boolean shouldLaunchCompanyPortal() { + final PackageHelper packageHelper = new PackageHelper(getActivity().getPackageManager()); + return packageHelper.isPackageInstalledAndEnabled(IPPHONE_APP_PACKAGE_NAME) + && IPPHONE_APP_SIGNATURE.equals(packageHelper.getSha1SignatureForPackage(IPPHONE_APP_PACKAGE_NAME)) + && packageHelper.isPackageInstalledAndEnabled(COMPANY_PORTAL_APP_PACKAGE_NAME); + } + + // Loads the device CA URL in the WebView if the flight is enabled, otherwise opens it in the browser. + @VisibleForTesting + protected void loadDeviceCaUrl(@NonNull final String originalUrl, @NonNull final WebView view) { + final String methodTag = TAG + ":loadDeviceCaUrl"; + final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null; + final Span span = spanContext != null ? + OTelUtility.createSpanFromParent(SpanName.ProcessWebCpRedirects.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessWebCpRedirects.name()); + if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW)) { + Logger.info(methodTag, "Loading device CA request in WebView."); + span.setAttribute(AttributeName.is_webcp_in_webview_enabled.name(), true); + String httpsUrl = originalUrl.replace(AuthenticationConstants.Broker.BROWSER_EXT_PREFIX, "https://"); + view.loadUrl(httpsUrl, mRequestHeaders); + } else { + Logger.info(methodTag, "Loading device CA request in browser."); + span.setAttribute(AttributeName.is_webcp_in_webview_enabled.name(), false); + openLinkInBrowser(originalUrl); returnResult(RawAuthorizationResult.ResultCode.MDM_FLOW); - return; } + } + // WebCP enrollment URL is a guided enrollment page showing instructions on how to enroll within the productivity app. + // This is a special case where the enrollment is not done in the WebView, but rather in the browser. + private void processWebCpEnrollmentUrl(@NonNull final WebView view, @NonNull final String url) { + final String methodTag = TAG + ":processWebCpEnrollmentUrl"; + final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null; + final Span span = spanContext != null ? + OTelUtility.createSpanFromParent(SpanName.ProcessWebCpRedirects.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessWebCpRedirects.name()); + span.setAttribute(AttributeName.is_webcp_enrollment_request.name(), true); + view.stopLoading(); + Logger.info(methodTag, "Loading WebCP enrollment url in browser."); + // This is a WebCP enrollment URL, so we need to open it in the browser (it does not work in WebView as google enrollment is enforced to be done in browser). openLinkInBrowser(url); - returnResult(RawAuthorizationResult.ResultCode.CANCELLED); + // We need to return MDM_FLOW result code as the enrollment is done in browser. But this may sometimes take a few seconds to launch the intent. + // So we will wait for a few seconds before returning the result so that the current page in webview does not get closed immediately. + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + returnResult(RawAuthorizationResult.ResultCode.MDM_FLOW); + } + }, TimeUnit.SECONDS.toMillis(THREAD_SLEEP_FOR_INTENT_LAUNCH_MS)); } private boolean processPlayStoreURL(@NonNull final WebView view, @NonNull final String url) { @@ -602,7 +703,6 @@ private void openLinkInBrowser(final String url) { Logger.warn(methodTag, "Unable to find an app to resolve the activity."); } } - private void processWebCpRequest(@NonNull final WebView view, @NonNull final String url) { view.stopLoading(); @@ -756,27 +856,41 @@ private void processNonceAndReAttachHeaders(@NonNull final WebView view, @NonNul } } + /** + * This method is used to process the webcp redirect and re-attach the PRT header to the request. + */ + private void processWebCpAuthorize(@NonNull final WebView view, @NonNull final String url) { + final String methodTag = TAG + ":processWebCPAuthorize"; + Logger.info(methodTag, "Processing WebCP authorize request."); + final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null; + final Span span = spanContext != null ? + OTelUtility.createSpanFromParent(SpanName.ProcessWebCpRedirects.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessWebCpRedirects.name()); + span.setAttribute(AttributeName.is_webcp_authorize_request.name(), true); + final ReAttachPrtHeaderHandler reAttachPrtHeaderHandler = new ReAttachPrtHeaderHandler(view, mRequestHeaders, span); + reAttachPrtHeader(url, reAttachPrtHeaderHandler, view, methodTag, span); + } + /** * This method is used to process the cross cloud redirect and attach the PRT header to the request. */ - private void processCloudRedirectAndPrtHeader(@NonNull final WebView view, @NonNull final String url) { - final String methodTag = TAG + ":processCloudRedirectAndPrtHeader"; + private void processCrossCloudRedirect(@NonNull final WebView view, @NonNull final String url) { + final String methodTag = TAG + ":processCrossCloudRedirect"; final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null; final Span span = spanContext != null ? OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessCrossCloudRedirect.name()); - final CrossCloudChallengeHandler crossCloudChallengeHandler = new CrossCloudChallengeHandler(view, mRequestHeaders, span); - processCloudRedirectAndPrtHeaderInternal(url, crossCloudChallengeHandler, view, methodTag, span); + final ReAttachPrtHeaderHandler reAttachPrtHeaderHandler = new ReAttachPrtHeaderHandler(view, mRequestHeaders, span); + reAttachPrtHeader(url, reAttachPrtHeaderHandler, view, methodTag, span); } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public void processCloudRedirectAndPrtHeaderInternal(@NonNull final String url, @NonNull final CrossCloudChallengeHandler crossCloudChallengeHandler, @NonNull final WebView view, @NonNull final String methodTag, @NonNull final Span span) { + public void reAttachPrtHeader(@NonNull final String url, @NonNull final ReAttachPrtHeaderHandler reAttachPrtHandler, @NonNull final WebView view, @NonNull final String methodTag, @NonNull final Span span) { try (final Scope scope = SpanExtension.makeCurrentSpan(span)) { - crossCloudChallengeHandler.processChallenge(url); + reAttachPrtHandler.processChallenge(url); span.setStatus(StatusCode.OK); } catch (final Exception e) { // No op if an exception happens - Logger.warn(methodTag, "Error processing cross cloud redirect and attaching PRT header." + e); + Logger.warn(methodTag, "Error attaching PRT header." + e); span.recordException(e); view.loadUrl(url, mRequestHeaders); } finally { diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandler.kt similarity index 90% rename from common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandler.kt rename to common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandler.kt index 13e38ec47e..bc430017b2 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandler.kt @@ -28,22 +28,20 @@ import com.microsoft.identity.common.adal.internal.util.StringExtensions import com.microsoft.identity.common.java.broker.CommonRefreshTokenCredentialProvider import com.microsoft.identity.common.java.opentelemetry.AttributeName import com.microsoft.identity.common.logging.Logger -import io.opentelemetry.api.internal.StringUtils import io.opentelemetry.api.trace.Span -import java.net.URL /** - * Handler for attaching prt credential header on web view in cross cloud redirect scenarios. + * Handler for attaching prt credential header on web view in redirect scenarios. */ -class CrossCloudChallengeHandler( +class ReAttachPrtHeaderHandler( private val webView: WebView, private val headers: HashMap, private val span : Span ) : IChallengeHandler { - private val TAG = CrossCloudChallengeHandler::class.java.simpleName + private val TAG = ReAttachPrtHeaderHandler::class.java.simpleName override fun processChallenge(inputUrl: String): Void? { - Logger.info(TAG, "Processing challenge of a cross cloud request.") + Logger.info(TAG, "Processing challenge to attach prt header.") modifyHeadersWithRefreshTokenCredential(inputUrl) webView.loadUrl(inputUrl, headers) return null diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java index 0fe7a413fb..c14eb5f968 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java @@ -31,8 +31,10 @@ import com.microsoft.identity.common.adal.internal.AuthenticationConstants; import com.microsoft.identity.common.internal.ui.DualScreenActivity; -import com.microsoft.identity.common.internal.ui.webview.challengehandlers.CrossCloudChallengeHandler; +import com.microsoft.identity.common.internal.ui.webview.challengehandlers.ReAttachPrtHeaderHandler; import com.microsoft.identity.common.java.exception.ClientException; +import com.microsoft.identity.common.java.flighting.CommonFlight; +import com.microsoft.identity.common.java.flighting.CommonFlightsManager; import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectory; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserRequestHandler; import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback; @@ -94,6 +96,8 @@ public class AzureActiveDirectoryWebViewClientTest { private static final String TEST_PASSKEY_REDIRECT_URL = "http-auth:PassKey?challenge=challenge&version=1.0&submitUrl=https://login.microsoftonline.com/common/credential?passKeyAuth=1.0%2fpasskey&context=&relyingPartyIdentifier=login.microsoft.com&allowedCredentials=somevalue"; private static final String TEST_INTENT_INSTALL_BROKER_REDIRECT_URL = "intent://play.google.com/store/apps/details?id=com.azure.authenticator&referrer=%20adjust_reftag%3Dc6f1p4ErudH2C%26utm_source%3DLanding%2BPage%2BOrganic%2B-%2Bapp%2Bstore%2Bbadges%26utm_campaign%3Dappstore_android&pcampaignid=web_auto_redirect&web_logged_in=0&redirect_entry_point=dp#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.vending;end"; + private static final String TEST_WEB_CP_ENROLLMENT_URL = "https://enterprise.google.com/android/enroll"; + @Before public void setup() throws ClientException { mContext = ApplicationProvider.getApplicationContext(); @@ -214,11 +218,31 @@ public void testUrlOverrideHandlesCrossCloudRedirectUrl() { assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_CROSS_CLOUD_REDIRECT_URL)); } + @Test + public void testUrlOverrideHandleWebCPEnrollmentUrl() { + if(CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW)) { + assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_WEB_CP_ENROLLMENT_URL)); + } else { + assertFalse(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_WEB_CP_ENROLLMENT_URL)); + } + } + + @Test + public void testLoadDeviceCaUrl() { + final WebView mockWebview = Mockito.mock(WebView.class); + mWebViewClient.loadDeviceCaUrl(TEST_BROWSER_DEVICE_CA_URL_QUERY_STRING_PARAMETER, mockWebview); + if(CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_CP_IN_WEBVIEW)) { + Mockito.verify(mockWebview).loadUrl(Mockito.anyString(), Mockito.any()); + } else { + Mockito.verify(mockWebview, Mockito.never()).loadUrl(Mockito.anyString(), Mockito.any()); + } + } + @Test public void testProcessCloudRedirectAndPrtHeaderInternalSuccess() { - CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class); + ReAttachPrtHeaderHandler mockCrossCloudChallengeHandler = Mockito.mock(ReAttachPrtHeaderHandler.class); try { - mWebViewClient.processCloudRedirectAndPrtHeaderInternal(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mMockWebView, "methodTag", Span.current()); + mWebViewClient.reAttachPrtHeader(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mMockWebView, "methodTag", Span.current()); } catch (Exception e) { Assert.fail("Unexpected exception occured " + e); } @@ -226,12 +250,12 @@ public void testProcessCloudRedirectAndPrtHeaderInternalSuccess() { @Test public void testProcessCloudRedirectAndPrtHeaderInternalException() { - CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class); + ReAttachPrtHeaderHandler mockReAttachPrtHandler = Mockito.mock(ReAttachPrtHeaderHandler.class); WebView mockWebView = Mockito.mock(WebView.class); - Mockito.doThrow(new RuntimeException("Test Exception")).when(mockCrossCloudChallengeHandler).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL); + Mockito.doThrow(new RuntimeException("Test Exception")).when(mockReAttachPrtHandler).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL); try { - mWebViewClient.processCloudRedirectAndPrtHeaderInternal(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mockWebView, "methodTag", Span.current()); - Mockito.verify(mockCrossCloudChallengeHandler, Mockito.times(1)).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL); + mWebViewClient.reAttachPrtHeader(TEST_CROSS_CLOUD_REDIRECT_URL, mockReAttachPrtHandler, mockWebView, "methodTag", Span.current()); + Mockito.verify(mockReAttachPrtHandler, Mockito.times(1)).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL); Mockito.verify(mockWebView).loadUrl(Mockito.anyString(), Mockito.any()); } catch (Exception e) { Assert.fail("Failure is not expected. We should have caught the exception and ignored it. " + e); diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandlerTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandlerTest.kt similarity index 87% rename from common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandlerTest.kt rename to common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandlerTest.kt index 56c2891269..1fef5505dd 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandlerTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandlerTest.kt @@ -35,25 +35,25 @@ import org.junit.Test import org.mockito.Mockito.* @RunWith(RobolectricTestRunner::class) -class CrossCloudChallengeHandlerTest { +class ReAttachPrtHeaderHandlerTest { private lateinit var webView: WebView private lateinit var headers: HashMap private lateinit var span: Span - private lateinit var crossCloudChallengeHandler: CrossCloudChallengeHandler + private lateinit var reAttachPrtHeaderHandler: ReAttachPrtHeaderHandler @Before fun setUp() { webView = mock(WebView::class.java) headers = HashMap() span = mock(Span::class.java) - crossCloudChallengeHandler = CrossCloudChallengeHandler(webView, headers, span) + reAttachPrtHeaderHandler = ReAttachPrtHeaderHandler(webView, headers, span) } @Test fun `testProcessChallenge success`() { val testUrl = "https://example.com?login_hint=testuser" - crossCloudChallengeHandler.processChallenge(testUrl) + reAttachPrtHeaderHandler.processChallenge(testUrl) verify(webView).loadUrl(eq(testUrl), eq(headers)) } @@ -70,7 +70,7 @@ class CrossCloudChallengeHandlerTest { } throws Exception() try { - crossCloudChallengeHandler.processChallenge(testUrl) + reAttachPrtHeaderHandler.processChallenge(testUrl) } catch (e: Exception) { verify(webView, never()).loadUrl(eq(testUrl), eq(headers)) } @@ -91,7 +91,7 @@ class CrossCloudChallengeHandlerTest { } returns refreshTokenCredential // Call the method - crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url) + reAttachPrtHeaderHandler.modifyHeadersWithRefreshTokenCredential(url) verify(span).setAttribute( AttributeName.is_new_refresh_token_cred_header_attached.name, true @@ -102,7 +102,7 @@ class CrossCloudChallengeHandlerTest { @Test fun `modifyHeadersWithRefreshTokenCredential should not update headers when login_hint is missing`() { val url = "https://login.microsoftonline.com" - crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url) + reAttachPrtHeaderHandler.modifyHeadersWithRefreshTokenCredential(url) verify(span, never()).setAttribute( AttributeName.is_new_refresh_token_cred_header_attached.name, true @@ -112,7 +112,7 @@ class CrossCloudChallengeHandlerTest { @Test fun `modifyHeadersWithRefreshTokenCredential null refresh token credential`() { val url = "https://login.microsoftonline.com?login_hint=testuser" - crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url) + reAttachPrtHeaderHandler.modifyHeadersWithRefreshTokenCredential(url) verify(span, never()).setAttribute( AttributeName.is_new_refresh_token_cred_header_attached.name, true diff --git a/common4j/src/main/com/microsoft/identity/common/java/flighting/CommonFlight.java b/common4j/src/main/com/microsoft/identity/common/java/flighting/CommonFlight.java index cc59c3d871..f23682a157 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/flighting/CommonFlight.java +++ b/common4j/src/main/com/microsoft/identity/common/java/flighting/CommonFlight.java @@ -124,7 +124,12 @@ public enum CommonFlight implements IFlightConfig { /** * Flight to enable handling the UI in edge to edge mode */ - ENABLE_HANDLING_FOR_EDGE_TO_EDGE("EnableHandlingEdgeToEdge", true); + ENABLE_HANDLING_FOR_EDGE_TO_EDGE("EnableHandlingEdgeToEdge", true), + + /** + * Flight to enable the Web CP in WebView. + */ + ENABLE_WEB_CP_IN_WEBVIEW("EnableWebCpInWebView", false); private String key; private Object defaultValue; diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java index 5c93535b33..8c65593f92 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java @@ -363,5 +363,20 @@ public enum AttributeName { /** * Records the if the broker handled a switch browser resume, */ - is_switch_browser_resume_handled + is_switch_browser_resume_handled, + + /** + * Records if the request is a webcp authorize request. + */ + is_webcp_authorize_request, + + /** + * Records if the request is a webcp enrollment request. + */ + is_webcp_enrollment_request, + + /** + * Records if the webcp is enabled in webview. + */ + is_webcp_in_webview_enabled } diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java index 2264ad57b9..c3d17ecf34 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java @@ -64,5 +64,6 @@ public enum SpanName { ProcessCrossCloudRedirect, SwitchBrowserResume, SwitchBrowserProcess, - WrappedKeyAlgorithmIdentifier + WrappedKeyAlgorithmIdentifier, + ProcessWebCpRedirects }