diff --git a/changelog.txt b/changelog.txt index 7829e11fe2..a5aac851f6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ vNext ---------- +- [MINOR] Updating handling of ssl error received in Android WebView's onReceivedSslError callback Version 21.3.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 7acfaeb766..99c7dcfd42 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 @@ -2023,6 +2023,8 @@ public static final class AuthorizationIntentKey { public static final String WEB_VIEW_ZOOM_CONTROLS_ENABLED = "com.microsoft.identity.web.view.zoom.controls.enabled"; public static final String WEB_VIEW_ZOOM_ENABLED = "com.microsoft.identity.web.view.zoom.enabled"; + + public static final String WEB_VIEW_NEW_SSL_ERROR_HANDLER_ENABLED = "com.microsoft.identity.web.view.new.ssl.error.handler.enabled"; } public static final class AuthorizationIntentAction { diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index 8eff748106..1430bb991e 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -22,6 +22,17 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTH_INTENT; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.POST_PAGE_LOADED_URL; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_HEADERS; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_NEW_SSL_ERROR_HANDLER_ENABLED; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_ZOOM_CONTROLS_ENABLED; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_ZOOM_ENABLED; +import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.PRODUCT; +import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.VERSION; + import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; @@ -36,7 +47,6 @@ import android.webkit.PermissionRequest; import android.webkit.WebChromeClient; import android.webkit.WebSettings; -import android.webkit.WebView; import android.widget.ProgressBar; import androidx.activity.result.ActivityResultLauncher; @@ -46,40 +56,31 @@ import androidx.fragment.app.FragmentActivity; import com.microsoft.identity.common.R; -import com.microsoft.identity.common.internal.fido.LegacyFidoActivityResultContract; -import com.microsoft.identity.common.internal.fido.LegacyFido2ApiObject; -import com.microsoft.identity.common.internal.ui.webview.ISendResultCallback; -import com.microsoft.identity.common.internal.ui.webview.ProcessUtil; -import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserProtocolCoordinator; -import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.adal.internal.AuthenticationConstants; import com.microsoft.identity.common.adal.internal.util.StringExtensions; +import com.microsoft.identity.common.internal.fido.LegacyFido2ApiObject; +import com.microsoft.identity.common.internal.fido.LegacyFidoActivityResultContract; +import com.microsoft.identity.common.internal.ui.webview.AzureActiveDirectoryWebView; import com.microsoft.identity.common.internal.ui.webview.AzureActiveDirectoryWebViewClient; +import com.microsoft.identity.common.internal.ui.webview.ISendResultCallback; import com.microsoft.identity.common.internal.ui.webview.OnPageLoadedCallback; +import com.microsoft.identity.common.internal.ui.webview.ProcessUtil; import com.microsoft.identity.common.internal.ui.webview.WebViewUtil; +import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserProtocolCoordinator; +import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.constants.FidoConstants; 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.ui.webview.authorization.IAuthorizationCompletionCallback; import com.microsoft.identity.common.java.providers.RawAuthorizationResult; +import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback; import com.microsoft.identity.common.java.util.ClientExtraSku; import com.microsoft.identity.common.logging.Logger; -import java.util.Arrays; -import java.util.HashMap; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; - -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTH_INTENT; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.POST_PAGE_LOADED_URL; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_HEADERS; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_ZOOM_CONTROLS_ENABLED; -import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_ZOOM_ENABLED; -import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.PRODUCT; -import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.VERSION; +import java.util.Arrays; +import java.util.HashMap; import io.opentelemetry.api.trace.SpanContext; @@ -93,7 +94,7 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { @VisibleForTesting private static final String PKEYAUTH_STATUS = "pkeyAuthStatus"; - private WebView mWebView; + private AzureActiveDirectoryWebView mWebView; private AzureActiveDirectoryWebViewClient mAADWebViewClient; @@ -116,6 +117,14 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { private boolean webViewZoomEnabled; + /** + * This is used to determine whether the new SSL error handler should be used. + * If null, means that the value has not provide. Use default logic. + * If true, the new SSL error handler will be used. + * If false, the old SSL error handler will be used. + */ + private Boolean webViewNewSslErrorHandlerEnabled; + private final CameraPermissionRequestHandler mCameraPermissionRequestHandler = new CameraPermissionRequestHandler(this); // This is used by LegacyFido2ApiManager to launch a PendingIntent received by the legacy API. @@ -206,6 +215,10 @@ public void onSaveInstanceState(@NonNull Bundle outState) { outState.putSerializable(POST_PAGE_LOADED_URL, mPostPageLoadedJavascript); outState.putBoolean(WEB_VIEW_ZOOM_CONTROLS_ENABLED, webViewZoomControlsEnabled); outState.putBoolean(WEB_VIEW_ZOOM_ENABLED, webViewZoomEnabled); + if (webViewNewSslErrorHandlerEnabled != null) { + // save only if not null, otherwise it will be false by default + outState.putBoolean(WEB_VIEW_NEW_SSL_ERROR_HANDLER_ENABLED, webViewNewSslErrorHandlerEnabled); + } } @Override @@ -223,6 +236,10 @@ void extractState(@NonNull final Bundle state) { mPostPageLoadedJavascript = state.getString(POST_PAGE_LOADED_URL); webViewZoomEnabled = state.getBoolean(WEB_VIEW_ZOOM_ENABLED, true); webViewZoomControlsEnabled = state.getBoolean(WEB_VIEW_ZOOM_CONTROLS_ENABLED, true); + if (state.containsKey(WEB_VIEW_NEW_SSL_ERROR_HANDLER_ENABLED)) { + // If the key exists, we retrieve the value. + webViewNewSslErrorHandlerEnabled = state.getBoolean(WEB_VIEW_NEW_SSL_ERROR_HANDLER_ENABLED, false); + } } @Nullable @@ -260,7 +277,8 @@ public void onPageLoaded(final String url) { } }, mRedirectUri, - getSwitchBrowserCoordinator().getSwitchBrowserRequestHandler() + getSwitchBrowserCoordinator().getSwitchBrowserRequestHandler(), + shouldUseWebViewNewSslErrorHandler() ); setUpWebView(view, mAADWebViewClient); mAADWebViewClient.initializeAuthUxJavaScriptApi(mWebView, mAuthorizationRequestUrl); @@ -268,6 +286,20 @@ public void onPageLoaded(final String url) { return view; } + /** + * Determines whether the new SSL error handler should be used. + * + * @return true if the new SSL error handler should be used, false otherwise. + */ + protected boolean shouldUseWebViewNewSslErrorHandler() { + if (webViewNewSslErrorHandlerEnabled == null) { + // if the value was not provided in intent extra, then use from flight. + return CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEB_VIEW_NEW_SSL_HANDLER); + } + + return webViewNewSslErrorHandlerEnabled; + } + @Override public void handleBackButtonPressed() { final String methodTag = TAG + ":handleBackButtonPressed"; diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebView.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebView.kt new file mode 100644 index 0000000000..7ca000cc9f --- /dev/null +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebView.kt @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.internal.ui.webview + +import android.webkit.WebView +import android.content.Context +import android.util.AttributeSet +import android.webkit.WebViewClient +import com.microsoft.identity.common.logging.Logger + +/** + * WebView implementation used for authentication in client libraries. + */ +class AzureActiveDirectoryWebView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : WebView(context,attrs, defStyleAttr) { + + private var azureActiveDirectoryWebViewClient: AzureActiveDirectoryWebViewClient? = null + + companion object { + private const val TAG = "AzureActiveDirectoryWebView" + } + + /** + * Sets the URL to load on the webview client and continues loading the URL. + */ + override fun loadUrl(url: String) { + azureActiveDirectoryWebViewClient?.setActiveLoadUrl(url) + super.loadUrl(url) + } + + /** + * Sets the URL to load on the webview client and continues loading the URL. + */ + override fun loadUrl(url: String, additionalHttpHeaders: Map) { + azureActiveDirectoryWebViewClient?.setActiveLoadUrl(url) + super.loadUrl(url, additionalHttpHeaders) + } + + /** + * Sets the [WebViewClient] for this WebView and tracks the [AzureActiveDirectoryWebViewClient] + * instance for further use. + */ + override fun setWebViewClient(webViewClient: WebViewClient) { + val methodTag = "$TAG:setWebViewClient" + // Add custom logic here if needed + super.setWebViewClient(webViewClient) + azureActiveDirectoryWebViewClient = webViewClient as? AzureActiveDirectoryWebViewClient + Logger.info(methodTag, "AzureActiveDirectoryWebViewClient set: ${azureActiveDirectoryWebViewClient != null}") + } +} \ No newline at end of file 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 94dd76afa5..febf66d546 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 @@ -132,7 +132,20 @@ public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity, @NonNull final OnPageLoadedCallback pageLoadedCallback, @NonNull final String redirectUrl, @NonNull final SwitchBrowserRequestHandler switchBrowserRequestHandler) { - super(activity, completionCallback, pageLoadedCallback); + super(activity, completionCallback, pageLoadedCallback, false); + mRedirectUrl = redirectUrl; + mCertBasedAuthFactory = new CertBasedAuthFactory(activity); + mSwitchBrowserRequestHandler = switchBrowserRequestHandler; + } + + public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity, + @NonNull final IAuthorizationCompletionCallback completionCallback, + @NonNull final OnPageLoadedCallback pageLoadedCallback, + @NonNull final String redirectUrl, + @NonNull final SwitchBrowserRequestHandler switchBrowserRequestHandler, + final boolean enableNewSslErrorHandling + ) { + super(activity, completionCallback, pageLoadedCallback, enableNewSslErrorHandling); mRedirectUrl = redirectUrl; mCertBasedAuthFactory = new CertBasedAuthFactory(activity); mSwitchBrowserRequestHandler = switchBrowserRequestHandler; @@ -336,6 +349,9 @@ else if (isRedirectUrl(formattedURL)) { } 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); + // By return false, deferring to WebView to continue loading the url. This does not call webview.loadUrl() + // so setting the url as next url to load and marking active. + setActiveLoadUrl(url); return false; } } catch (final ClientException exception) { diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/OAuth2WebViewClient.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/OAuth2WebViewClient.java index 25da7ae15c..d560f03aa9 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/OAuth2WebViewClient.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/OAuth2WebViewClient.java @@ -41,13 +41,13 @@ import com.microsoft.identity.common.adal.internal.AuthenticationConstants; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.ChallengeFactory; -import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.IChallengeHandler; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.NtlmChallenge; import com.microsoft.identity.common.internal.ui.webview.challengehandlers.NtlmChallengeHandler; import com.microsoft.identity.common.internal.util.StringUtil; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.providers.RawAuthorizationResult; +import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback; import com.microsoft.identity.common.logging.Logger; import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Browser.SSL_HELP_URL; @@ -62,6 +62,13 @@ public abstract class OAuth2WebViewClient extends WebViewClient { private final OnPageLoadedCallback mPageLoadedCallback; private final Activity mActivity; + /** + * Used to verify that the page loaded in the webview is the expected page. + */ + private String mActiveLoadUrl; + + private final boolean mEnableNewSslErrorHandling; + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "This is only exposed in testing") @VisibleForTesting public static ExpectedPage mExpectedPage = null; @@ -89,12 +96,31 @@ IAuthorizationCompletionCallback getCompletionCallback() { */ OAuth2WebViewClient(@NonNull final Activity activity, @NonNull final IAuthorizationCompletionCallback completionCallback, - @NonNull final OnPageLoadedCallback pageLoadedCallback) { - //the validation of redirect url and authorization request should be in upper level before launching the webview. + @NonNull final OnPageLoadedCallback pageLoadedCallback, + boolean enableNewSslErrorHandling + ) { + // the validation of redirect url and authorization request should be in upper level before launching the webview. mActivity = activity; mCompletionCallback = completionCallback; mPageLoadedCallback = pageLoadedCallback; - } + mEnableNewSslErrorHandling = enableNewSslErrorHandling; + } + + /** + * Sets the expected page to be loaded in the webview. + * + * @param activeLoadUrl The expected page to be loaded. + */ + public void setActiveLoadUrl(String activeLoadUrl) { + mActiveLoadUrl = activeLoadUrl; + } + + /** + * For Testing + */ + String getActiveLoadUrl() { + return mActiveLoadUrl; + } @Override public void onReceivedHttpAuthRequest(WebView view, final HttpAuthHandler handler, @@ -170,9 +196,21 @@ private void sendErrorToCallback(@NonNull final WebView view, public void onReceivedSslError(final WebView view, final SslErrorHandler handler, final SslError error) { + final String methodTag = TAG + ":onReceivedSslError"; + if (this.mEnableNewSslErrorHandling) { + onReceivedSslErrorInternal(view, handler, error); + } else { + // Legacy flow + onReceivedSslErrorInternalLegacy(view, handler, error); + } + } + + private void onReceivedSslErrorInternalLegacy(final WebView view, + final SslErrorHandler handler, + final SslError error) { // Developer does not have option to control this for now + // this calls handler.cancel() to stop loading the affected resource. super.onReceivedSslError(view, handler, error); - handler.cancel(); final String errMsg = "Received SSL Error during request. For more info see: " + SSL_HELP_URL; @@ -184,6 +222,56 @@ public void onReceivedSslError(final WebView view, new ClientException("Code:" + ERROR_FAILED_SSL_HANDSHAKE, error.toString()))); } + private void onReceivedSslErrorInternal(final WebView view, + final SslErrorHandler handler, + final SslError error) { + final String methodTag = TAG + ":onReceivedSslErrorInternal"; + // this calls handler.cancel() to stop loading the affected resource. + super.onReceivedSslError(view, handler, error); + + // only if the error is for the main frame URL, cancel the flow. + // otherwise only handler.cancel() is called (above) to stop loading affected resource. + if (isMainFrameUrl(error.getUrl())) { + final String errMsg = "Received SSL Error during request. For more info see: " + SSL_HELP_URL; + + Logger.error(methodTag, errMsg, null); + + // Send the result back to the calling activity + mCompletionCallback.onChallengeResponseReceived( + RawAuthorizationResult.fromException( + new ClientException("Code:" + ERROR_FAILED_SSL_HANDSHAKE, error.toString()))); + } + } + + /** + * Check if the error url is the same as the main url to load. Compares host + * as SSL errors apply to the domain + */ + private boolean isMainFrameUrl(final String errorUrl) { + final String methodTag = TAG + ":isMainFrameUrl"; + if (StringUtil.isEmpty(mActiveLoadUrl)) { + Logger.warn(methodTag, "Main url is empty."); + return false; + } + if (StringUtil.isEmpty(errorUrl)) { + Logger.warn(methodTag, "Received SSL error with null or empty URL."); + return false; + } + + final String mainUrlHost = Uri.parse(mActiveLoadUrl).getHost(); + if (StringUtil.isEmpty(mainUrlHost)) { + Logger.warn(methodTag, "Main url is malformed: " + mActiveLoadUrl); + return false; + } + + final String errorUrlHost = Uri.parse(errorUrl).getHost(); + if (StringUtil.isEmpty(errorUrlHost)) { + Logger.warn(methodTag, "Received SSL error with malformed URL: " + errorUrl); + return false; + } + return errorUrlHost.equalsIgnoreCase(mainUrlHost); + } + @Override public void onPageFinished(final WebView view, final String url) { diff --git a/common/src/main/res/layout/common_activity_authentication.xml b/common/src/main/res/layout/common_activity_authentication.xml index ec16165900..c5d138d942 100644 --- a/common/src/main/res/layout/common_activity_authentication.xml +++ b/common/src/main/res/layout/common_activity_authentication.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:background="@android:color/white"> - {}, + TEST_REDIRECT_URI, + Mockito.mock(SwitchBrowserRequestHandler.class)); + final AzureActiveDirectoryWebView mockWebView = new AzureActiveDirectoryWebView(mContext); + mockWebView.setWebViewClient(mockWebViewClient); + mockWebViewClient.setActiveLoadUrl(mockActiveUrl); + + // act + mockWebViewClient.onReceivedSslError(mockWebView, mockHandler, mockError); + + Mockito.verify(mockHandler, Mockito.times(1)).cancel(); + Mockito.verify(mockCallback, Mockito.times(1)).onChallengeResponseReceived(any()); + } + + @Test + public void testOnReceivedSslError_NotMainFrame() { + final String mockActiveUrl = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize"; + final SslErrorHandler mockHandler = Mockito.mock(android.webkit.SslErrorHandler.class); + final SslError mockError = Mockito.mock(android.net.http.SslError.class); + final IAuthorizationCompletionCallback mockCallback = Mockito.mock(IAuthorizationCompletionCallback.class); + when(mockError.getUrl()).thenReturn("https://example.com"); + final AzureActiveDirectoryWebViewClient mockWebViewClient = new AzureActiveDirectoryWebViewClient( + mActivity, + mockCallback, + url -> {}, + TEST_REDIRECT_URI, + Mockito.mock(SwitchBrowserRequestHandler.class), + true + ); + final AzureActiveDirectoryWebView mockWebView = new AzureActiveDirectoryWebView(mContext); + mockWebView.setWebViewClient(mockWebViewClient); + mockWebViewClient.setActiveLoadUrl(mockActiveUrl); + + // act + mockWebViewClient.onReceivedSslError(mockWebView, mockHandler, mockError); + + Mockito.verify(mockHandler, Mockito.times(1)).cancel(); + Mockito.verify(mockCallback, never()).onChallengeResponseReceived(any()); + } + + @Test + public void testOnReceivedSslError_MainFrame() { + final String mockActiveUrl = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize"; + final SslErrorHandler mockHandler = Mockito.mock(android.webkit.SslErrorHandler.class); + final SslError mockError = Mockito.mock(android.net.http.SslError.class); + final IAuthorizationCompletionCallback mockCallback = Mockito.mock(IAuthorizationCompletionCallback.class); + when(mockError.getUrl()).thenReturn("https://login.microsoftonline.com/organizations"); + final AzureActiveDirectoryWebViewClient mockWebViewClient = new AzureActiveDirectoryWebViewClient( + mActivity, + mockCallback, + url -> {}, + TEST_REDIRECT_URI, + Mockito.mock(SwitchBrowserRequestHandler.class), + true + ); + final AzureActiveDirectoryWebView mockWebView = new AzureActiveDirectoryWebView(mContext); + mockWebView.setWebViewClient(mockWebViewClient); + mockWebViewClient.setActiveLoadUrl(mockActiveUrl); + + // act + mockWebViewClient.onReceivedSslError(mockWebView, mockHandler, mockError); + + Mockito.verify(mockHandler, Mockito.times(1)).cancel(); + Mockito.verify(mockCallback, Mockito.times(1)).onChallengeResponseReceived(any()); + } } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewTest.kt new file mode 100644 index 0000000000..56a81eb66b --- /dev/null +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewTest.kt @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.internal.ui.webview + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.robolectric.RobolectricTestRunner + + +/** + * Test class for [AzureActiveDirectoryWebView]. + */ +@RunWith(RobolectricTestRunner::class) +class AzureActiveDirectoryWebViewTest { + + private lateinit var context: Context + private lateinit var webView: AzureActiveDirectoryWebView + private lateinit var mockWebViewClient: AzureActiveDirectoryWebViewClient + + @Before + fun setUp() { + context = ApplicationProvider.getApplicationContext() + webView = AzureActiveDirectoryWebView(context) + mockWebViewClient = mock() + } + + @Test + fun testLoadUrl() { + // Given + val testUrl = "https://login.microsoftonline.com" + webView.setWebViewClient(mockWebViewClient) + + // When + webView.loadUrl(testUrl) + + // Then + verify(mockWebViewClient).activeLoadUrl = testUrl + } + + @Test + fun testLoadUrlWithHeaders() { + // Given + val testUrl = "https://login.microsoftonline.com" + val headers = mapOf("header1" to "value1", "header2" to "value2") + webView.setWebViewClient(mockWebViewClient) + + // When + webView.loadUrl(testUrl, headers) + + // Then + verify(mockWebViewClient).activeLoadUrl = testUrl + } +} 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 f23682a157..2a2c6eaadd 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 @@ -129,7 +129,9 @@ public enum CommonFlight implements IFlightConfig { /** * Flight to enable the Web CP in WebView. */ - ENABLE_WEB_CP_IN_WEBVIEW("EnableWebCpInWebView", false); + ENABLE_WEB_CP_IN_WEBVIEW("EnableWebCpInWebView", false), + + ENABLE_WEB_VIEW_NEW_SSL_HANDLER("EnableWebViewNewSslHandler", true); private String key; private Object defaultValue;