diff --git a/changelog.txt b/changelog.txt index 69f62dc1a1..6e9a99be5d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ vNext ---------- +- [MINOR] Pass Work Profile existence, OS Version, and Manufacturer to ESTS (#2627) - [MINOR] Add telemetry for the switch browser protocol (#2612) Version 21.0.0 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 05d732f3c3..2363736614 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 @@ -36,6 +36,7 @@ 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.AndroidBrowserSelector; +import com.microsoft.identity.common.internal.util.WorkProfileUtil; import com.microsoft.identity.common.java.WarningType; import com.microsoft.identity.common.java.interfaces.IPlatformComponents; import com.microsoft.identity.common.java.interfaces.PlatformComponents; @@ -67,6 +68,10 @@ public static synchronized void initializeGlobalStates(@NonNull final Context co if (!sGlobalStateInitalized) { HttpCache.initialize(context); Device.setDeviceMetadata(new AndroidDeviceMetadata()); + + // Denotes whether or not request is from personal profile but device has a Work Profile Available + Device.setIsInPersonalProfileButClouddpcWorkProfileAvailable( + WorkProfileUtil.checkIfIsInPersonalProfileButClouddpcWorkProfileAvailable(context)); Logger.setAndroidLogger(); final File cacheDir = context.getCacheDir(); diff --git a/common/src/main/java/com/microsoft/identity/common/internal/util/WorkProfileUtil.java b/common/src/main/java/com/microsoft/identity/common/internal/util/WorkProfileUtil.java new file mode 100644 index 0000000000..e43f14f222 --- /dev/null +++ b/common/src/main/java/com/microsoft/identity/common/internal/util/WorkProfileUtil.java @@ -0,0 +1,70 @@ +// 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.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.Build; + +import com.microsoft.identity.common.logging.Logger; + +import java.util.List; + +import lombok.NonNull; + +public class WorkProfileUtil { + private static final String TAG = WorkProfileUtil.class.getSimpleName(); + + /** + * Helper method to check if we are in personal profile but a work profile managed by clouddpc + * is available. + * Google Docs for intent used + * @param context context needed to check for intent + * @return true if called in personal profile and a work profile managed by clouddpc exists, false otherwise + */ + public static boolean checkIfIsInPersonalProfileButClouddpcWorkProfileAvailable(@NonNull final Context context) { + try { + final Intent intent = new Intent("com.google.android.apps.work.clouddpc.ACTION_DETECT_WORK_PROFILE"); + final List activities = context.getPackageManager().queryIntentActivities(intent, 0); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return activities.stream() + .anyMatch( + (ResolveInfo resolveInfo) -> resolveInfo.isCrossProfileIntentForwarderActivity()); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return activities.stream() + .anyMatch( + (ResolveInfo resolveInfo) -> resolveInfo.activityInfo.name.equals("com.android.internal.app.ForwardIntentToManagedProfile")); + } + } + + return false; + } catch (Exception e) { + // If we run into exception for any reason, we'll just return false + Logger.warn(TAG, "Received an exception while trying to check if clouddpc work profile is available: " + e.getMessage()); + return false; + } + } +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/platform/Device.java b/common4j/src/main/com/microsoft/identity/common/java/platform/Device.java index 6c3f203704..6dff7a1832 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/platform/Device.java +++ b/common4j/src/main/com/microsoft/identity/common/java/platform/Device.java @@ -50,6 +50,11 @@ public class Device { private static IDeviceMetadata sDeviceMetadata; + /** + * Denotes whether or not request is from personal profile but device has a Work Profile Available + */ + private static boolean sIsInPersonalProfileButClouddpcWorkProfileAvailable = false; + private static final ReentrantReadWriteLock sLock = new ReentrantReadWriteLock(); @GuardedBy("sLock") @@ -73,6 +78,26 @@ public static void clearDeviceMetadata(){ } } + @GuardedBy("sLock") + public static void setIsInPersonalProfileButClouddpcWorkProfileAvailable(final boolean isWorkProfileAvailable) { + sLock.writeLock().lock(); + try { + sIsInPersonalProfileButClouddpcWorkProfileAvailable = isWorkProfileAvailable; + } finally { + sLock.writeLock().unlock(); + } + } + + @GuardedBy("sLock") + public static boolean isInPersonalProfileButClouddpcWorkProfileAvailable() { + sLock.readLock().lock(); + try { + return sIsInPersonalProfileButClouddpcWorkProfileAvailable; + } finally { + sLock.readLock().unlock(); + } + } + @NonNull @GuardedBy("sLock") public static Map getPlatformIdParameters() { @@ -84,10 +109,12 @@ public static Map getPlatformIdParameters() { platformParameters.put(PlatformIdParameters.CPU_PLATFORM, sDeviceMetadata.getCpu()); platformParameters.put(PlatformIdParameters.OS, sDeviceMetadata.getOsForEsts()); platformParameters.put(PlatformIdParameters.DEVICE_MODEL, sDeviceMetadata.getDeviceModel()); + platformParameters.put(PlatformIdParameters.MANUFACTURER, sDeviceMetadata.getManufacturer()); } else { platformParameters.put(PlatformIdParameters.CPU_PLATFORM, NOT_SET); platformParameters.put(PlatformIdParameters.OS, NOT_SET); platformParameters.put(PlatformIdParameters.DEVICE_MODEL, NOT_SET); + platformParameters.put(PlatformIdParameters.MANUFACTURER, NOT_SET); } return Collections.unmodifiableMap(platformParameters); @@ -246,6 +273,11 @@ public static final class PlatformIdParameters { */ public static final String OS = "x-client-OS"; + /** + * The String representing the device Manufacturer. + */ + public static final String MANUFACTURER = "x-client-MN"; + /** * The String representing the device model. */ diff --git a/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequest.java b/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequest.java index 57488a5c67..84cba2bd8f 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequest.java +++ b/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequest.java @@ -123,6 +123,12 @@ public abstract class MicrosoftAuthorizationRequest> extends AuthorizationRequest.Builder { diff --git a/common4j/src/test/com/microsoft/identity/common/java/platform/DeviceTest.java b/common4j/src/test/com/microsoft/identity/common/java/platform/DeviceTest.java index 728e26e513..a2a90616b1 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/platform/DeviceTest.java +++ b/common4j/src/test/com/microsoft/identity/common/java/platform/DeviceTest.java @@ -51,10 +51,11 @@ public void tearDown() { public void testGetDataWhenMetadataIsNotSet(){ // Shouldn't crash. final Map platformParameter = Device.getPlatformIdParameters(); - Assert.assertEquals(3, platformParameter.size()); + Assert.assertEquals(4, platformParameter.size()); Assert.assertEquals(NOT_SET, platformParameter.get(Device.PlatformIdParameters.CPU_PLATFORM)); Assert.assertEquals(NOT_SET, platformParameter.get(Device.PlatformIdParameters.DEVICE_MODEL)); Assert.assertEquals(NOT_SET, platformParameter.get(Device.PlatformIdParameters.OS)); + Assert.assertEquals(NOT_SET, platformParameter.get(Device.PlatformIdParameters.MANUFACTURER)); Assert.assertEquals(NOT_SET, Device.getManufacturer()); Assert.assertEquals(NOT_SET, Device.getModel()); @@ -66,10 +67,11 @@ public void testGetPlatformIdParameters(){ Device.setDeviceMetadata(new MockDeviceMetadata()); final Map platformParameter = Device.getPlatformIdParameters(); - Assert.assertEquals(3, platformParameter.size()); + Assert.assertEquals(4, platformParameter.size()); Assert.assertEquals(MockDeviceMetadata.TEST_CPU, platformParameter.get(Device.PlatformIdParameters.CPU_PLATFORM)); Assert.assertEquals(MockDeviceMetadata.TEST_DEVICE_MODEL, platformParameter.get(Device.PlatformIdParameters.DEVICE_MODEL)); Assert.assertEquals(MockDeviceMetadata.TEST_OS_ESTS, platformParameter.get(Device.PlatformIdParameters.OS)); + Assert.assertEquals(MockDeviceMetadata.TEST_MANUFACTURER, platformParameter.get(Device.PlatformIdParameters.MANUFACTURER)); } @Test diff --git a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequestTest.java b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequestTest.java index eb28ae5cf2..e7d94e54fb 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequestTest.java +++ b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/MicrosoftAuthorizationRequestTest.java @@ -49,6 +49,7 @@ public class MicrosoftAuthorizationRequestTest { @After public void tearDown() { Device.clearDeviceMetadata(); + Device.setIsInPersonalProfileButClouddpcWorkProfileAvailable(false); } public static final String MOCK_AUTHORITY = "http://mock_authority"; @@ -85,13 +86,51 @@ public void testCreateUriFromAuthorizationRequest() throws MalformedURLException "&x-client-OS=" + MockDeviceMetadata.TEST_OS_ESTS + "&x-client-CPU=" + MockDeviceMetadata.TEST_CPU + "&x-client-DM=" + MockDeviceMetadata.TEST_DEVICE_MODEL + + "&x-client-MN=" + MockDeviceMetadata.TEST_MANUFACTURER + "&instance_aware=" + MOCK_MULTIPLE_CLOUD_AWARE + + "&x-client-WPAvailable=false" + // Base class fields start here. "&response_type=code" + "&state=" + MOCK_STATE_ENCODED, request.getAuthorizationRequestAsHttpRequest().toString()); } + @Test + public void testCreateUriFromAuthorizationRequestWithWPAvailable() throws MalformedURLException, ClientException { + Device.setDeviceMetadata(new MockDeviceMetadata()); + + Device.setIsInPersonalProfileButClouddpcWorkProfileAvailable(true); + + final MockMicrosoftAuthorizationRequest request = new MockMicrosoftAuthorizationRequest.Builder() + .setAuthority(new URL(MOCK_AUTHORITY)) + .setLibraryVersion(MOCK_LIBRARY_VERSION) + .setLibraryName(MOCK_LIBRARY_NAME) + .setMultipleCloudAware(MOCK_MULTIPLE_CLOUD_AWARE) + .setCorrelationId(MOCK_CORRELATION_ID) + .setLoginHint(MOCK_LOGIN_HINT) + .setPkceChallenge(MOCK_PKCE_CHALLENGE) + .setState(MOCK_STATE) + .build(); + + Assert.assertEquals(MockAuthorizationRequest.MOCK_AUTH_ENDPOINT + + "?login_hint=" + MOCK_LOGIN_HINT + + "&client-request-id=" + MOCK_CORRELATION_ID + + "&code_challenge=" + MOCK_PKCE_CHALLENGE.getCodeChallenge() + + "&code_challenge_method=" + MOCK_PKCE_CHALLENGE.getCodeChallengeMethod() + + "&x-client-Ver=" + MOCK_LIBRARY_VERSION + + "&x-client-SKU=" + MOCK_LIBRARY_NAME + + "&x-client-OS=" + MockDeviceMetadata.TEST_OS_ESTS + + "&x-client-CPU=" + MockDeviceMetadata.TEST_CPU + + "&x-client-DM=" + MockDeviceMetadata.TEST_DEVICE_MODEL + + "&x-client-MN=" + MockDeviceMetadata.TEST_MANUFACTURER + + "&instance_aware=" + MOCK_MULTIPLE_CLOUD_AWARE + + "&x-client-WPAvailable=true" + + // Base class fields start here. + "&response_type=code" + + "&state=" + MOCK_STATE_ENCODED, + request.getAuthorizationRequestAsHttpRequest().toString()); + } + // If state is not provided, MicrosoftAuthorizationRequest should generate a default one. @Test public void testDefaultStateGenerated(){ diff --git a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsAuthorizationRequestTests.java b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsAuthorizationRequestTests.java index affdc4a404..115ff5e609 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsAuthorizationRequestTests.java +++ b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsAuthorizationRequestTests.java @@ -95,6 +95,7 @@ public class MicrosoftStsAuthorizationRequestTests { @After public void tearDown() { Device.clearDeviceMetadata(); + Device.setIsInPersonalProfileButClouddpcWorkProfileAvailable(false); } static URL getValidRequestUrl() throws MalformedURLException { @@ -139,6 +140,61 @@ public void testCreateUriFromAuthorizationRequest() throws MalformedURLException "&x-client-OS=" + MockDeviceMetadata.TEST_OS_ESTS + "&x-client-CPU=" + MockDeviceMetadata.TEST_CPU + "&x-client-DM=" + MockDeviceMetadata.TEST_DEVICE_MODEL + + "&x-client-MN=" + MockDeviceMetadata.TEST_MANUFACTURER + + "&x-client-WPAvailable=false" + + "&response_type=code" + + "&client_id=" + DEFAULT_TEST_CLIENT_ID + + "&redirect_uri=" + DEFAULT_TEST_REDIRECT_URI_ENCODED + + "&state=" + MOCK_STATE_ENCODED + + "&scope=" + DEFAULT_TEST_SCOPE_ENCODED + + "&" + MOCK_FLIGHT_QUERY_1 + "=" + MOCK_FLIGHT_VALUE_1 + + "&" + MOCK_FLIGHT_QUERY_2 + "=" + MOCK_FLIGHT_VALUE_2 + + "&slice=" + DEFAULT_TEST_SLICE_PARAMETER + + "&dc=" + DEFAULT_TEST_DATA_CENTER, + request.getAuthorizationRequestAsHttpRequest().toString()); + + Assert.assertEquals(DEFAULT_TEST_DISPLAYABLEID, request.getDisplayableId()); + } + + @Test + public void testCreateUriFromAuthorizationRequestWithWPAvailable() throws MalformedURLException, URISyntaxException, ClientException { + Device.setDeviceMetadata(new MockDeviceMetadata()); + Device.setIsInPersonalProfileButClouddpcWorkProfileAvailable(true); + + final MicrosoftStsAuthorizationRequest request = new MicrosoftStsAuthorizationRequest.Builder() + .setPrompt(DEFAULT_TEST_PROMPT) + .setUid(DEFAULT_TEST_UID) + .setUtid(DEFAULT_TEST_UTID) + .setInstalledCompanyPortalVersion(TEST_CP_VERSION) + .setSlice(DEFAULT_TEST_SLICE) + .setFlightParameters(DEFAULT_FLIGHT_PARAMETER) + .setDisplayableId(DEFAULT_TEST_DISPLAYABLEID) + + // Values from base class. + .setCorrelationId(DEFAULT_TEST_CORRELATION_ID) + .setPkceChallenge(MOCK_PKCE_CHALLENGE) + .setAuthority(getValidRequestUrl()) + + .setClientId(DEFAULT_TEST_CLIENT_ID) + .setRedirectUri(DEFAULT_TEST_REDIRECT_URI) + .setState(MOCK_STATE) + .setScope(DEFAULT_TEST_SCOPE) + .build(); + + Assert.assertEquals(DEFAULT_TEST_AUTHORIZATION_ENDPOINT + + "?prompt=" + DEFAULT_TEST_PROMPT + + "&login_req=" + DEFAULT_TEST_UID + + "&domain_req=" + DEFAULT_TEST_UTID + + "&cpVersion=" + TEST_CP_VERSION + + // Base class fields start here. + "&client-request-id=" + DEFAULT_TEST_CORRELATION_ID + + "&code_challenge=" + MOCK_PKCE_CHALLENGE.getCodeChallenge() + + "&code_challenge_method=" + MOCK_PKCE_CHALLENGE.getCodeChallengeMethod() + + "&x-client-OS=" + MockDeviceMetadata.TEST_OS_ESTS + + "&x-client-CPU=" + MockDeviceMetadata.TEST_CPU + + "&x-client-DM=" + MockDeviceMetadata.TEST_DEVICE_MODEL + + "&x-client-MN=" + MockDeviceMetadata.TEST_MANUFACTURER + + "&x-client-WPAvailable=true" + "&response_type=code" + "&client_id=" + DEFAULT_TEST_CLIENT_ID + "&redirect_uri=" + DEFAULT_TEST_REDIRECT_URI_ENCODED +