Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to browser on DUNA redirect URL., Fixes AB#3079799 #2553

Open
wants to merge 71 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
560d0fb
draft
p3dr0rv Dec 3, 2024
7632017
draft 2
p3dr0rv Dec 4, 2024
f30b5bc
nits
p3dr0rv Dec 4, 2024
82251aa
nits
p3dr0rv Dec 5, 2024
e7636cb
javadoc
p3dr0rv Dec 5, 2024
a8720b9
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Dec 5, 2024
ca4f0eb
add tests
p3dr0rv Dec 6, 2024
19e0498
update names
p3dr0rv Dec 7, 2024
76155d9
fix tests
p3dr0rv Dec 7, 2024
a389026
Refactor startActivity call in WebViewAuthorizationFragment
p3dr0rv Dec 11, 2024
6178335
Add redirect URI query parameter
p3dr0rv Dec 11, 2024
744d212
create snippets
p3dr0rv Dec 11, 2024
e3da415
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Dec 11, 2024
d569aa6
update tests
p3dr0rv Dec 11, 2024
c404af1
Merge branch 'pedroro/switch-to-browser' of https://github.com/AzureA…
p3dr0rv Dec 11, 2024
9385aba
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Dec 17, 2024
58c771c
used defined browsers for duna flow
p3dr0rv Dec 18, 2024
fae212f
feedback
p3dr0rv Dec 18, 2024
1f3402b
fix pmd issue
p3dr0rv Dec 18, 2024
762d67c
fix compilation error
p3dr0rv Dec 18, 2024
7f266eb
avoid double run
p3dr0rv Dec 18, 2024
5baaba0
fix tests
p3dr0rv Dec 18, 2024
c380714
add NON_FUNCTIONAL_BROWSER_SELECTOR
p3dr0rv Dec 18, 2024
d6bf4a8
fix test
p3dr0rv Dec 18, 2024
f15596c
trigger only once
p3dr0rv Dec 18, 2024
960c7c4
nit
p3dr0rv Dec 18, 2024
b948e9f
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Dec 18, 2024
4284ad5
implement feedback
p3dr0rv Dec 19, 2024
25ad56f
rework switchBrowser
p3dr0rv Dec 31, 2024
4584e13
fix testing
p3dr0rv Dec 31, 2024
c8f6328
spotbugs
p3dr0rv Dec 31, 2024
b8d0515
nit
p3dr0rv Dec 31, 2024
a56a813
feedback
p3dr0rv Dec 31, 2024
8c33115
Merge branch 'pedroro/duna-rework' into pedroro/switch-to-browser
p3dr0rv Dec 31, 2024
8a5a96e
draft
p3dr0rv Jan 3, 2025
003fe74
Merge branch 'dev' into pedroro/browser-selector
p3dr0rv Jan 3, 2025
98deb1c
RESTORE util
p3dr0rv Jan 3, 2025
c9b5f83
Merge branch 'pedroro/browser-selector' of https://github.com/AzureAD…
p3dr0rv Jan 3, 2025
755d9da
move to 4j
p3dr0rv Jan 3, 2025
4691ad6
add noopBrowserSelector
p3dr0rv Jan 3, 2025
1e624de
Convert to Java to maintain compatibility with older tests
p3dr0rv Jan 3, 2025
33740e2
Merge branch 'dev' into pedroro/browser-selector
p3dr0rv Jan 3, 2025
859d677
update changelog
p3dr0rv Jan 3, 2025
e410152
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Jan 4, 2025
d5a8697
Merge branch 'pedroro/browser-selector' into pedroro/switch-to-browser
p3dr0rv Jan 4, 2025
5c22a55
remove unused strings in ErrorString
p3dr0rv Jan 4, 2025
a697720
clean changes
p3dr0rv Jan 4, 2025
db8b608
nit
p3dr0rv Jan 4, 2025
82cc1c2
Merge branch 'pedroro/browser-selector' into pedroro/switch-to-browser
p3dr0rv Jan 4, 2025
20a8143
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Jan 24, 2025
ec3fd80
nits
p3dr0rv Jan 24, 2025
ebfaabc
update switchBrowserChallenge
p3dr0rv Jan 24, 2025
5c5b2fa
eof
p3dr0rv Jan 24, 2025
ca6e112
update java doc
p3dr0rv Jan 25, 2025
2b5a4f9
test need to be fixed
p3dr0rv Jan 25, 2025
8c7ae24
update test
p3dr0rv Jan 28, 2025
b7a104a
update chnagelog
p3dr0rv Jan 28, 2025
b0343eb
add SwitchBrowserUriHelper
p3dr0rv Jan 28, 2025
914856b
update constants
p3dr0rv Jan 29, 2025
9ea834a
license
p3dr0rv Jan 29, 2025
15511ee
remove action
p3dr0rv Feb 19, 2025
ceeb3f0
Merge branch 'dev' into pedroro/switch-to-browser
p3dr0rv Feb 19, 2025
d8c2d4e
Update SwitchBrowserUriHelper.kt
p3dr0rv Feb 19, 2025
2e47d40
update flag
p3dr0rv Feb 19, 2025
75b14e6
update test
p3dr0rv Feb 19, 2025
273a4cc
Update common/src/main/java/com/microsoft/identity/common/adal/intern…
p3dr0rv Feb 19, 2025
c045005
Update common/src/main/java/com/microsoft/identity/common/internal/ui…
p3dr0rv Feb 19, 2025
86c8fee
throw exceptions instead of null
p3dr0rv Feb 19, 2025
d793623
Merge branch 'pedroro/switch-to-browser' of https://github.com/AzureA…
p3dr0rv Feb 19, 2025
2b6c296
update comment
p3dr0rv Feb 19, 2025
f28b129
Update common/src/main/java/com/microsoft/identity/common/internal/ui…
p3dr0rv Feb 20, 2025
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
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
vNext
----------
- [MINOR] Launch browser when a switch_browser redirect URL is present (#2553)

Version 20.0.0
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,29 @@ public static final class AAD {
public static final String APP_VERSION = "x-app-ver";
}

/**
* Represents the constants value for the SwitchBrowser protocol.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static final class SWITCH_BROWSER {

/**
* String Query parameter key to indicate support for SWITCH_BROWSER protocol.
*/
public static final String PATH = "switch_browser";

/**
* String Query parameter key for the session token.
*/
public static final String CODE = "code";

/**
* String Query parameter key for the switchBrowser action uri.
*/
public static final String ACTION_URI = "action_uri";

}

/**
* Represents the constants for broker.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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.adal.internal.AuthenticationConstants.SWITCH_BROWSER;
import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.PRODUCT;
import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.VERSION;
import static com.microsoft.identity.common.java.logging.DiagnosticContext.CORRELATION_ID;
Expand All @@ -41,8 +42,6 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

import com.microsoft.identity.common.internal.msafederation.MsaFederatedSignInProviderName;
import com.microsoft.identity.common.internal.msafederation.MsaFederationConstants;
import com.microsoft.identity.common.internal.msafederation.MsaFederationExtensions;
import com.microsoft.identity.common.internal.msafederation.google.SignInWithGoogleCredential;
import com.microsoft.identity.common.internal.msafederation.google.SignInWithGoogleParameters;
Expand Down Expand Up @@ -121,6 +120,11 @@ public static Intent getAuthorizationActivityIntent(final Context context,
final LibraryConfiguration libraryConfig = LibraryConfiguration.getInstance();
if (ProcessUtil.isBrokerProcess(context)) {
intent = new Intent(context, BrokerAuthorizationActivity.class);
if(requestUrl.contains(SWITCH_BROWSER.PATH)) {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// In the case of a SwitchBrowser protocol, we need to transition from the browser to the WebView.
// These flags ensure that we have a new task stack that allows for this transition.
}
} else if (libraryConfig.isAuthorizationInCurrentTask() && !authorizationAgent.equals(AuthorizationAgent.WEBVIEW)) {
// We exclude the case when the authorization agent is already selected as WEBVIEW because of confusion
// that results from attempting to use the CurrentTaskAuthorizationActivity in that case, because as webview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
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.SwitchBrowserChallenge;
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserHandler;
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.NonceRedirectHandler;
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserUriHelper;
import com.microsoft.identity.common.java.constants.FidoConstants;
import com.microsoft.identity.common.java.flighting.CommonFlight;
import com.microsoft.identity.common.java.flighting.CommonFlightsManager;
Expand Down Expand Up @@ -102,13 +105,15 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient {
private static final String TAG = AzureActiveDirectoryWebViewClient.class.getSimpleName();

public static final String ERROR = "error";
public static final String ERROR_SUBCODE = "error_subcode";
public static final String ERROR_DESCRIPTION = "error_description";
private static final String DEVICE_CERT_ISSUER = "CN=MS-Organization-Access";
private final String mRedirectUrl;
private final CertBasedAuthFactory mCertBasedAuthFactory;
private AbstractCertBasedAuthChallengeHandler mCertBasedAuthChallengeHandler;

private final SwitchBrowserHandler mSwitchBrowserHandler;


private HashMap<String, String> mRequestHeaders;

public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
Expand All @@ -118,6 +123,7 @@ public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
super(activity, completionCallback, pageLoadedCallback);
mRedirectUrl = redirectUrl;
mCertBasedAuthFactory = new CertBasedAuthFactory(activity);
mSwitchBrowserHandler= new SwitchBrowserHandler(activity);
}

/**
Expand Down Expand Up @@ -208,7 +214,13 @@ private boolean handleUrl(final WebView view, final String url) {
}
else if (isRedirectUrl(formattedURL)) {
Logger.info(methodTag,"Navigation starts with the redirect uri.");
processRedirectUrl(view, url);
if (SwitchBrowserUriHelper.INSTANCE.isSwitchBrowserRequest(formattedURL)) {
Logger.info(methodTag,"Request to switch browser.");
return processSwitchBrowserRequest(formattedURL);
} else {
Logger.info(methodTag,"It is a redirect request.");
processRedirectUrl(view, url);
}
} else if (isWebsiteRequestUrl(formattedURL)) {
Logger.info(methodTag,"It is an external website request");
processWebsiteRequest(view, url);
Expand Down Expand Up @@ -325,7 +337,7 @@ private boolean isHeaderForwardingRequiredUri(@NonNull final String url) {

// This function is only called when the client received a redirect that starts with the apps
// redirect uri.
protected void processRedirectUrl(@NonNull final WebView view, @NonNull final String url) {
private void processRedirectUrl(@NonNull final WebView view, @NonNull final String url) {
final String methodTag = TAG + ":processRedirectUrl";

Logger.info(methodTag, "It is pointing to redirect. Final url can be processed to get the code or error.");
Expand All @@ -335,6 +347,23 @@ protected void processRedirectUrl(@NonNull final WebView view, @NonNull final St
//the TokenTask should be processed at after the authorization process in the upper calling layer.
}

/**
* Launch the browser with the given action URI and code.
* <p>
* From the query parameters, extract the action URI and code,
* The constructs the URI with the action URI and code.
*
* @param url The URL to be opened in the browser.
*/
private boolean processSwitchBrowserRequest(@NonNull final String url) {
final SwitchBrowserChallenge switchBrowserChallenge =
SwitchBrowserChallenge.constructFromRedirectUri(Uri.parse(url));
if (switchBrowserChallenge == null) {
return false;
}
return mSwitchBrowserHandler.processChallenge(switchBrowserChallenge);
}

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

Expand Down Expand Up @@ -633,6 +662,7 @@ public void onDestroy() {
mCertBasedAuthChallengeHandler.cleanUp();
}
mCertBasedAuthFactory.onDestroy();
mSwitchBrowserHandler.unbind();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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.net.Uri

/**
* SwitchBrowserChallenge is a challenge to switch from WebView to browser.
* It contains the URI to be opened in the new browser.
*/
data class SwitchBrowserChallenge(
val uri: Uri,
) {

companion object {
/**
* Construct a SwitchBrowserChallenge from the redirect URI.
*
* @param redirectUri The redirect URI containing the switch browser code and action URI.
* e.g. msauth://com.microsoft.identity.client/your-redirect-uri?code=your-switch-browser-code&action_uri=your-action-uri
*
* @return The SwitchBrowserChallenge constructed from the redirect URI.
* e.g. SwitchBrowserChallenge(uri = your-action-uri?code=your-switch-browser-code)
* params: redirectUri: Uri
*/
@JvmStatic
fun constructFromRedirectUri(redirectUri: Uri): SwitchBrowserChallenge? {
SwitchBrowserUriHelper.buildProcessUri(redirectUri)?.let { processUri ->
return SwitchBrowserChallenge(uri = processUri)
}
return null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.app.Activity
import android.content.Context
import android.content.Intent
import com.microsoft.identity.common.internal.ui.browser.AndroidBrowserSelector
import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager
import com.microsoft.identity.common.java.browser.IBrowserSelector
import com.microsoft.identity.common.java.ui.BrowserDescriptor
import com.microsoft.identity.common.logging.Logger

/**
* SwitchBrowserHandler is a challenge handler for SwitchBrowserChallenge.
* It handles the challenge by selecting a valid browser to launch the Switch browser URI.
*/
class SwitchBrowserHandler(
private val activity: Activity,
private val context: Context,
private val customTabsManager: CustomTabsManager,
private val browserSelector: IBrowserSelector
) : IChallengeHandler<SwitchBrowserChallenge, Boolean> {

companion object {
private val TAG = SwitchBrowserHandler::class.simpleName
}

constructor( activity: Activity) : this(
activity,
activity.applicationContext,
CustomTabsManager(activity.applicationContext),
AndroidBrowserSelector(activity.applicationContext)
)

/**
* Process the SwitchBrowserChallenge, which is a request to switch the browser.
* This method will select a valid browser to launch the challenge URI.
*
* @param switchBrowserChallenge challenge request
* @return true if the challenge is handled successfully, false otherwise.
*/
override fun processChallenge(switchBrowserChallenge: SwitchBrowserChallenge): Boolean {
val methodTag = "$TAG:processChallenge"

// Select a browser to handle the switch browser challenge
val browser = browserSelector.selectBrowser(
BrowserDescriptor.getBrowserSafeListForSwitchBrowser(),
null
)
if (browser == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a case of browser being uninstalled between the time interactive request started and got to this point? I'm asking because I see we already check for browser being available even before we declare switch browser capable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, at this point we expect the browser not to be null as we check prior to initiate DUNA, but because the function can return null, we do this check

Logger.warn(methodTag, "No browser found for SwitchBrowserChallenge.")
return false
}

// Create an intent to launch the browser
val browserIntent: Intent
if (browser.isCustomTabsServiceSupported) {
Logger.info(methodTag, "CustomTabsService is supported.")
//create customTabsIntent
if (!customTabsManager.bind(context, browser.packageName)) {
Logger.warn(methodTag, "Failed to bind CustomTabsService.")
browserIntent = Intent(Intent.ACTION_VIEW)
} else {
browserIntent = customTabsManager.customTabsIntent.intent
}
} else {
Logger.warn(methodTag, "CustomTabsService is NOT supported")
browserIntent = Intent(Intent.ACTION_VIEW)
}
Logger.info(methodTag, "Launching switch browser request on browser: ${browser.packageName}")
browserIntent.setPackage(browser.packageName)
browserIntent.setData(switchBrowserChallenge.uri)
activity.startActivity(browserIntent)
return true
}

fun unbind() {
customTabsManager.unbind()
}
}
Loading