From 8a5a96e92fca361726946224154ea10e5881276e Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Thu, 2 Jan 2025 16:12:47 -0800 Subject: [PATCH 01/10] draft --- .../AndroidPlatformComponentsFactory.java | 4 +- .../controllers/LocalMSALController.java | 11 +- .../platform/AndroidPlatformUtil.java | 53 -------- .../request/MsalBrokerRequestAdapter.java | 25 ---- .../AndroidAuthorizationStrategyFactory.java | 81 ++++++----- ...rrentTaskBrowserAuthorizationStrategy.java | 6 +- .../browser/BrowserAuthorizationStrategy.java | 12 +- .../internal/ui/browser/BrowserBlocklist.java | 1 + .../internal/ui/browser/BrowserSelector.java | 105 ++++++++++----- .../ui/browser/CustomTabsManager.java | 2 +- .../DefaultBrowserAuthorizationStrategy.java | 6 +- .../ui/browser/BrowserSelectorTest.java | 93 +++++-------- ...AndroidAuthorizationStrategyFactoryTest.kt | 96 +++++++++++++ .../identity/common/java/browser/Browser.java | 126 ++++++++++++++++++ .../common/java/browser/IBrowserSelector.java | 49 +++++++ .../java/interfaces/IPlatformComponents.java | 4 + .../java/interfaces/PlatformComponents.java | 4 + .../IAuthorizationStrategyFactory.java | 12 +- .../common/java/ui/BrowserDescriptor.java | 55 +++++++- .../common/java/util/IPlatformUtil.java | 7 - .../MockPlatformComponentsFactory.java | 21 ++- 21 files changed, 523 insertions(+), 250 deletions(-) create mode 100644 common/src/test/java/com/microsoft/identity/common/internal/util/AndroidAuthorizationStrategyFactoryTest.kt create mode 100644 common4j/src/main/com/microsoft/identity/common/java/browser/Browser.java create mode 100644 common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java diff --git a/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java b/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java index 28e2902ef9..e7529a1d49 100644 --- a/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java @@ -35,6 +35,7 @@ import com.microsoft.identity.common.internal.platform.AndroidPlatformUtil; import com.microsoft.identity.common.internal.providers.oauth2.AndroidTaskStateGenerator; import com.microsoft.identity.common.internal.ui.AndroidAuthorizationStrategyFactory; +import com.microsoft.identity.common.internal.ui.browser.BrowserSelector; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.interfaces.IPlatformComponents; import com.microsoft.identity.common.java.interfaces.PlatformComponents; @@ -127,7 +128,8 @@ public static void fillBuilderWithBasicImplementations( .storageSupplier(new AndroidStorageSupplier(context, new AndroidAuthSdkStorageEncryptionManager(context))) .platformUtil(new AndroidPlatformUtil(context, activity)) - .httpClientWrapper(new DefaultHttpClientWrapper()); + .httpClientWrapper(new DefaultHttpClientWrapper()) + .browserSelector(new BrowserSelector(context)); if (activity != null){ builder.authorizationStrategyFactory( diff --git a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java index 0306d8550b..d732572760 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java @@ -22,6 +22,7 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.controllers; +import android.content.pm.PackageManager; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -31,10 +32,12 @@ import com.microsoft.identity.common.internal.telemetry.Telemetry; import com.microsoft.identity.common.internal.telemetry.events.ApiEndEvent; import com.microsoft.identity.common.internal.telemetry.events.ApiStartEvent; +import com.microsoft.identity.common.internal.ui.browser.BrowserSelector; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.authorities.Authority; import com.microsoft.identity.common.java.authscheme.AbstractAuthenticationScheme; import com.microsoft.identity.common.java.authscheme.IPoPAuthenticationSchemeParams; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.java.cache.ICacheRecord; import com.microsoft.identity.common.java.commands.parameters.CommandParameters; import com.microsoft.identity.common.java.commands.parameters.DeviceCodeFlowCommandParameters; @@ -226,7 +229,13 @@ private AuthorizationResult performAuthorizationRequest(@NonNull final OAuth2Str .getPlatformUtil() .throwIfNetworkNotAvailable(parameters.isPowerOptCheckEnabled()); - mAuthorizationStrategy = parameters.getPlatformComponents().getAuthorizationStrategyFactory().getAuthorizationStrategy(parameters); + final Browser browser = parameters.getPlatformComponents().getBrowserSelector().select( + parameters.getBrowserSafeList(), + parameters.getPreferredBrowser() + ); + mAuthorizationStrategy = parameters.getPlatformComponents() + .getAuthorizationStrategyFactory() + .getAuthorizationStrategy(parameters.getAuthorizationAgent(),browser, false); mAuthorizationRequest = getAuthorizationRequest(strategy, parameters); // Suppressing unchecked warnings due to casting of AuthorizationRequest to GenericAuthorizationRequest and AuthorizationStrategy to GenericAuthorizationStrategy in the arguments of call to requestAuthorization method diff --git a/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java b/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java index 7f49212823..35af6d6f3a 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java @@ -28,7 +28,6 @@ import android.app.Activity; import android.app.ActivityManager; -import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -38,7 +37,6 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; -import android.os.UserManager; import com.microsoft.identity.common.BuildConfig; import com.microsoft.identity.common.adal.internal.AuthenticationConstants; @@ -55,14 +53,12 @@ import com.microsoft.identity.common.java.flighting.CommonFlight; import com.microsoft.identity.common.java.flighting.CommonFlightsManager; import com.microsoft.identity.common.java.logging.Logger; -import com.microsoft.identity.common.java.ui.BrowserDescriptor; import com.microsoft.identity.common.java.util.IPlatformUtil; import com.microsoft.identity.common.java.util.StringUtil; import java.security.NoSuchAlgorithmException; import java.util.AbstractMap; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; @@ -82,28 +78,6 @@ public class AndroidPlatformUtil implements IPlatformUtil { @Nullable private final Activity mActivity; - /** - * List of System Browsers which can be used from broker, currently only Chrome is supported. - * This information here is populated from the default browser safe-list in MSAL. - * - * @return - */ - @Override - public List getBrowserSafeListForBroker() { - List browserDescriptors = new ArrayList<>(); - final HashSet signatureHashes = new HashSet(); - signatureHashes.add("7fmduHKTdHHrlMvldlEqAIlSfii1tl35bxj1OXN5Ve8c4lU6URVu4xtSHc3BVZxS6WWJnxMDhIfQN0N0K2NDJg=="); - final BrowserDescriptor chrome = new BrowserDescriptor( - "com.android.chrome", - signatureHashes, - null, - null - ); - browserDescriptors.add(chrome); - - return browserDescriptors; - } - @Nullable @Override public String getInstalledCompanyPortalVersion() { @@ -263,33 +237,6 @@ public static ArrayList> updateWithOrDeleteWebAuthnPar return result; } - /** - * Check if the host app is running within a managed profile. - * @param appContext current application context. - * @return true if app is in a managed profile, false if in personal profile or OS is below LOLLIPOP. - */ - public static boolean isInManagedProfile(@NonNull final Context appContext) { - // If the device is running on Android R or above, we can use the UserManager method isManagedProfile. - // Otherwise, if the device is running on Lollipop or above, we'll use DPM's isProfileOwnerApp. We return false for lower versions. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - final UserManager um = (UserManager) appContext.getSystemService(Context.USER_SERVICE); - return um.isManagedProfile(); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - final DevicePolicyManager dpm = (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - final List activeAdmins = dpm.getActiveAdmins(); - if (activeAdmins != null) { - // If any active admin apps are the profile owner, then the current calling app is in a managed profile. - for (final ComponentName admin : activeAdmins) { - final String packageName = admin.getPackageName(); - if (dpm.isProfileOwnerApp(packageName)) { - return true; - } - } - } - } - return false; - } - /** * This method optionally re-orders tasks to bring the task that launched * the interactive activity to the foreground. This is useful when the activity provided diff --git a/common/src/main/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapter.java b/common/src/main/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapter.java index 06b93b8bb9..0b94400a1d 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapter.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapter.java @@ -57,7 +57,6 @@ import com.microsoft.identity.common.java.opentelemetry.SpanExtension; import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResult; import com.microsoft.identity.common.java.providers.oauth2.AuthorizationResult; -import com.microsoft.identity.common.java.ui.BrowserDescriptor; import com.microsoft.identity.common.java.util.BrokerProtocolVersionUtil; import com.microsoft.identity.common.java.util.ObjectMapper; import com.microsoft.identity.common.java.authorities.AzureActiveDirectoryAuthority; @@ -77,9 +76,6 @@ import com.microsoft.identity.common.logging.Logger; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; public class MsalBrokerRequestAdapter implements IBrokerRequestAdapter { @@ -490,27 +486,6 @@ private boolean getMultipleCloudsSupported(@NonNull final TokenCommandParameters } } - /** - * List of System Browsers which can be used from broker, currently only Chrome is supported. - * This information here is populated from the default browser safelist in MSAL. - * - * @return - */ - public static List getBrowserSafeListForBroker() { - List browserDescriptors = new ArrayList<>(); - final HashSet signatureHashes = new HashSet(); - signatureHashes.add("7fmduHKTdHHrlMvldlEqAIlSfii1tl35bxj1OXN5Ve8c4lU6URVu4xtSHc3BVZxS6WWJnxMDhIfQN0N0K2NDJg=="); - final BrowserDescriptor chrome = new BrowserDescriptor( - "com.android.chrome", - signatureHashes, - null, - null - ); - browserDescriptors.add(chrome); - - return browserDescriptors; - } - /** * adds required broker protocol version key in request bundle if not null. */ diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java index aeceb4dfae..35ddf33421 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java @@ -29,23 +29,16 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.microsoft.identity.common.internal.ui.browser.Browser; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.internal.ui.browser.DefaultBrowserAuthorizationStrategy; import com.microsoft.identity.common.java.WarningType; -import com.microsoft.identity.common.java.exception.ClientException; -import com.microsoft.identity.common.java.exception.ErrorStrings; -import com.microsoft.identity.common.java.commands.parameters.BrokerInteractiveTokenCommandParameters; -import com.microsoft.identity.common.java.commands.parameters.InteractiveTokenCommandParameters; import com.microsoft.identity.common.java.configuration.LibraryConfiguration; import com.microsoft.identity.common.java.providers.oauth2.IAuthorizationStrategy; -import com.microsoft.identity.common.internal.ui.browser.BrowserSelector; import com.microsoft.identity.common.internal.ui.webview.EmbeddedWebViewAuthorizationStrategy; import com.microsoft.identity.common.java.ui.AuthorizationAgent; -import com.microsoft.identity.common.java.ui.BrowserDescriptor; import com.microsoft.identity.common.logging.Logger; import com.microsoft.identity.common.java.strategies.IAuthorizationStrategyFactory; -import java.util.List; import lombok.Builder; import lombok.experimental.Accessors; @@ -54,65 +47,71 @@ @SuppressWarnings(WarningType.rawtype_warning) @Builder @Accessors(prefix = "m") -public class AndroidAuthorizationStrategyFactory implements IAuthorizationStrategyFactory{ +public class AndroidAuthorizationStrategyFactory implements IAuthorizationStrategyFactory { private static final String TAG = AndroidAuthorizationStrategyFactory.class.getSimpleName(); private final Context mContext; private final Activity mActivity; private final Fragment mFragment; + /** + * Get the authorization strategy. + * + * @param authorizationAgent The authorization agent provided by the caller. + * @param browser The browser to use for authorization. + * @param isBrowserRequest True if the request is from browser. + * @return The authorization strategy. + */ @Override + @NonNull public IAuthorizationStrategy getAuthorizationStrategy( - @NonNull final InteractiveTokenCommandParameters parameters) { + @NonNull final AuthorizationAgent authorizationAgent, + @Nullable final Browser browser, + final boolean isBrowserRequest) { final String methodTag = TAG + ":getAuthorizationStrategy"; - //Valid if available browser installed. Will fallback to embedded webView if no browser available. - if (parameters.getAuthorizationAgent() == AuthorizationAgent.WEBVIEW) { - Logger.info(methodTag, "Use webView for authorization."); + // Use embedded webView if no browser available or authorization agent is webView + if (authorizationAgent == AuthorizationAgent.WEBVIEW || browser == null) { + Logger.info(methodTag, "WebView authorization, browser: " + browser); return getGenericAuthorizationStrategy(); } - try { - final Browser browser = BrowserSelector.select( - mContext, - parameters.getBrowserSafeList(), - parameters.getPreferredBrowser()); - - Logger.info(methodTag, "Use browser for authorization."); - return getBrowserAuthorizationStrategy( - browser, - (parameters instanceof BrokerInteractiveTokenCommandParameters)); - } catch (final ClientException e) { - Logger.info(methodTag, "Unable to use browser to do the authorization because " - + ErrorStrings.NO_AVAILABLE_BROWSER_FOUND + " Use embedded webView instead."); - return getGenericAuthorizationStrategy(); - } + Logger.info(methodTag, "Browser authorization, browser: " + browser); + return getBrowserAuthorizationStrategy(browser, isBrowserRequest); } + /** + * Get current task browser authorization strategy or default browser authorization strategy. + * If the authorization is in current task, use current task browser authorization strategy. + * + * @param browser The browser to use for authorization. + * @param isBrokerRequest True if the request is from broker. + * @return The browser authorization strategy. + */ private IAuthorizationStrategy getBrowserAuthorizationStrategy(@NonNull final Browser browser, final boolean isBrokerRequest) { if (LibraryConfiguration.getInstance().isAuthorizationInCurrentTask()) { - final CurrentTaskBrowserAuthorizationStrategy currentTaskBrowserAuthorizationStrategy = - new CurrentTaskBrowserAuthorizationStrategy( - mContext, - mActivity, - mFragment); - currentTaskBrowserAuthorizationStrategy.setBrowser(browser); - return currentTaskBrowserAuthorizationStrategy; + return new CurrentTaskBrowserAuthorizationStrategy( + mContext, + mActivity, + mFragment, + browser); } else { - final DefaultBrowserAuthorizationStrategy defaultBrowserAuthorizationStrategy = new DefaultBrowserAuthorizationStrategy( + return new DefaultBrowserAuthorizationStrategy( mContext, mActivity, mFragment, - isBrokerRequest + isBrokerRequest, + browser ); - defaultBrowserAuthorizationStrategy.setBrowser(browser); - return defaultBrowserAuthorizationStrategy; } } - // Suppressing unchecked warnings due to casting of EmbeddedWebViewAuthorizationStrategy to GenericAuthorizationStrategy - @SuppressWarnings(WarningType.unchecked_warning) + /** + * Get the generic authorization strategy. + * + * @return The embedded web view authorization strategy. + */ private IAuthorizationStrategy getGenericAuthorizationStrategy() { return new EmbeddedWebViewAuthorizationStrategy( mContext, diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java index 6925a297e2..274dc2a70c 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.internal.ui.browser.BrowserAuthorizationStrategy; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.providers.oauth2.AuthorizationRequest; @@ -43,8 +44,9 @@ public class CurrentTaskBrowserAuthorizationStrategy< extends BrowserAuthorizationStrategy { public CurrentTaskBrowserAuthorizationStrategy(@NonNull Context applicationContext, @NonNull Activity activity, - @Nullable Fragment fragment) { - super(applicationContext, activity, fragment); + @Nullable Fragment fragment, + @NonNull final Browser browser) { + super(applicationContext, activity, fragment, browser); } @Override diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserAuthorizationStrategy.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserAuthorizationStrategy.java index 916f873663..b58d906e2a 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserAuthorizationStrategy.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserAuthorizationStrategy.java @@ -34,6 +34,7 @@ import com.microsoft.identity.common.internal.providers.oauth2.AndroidAuthorizationStrategy; import com.microsoft.identity.common.internal.providers.oauth2.AuthorizationActivityFactory; import com.microsoft.identity.common.java.WarningType; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.providers.RawAuthorizationResult; import com.microsoft.identity.common.java.providers.oauth2.AuthorizationRequest; @@ -59,18 +60,17 @@ public abstract class BrowserAuthorizationStrategy< private CustomTabsManager mCustomTabManager; private ResultFuture mAuthorizationResultFuture; - private Browser mBrowser; private boolean mDisposed; private GenericOAuth2Strategy mOAuth2Strategy; //NOPMD private GenericAuthorizationRequest mAuthorizationRequest; //NOPMD + private final Browser mBrowser; + public BrowserAuthorizationStrategy(@NonNull Context applicationContext, @NonNull Activity activity, - @Nullable Fragment fragment) { + @Nullable Fragment fragment, + @NonNull Browser browser) { super(applicationContext, activity, fragment); - } - - public void setBrowser(final Browser browser) { mBrowser = browser; } @@ -169,7 +169,7 @@ public void completeAuthorization(int requestCode, @NonNull final RawAuthorizati /** * Disposes state that will not normally be handled by garbage collection. This should be * called when the authorization service is no longer required, including when any owning - * activity is paused or destroyed (i.e. in {@link android.app.Activity#onStop()}). + * activity is paused or destroyed (i.e. in Activity#onStop()). */ public void dispose() { if (mDisposed) { diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java index 04efa01b6a..dff43b8a74 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java @@ -24,6 +24,7 @@ import androidx.annotation.NonNull; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.logging.Logger; import java.util.Arrays; diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java index 8260dd851b..a01e9a9c30 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java @@ -29,60 +29,74 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.Signature; import android.net.Uri; import android.os.Build; +import android.util.Base64; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.microsoft.identity.common.java.exception.ClientException; -import com.microsoft.identity.common.java.exception.ErrorStrings; +import com.microsoft.identity.common.java.browser.Browser; +import com.microsoft.identity.common.java.browser.IBrowserSelector; import com.microsoft.identity.common.java.ui.BrowserDescriptor; import com.microsoft.identity.common.java.util.StringUtil; import com.microsoft.identity.common.logging.Logger; import com.microsoft.identity.common.internal.broker.PackageHelper; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; -public class BrowserSelector { +public class BrowserSelector implements IBrowserSelector { private static final String TAG = BrowserSelector.class.getSimpleName(); private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; + private static final String DIGEST_SHA_512 = "SHA-512"; // Added to avoid "avoidduplicateliterals" issues in pmd. private static final String LOGGING_MSG_BROWSER = "Browser: "; + private final PackageManager mPackageManager; + + public BrowserSelector(@NonNull final Context context) { + mPackageManager = context.getPackageManager(); + } + /** * Searches through all browsers for the best match. * Browsers are evaluated in the order returned by the package manager, * which should indirectly match the user's preferences. * First matched browser in the list will be preferred no matter weather or not the custom tabs supported. * - * @param context {@link Context} to use for accessing {@link PackageManager}. * @return Browser selected to use. */ - public static Browser select(@NonNull final Context context, - @NonNull final List browserSafeList, - @Nullable final BrowserDescriptor preferredBrowserDescriptor) throws ClientException { + @Nullable + @Override + public Browser select( + @NonNull final List browserSafeList, + @Nullable final BrowserDescriptor preferredBrowserDescriptor) { final String methodTag = TAG + ":select"; Logger.verbose(methodTag, "Select the browser to launch."); if (preferredBrowserDescriptor != null){ - final Browser preferredBrowser = getPreferredBrowser(context, preferredBrowserDescriptor); + final Browser preferredBrowser = getPreferredBrowser(preferredBrowserDescriptor); if (preferredBrowser != null) { return preferredBrowser; } } - final Browser defaultBrowser = getDefaultBrowser(context, browserSafeList); + final Browser defaultBrowser = getDefaultBrowser(browserSafeList); if (defaultBrowser != null) { return defaultBrowser; } - Logger.error(methodTag, "No available browser installed on the device.", null); - throw new ClientException(ErrorStrings.NO_AVAILABLE_BROWSER_FOUND, "No available browser installed on the device."); + Logger.warn(methodTag, "No available browser installed on the device."); + return null; } private static boolean matches(@NonNull final BrowserDescriptor descriptor, @@ -117,11 +131,10 @@ && compareSemanticVersion(browser.getVersion(), descriptor.getVersionUpperBound( return true; } - private static Browser getPreferredBrowser(@NonNull final Context context, - @NonNull final BrowserDescriptor preferredBrowserDescriptor){ + private Browser getPreferredBrowser(@NonNull final BrowserDescriptor preferredBrowserDescriptor){ final String methodTag = TAG + ":getPreferredBrowser"; - final List allBrowsers = getBrowsers(context, preferredBrowserDescriptor); + final List allBrowsers = getBrowsers(preferredBrowserDescriptor); for (final Browser browser : allBrowsers) { if (matches(preferredBrowserDescriptor, browser)) { Logger.info( @@ -137,11 +150,10 @@ private static Browser getPreferredBrowser(@NonNull final Context context, return null; } - private static Browser getDefaultBrowser(@NonNull final Context context, - @NonNull final List browserSafeList) { + private Browser getDefaultBrowser(@NonNull final List browserSafeList) { final String methodTag = TAG + ":getDefaultBrowser"; - final List allBrowsers = getBrowsers(context, null); + final List allBrowsers = getBrowsers(null); for (final Browser browser : allBrowsers) { for (final BrowserDescriptor browserDescriptor : browserSafeList) { if (matches(browserDescriptor, browser)) { @@ -167,8 +179,7 @@ private static Browser getDefaultBrowser(@NonNull final Context context, * order returned by the package manager, so indirectly reflects the user's preferences * (i.e. their default browser, if set, should be the first entry in the list). */ - protected static List getBrowsers(@NonNull final Context context, - @Nullable final BrowserDescriptor preferredBrowserDescriptor) { + protected List getBrowsers(@Nullable final BrowserDescriptor preferredBrowserDescriptor) { final String methodTag = TAG + ":getBrowsers"; //get the list of browsers @@ -181,15 +192,13 @@ protected static List getBrowsers(@NonNull final Context context, BROWSER_INTENT.setPackage(preferredBrowserDescriptor.getPackageName()); } - final PackageManager pm = context.getPackageManager(); - int queryFlag = PackageManager.GET_RESOLVED_FILTER; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { queryFlag |= PackageManager.MATCH_DEFAULT_ONLY; } final List resolvedActivityList = - pm.queryIntentActivities(BROWSER_INTENT, queryFlag); + mPackageManager.queryIntentActivities(BROWSER_INTENT, queryFlag); Logger.verbose(methodTag, "Querying browsers. Got back " + resolvedActivityList.size() + " browsers."); @@ -202,16 +211,19 @@ protected static List getBrowsers(@NonNull final Context context, } try { - final PackageInfo packageInfo = PackageHelper.getPackageInfo(pm, info.activityInfo.packageName); - //TODO if the browser is in the block list, do not add it into the return browserList. - if (isCustomTabsServiceSupported(context, packageInfo)) { - //if the browser has custom tab enabled, set the custom tab support as true. - Logger.verbose(methodTag,LOGGING_MSG_BROWSER + info.activityInfo.packageName + " supports custom tab."); - browserList.add(new Browser(packageInfo, true)); - } else { - Logger.verbose(methodTag,LOGGING_MSG_BROWSER + info.activityInfo.packageName + " does NOT support custom tab."); - browserList.add(new Browser(packageInfo, false)); - } + final PackageInfo packageInfo = PackageHelper.getPackageInfo(mPackageManager, info.activityInfo.packageName); + final boolean isCustomTabsServiceSupported = isCustomTabsServiceSupported(packageInfo); + Logger.verbose(methodTag, LOGGING_MSG_BROWSER + info.activityInfo.packageName + + " supports custom tab: " + isCustomTabsServiceSupported); + + final Browser browser = new Browser( + packageInfo.packageName, + generateSignatureHashes(PackageHelper.getSignatures(packageInfo)), + packageInfo.versionName, + isCustomTabsServiceSupported + ); + + browserList.add(browser); } catch (PackageManager.NameNotFoundException e) { // a browser cannot be generated without the package info Logger.warn(methodTag,LOGGING_MSG_BROWSER + info.activityInfo.packageName + " cannot be generated without the package info."); @@ -222,16 +234,37 @@ protected static List getBrowsers(@NonNull final Context context, return browserList; } - private static boolean isCustomTabsServiceSupported(@NonNull final Context context, @NonNull final PackageInfo packageInfo) { + /** + * Generates a set of SHA-512, Base64 url-safe encoded signature hashes from the provided + * array of signatures. + */ + @NonNull + public static Set generateSignatureHashes(@NonNull Signature[] signatures) { + Set signatureHashes = new HashSet<>(); + for (Signature signature : signatures) { + try { + MessageDigest digest = MessageDigest.getInstance(DIGEST_SHA_512); + byte[] hashBytes = digest.digest(signature.toByteArray()); + signatureHashes.add(Base64.encodeToString(hashBytes, Base64.URL_SAFE | Base64.NO_WRAP)); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException( + "Platform does not support" + DIGEST_SHA_512 + " hashing"); + } + } + + return signatureHashes; + } + + private boolean isCustomTabsServiceSupported(@NonNull final PackageInfo packageInfo) { // https://issuetracker.google.com/issues/119183822 // When above AndroidX issue is fixed, switch back to CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION - Intent serviceIntent = new Intent(new StringBuilder("android").append(".support.customtabs.action.CustomTabsService").toString()); + Intent serviceIntent = new Intent("android.support.customtabs.action.CustomTabsService"); serviceIntent.setPackage(packageInfo.packageName); - List resolveInfos = context.getPackageManager().queryIntentServices(serviceIntent, 0); + List resolveInfos = mPackageManager.queryIntentServices(serviceIntent, 0); return !(resolveInfos == null || resolveInfos.isEmpty()); } - private static boolean isFullBrowser(final ResolveInfo resolveInfo) { + private boolean isFullBrowser(final ResolveInfo resolveInfo) { // The filter must match ACTION_VIEW, CATEGORY_BROWSEABLE, and at least one scheme, if (!resolveInfo.filter.hasAction(Intent.ACTION_VIEW) || !resolveInfo.filter.hasCategory(Intent.CATEGORY_BROWSABLE) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/CustomTabsManager.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/CustomTabsManager.java index 41c9922962..12dc527025 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/CustomTabsManager.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/CustomTabsManager.java @@ -165,7 +165,7 @@ private CustomTabsSession createSession(@Nullable final CustomTabsCallback callb * if available when the {@link CustomTabsServiceConnection} is connected or the * {@link CustomTabsManager#CUSTOM_TABS_MAX_CONNECTION_TIMEOUT} is timed out. */ - public CustomTabsClient getClient() { + private CustomTabsClient getClient() { final String methodTag = TAG + ":getClient"; try { mClientLatch.await(CUSTOM_TABS_MAX_CONNECTION_TIMEOUT, TimeUnit.SECONDS); diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/DefaultBrowserAuthorizationStrategy.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/DefaultBrowserAuthorizationStrategy.java index 54e6998e86..68425cc6c8 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/DefaultBrowserAuthorizationStrategy.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/DefaultBrowserAuthorizationStrategy.java @@ -31,6 +31,7 @@ import androidx.fragment.app.Fragment; import com.microsoft.identity.common.java.WarningType; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.java.providers.oauth2.AuthorizationRequest; import com.microsoft.identity.common.java.providers.oauth2.OAuth2Strategy; @@ -46,8 +47,9 @@ public class DefaultBrowserAuthorizationStrategy< public DefaultBrowserAuthorizationStrategy(@NonNull Context applicationContext, @NonNull Activity activity, @Nullable Fragment fragment, - boolean isRequestFromBroker) { - super(applicationContext, activity, fragment); + boolean isRequestFromBroker, + @NonNull final Browser browser) { + super(applicationContext, activity, fragment, browser); mIsRequestFromBroker = isRequestFromBroker; } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java b/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java index e6f36b6857..bd114d68da 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java @@ -22,7 +22,9 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.ui.browser; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -32,7 +34,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.content.pm.SigningInfo; @@ -41,15 +42,13 @@ import androidx.test.core.app.ApplicationProvider; -import com.microsoft.identity.common.java.exception.ClientException; -import com.microsoft.identity.common.java.exception.ErrorStrings; +import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.java.ui.BrowserDescriptor; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowPackageManager; import java.nio.charset.Charset; @@ -61,7 +60,6 @@ public class BrowserSelectorTest { private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; - static final Intent BROWSER_INTENT = new Intent( Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); @@ -102,29 +100,25 @@ public class BrowserSelectorTest { //Currently package manager call returns an empty list... failing this test. Needs investigation. //Ignored while updating to latest Mockito version @Test - public void testSelect_getAllBrowser() throws NameNotFoundException { + public void testSelect_getAllBrowser() { setBrowserList(CHROME, FIREFOX); - List allBrowsers = BrowserSelector.getBrowsers(ApplicationProvider.getApplicationContext(), null); + List allBrowsers = new BrowserSelector(ApplicationProvider.getApplicationContext()).getBrowsers( null); assert (allBrowsers.get(0).getPackageName().equals(CHROME.mPackageName)); assert (allBrowsers.get(1).getPackageName().equals(FIREFOX.mPackageName)); } @Test - public void testSelect_noMatchingBrowser() throws NameNotFoundException { + public void testSelect_noMatchingBrowser() { setBrowserList(CHROME, FIREFOX); final List browserSafelist = new ArrayList<>(); - try { - BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, null); - } catch (final ClientException exception) { - assertNotNull(exception); - assert (exception.getErrorCode().equalsIgnoreCase(ErrorStrings.NO_AVAILABLE_BROWSER_FOUND)); - } + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + assertNull(browser); } @Test - public void testSelect_versionNotSupported() throws NameNotFoundException { + public void testSelect_versionNotSupported() { setBrowserList(CHROME, FIREFOX); final List browserSafelist = new ArrayList<>(); @@ -136,16 +130,13 @@ public void testSelect_versionNotSupported() throws NameNotFoundException { null) ); - try { - BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, null); - } catch (final ClientException exception) { - assertNotNull(exception); - assert (exception.getErrorCode().equalsIgnoreCase(ErrorStrings.NO_AVAILABLE_BROWSER_FOUND)); - } + + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + assertNull(browser); } @Test - public void testSelect_customTabsNotSupported() throws NameNotFoundException { + public void testSelect_customTabsNotSupported() { setBrowserList(FIREFOX_NO_CUSTOM_TAB); final List browserSafelist = new ArrayList<>(); @@ -156,17 +147,13 @@ public void testSelect_customTabsNotSupported() throws NameNotFoundException { null, null) ); - - try { - BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, null); - } catch (final ClientException exception) { - assertNotNull(exception); - assert (exception.getErrorCode().equalsIgnoreCase(ErrorStrings.NO_AVAILABLE_BROWSER_FOUND)); - } - + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + assertNotNull(browser); + assertEquals(browser.getPackageName(), FIREFOX_NO_CUSTOM_TAB.mPackageName); } + @Test - public void testSelect_preferredBrowserSelected() throws NameNotFoundException { + public void testSelect_preferredBrowserSelected() { setBrowserList(CHROME, DOLPHIN, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -191,19 +178,14 @@ public void testSelect_preferredBrowserSelected() throws NameNotFoundException { "10", null) ); + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); + Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); - - try { - final Browser browser = BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, preferredBrowser); - Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); - Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); - } catch (final ClientException exception) { - Assert.fail(); - } } @Test - public void testSelect_preferredBrowserSelected_preferredBrowserNotInstalled() throws NameNotFoundException { + public void testSelect_preferredBrowserSelected_preferredBrowserNotInstalled() { setBrowserList(CHROME, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -229,18 +211,13 @@ public void testSelect_preferredBrowserSelected_preferredBrowserNotInstalled() t null) ); - - try { - // It should return the first installed browser. - final Browser browser = BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, preferredBrowser); - Assert.assertEquals(CHROME.mPackageName, browser.getPackageName()); - } catch (final ClientException exception) { - Assert.fail(); - } + // It should return the first installed browser. + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + Assert.assertEquals(CHROME.mPackageName, browser.getPackageName()); } @Test - public void testSelect_preferredBrowserSelected_preferredBrowserNotInSafeList() throws NameNotFoundException { + public void testSelect_preferredBrowserSelected_preferredBrowserNotInSafeList() { setBrowserList(CHROME, DOLPHIN, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -264,22 +241,16 @@ public void testSelect_preferredBrowserSelected_preferredBrowserNotInSafeList() "10", null) ); - - - try { - // The safe list shouldn't matter, given that we've already specified all info in preferredBrowser's BrowserDescriptor. - final Browser browser = BrowserSelector.select(ApplicationProvider.getApplicationContext(), browserSafelist, preferredBrowser); - Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); - Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); - } catch (final ClientException exception) { - Assert.fail(); - } + // The safe list shouldn't matter, given that we've already specified all info in preferredBrowser's BrowserDescriptor. + final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); + Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); } /** * Browsers are expected to be in priority order, such that the default would be first. */ - private void setBrowserList(TestBrowser... browsers) throws NameNotFoundException { + private void setBrowserList(TestBrowser... browsers) { if (browsers == null) { return; } @@ -406,7 +377,7 @@ public TestBrowser build() { pi.packageName = mPackageName; pi.versionName = mVersion; - Set signatureHashes = Browser.generateSignatureHashes(pi.signatures); + Set signatureHashes = BrowserSelector.generateSignatureHashes(pi.signatures); ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); diff --git a/common/src/test/java/com/microsoft/identity/common/internal/util/AndroidAuthorizationStrategyFactoryTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/util/AndroidAuthorizationStrategyFactoryTest.kt new file mode 100644 index 0000000000..ac78a8657f --- /dev/null +++ b/common/src/test/java/com/microsoft/identity/common/internal/util/AndroidAuthorizationStrategyFactoryTest.kt @@ -0,0 +1,96 @@ +// 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.util + +import android.app.Activity +import androidx.fragment.app.Fragment +import com.microsoft.identity.common.internal.ui.AndroidAuthorizationStrategyFactory +import com.microsoft.identity.common.internal.ui.browser.DefaultBrowserAuthorizationStrategy +import com.microsoft.identity.common.internal.ui.webview.EmbeddedWebViewAuthorizationStrategy +import com.microsoft.identity.common.java.browser.Browser +import com.microsoft.identity.common.java.providers.oauth2.IAuthorizationStrategy +import com.microsoft.identity.common.java.ui.AuthorizationAgent +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class AndroidAuthorizationStrategyFactoryTest { + + companion object { + private val browser = mock(Browser::class.java) + } + + @Test + fun `test getAuthorizationStrategy with authorization agent WEBVIEW`() { + val strategy = getAuthorizationStrategy( + authorizationAgent = AuthorizationAgent.WEBVIEW, + browser = browser, + ) + assert(strategy is EmbeddedWebViewAuthorizationStrategy) + } + + @Test + fun `test getAuthorizationStrategy with authorization agent WEBVIEW, browser null`() { + val strategy = getAuthorizationStrategy( + authorizationAgent = AuthorizationAgent.WEBVIEW, + browser = null, + ) + assert(strategy is EmbeddedWebViewAuthorizationStrategy) + } + + @Test + fun `test getAuthorizationStrategy with authorization agent BROWSER`() { + val strategy = getAuthorizationStrategy( + authorizationAgent = AuthorizationAgent.BROWSER, + browser = browser, + ) + assert(strategy is DefaultBrowserAuthorizationStrategy) + } + + + @Test + fun `test getAuthorizationStrategy with authorization agent BROWSER, browser null`() { + val strategy = getAuthorizationStrategy( + authorizationAgent = AuthorizationAgent.BROWSER, + browser = null, + ) + assert(strategy is EmbeddedWebViewAuthorizationStrategy) + } + + private fun getAuthorizationStrategy( + browser: Browser?, + authorizationAgent: AuthorizationAgent, + ): IAuthorizationStrategy<*, *> { + // Construct the factory + val strategyFactory = AndroidAuthorizationStrategyFactory.builder() + .context(org.robolectric.RuntimeEnvironment.getApplication()) + .activity(mock(Activity::class.java)) + .fragment(mock(Fragment::class.java)) + .build() + + return strategyFactory.getAuthorizationStrategy(authorizationAgent, browser, true) + } +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/Browser.java b/common4j/src/main/com/microsoft/identity/common/java/browser/Browser.java new file mode 100644 index 0000000000..37c4da6303 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/Browser.java @@ -0,0 +1,126 @@ +// 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.browser; + +import java.util.Set; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Represents a browser used for an authorization flow. + */ +public class Browser { + private static final int PRIME_HASH_FACTOR = 92821; + + /** + * The package name of the browser app. + */ + private final String mPackageName; + + /** + * The set of signatures of the browser app, + * which have been hashed with SHA-512, and Base-64 URL-safe encoded. + */ + private final Set mSignatureHashes; + + /** + * The version string of the browser app. + */ + private final String mVersion; + + private final Boolean mIsCustomTabsServiceSupported; //NOPMD + + /** + * Creates a browser object with the core properties. + * + * @param packageName The Android package name of the browser. + * @param signatureHashes The set of SHA-512, Base64 url safe encoded signatures for the app. + * @param version The version name of the browser. + */ + public Browser(@NonNull String packageName, @NonNull Set signatureHashes, @NonNull String version, boolean isCustomTabsServiceSupported) { + mPackageName = packageName; + mSignatureHashes = signatureHashes; + mVersion = version; + mIsCustomTabsServiceSupported = isCustomTabsServiceSupported; + } + + /** + * Return the package name. + * + * @return String of package name. + */ + public String getPackageName() { + return mPackageName; + } + + /** + * Return the signature hashes of the browser application. + * + * @return Set of String + */ + public Set getSignatureHashes() { + return mSignatureHashes; + } + + /** + * Return the version of the browser application. + * + * @return String of the version + */ + public String getVersion() { + return mVersion; + } + + public boolean isCustomTabsServiceSupported() { + return mIsCustomTabsServiceSupported; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof Browser)) { + return false; + } + + final Browser other = (Browser) obj; + return mPackageName.equals(other.getPackageName()) + && mVersion.equals(other.getVersion()) + && mSignatureHashes.equals(other.getSignatureHashes()); + } + + @Override + public int hashCode() { + int hash = mPackageName.hashCode(); + + hash = PRIME_HASH_FACTOR * hash + mVersion.hashCode(); + hash = PRIME_HASH_FACTOR * hash + (mIsCustomTabsServiceSupported ? 1 : 0); + + for (String signatureHash : mSignatureHashes) { + hash = PRIME_HASH_FACTOR * hash + signatureHash.hashCode(); + } + + return hash; + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java b/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java new file mode 100644 index 0000000000..20a1be360d --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java @@ -0,0 +1,49 @@ +// 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.browser; + +import com.microsoft.identity.common.java.ui.BrowserDescriptor; + +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Interface for selecting a browser. + */ +public interface IBrowserSelector { + + /** + * Selects a valid installed browser from the list of safe browsers. + * If no browser is present in the list of safe browser, null is returned. + * + * @param browserSafeList The list of browsers to choose from. + * @param preferredBrowserDescriptor The preferred browser descriptor. + * @return The selected browser. + */ + @Nullable + Browser select( + @NonNull List browserSafeList, + @Nullable BrowserDescriptor preferredBrowserDescriptor); +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/interfaces/IPlatformComponents.java b/common4j/src/main/com/microsoft/identity/common/java/interfaces/IPlatformComponents.java index 85226c39cb..ae756cbc17 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/interfaces/IPlatformComponents.java +++ b/common4j/src/main/com/microsoft/identity/common/java/interfaces/IPlatformComponents.java @@ -23,6 +23,7 @@ package com.microsoft.identity.common.java.interfaces; import com.microsoft.identity.common.java.WarningType; +import com.microsoft.identity.common.java.browser.IBrowserSelector; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.providers.oauth2.IStateGenerator; import com.microsoft.identity.common.java.strategies.IAuthorizationStrategyFactory; @@ -86,4 +87,7 @@ public interface IPlatformComponents extends IPopManagerSupplier { @NonNull IStorageSupplier getStorageSupplier(); + + @NonNull + IBrowserSelector getBrowserSelector(); } diff --git a/common4j/src/main/com/microsoft/identity/common/java/interfaces/PlatformComponents.java b/common4j/src/main/com/microsoft/identity/common/java/interfaces/PlatformComponents.java index 1b0b79a70c..3e09820a78 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/interfaces/PlatformComponents.java +++ b/common4j/src/main/com/microsoft/identity/common/java/interfaces/PlatformComponents.java @@ -23,6 +23,7 @@ package com.microsoft.identity.common.java.interfaces; import com.microsoft.identity.common.java.WarningType; +import com.microsoft.identity.common.java.browser.IBrowserSelector; import com.microsoft.identity.common.java.cache.IMultiTypeNameValueStorage; import com.microsoft.identity.common.java.crypto.IDevicePopManager; import com.microsoft.identity.common.java.crypto.IKeyAccessor; @@ -72,6 +73,9 @@ public class PlatformComponents implements IPlatformComponents { @NonNull private final IHttpClientWrapper mHttpClientWrapper; + @NonNull + private final IBrowserSelector mBrowserSelector; + // TODO: Remove these methods and have the caller invoke IPopManagerSupplier directly. // Keeping this for now to minimize the PR size. diff --git a/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java b/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java index a6aee48d83..6c20078b0e 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java +++ b/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java @@ -23,15 +23,19 @@ package com.microsoft.identity.common.java.strategies; import com.microsoft.identity.common.java.WarningType; -import com.microsoft.identity.common.java.commands.parameters.InteractiveTokenCommandParameters; -import com.microsoft.identity.common.java.providers.oauth2.IAuthorizationStrategy; +import com.microsoft.identity.common.java.browser.Browser;import com.microsoft.identity.common.java.providers.oauth2.IAuthorizationStrategy; +import com.microsoft.identity.common.java.ui.AuthorizationAgent; -import lombok.NonNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; // Suppressing rawtype warnings due to the generic types IAuthorizationStrategy @SuppressWarnings(WarningType.rawtype_warning) public interface IAuthorizationStrategyFactory { GenericAuthorizationStrategy getAuthorizationStrategy( - @NonNull final InteractiveTokenCommandParameters parameters); + @NonNull final AuthorizationAgent authorizationAgent, + @Nullable final Browser browser, + final boolean isBrowserRequest + ); } diff --git a/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java b/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java index 59a0f6e36b..4fb013aedc 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java +++ b/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java @@ -25,7 +25,10 @@ import com.google.gson.annotations.SerializedName; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Set; import edu.umd.cs.findbugs.annotations.Nullable; @@ -39,16 +42,16 @@ public class BrowserDescriptor implements Serializable { private static final long serialVersionUID = 3745812401643512530L; @SerializedName("browser_package_name") - private String mPackageName; + final private String mPackageName; @SerializedName("browser_signature_hashes") - private Set mSignatureHashes; + final private Set mSignatureHashes; @SerializedName("browser_version_lower_bound") - private String mVersionLowerBound; + final private String mVersionLowerBound; @SerializedName("browser_version_upper_bound") - private String mVersionUpperBound; + final private String mVersionUpperBound; public BrowserDescriptor( @NonNull final String packageName, @@ -71,4 +74,48 @@ public BrowserDescriptor( mVersionLowerBound = versionLowerBound; mVersionUpperBound = versionUpperBound; } + + static private BrowserDescriptor getBrowserDescriptorForEdge() { + final HashSet edgeSignatureHashes = new HashSet<>(); + edgeSignatureHashes.add("Ivy-Rk6ztai_IudfbyUrSHugzRqAtHWslFvHT0PTvLMsEKLUIgv7ZZbVxygWy_M5mOPpfjZrd3vOx3t-cA6fVQ=="); + return new BrowserDescriptor( + "com.microsoft.emmx", + edgeSignatureHashes, + null, + null + ); + } + + static private BrowserDescriptor getBrowserDescriptorForChrome() { + final HashSet signatureHashes = new HashSet<>(); + signatureHashes.add("7fmduHKTdHHrlMvldlEqAIlSfii1tl35bxj1OXN5Ve8c4lU6URVu4xtSHc3BVZxS6WWJnxMDhIfQN0N0K2NDJg=="); + return new BrowserDescriptor( + "com.android.chrome", + signatureHashes, + null, + null + ); + } + + /** + * Return a list of BrowserDescriptors that are considered safe for the Switch to browser flow. + */ + static public List getBrowserSafeListForSwitchBrowser() { + List browserDescriptors = new ArrayList<>(); + browserDescriptors.add(getBrowserDescriptorForChrome()); + browserDescriptors.add(getBrowserDescriptorForEdge()); + return browserDescriptors; + } + + /** + * List of System Browsers which can be used from broker, currently only Chrome is supported. + * This information here is populated from the default browser safe-list in MSAL. + * + * @return List of BrowserDescriptors which are considered safe for the broker. + */ + static public List getBrowserSafeListForBroker() { + List browserDescriptors = new ArrayList<>(); + browserDescriptors.add(getBrowserDescriptorForChrome()); + return browserDescriptors; + } } diff --git a/common4j/src/main/com/microsoft/identity/common/java/util/IPlatformUtil.java b/common4j/src/main/com/microsoft/identity/common/java/util/IPlatformUtil.java index 35a16d5863..e16a7599b9 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/util/IPlatformUtil.java +++ b/common4j/src/main/com/microsoft/identity/common/java/util/IPlatformUtil.java @@ -25,7 +25,6 @@ import com.microsoft.identity.common.java.commands.ICommand; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.exception.ErrorStrings; -import com.microsoft.identity.common.java.ui.BrowserDescriptor; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -37,12 +36,6 @@ import edu.umd.cs.findbugs.annotations.Nullable; public interface IPlatformUtil { - - /** - * Return a list of BrowserDescriptors that are considered safe for the given platform. - */ - List getBrowserSafeListForBroker(); - /** * Gets version of the installed Company Portal app. * Returns null if the app is not installed, the value cannot be retrieved, or this operation is not supported. diff --git a/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java b/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java index 7c60bbfa8b..22198de78b 100644 --- a/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java +++ b/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java @@ -23,6 +23,8 @@ package com.microsoft.identity.common.components; import com.microsoft.identity.common.java.WarningType; +import com.microsoft.identity.common.java.browser.Browser; +import com.microsoft.identity.common.java.browser.IBrowserSelector; import com.microsoft.identity.common.java.commands.ICommand; import com.microsoft.identity.common.java.crypto.IDevicePopManager; import com.microsoft.identity.common.java.exception.ClientException; @@ -66,6 +68,7 @@ public static PlatformComponents.PlatformComponentsBuilder getNonFunctionalBuild .authorizationStrategyFactory(NON_FUNCTIONAL_AUTH_STRATEGY_FACTORY) .stateGenerator(NON_FUNCTIONAL_STATE_GENERATOR) .platformUtil(NON_FUNCTIONAL_PLATFORM_UTIL) + .browserSelector(NON_FUNCTIONAL_BROWSER_SELECTOR) .httpClientWrapper(new DefaultHttpClientWrapper()); return builder; } @@ -115,7 +118,7 @@ public IDevicePopManager getDevicePopManager(@Nullable final String alias) throw }; @SuppressWarnings(WarningType.rawtype_warning) - public static final IAuthorizationStrategyFactory NON_FUNCTIONAL_AUTH_STRATEGY_FACTORY = parameters -> { + public static final IAuthorizationStrategyFactory NON_FUNCTIONAL_AUTH_STRATEGY_FACTORY = (authorizationAgent, browser, isBrokerRequest) -> { throw new UnsupportedOperationException(); }; @@ -128,11 +131,6 @@ public String generate() { }; public static final IPlatformUtil NON_FUNCTIONAL_PLATFORM_UTIL = new IPlatformUtil() { - @Override - public List getBrowserSafeListForBroker() { - throw new UnsupportedOperationException(); - } - @Nullable @Override public String getInstalledCompanyPortalVersion() { @@ -192,4 +190,15 @@ public List> updateWithAndGetPlatformSpecificExtraQuer return originalList; } }; + + public static final IBrowserSelector NON_FUNCTIONAL_BROWSER_SELECTOR = new IBrowserSelector() { + + @Nullable + @Override + public Browser select(@NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor) { + return null; + } + }; + + } From 98deb1c2596964c179cf2d47c06ab64ab8c9d1b2 Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Thu, 2 Jan 2025 17:18:20 -0800 Subject: [PATCH 02/10] RESTORE util --- .../platform/AndroidPlatformUtil.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java b/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java index 35af6d6f3a..bcd7acbbea 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/platform/AndroidPlatformUtil.java @@ -28,6 +28,7 @@ import android.app.Activity; import android.app.ActivityManager; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -37,6 +38,7 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.os.UserManager; import com.microsoft.identity.common.BuildConfig; import com.microsoft.identity.common.adal.internal.AuthenticationConstants; @@ -237,6 +239,33 @@ public static ArrayList> updateWithOrDeleteWebAuthnPar return result; } + /** + * Check if the host app is running within a managed profile. + * @param appContext current application context. + * @return true if app is in a managed profile, false if in personal profile or OS is below LOLLIPOP. + */ + public static boolean isInManagedProfile(@NonNull final Context appContext) { + // If the device is running on Android R or above, we can use the UserManager method isManagedProfile. + // Otherwise, if the device is running on Lollipop or above, we'll use DPM's isProfileOwnerApp. We return false for lower versions. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final UserManager um = (UserManager) appContext.getSystemService(Context.USER_SERVICE); + return um.isManagedProfile(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final DevicePolicyManager dpm = (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + final List activeAdmins = dpm.getActiveAdmins(); + if (activeAdmins != null) { + // If any active admin apps are the profile owner, then the current calling app is in a managed profile. + for (final ComponentName admin : activeAdmins) { + final String packageName = admin.getPackageName(); + if (dpm.isProfileOwnerApp(packageName)) { + return true; + } + } + } + } + return false; + } + /** * This method optionally re-orders tasks to bring the task that launched * the interactive activity to the foreground. This is useful when the activity provided From 755d9daeee691f90060a34ad675359b6ed871824 Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Thu, 2 Jan 2025 17:23:08 -0800 Subject: [PATCH 03/10] move to 4j --- .../common/internal/ui/browser/Browser.java | 175 ------------------ 1 file changed, 175 deletions(-) delete mode 100644 common/src/main/java/com/microsoft/identity/common/internal/ui/browser/Browser.java diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/Browser.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/Browser.java deleted file mode 100644 index 0d46cafd2e..0000000000 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/Browser.java +++ /dev/null @@ -1,175 +0,0 @@ -// 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.browser; - -import android.content.pm.PackageInfo; -import android.content.pm.Signature; -import android.util.Base64; - -import androidx.annotation.NonNull; - -import com.microsoft.identity.common.internal.broker.PackageHelper; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashSet; -import java.util.Set; - -/** - * Represents a browser used for an authorization flow. - */ -public class Browser { - private static final String DIGEST_SHA_512 = "SHA-512"; - private static final int PRIME_HASH_FACTOR = 92821; - - /** - * The package name of the browser app. - */ - private final String mPackageName; - - /** - * The set of {@link android.content.pm.Signature signatures} of the browser app, - * which have been hashed with SHA-512, and Base-64 URL-safe encoded. - */ - private final Set mSignatureHashes; - - /** - * The version string of the browser app. - */ - private final String mVersion; - - private final Boolean mIsCustomTabsServiceSupported; //NOPMD - - /** - * Creates a browser object from a {@link PackageInfo} object returned from the - * {@link android.content.pm.PackageManager}. The object is expected to include the - * signatures of the app, which can be retrieved with the - * {@link android.content.pm.PackageManager#GET_SIGNATURES GET_SIGNATURES} flag when - * calling {@link android.content.pm.PackageManager#getPackageInfo(String, int)}. - */ - @SuppressWarnings("deprecation") - public Browser(@NonNull PackageInfo packageInfo) { - this(packageInfo.packageName, generateSignatureHashes(PackageHelper.getSignatures(packageInfo)), packageInfo.versionName, false); - } - - @SuppressWarnings("deprecation") - public Browser(@NonNull PackageInfo packageInfo, final Boolean isCustomTabsServiceSupported) { - this(packageInfo.packageName, generateSignatureHashes(PackageHelper.getSignatures(packageInfo)), packageInfo.versionName, isCustomTabsServiceSupported); - } - - /** - * Creates a browser object with the core properties. - * - * @param packageName The Android package name of the browser. - * @param signatureHashes The set of SHA-512, Base64 url safe encoded signatures for the app. - * @param version The version name of the browser. - */ - public Browser(@NonNull String packageName, @NonNull Set signatureHashes, @NonNull String version, boolean isCustomTabsServiceSupported) { - mPackageName = packageName; - mSignatureHashes = signatureHashes; - mVersion = version; - mIsCustomTabsServiceSupported = isCustomTabsServiceSupported; - } - - /** - * Return the package name. - * - * @return String of package name. - */ - public String getPackageName() { - return mPackageName; - } - - /** - * Return the signature hashes of the browser application. - * - * @return Set of String - */ - public Set getSignatureHashes() { - return mSignatureHashes; - } - - /** - * Return the version of the browser application. - * - * @return String of the version - */ - public String getVersion() { - return mVersion; - } - - /** - * Generates a set of SHA-512, Base64 url-safe encoded signature hashes from the provided - * array of signatures. - */ - @NonNull - public static Set generateSignatureHashes(@NonNull Signature[] signatures) { - Set signatureHashes = new HashSet<>(); - for (Signature signature : signatures) { - try { - MessageDigest digest = MessageDigest.getInstance(DIGEST_SHA_512); - byte[] hashBytes = digest.digest(signature.toByteArray()); - signatureHashes.add(Base64.encodeToString(hashBytes, Base64.URL_SAFE | Base64.NO_WRAP)); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalStateException( - "Platform does not support" + DIGEST_SHA_512 + " hashing"); - } - } - - return signatureHashes; - } - - public boolean isCustomTabsServiceSupported() { - return mIsCustomTabsServiceSupported; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || !(obj instanceof Browser)) { - return false; - } - - final Browser other = (Browser) obj; - return mPackageName.equals(other.getPackageName()) - && mVersion.equals(other.getVersion()) - && mSignatureHashes.equals(other.getSignatureHashes()); - } - - @Override - public int hashCode() { - int hash = mPackageName.hashCode(); - - hash = PRIME_HASH_FACTOR * hash + mVersion.hashCode(); - hash = PRIME_HASH_FACTOR * hash + (mIsCustomTabsServiceSupported ? 1 : 0); - - for (String signatureHash : mSignatureHashes) { - hash = PRIME_HASH_FACTOR * hash + signatureHash.hashCode(); - } - - return hash; - } -} \ No newline at end of file From 4691ad6b0952b771bedad263240e1c188fb4cabe Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Thu, 2 Jan 2025 21:08:02 -0800 Subject: [PATCH 04/10] add noopBrowserSelector --- .../java/browser/NoopBrowserSelector.kt | 44 +++++++++++++++++++ .../MockPlatformComponentsFactory.java | 14 +----- 2 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt new file mode 100644 index 0000000000..62898f696d --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt @@ -0,0 +1,44 @@ +// 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.browser + +import com.microsoft.identity.common.java.ui.BrowserDescriptor + +/** + * Noop implementation of [IBrowserSelector]. + */ +class NoopBrowserSelector : IBrowserSelector { + + /** + * Selects a valid installed browser from the list of safe browsers. + * If no browser is present in the list of safe browser, null is returned. + * + * @param browserSafeList The list of browsers to choose from. + * @param preferredBrowserDescriptor The preferred browser descriptor. + * @return The selected browser. + */ + override fun select( + browserSafeList: MutableList, + preferredBrowserDescriptor: BrowserDescriptor? + ) = null +} diff --git a/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java b/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java index 22198de78b..44ca5bae31 100644 --- a/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java +++ b/common4j/src/testFixtures/java/com/microsoft/identity/common/components/MockPlatformComponentsFactory.java @@ -23,8 +23,8 @@ package com.microsoft.identity.common.components; import com.microsoft.identity.common.java.WarningType; -import com.microsoft.identity.common.java.browser.Browser; import com.microsoft.identity.common.java.browser.IBrowserSelector; +import com.microsoft.identity.common.java.browser.NoopBrowserSelector; import com.microsoft.identity.common.java.commands.ICommand; import com.microsoft.identity.common.java.crypto.IDevicePopManager; import com.microsoft.identity.common.java.exception.ClientException; @@ -33,7 +33,6 @@ import com.microsoft.identity.common.java.net.DefaultHttpClientWrapper; import com.microsoft.identity.common.java.providers.oauth2.IStateGenerator; import com.microsoft.identity.common.java.strategies.IAuthorizationStrategyFactory; -import com.microsoft.identity.common.java.ui.BrowserDescriptor; import com.microsoft.identity.common.java.util.IBroadcaster; import com.microsoft.identity.common.java.util.IClockSkewManager; import com.microsoft.identity.common.java.util.IPlatformUtil; @@ -191,14 +190,5 @@ public List> updateWithAndGetPlatformSpecificExtraQuer } }; - public static final IBrowserSelector NON_FUNCTIONAL_BROWSER_SELECTOR = new IBrowserSelector() { - - @Nullable - @Override - public Browser select(@NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor) { - return null; - } - }; - - + public static final IBrowserSelector NON_FUNCTIONAL_BROWSER_SELECTOR = new NoopBrowserSelector(); } From 1e624de0b1cdd623b86c690d13681cc3e54311ca Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Thu, 2 Jan 2025 22:34:02 -0800 Subject: [PATCH 05/10] Convert to Java to maintain compatibility with older tests --- ...erSelector.kt => NoopBrowserSelector.java} | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) rename common4j/src/main/com/microsoft/identity/common/java/browser/{NoopBrowserSelector.kt => NoopBrowserSelector.java} (68%) diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java similarity index 68% rename from common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt rename to common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java index 62898f696d..5db81e8619 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java @@ -20,25 +20,28 @@ // 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.browser +package com.microsoft.identity.common.java.browser; -import com.microsoft.identity.common.java.ui.BrowserDescriptor +import com.microsoft.identity.common.java.ui.BrowserDescriptor; -/** - * Noop implementation of [IBrowserSelector]. - */ -class NoopBrowserSelector : IBrowserSelector { +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +public class NoopBrowserSelector implements IBrowserSelector { /** * Selects a valid installed browser from the list of safe browsers. * If no browser is present in the list of safe browser, null is returned. * - * @param browserSafeList The list of browsers to choose from. - * @param preferredBrowserDescriptor The preferred browser descriptor. + * @param browserSafeList The list of browsers to choose from. + * @param preferredBrowserDescriptor The preferred browser descriptor. * @return The selected browser. */ - override fun select( - browserSafeList: MutableList, - preferredBrowserDescriptor: BrowserDescriptor? - ) = null + @Nullable + @Override + public Browser select(@NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor) { + return null; + } } From 859d6772076fb8f458241df4ee0a354dbddcfbee Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Fri, 3 Jan 2025 13:18:03 -0800 Subject: [PATCH 06/10] update changelog --- changelog.txt | 1 + .../internal/ui/AndroidAuthorizationStrategyFactory.java | 6 +++--- .../java/strategies/IAuthorizationStrategyFactory.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6a1f88ac14..5aa5e609ec 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ vNext ---------- +- [MINOR] Organize browser selection classes and change signature for get AuthorizationStrategy (#2564) - [MINOR] Add Sign in With Google component for MSA federation (#2551) - [MINOR] Add SDMBroadcastReceiver for applications to register callbacks for SDM broadcasts (#2547) - [MINOR] Add switch_browser toMicrosoftStsAuthorizationRequest (#2550) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java index 35ddf33421..7311772c96 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java @@ -59,7 +59,7 @@ public class AndroidAuthorizationStrategyFactory implements IAuthorizationStrate * * @param authorizationAgent The authorization agent provided by the caller. * @param browser The browser to use for authorization. - * @param isBrowserRequest True if the request is from browser. + * @param isBrokerRequest True if the request is from broker. * @return The authorization strategy. */ @Override @@ -67,7 +67,7 @@ public class AndroidAuthorizationStrategyFactory implements IAuthorizationStrate public IAuthorizationStrategy getAuthorizationStrategy( @NonNull final AuthorizationAgent authorizationAgent, @Nullable final Browser browser, - final boolean isBrowserRequest) { + final boolean isBrokerRequest) { final String methodTag = TAG + ":getAuthorizationStrategy"; // Use embedded webView if no browser available or authorization agent is webView @@ -77,7 +77,7 @@ public IAuthorizationStrategy getAuthorizationStrategy( } Logger.info(methodTag, "Browser authorization, browser: " + browser); - return getBrowserAuthorizationStrategy(browser, isBrowserRequest); + return getBrowserAuthorizationStrategy(browser, isBrokerRequest); } /** diff --git a/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java b/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java index 6c20078b0e..708241d98c 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java +++ b/common4j/src/main/com/microsoft/identity/common/java/strategies/IAuthorizationStrategyFactory.java @@ -36,6 +36,6 @@ public interface IAuthorizationStrategyFactory Date: Fri, 3 Jan 2025 18:03:19 -0800 Subject: [PATCH 07/10] remove unused strings in ErrorString --- .../common/java/exception/ErrorStrings.java | 101 ------------------ 1 file changed, 101 deletions(-) diff --git a/common4j/src/main/com/microsoft/identity/common/java/exception/ErrorStrings.java b/common4j/src/main/com/microsoft/identity/common/java/exception/ErrorStrings.java index 9909808d14..3d2378bf07 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/exception/ErrorStrings.java +++ b/common4j/src/main/com/microsoft/identity/common/java/exception/ErrorStrings.java @@ -101,11 +101,6 @@ private ErrorStrings() { */ public static final String STATE_MISMATCH = "state_mismatch"; - /** - * The intent to launch Activity is not resolvable by the OS or the intent doesn't contain the required data. - */ - public static final String UNRESOLVABLE_INTENT = "unresolvable_intent"; - /** * Unsupported url, cannot perform adfs authority validation. */ @@ -133,12 +128,6 @@ private ErrorStrings() { */ public static final String DUPLICATE_QUERY_PARAMETER = "duplicate_query_parameter"; - /** - * Temporary non-exposed error code to indicate that ADFS authority validation fails. ADFS as authority is not supported - * for preview. - */ - static final String ADFS_AUTHORITY_VALIDATION_FAILED = "adfs_authority_validation_failed"; - /** * Failed to unwrap with the android keystore. */ @@ -225,31 +214,11 @@ private ErrorStrings() { */ public static final String CERTIFICATE_ENCODING_ERROR = "Certificate encoding is not generated"; - /** - * Key Chain private key exception. - */ - public static final String KEY_CHAIN_PRIVATE_KEY_EXCEPTION = "Key Chain private key exception"; - - /** - * Key Chain public key exception. - */ - public static final String KEY_CHAIN_PUBLIC_KEY_EXCEPTION = "Key Chain public key exception"; - - /** - * Key Chain certificate exception. - */ - public static final String KEY_CHAIN_CERTIFICATE_EXCEPTION = "Key Chain certificate exception"; - /** * Signature exception. */ public static final String SIGNATURE_EXCEPTION = "Signature exception"; - /** - * Device certificate API has exception. - */ - public static final String DEVICE_CERTIFICATE_API_EXCEPTION = "Device certificate API has exception"; - /** * The redirectUri for broker is invalid. */ @@ -285,46 +254,15 @@ private ErrorStrings() { */ public static final String BROKER_VERIFICATION_FAILED = "Signature could not be verified"; - /** - * The broker app is not responding. - */ - public static final String BROKER_APP_NOT_RESPONDING = "Broker application is not responding"; - /** * Failed to bind the service in broker app. */ public static final String BROKER_BIND_SERVICE_FAILED = "Failed to bind the service in broker app"; - /** - * Could not retrieve capabilities from broker apps. - */ - public static final String FAILED_TO_GET_CAPABILITIES = "Could not get the capabilities"; - - /** - * Empty Context. - */ - public static final String ANDROID_CONTEXT_IS_NULL = "Android Context is null."; - /** * Empty Authorization Intent. */ public static final String AUTHORIZATION_INTENT_IS_NULL = "Authorization intent is null."; - - /** - * No available browser installed on the device. - */ - public static final String NO_AVAILABLE_BROWSER_FOUND = "No available browser installed on the device."; - - /** - * Refresh token request failed. - */ - public static final String AUTH_REFRESH_FAILED = "Refresh token request failed"; - - /** - * STK patching failed. - */ - public static final String STK_PATCHING_FAILED = "STK patching failed"; - /** * Primary refresh token request failed. */ @@ -335,16 +273,6 @@ private ErrorStrings() { */ public static final String INVALID_BROKER_REFRESH_TOKEN = "Broker refresh token is invalid"; - /** - * Device registration data is missing in a flow that expects it. - */ - public static final String DEVICE_REGISTRATION_MISSING_FROM_CLIENT = "Device registration data not found."; - - /** - * Failed to retreive device state. - */ - public static final String ERROR_RETRIEVING_DEVICE_STATE = "Error retrieving device state"; - /** * Device registration failed. */ @@ -397,11 +325,6 @@ private ErrorStrings() { */ public static final String KEY_NOT_FOUND = "key_not_found"; - /** - * Restore MSA account using transfer token failed. - */ - public static final String RESTORE_MSA_ACCOUNT_WITH_TRANSFER_TOKEN = "restore_msa_accounts_failed"; - /** * AccountMode in configuration is set to multiple. However, the device is marked as shared (which requires single account mode). */ @@ -434,12 +357,6 @@ private ErrorStrings() { public static final String SINGLE_ACCOUNT_PCA_INIT_FAIL_UNKNOWN_REASON_ERROR_CODE = "single_account_pca_init_fail_unknown_reason"; public static final String SINGLE_ACCOUNT_PCA_INIT_FAIL_UNKNOWN_REASON_ERROR_MESSAGE = "A single account public client application could not be created for unknown reasons."; - /** - * A Native Auth public client application could not be created for unknown reasons. - */ - public static final String NATIVE_AUTH_PCA_INIT_FAIL_UNKNOWN_REASON_ERROR_CODE = "native_auth_pca_init_fail_unknown_reason"; - public static final String NATIVE_AUTH_PCA_INIT_FAIL_UNKNOWN_REASON_ERROR_MESSAGE = "A native auth public client application could not be created for unknown reasons."; - /** * Some or all requested scopes where declined by the server. Developer should decide whether to continue * authentication with the granted scopes or end the authentication process. @@ -454,13 +371,6 @@ private ErrorStrings() { public static final String REGISTERED_SHARED_DEVICE_DELETED_ON_SERVER_ERROR_CODE = "registered_shared_device_deleted_on_server"; - /** - * The device is registered with precreate/userless WPJ, and its registration was deleted by the admin. - * This is an irrecoverable error, and the admin has to re-prep the device. - * */ - public static final String USERLESS_DEVICE_DELETED_ON_SERVER_ERROR_CODE = - "userless_device_deleted_on_server"; - public static final String DEVICE_DELETED_ON_SERVER_IRRECOVERABLE_ERROR_MESSAGE = "This device was deleted from the tenant. " + "This is an irrecoverable error. Only tenant administrator can re-register this device."; @@ -473,12 +383,6 @@ private ErrorStrings() { "Please make sure to use your organizational account. " + "If that doesn’t help, please return the device to your administrator."; - /** - * Home tenant of the BRT acccount doesn't match with WPJ account's UPN. - */ - public static final String BRT_USER_MISMATCH_ERROR_MESSAGE = - "The signed in user doesn't match with the user this device is registered to."; - /** * Device Code Flow only. * Device Code Flow (DCF) is not supported in broker exception @@ -531,11 +435,6 @@ private ErrorStrings() { */ public final static String DEVICE_CODE_FLOW_DEFAULT_ERROR_MESSAGE = "Device Code Flow has failed with an unexpected error. The error code shown was received from the result object."; - /** - * Access token doesn't exist and is required for NativeAuth token refresh - */ - public static final String NATIVE_AUTH_NO_ACCESS_TOKEN_FOUND = "native_auth_no_access_token_found"; - /** * Unexpected content type received in http response. */ From db8b6087c2854a38fe6b94413dd3f6431c6dfba4 Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Fri, 3 Jan 2025 18:10:42 -0800 Subject: [PATCH 08/10] nit --- .../internal/ui/AndroidAuthorizationStrategyFactory.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java index 7311772c96..f6ad09b6bd 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java @@ -113,9 +113,6 @@ private IAuthorizationStrategy getBrowserAuthorizationStrategy(@NonNull final Br * @return The embedded web view authorization strategy. */ private IAuthorizationStrategy getGenericAuthorizationStrategy() { - return new EmbeddedWebViewAuthorizationStrategy( - mContext, - mActivity, - mFragment); + return new EmbeddedWebViewAuthorizationStrategy(mContext, mActivity, mFragment); } } From 40727d98e5ba6f2cf4bc9cf9247dcb844fa75ac4 Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Fri, 3 Jan 2025 18:27:09 -0800 Subject: [PATCH 09/10] nits --- .../common/internal/controllers/LocalMSALController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java index d732572760..bef1c61b89 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java @@ -22,7 +22,6 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.controllers; -import android.content.pm.PackageManager; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -32,7 +31,6 @@ import com.microsoft.identity.common.internal.telemetry.Telemetry; import com.microsoft.identity.common.internal.telemetry.events.ApiEndEvent; import com.microsoft.identity.common.internal.telemetry.events.ApiStartEvent; -import com.microsoft.identity.common.internal.ui.browser.BrowserSelector; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.authorities.Authority; import com.microsoft.identity.common.java.authscheme.AbstractAuthenticationScheme; From 6aabd68907555d4ff70f6b732213c1c6bfc3e2f2 Mon Sep 17 00:00:00 2001 From: pedro romero vargas Date: Tue, 7 Jan 2025 16:51:23 -0800 Subject: [PATCH 10/10] Addressing comments --- .../AndroidPlatformComponentsFactory.java | 4 +- .../controllers/LocalMSALController.java | 2 +- .../AndroidAuthorizationStrategyFactory.java | 6 +- ...rrentTaskBrowserAuthorizationStrategy.java | 6 +- ...ector.java => AndroidBrowserSelector.java} | 19 +++--- .../internal/ui/browser/BrowserBlocklist.java | 62 ------------------- ...t.java => AndroidBrowserSelectorTest.java} | 32 +++++----- .../common/java/browser/IBrowserSelector.java | 2 +- .../java/browser/NoopBrowserSelector.java | 5 +- .../common/java/ui/BrowserDescriptor.java | 4 +- 10 files changed, 42 insertions(+), 100 deletions(-) rename common/src/main/java/com/microsoft/identity/common/internal/ui/browser/{BrowserSelector.java => AndroidBrowserSelector.java} (95%) delete mode 100644 common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java rename common/src/test/java/com/microsoft/identity/common/internal/ui/browser/{BrowserSelectorTest.java => AndroidBrowserSelectorTest.java} (89%) diff --git a/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java b/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java index e7529a1d49..74c5b4122e 100644 --- a/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/components/AndroidPlatformComponentsFactory.java @@ -35,7 +35,7 @@ import com.microsoft.identity.common.internal.platform.AndroidPlatformUtil; import com.microsoft.identity.common.internal.providers.oauth2.AndroidTaskStateGenerator; import com.microsoft.identity.common.internal.ui.AndroidAuthorizationStrategyFactory; -import com.microsoft.identity.common.internal.ui.browser.BrowserSelector; +import com.microsoft.identity.common.internal.ui.browser.AndroidBrowserSelector; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.interfaces.IPlatformComponents; import com.microsoft.identity.common.java.interfaces.PlatformComponents; @@ -129,7 +129,7 @@ public static void fillBuilderWithBasicImplementations( new AndroidAuthSdkStorageEncryptionManager(context))) .platformUtil(new AndroidPlatformUtil(context, activity)) .httpClientWrapper(new DefaultHttpClientWrapper()) - .browserSelector(new BrowserSelector(context)); + .browserSelector(new AndroidBrowserSelector(context)); if (activity != null){ builder.authorizationStrategyFactory( diff --git a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java index bef1c61b89..3cfcc8e58b 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java @@ -227,7 +227,7 @@ private AuthorizationResult performAuthorizationRequest(@NonNull final OAuth2Str .getPlatformUtil() .throwIfNetworkNotAvailable(parameters.isPowerOptCheckEnabled()); - final Browser browser = parameters.getPlatformComponents().getBrowserSelector().select( + final Browser browser = parameters.getPlatformComponents().getBrowserSelector().selectBrowser( parameters.getBrowserSafeList(), parameters.getPreferredBrowser() ); diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java index f6ad09b6bd..6b20fe3835 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/AndroidAuthorizationStrategyFactory.java @@ -55,7 +55,10 @@ public class AndroidAuthorizationStrategyFactory implements IAuthorizationStrate private final Fragment mFragment; /** - * Get the authorization strategy. + * Get the authorization strategy based on the authorization agent and browser. + * If the authorization agent is WEBVIEW or the browser is null, + * return the embedded web view authorization strategy. + * Otherwise, return the browser authorization strategy. * * @param authorizationAgent The authorization agent provided by the caller. * @param browser The browser to use for authorization. @@ -70,7 +73,6 @@ public IAuthorizationStrategy getAuthorizationStrategy( final boolean isBrokerRequest) { final String methodTag = TAG + ":getAuthorizationStrategy"; - // Use embedded webView if no browser available or authorization agent is webView if (authorizationAgent == AuthorizationAgent.WEBVIEW || browser == null) { Logger.info(methodTag, "WebView authorization, browser: " + browser); return getGenericAuthorizationStrategy(); diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java index 274dc2a70c..0d13fd8dc2 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/CurrentTaskBrowserAuthorizationStrategy.java @@ -42,9 +42,9 @@ public class CurrentTaskBrowserAuthorizationStrategy< GenericOAuth2Strategy extends OAuth2Strategy, GenericAuthorizationRequest extends AuthorizationRequest> extends BrowserAuthorizationStrategy { - public CurrentTaskBrowserAuthorizationStrategy(@NonNull Context applicationContext, - @NonNull Activity activity, - @Nullable Fragment fragment, + public CurrentTaskBrowserAuthorizationStrategy(@NonNull final Context applicationContext, + @NonNull final Activity activity, + @Nullable final Fragment fragment, @NonNull final Browser browser) { super(applicationContext, activity, fragment, browser); } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelector.java similarity index 95% rename from common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java rename to common/src/main/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelector.java index a01e9a9c30..f471d92332 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelector.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelector.java @@ -52,8 +52,8 @@ import java.util.List; import java.util.Set; -public class BrowserSelector implements IBrowserSelector { - private static final String TAG = BrowserSelector.class.getSimpleName(); +public class AndroidBrowserSelector implements IBrowserSelector { + private static final String TAG = AndroidBrowserSelector.class.getSimpleName(); private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; private static final String DIGEST_SHA_512 = "SHA-512"; @@ -63,7 +63,7 @@ public class BrowserSelector implements IBrowserSelector { private final PackageManager mPackageManager; - public BrowserSelector(@NonNull final Context context) { + public AndroidBrowserSelector(@NonNull final Context context) { mPackageManager = context.getPackageManager(); } @@ -77,7 +77,7 @@ public BrowserSelector(@NonNull final Context context) { */ @Nullable @Override - public Browser select( + public Browser selectBrowser( @NonNull final List browserSafeList, @Nullable final BrowserDescriptor preferredBrowserDescriptor) { final String methodTag = TAG + ":select"; @@ -240,15 +240,14 @@ protected List getBrowsers(@Nullable final BrowserDescriptor preferred */ @NonNull public static Set generateSignatureHashes(@NonNull Signature[] signatures) { - Set signatureHashes = new HashSet<>(); - for (Signature signature : signatures) { + final Set signatureHashes = new HashSet<>(); + for (final Signature signature : signatures) { try { - MessageDigest digest = MessageDigest.getInstance(DIGEST_SHA_512); - byte[] hashBytes = digest.digest(signature.toByteArray()); + final MessageDigest digest = MessageDigest.getInstance(DIGEST_SHA_512); + final byte[] hashBytes = digest.digest(signature.toByteArray()); signatureHashes.add(Base64.encodeToString(hashBytes, Base64.URL_SAFE | Base64.NO_WRAP)); } catch (final NoSuchAlgorithmException e) { - throw new IllegalStateException( - "Platform does not support" + DIGEST_SHA_512 + " hashing"); + throw new IllegalStateException("Platform does not support" + DIGEST_SHA_512 + " hashing"); } } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java deleted file mode 100644 index dff43b8a74..0000000000 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/browser/BrowserBlocklist.java +++ /dev/null @@ -1,62 +0,0 @@ -// 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.browser; - -import androidx.annotation.NonNull; - -import com.microsoft.identity.common.java.browser.Browser; -import com.microsoft.identity.common.logging.Logger; - -import java.util.Arrays; -import java.util.List; - -/** - * A blocked list of browsers. This will reject a match for any browser on the list, and permit - * all others. - */ -public class BrowserBlocklist { - private static final String TAG = BrowserBlocklist.class.getSimpleName(); - private List mBrowsers; - - /** - * Create a block list from given set of browsers. - */ - public BrowserBlocklist(Browser... browsers) { - mBrowsers = Arrays.asList(browsers); - } - - /** - * @return true if the browser is in the block list. - */ - public boolean matches(@NonNull Browser targetBrowser) { - final String methodTag = TAG + ":matches"; - for (Browser browser : mBrowsers) { - if (browser.equals(targetBrowser)) { - Logger.verbose(methodTag, "The target browser is in the block list."); - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java b/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelectorTest.java similarity index 89% rename from common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java rename to common/src/test/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelectorTest.java index bd114d68da..9a02ff1bda 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/BrowserSelectorTest.java +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/browser/AndroidBrowserSelectorTest.java @@ -57,7 +57,7 @@ import java.util.Set; @RunWith(RobolectricTestRunner.class) -public class BrowserSelectorTest { +public class AndroidBrowserSelectorTest { private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; static final Intent BROWSER_INTENT = new Intent( @@ -100,25 +100,25 @@ public class BrowserSelectorTest { //Currently package manager call returns an empty list... failing this test. Needs investigation. //Ignored while updating to latest Mockito version @Test - public void testSelect_getAllBrowser() { + public void testSelect_Browser_getAllBrowser() { setBrowserList(CHROME, FIREFOX); - List allBrowsers = new BrowserSelector(ApplicationProvider.getApplicationContext()).getBrowsers( null); + List allBrowsers = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).getBrowsers( null); assert (allBrowsers.get(0).getPackageName().equals(CHROME.mPackageName)); assert (allBrowsers.get(1).getPackageName().equals(FIREFOX.mPackageName)); } @Test - public void testSelect_noMatchingBrowser() { + public void testSelect_Browser_noMatchingBrowser() { setBrowserList(CHROME, FIREFOX); final List browserSafelist = new ArrayList<>(); - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, null); assertNull(browser); } @Test - public void testSelect_versionNotSupported() { + public void testSelect_Browser_versionNotSupported() { setBrowserList(CHROME, FIREFOX); final List browserSafelist = new ArrayList<>(); @@ -131,12 +131,12 @@ public void testSelect_versionNotSupported() { ); - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, null); assertNull(browser); } @Test - public void testSelect_customTabsNotSupported() { + public void testSelect_Browser_customTabsNotSupported() { setBrowserList(FIREFOX_NO_CUSTOM_TAB); final List browserSafelist = new ArrayList<>(); @@ -147,13 +147,13 @@ public void testSelect_customTabsNotSupported() { null, null) ); - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, null); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, null); assertNotNull(browser); assertEquals(browser.getPackageName(), FIREFOX_NO_CUSTOM_TAB.mPackageName); } @Test - public void testSelect_preferredBrowserSelected() { + public void testSelect_Browser_preferredBrowserSelected() { setBrowserList(CHROME, DOLPHIN, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -178,14 +178,14 @@ public void testSelect_preferredBrowserSelected() { "10", null) ); - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, preferredBrowser); Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); } @Test - public void testSelect_preferredBrowserSelected_preferredBrowserNotInstalled() { + public void testSelect_Browser_preferredBrowserSelected_preferredBrowserNotInstalled() { setBrowserList(CHROME, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -212,12 +212,12 @@ public void testSelect_preferredBrowserSelected_preferredBrowserNotInstalled() { ); // It should return the first installed browser. - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, preferredBrowser); Assert.assertEquals(CHROME.mPackageName, browser.getPackageName()); } @Test - public void testSelect_preferredBrowserSelected_preferredBrowserNotInSafeList() { + public void testSelect_Browser_preferredBrowserSelected_preferredBrowserNotInSafeList() { setBrowserList(CHROME, DOLPHIN, FIREFOX); final BrowserDescriptor preferredBrowser = new BrowserDescriptor( @@ -242,7 +242,7 @@ public void testSelect_preferredBrowserSelected_preferredBrowserNotInSafeList() null) ); // The safe list shouldn't matter, given that we've already specified all info in preferredBrowser's BrowserDescriptor. - final Browser browser = new BrowserSelector(ApplicationProvider.getApplicationContext()).select(browserSafelist, preferredBrowser); + final Browser browser = new AndroidBrowserSelector(ApplicationProvider.getApplicationContext()).selectBrowser(browserSafelist, preferredBrowser); Assert.assertEquals(preferredBrowser.getPackageName(), browser.getPackageName()); Assert.assertEquals(preferredBrowser.getSignatureHashes(), browser.getSignatureHashes()); } @@ -377,7 +377,7 @@ public TestBrowser build() { pi.packageName = mPackageName; pi.versionName = mVersion; - Set signatureHashes = BrowserSelector.generateSignatureHashes(pi.signatures); + Set signatureHashes = AndroidBrowserSelector.generateSignatureHashes(pi.signatures); ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java b/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java index 20a1be360d..33251f6f0f 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/IBrowserSelector.java @@ -43,7 +43,7 @@ public interface IBrowserSelector { * @return The selected browser. */ @Nullable - Browser select( + Browser selectBrowser( @NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor); } diff --git a/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java index 5db81e8619..b963aac117 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java +++ b/common4j/src/main/com/microsoft/identity/common/java/browser/NoopBrowserSelector.java @@ -29,6 +29,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +/** + * A no-op implementation of {@link IBrowserSelector}. + */ public class NoopBrowserSelector implements IBrowserSelector { /** @@ -41,7 +44,7 @@ public class NoopBrowserSelector implements IBrowserSelector { */ @Nullable @Override - public Browser select(@NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor) { + public Browser selectBrowser(@NonNull List browserSafeList, @Nullable BrowserDescriptor preferredBrowserDescriptor) { return null; } } diff --git a/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java b/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java index 4fb013aedc..1a4fd6fc2a 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java +++ b/common4j/src/main/com/microsoft/identity/common/java/ui/BrowserDescriptor.java @@ -101,7 +101,7 @@ static private BrowserDescriptor getBrowserDescriptorForChrome() { * Return a list of BrowserDescriptors that are considered safe for the Switch to browser flow. */ static public List getBrowserSafeListForSwitchBrowser() { - List browserDescriptors = new ArrayList<>(); + final List browserDescriptors = new ArrayList<>(); browserDescriptors.add(getBrowserDescriptorForChrome()); browserDescriptors.add(getBrowserDescriptorForEdge()); return browserDescriptors; @@ -114,7 +114,7 @@ static public List getBrowserSafeListForSwitchBrowser() { * @return List of BrowserDescriptors which are considered safe for the broker. */ static public List getBrowserSafeListForBroker() { - List browserDescriptors = new ArrayList<>(); + final List browserDescriptors = new ArrayList<>(); browserDescriptors.add(getBrowserDescriptorForChrome()); return browserDescriptors; }