Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewTreeLifecycleOwner;

import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
Expand Down Expand Up @@ -643,8 +644,13 @@ private void processCloudRedirectAndPrtHeader(@NonNull final WebView view, @NonN
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);
}

@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) {
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
final CrossCloudChallengeHandler crossCloudChallengeHandler = new CrossCloudChallengeHandler(view, mRequestHeaders, span);
crossCloudChallengeHandler.processChallenge(url);
span.setStatus(StatusCode.OK);
} catch (final Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class CrossCloudChallengeHandler(
}

// Updates the headers by attaching a refresh token credential header.
private fun modifyHeadersWithRefreshTokenCredential(
// Making it accessible for testing.
fun modifyHeadersWithRefreshTokenCredential(
url: String,
) {
val methodTag = "$TAG:modifyHeadersWithRefreshTokenCredential"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
import androidx.test.core.app.ApplicationProvider;

import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.CrossCloudChallengeHandler;
import com.microsoft.identity.common.java.exception.ClientException;
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;
import com.microsoft.identity.common.java.providers.RawAuthorizationResult;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -49,6 +53,8 @@

import java.util.HashMap;

import io.opentelemetry.api.trace.Span;


@RunWith(RobolectricTestRunner.class)
public class AzureActiveDirectoryWebViewClientTest {
Expand Down Expand Up @@ -82,9 +88,11 @@ public class AzureActiveDirectoryWebViewClientTest {
private static final String TEST_MSA_HEADER_FORWARDING_NEGATIVE_URL = "https://login.blah.com/oauth20_authorize.srf";

private static final String TEST_NONCE_REDIRECT_URL = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize?&sso_nonce=ABCD";
private static final String TEST_CROSS_CLOUD_REDIRECT_URL = "https://login.microsoftonline.us/organizations/oAuth2/v2.0/authorize?x=10";
private static final String TEST_PUBLIC_CLOUD_REDIRECT_URL = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize?x=10";

@Before
public void setup() {
public void setup() throws ClientException {
mContext = ApplicationProvider.getApplicationContext();
mMockWebView = new WebView(mContext);
mActivity = Robolectric.buildActivity(Activity.class).get();
Expand Down Expand Up @@ -112,6 +120,10 @@ public void onPageLoaded(final String url) {
HashMap<String, String> dummyHeaders = new HashMap<>();
dummyHeaders.put("key", "value");
mWebViewClient.setRequestHeaders(dummyHeaders);
mWebViewClient.setRequestUrl(TEST_PUBLIC_CLOUD_REDIRECT_URL);
if (!AzureActiveDirectory.isInitialized()) {
AzureActiveDirectory.performCloudDiscovery();
}
}

@Test(expected = IllegalArgumentException.class)
Expand Down Expand Up @@ -193,4 +205,33 @@ public void testUrlOverrideHandlesHeaderForwardingRequiredUrl() {
public void testUrlOverrideHandlesNonceRedirectUrl() {
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_NONCE_REDIRECT_URL));
}

@Test
public void testUrlOverrideHandlesCrossCloudRedirectUrl() {
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_CROSS_CLOUD_REDIRECT_URL));
}

@Test
public void testProcessCloudRedirectAndPrtHeaderInternalSuccess() {
CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class);
try {
mWebViewClient.processCloudRedirectAndPrtHeaderInternal(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mMockWebView, "methodTag", Span.current());
} catch (Exception e) {
Assert.fail("Unexpected exception occured " + e);
}
}

@Test
public void testProcessCloudRedirectAndPrtHeaderInternalException() {
CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class);
WebView mockWebView = Mockito.mock(WebView.class);
Mockito.doThrow(new RuntimeException("Test Exception")).when(mockCrossCloudChallengeHandler).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);
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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.challengehandlers

import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import android.webkit.WebView
import com.microsoft.identity.common.java.broker.CommonRefreshTokenCredentialProvider
import com.microsoft.identity.common.java.opentelemetry.AttributeName
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.opentelemetry.api.trace.Span
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.*

@RunWith(RobolectricTestRunner::class)
class CrossCloudChallengeHandlerTest {

private lateinit var webView: WebView
private lateinit var headers: HashMap<String, String>
private lateinit var span: Span
private lateinit var crossCloudChallengeHandler: CrossCloudChallengeHandler

@Before
fun setUp() {
webView = mock(WebView::class.java)
headers = HashMap()
span = mock(Span::class.java)
crossCloudChallengeHandler = CrossCloudChallengeHandler(webView, headers, span)
}

@Test
fun `testProcessChallenge success`() {
val testUrl = "https://example.com?login_hint=testuser"
crossCloudChallengeHandler.processChallenge(testUrl)
verify(webView).loadUrl(eq(testUrl), eq(headers))
}
Copy link
Copy Markdown
Contributor

@mohitc1 mohitc1 Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you already have test for processChallenge, why add test for modifyHeadersWithRefreshTokenCredential (and make it non private)?

If there's some extra that's getting tested that can be tested on processChallenge itself.


@Test
fun `testProcessChallenge when exception is thrown`() {
val testUrl = "https://example.com?login_hint=testuser"

mockkObject(CommonRefreshTokenCredentialProvider)
every {
CommonRefreshTokenCredentialProvider.getRefreshTokenCredential(
testUrl,
"testuser"
)
} throws Exception()

try {
crossCloudChallengeHandler.processChallenge(testUrl)
} catch (e: Exception) {
verify(webView, never()).loadUrl(eq(testUrl), eq(headers))
}
}

@Test
fun `modifyHeadersWithRefreshTokenCredential should update headers when prt is available`() {
val url = "https://login.microsoftonline.com?login_hint=testuser"
val username = "testuser"
val refreshTokenCredential = "refreshTokenCredential"

mockkObject(CommonRefreshTokenCredentialProvider)
every {
CommonRefreshTokenCredentialProvider.getRefreshTokenCredential(
url,
username
)
} returns refreshTokenCredential

// Call the method
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
verify(span).setAttribute(
AttributeName.is_new_refresh_token_cred_header_attached.name,
true
)
unmockkAll()
}

@Test
fun `modifyHeadersWithRefreshTokenCredential should not update headers when login_hint is missing`() {
val url = "https://login.microsoftonline.com"
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
verify(span, never()).setAttribute(
AttributeName.is_new_refresh_token_cred_header_attached.name,
true
)
}

@Test
fun `modifyHeadersWithRefreshTokenCredential null refresh token credential`() {
val url = "https://login.microsoftonline.com?login_hint=testuser"
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
verify(span, never()).setAttribute(
AttributeName.is_new_refresh_token_cred_header_attached.name,
true
)
}
}