Skip to content

Commit

Permalink
Merge branch 'dev' into pedroro/browser-selector
Browse files Browse the repository at this point in the history
  • Loading branch information
p3dr0rv authored Jan 3, 2025
2 parents 8a5a96e + 690be86 commit 003fe74
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ vNext
- [MINOR] Replace Deprecated Keystore API for Android 28+ (#2558)
- [MINOR] Managed profile Android util method (#2561)
- [PATCH] Make userHandle response field optional (#2560)
- [MINOR] Nonce redirect changes (#2552)

Version 18.2.2
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,8 @@ public static String computeMaxHostBrokerProtocol() {
*/
public static final String COMPANY_PORTAL_APP_LAUNCH_ACTIVITY_NAME = Broker.COMPANY_PORTAL_APP_PACKAGE_NAME + ".views.SplashActivity";

public static final String SSO_NONCE_PARAMETER = "sso_nonce";

/**
* PRT nonce.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@
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.NonceRedirectHandler;
import com.microsoft.identity.common.java.constants.FidoConstants;
import com.microsoft.identity.common.java.flighting.CommonFlight;
import com.microsoft.identity.common.java.flighting.CommonFlightsManager;
import com.microsoft.identity.common.java.opentelemetry.AttributeName;
import com.microsoft.identity.common.java.opentelemetry.OTelUtility;
import com.microsoft.identity.common.java.opentelemetry.SpanExtension;
import com.microsoft.identity.common.java.opentelemetry.SpanName;
import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback;
import com.microsoft.identity.common.java.challengehandlers.PKeyAuthChallenge;
import com.microsoft.identity.common.java.challengehandlers.PKeyAuthChallengeFactory;
Expand All @@ -65,7 +70,9 @@
import com.microsoft.identity.common.java.util.StringUtil;
import com.microsoft.identity.common.logging.Logger;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Principal;
import java.util.HashMap;
import java.util.Locale;
Expand All @@ -78,7 +85,10 @@
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.PLAY_STORE_INSTALL_PREFIX;
import static com.microsoft.identity.common.java.AuthenticationConstants.AAD.APP_LINK_KEY;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;

/**
* For web view client, we do not distinguish V1 from V2.
Expand Down Expand Up @@ -192,7 +202,11 @@ private boolean handleUrl(final WebView view, final String url) {
spanContext,
ViewTreeLifecycleOwner.get(view));
challengeHandler.processChallenge(challenge);
} else if (isRedirectUrl(formattedURL)) {
} else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_ATTACH_NEW_PRT_HEADER_WHEN_NONCE_EXPIRED) && isNonceRedirect(formattedURL)) {
Logger.info(methodTag,"Navigation contains new nonce within the redirect uri. "+ url);
processNonceAndReAttachHeaders(view, url);
}
else if (isRedirectUrl(formattedURL)) {
Logger.info(methodTag,"Navigation starts with the redirect uri.");
processRedirectUrl(view, url);
} else if (isWebsiteRequestUrl(formattedURL)) {
Expand Down Expand Up @@ -271,6 +285,10 @@ private boolean isRedirectUrl(@NonNull final String url) {
return url.startsWith(mRedirectUrl.toLowerCase(Locale.US));
}

private boolean isNonceRedirect(@NonNull final String url) {
return url.contains(AuthenticationConstants.Broker.SSO_NONCE_PARAMETER);
}

private boolean isWebsiteRequestUrl(@NonNull final String url) {
return url.startsWith(AuthenticationConstants.Broker.BROWSER_EXT_PREFIX);
}
Expand Down Expand Up @@ -515,6 +533,38 @@ private void processHeaderForwardingRequiredUri(@NonNull final WebView view, @No
view.loadUrl(url, mRequestHeaders);
}

private void processNonceAndReAttachHeaders(@NonNull final WebView view, @NonNull final String url) {
final String methodTag = TAG + ":processNonceAndReAttachHeaders";

final HashMap<String, String> queryParams = StringExtensions.getUrlParameters(url);
final String nonceQueryParam = queryParams.get("sso_nonce");
SpanExtension.current().setAttribute(
AttributeName.is_sso_nonce_found_in_ests_request.name(), nonceQueryParam != null
);
if (nonceQueryParam != null) {
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
final Span span = spanContext != null ?
OTelUtility.createSpanFromParent(SpanName.ProcessNonceFromEstsRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessNonceFromEstsRedirect.name());
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
final NonceRedirectHandler nonceRedirect = new NonceRedirectHandler(view, mRequestHeaders, span);
nonceRedirect.processChallenge(new URL(url));
span.setStatus(StatusCode.OK);
} catch (MalformedURLException e) {
// No need to throw the error as we don't want to break the original flow.
Logger.errorPII(methodTag, "Redirect URI has invalid syntax, unable to parse", e);
span.setStatus(StatusCode.ERROR, "Redirect URI has invalid syntax, unable to parse");
span.recordException(e);
} catch (final Throwable throwable) {
// No need to throw the error as we don't want to break the original flow.
Logger.error(methodTag, "Error processing nonce and re-attaching headers", throwable);
span.setStatus(StatusCode.ERROR, "Error processing nonce and re-attaching headers");
span.recordException(throwable);
} finally {
span.end();
}
}
}

private String removeQueryParametersOrRedact(@NonNull final String url) {
final String methodTag = TAG + ":removeQueryParametersOrRedact";
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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.challengehandlers

import android.webkit.WebView
import com.microsoft.identity.common.java.broker.CommonRefreshTokenCredentialProvider
import com.microsoft.identity.common.adal.internal.AuthenticationConstants
import com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.SSO_NONCE_PARAMETER
import com.microsoft.identity.common.adal.internal.util.StringExtensions
import com.microsoft.identity.common.java.opentelemetry.AttributeName
import com.microsoft.identity.common.logging.Logger
import io.opentelemetry.api.trace.Span
import java.net.URL

/**
* Handler for processing nonce from redirect and attaching new prt credential header on web view.
*/
class NonceRedirectHandler(
private val webView: WebView,
private val headers: HashMap<String, String>,
private val span : Span
) : IChallengeHandler<URL, Void> {
private val TAG = NonceRedirectHandler::class.java.simpleName

override fun processChallenge(input: URL) : Void? {
val nonce = getNonceFromRedirectUrl(input)
if (nonce != null) {
modifyHeadersWithNewRefreshTokenCredential(nonce, input.toString())
}
webView.loadUrl(input.toString(), headers)
return null
}

private fun getNonceFromRedirectUrl(url: URL): String? {
val parameters = StringExtensions.getUrlParameters(url.toString())
return parameters[SSO_NONCE_PARAMETER]
}

private fun getPrtHeader(requestHeaders: HashMap<String, String>): String? {
return requestHeaders[AuthenticationConstants.Broker.PRT_RESPONSE_HEADER]
}

// Updates the headers by attaching a new refresh token credential header (Generated using the new nonce).
private fun modifyHeadersWithNewRefreshTokenCredential(
nonce: String,
url: String
) {
val methodTag = "$TAG:getHeadersWithNewRefreshTokenCredential"
val prtHeader = getPrtHeader(headers)
if (!prtHeader.isNullOrEmpty()) {
Logger.info(methodTag, "PRT credential header found in headers!")
val username = getUserNameFromWebViewUrl(url)
if (username != null) {
val updatedRefreshTokenCredentialHeader =
CommonRefreshTokenCredentialProvider.getRefreshTokenCredentialUsingNewNonce(
url, username,
nonce
)
if (updatedRefreshTokenCredentialHeader != null) {
headers[AuthenticationConstants.Broker.PRT_RESPONSE_HEADER] =
updatedRefreshTokenCredentialHeader
span.setAttribute(AttributeName.is_new_refresh_token_cred_header_attached.name, true)
}
}
}
}

private fun getUserNameFromWebViewUrl(url: String): String? {
val parameters: Map<String, String> = StringExtensions.getUrlParameters(url)
return parameters["login_hint"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public class AzureActiveDirectoryWebViewClientTest {
private static final String TEST_MSA_HEADER_FORWARDING_POSITIVE_URL = "https://login.live.com/oauth20_authorize.srf";
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";

@Before
public void setup() {
mContext = ApplicationProvider.getApplicationContext();
Expand Down Expand Up @@ -184,5 +186,8 @@ public void testUrlOverrideHandlesHeaderForwardingRequiredUrl() {
assertFalse(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_MSA_HEADER_FORWARDING_NEGATIVE_URL));
}


@Test
public void testUrlOverrideHandlesNonceRedirectUrl() {
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_NONCE_REDIRECT_URL));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.java.broker

import com.microsoft.identity.common.java.interfaces.IRefreshTokenCredentialProvider
import com.microsoft.identity.common.java.logging.Logger

/**
* Consumer of commons needs to implement [IRefreshTokenCredentialProvider] interface
* and set it using CommonRefreshTokenCredentialProvider.initializeCommonRefreshTokenCredentialProvider(@NonNull refreshTokenCredentialProvider: IRefreshTokenCredentialProvider)
* to provide prtCredentialHolder to common module.
*/
object CommonRefreshTokenCredentialProvider : IRefreshTokenCredentialProvider {
private val TAG = CommonRefreshTokenCredentialProvider::class.java.simpleName
private var mRefreshTokenCredentialProvider: IRefreshTokenCredentialProvider? = null

// Note : This method should only be invoked by broker module.
fun initializeCommonRefreshTokenCredentialProvider(refreshTokenCredentialProvider: IRefreshTokenCredentialProvider) {
val methodTag = "$TAG:initializeCommonRefreshTokenCredentialProvider"
Logger.info(methodTag, "Initializing common prt credential provider with " + refreshTokenCredentialProvider.javaClass.simpleName)
mRefreshTokenCredentialProvider = refreshTokenCredentialProvider
}

override fun getRefreshTokenCredentialUsingNewNonce(inputUrl : String, username : String, nonce : String) : String? {
val methodTag = "$TAG:getRefreshTokenCredentialUsingNewNonce";
if (mRefreshTokenCredentialProvider != null) {
return mRefreshTokenCredentialProvider!!.getRefreshTokenCredentialUsingNewNonce(inputUrl, username, nonce)
}
Logger.warn(methodTag, "mRefreshTokenCredentialHolder is not initialized!")
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ public enum CommonFlight implements IFlightConfig {
/**
* Flight to enable the legacy FIDO security key additional logic. Default is true for common.
*/
ENABLE_LEGACY_FIDO_SECURITY_KEY_LOGIC("EnableLegacyFidoSecurityKeyLogic", true);
ENABLE_LEGACY_FIDO_SECURITY_KEY_LOGIC("EnableLegacyFidoSecurityKeyLogic", true),

/**
* Flight to enable the re-attachment of new PRT header logic. Default is true.
*/
ENABLE_ATTACH_NEW_PRT_HEADER_WHEN_NONCE_EXPIRED("EnableAttachNewPrtHeaderWhenNonceExpired", true);
private String key;
private Object defaultValue;
CommonFlight(@NonNull String key, @NonNull Object defaultValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.java.interfaces;


/**
* Consumer of commons needs to implement [IRefreshTokenCredentialProvider] interface
* and set it using CommonRefreshTokenCredentialProvider.initializeCommonRefreshTokenCredentialProvider(@NonNull refreshTokenCredentialProvider: IRefreshTokenCredentialProvider)
* to provide prtCredentialHolder to common module.
*/
interface IRefreshTokenCredentialProvider {

/**
* Gets refresh token credential using nonce retrieved from webview.
*/
fun getRefreshTokenCredentialUsingNewNonce(inputUrl : String, username : String, nonce : String) : String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,14 @@ public enum AttributeName {
* Indicates the stack trace from a Android KeyStore operation exception.
*/
keystore_exception_stack_trace,

/**
* Indicates the new nonce found in the eSTS request.
*/
is_sso_nonce_found_in_ests_request,

/**
* Indicates the new refresh token credential header attached in the eSTS request.
*/
is_new_refresh_token_cred_header_attached
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ public enum SpanName {
OnUpgradeReceiver,
UpgradeDeviceRegistration,
RemoveBrokerAccount,
ProcessNonceFromEstsRedirect
}

0 comments on commit 003fe74

Please sign in to comment.