Skip to content
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>,
private val span : Span
) : IChallengeHandler<String, Void> {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -214,24 +218,44 @@ 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);
}
}

@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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>
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))
}

Expand All @@ -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))
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ public enum SpanName {
ProcessCrossCloudRedirect,
SwitchBrowserResume,
SwitchBrowserProcess,
WrappedKeyAlgorithmIdentifier
WrappedKeyAlgorithmIdentifier,
ProcessWebCpRedirects
}