Skip to content

Commit

Permalink
isQrPinAvailable API (#2219)
Browse files Browse the repository at this point in the history
## Why?
To determine if QR + PIN authorization is available for MDM devices,
Teams app will call this API.
if the result is true: Teams will show a button to login using QR + PIN,
if the result is false: Teams will not show this button.

## How does this API works?
The QR + PIN feature will be configured by the administrator through app
configuration policies for managed Android Enterprise devices.
https://learn.microsoft.com/en-us/mem/intune/apps/app-configuration-policies-use-android
Where the administrator can enable/disable this type of authorization
for all managed devices, by seating this key "isQrPinAvailable" to true
in the Authenticator app.

On Android we have 3 apps which can be the broker, Authenticator, CP
(Company Portal) and LTW (Link to Windows). which makes things extremely
complex.
To keep the configuration simple for the tenant's administrator, we
propose to use the managed configurations of the Authenticator app to
read/set the admin flag. Meaning that authenticator app must be
installed on Android devices to use the QR + PIN feature.

![qrpinapi
drawio2](https://github.com/AzureAD/microsoft-authentication-library-common-for-android/assets/76129899/fdc414af-b66b-40b8-a03b-a256d0d614ad)

[Client design
doc](https://microsoft-my.sharepoint-df.com/:w:/p/mipetriu/EWbcvu99HRxEgy-BC9jilKoBnqIr3yCJaNPZMlGSG5c3ow?e=aP0ey1)

## Changes

- Add constants for API
- Add BaseCommand for isQrPinAvailable, used by BrokerMsal API
- Add behavior for LocalMsalController (not supported)
BrokerMsalController and (supported, call the broker)

## Related PR
AzureAD/ad-accounts-for-android#2587

AzureAD/microsoft-authentication-library-for-android#1931
  • Loading branch information
p3dr0rv authored Jan 17, 2024
1 parent 722227e commit 389106f
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
V.Next
---------
- [MINOR] Add IsQrPinAvailableCommand, controllers behavior and define constants for the isQrAvailable API (#2219)
- [MINOR] Add PreferredAuthMethod to interactive token flow (#2245)
- [MINOR] Implement updates of the native auth web API (#2261)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,10 @@ public GenerateShrResult generateSignedHttpRequest(GenerateShrCommandParameters
return null;
}

@Override
public boolean isQrPinAvailable() throws Exception {
return false;
}
}

private static CommandParameters getEmptyTestParams() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,11 @@ public static String computeMaxHostBrokerProtocol() {
*/
public static final String BROKER_GENERATE_SHR_RESULT = "broker_generate_shr_result";

/**
* String for QR + PIN result.
*/
public static final String IS_QR_PIN_AVAILABLE = "is_qr_pin_available";

/**
* String to return a true if the request succeeded, false otherwise.
*/
Expand Down Expand Up @@ -1556,7 +1561,9 @@ public enum API {
BROKER_DISCOVERY_FROM_SDK(BROKER_DISCOVERY_FROM_SDK_PATH, null, null),
BROKER_DISCOVERY_SET_ACTIVE_BROKER(BROKER_DISCOVERY_SET_ACTIVE_BROKER_PATH, null, null),
PASSTHROUGH(PASSTHROUGH_PATH, null, null),
BROKER_INDIVIDUAL_LOGS_UPLOAD(BROKER_INDIVIDUAL_LOGS_UPLOAD_PATH, null, null),;
READ_RESTRICTIONS_MANAGER(READ_RESTRICTIONS_MANAGER_PATH, null, null),
IS_QR_PIN_AVAILABLE(IS_QR_PIN_AVAILABLE_PATH, null, null),
BROKER_INDIVIDUAL_LOGS_UPLOAD(BROKER_INDIVIDUAL_LOGS_UPLOAD_PATH, null, null);

/**
* The content provider path that the API exists behind.
Expand Down Expand Up @@ -1690,6 +1697,16 @@ public enum API {
*/
public static final String GET_SSO_TOKEN_PATH = "/ssoToken";

/**
* ContentProvider path to check if QR + PIN should de available.
*/
public static final String IS_QR_PIN_AVAILABLE_PATH = "/isQrPinAvailable";

/**
* ContentProvider path to read the restrictions manager.
*/
public static final String READ_RESTRICTIONS_MANAGER_PATH = "/readRestrictionsManager";

/**
* Broker api path constant for execute device registration protocols.
* Note: The path was updated because in release 9.0.1 a part of the NEW WPJ API was exposed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ abstract class AbstractIpcStrategyWithServiceValidation(
*/
abstract fun isSupportedByTargetedBroker(targetedBrokerPackageName: String): Boolean

@Throws(BrokerCommunicationException::class)
override fun communicateToBroker(bundle: BrokerOperationBundle): Bundle? {
val methodTag = "$TAG:communicateToBroker"
if (!shouldBypassSupportValidation && !isSupportedByTargetedBroker(bundle.targetBrokerAppPackageName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public enum Operation {
BROKER_DISCOVERY_FROM_SDK(API.BROKER_DISCOVERY_FROM_SDK, null),
BROKER_DISCOVERY_SET_ACTIVE_BROKER(API.BROKER_DISCOVERY_SET_ACTIVE_BROKER, null),
PASSTHROUGH(API.PASSTHROUGH, null),
BROKER_READ_RESTRICTIONS_MANAGER(API.READ_RESTRICTIONS_MANAGER, null),
MSAL_IS_QR_PIN_AVAILABLE(API.IS_QR_PIN_AVAILABLE, null),
BROKER_INDIVIDUAL_LOGS_UPLOAD(API.BROKER_INDIVIDUAL_LOGS_UPLOAD, null);

final API mContentApi;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.commands

import com.microsoft.identity.common.java.commands.BaseCommand
import com.microsoft.identity.common.java.commands.CommandCallback
import com.microsoft.identity.common.java.commands.parameters.CommandParameters
import com.microsoft.identity.common.java.controllers.BaseController
import lombok.EqualsAndHashCode

/**
* Command class to call controllers to check if QR code + PIN authorization is available.
* {@see com.microsoft.identity.common.java.controllers.CommandDispatcher}.
*/
@EqualsAndHashCode(callSuper = true)
class IsQrPinAvailableCommand(
parameters: CommandParameters,
controller: BaseController,
callback: CommandCallback<*, *>,
publicApiId: String
) : BaseCommand<Boolean?>(parameters, controller, callback, publicApiId) {

override fun execute(): Boolean {
return defaultController.isQrPinAvailable
}

override fun isEligibleForEstsTelemetry(): Boolean {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_GET_CURRENT_ACCOUNT_IN_SHARED_DEVICE;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_GET_DEVICE_MODE;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_GET_INTENT_FOR_INTERACTIVE_REQUEST;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_IS_QR_PIN_AVAILABLE;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_REMOVE_ACCOUNT;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_SIGN_OUT_FROM_SHARED_DEVICE;
import static com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle.Operation.MSAL_SSO_TOKEN;
Expand All @@ -55,6 +56,7 @@

import com.microsoft.identity.common.PropertyBagUtil;
import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
import com.microsoft.identity.common.exception.BrokerCommunicationException;
import com.microsoft.identity.common.internal.broker.BrokerActivity;
import com.microsoft.identity.common.internal.broker.BrokerResult;
import com.microsoft.identity.common.internal.broker.MicrosoftAuthClient;
Expand Down Expand Up @@ -114,6 +116,7 @@
import com.microsoft.identity.common.logging.Logger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -800,6 +803,71 @@ public void putValueInSuccessEvent(final @NonNull ApiEndEvent event, final @NonN
});
}


/**
* Checks if QR + PIN authorization is available.
*
* @return true if if QR + PIN authorization is available. False otherwise.
*/
@Override
public boolean isQrPinAvailable() throws BaseException {
final String methodTag = TAG + ":isQrPinAvailable";
return mBrokerOperationExecutor.execute(
null,
new BrokerOperation<Boolean>() {

@Override
public void performPrerequisites(final @NonNull IIpcStrategy strategy) throws BrokerCommunicationException {
// AccountManagerAddAccountStrategy and BoundServiceStrategy are not supported for this operation.
// We are failing fast here to avoid unnecessary IPC calls.
// IPC calls through BoundServiceStrategy takes approximate 25s to timeout.
if (strategy instanceof BoundServiceStrategy || strategy instanceof AccountManagerAddAccountStrategy) {
final String errorMessage = strategy + " is not supported for isQrPinAvailable operation";
Logger.warn(methodTag, errorMessage);
throw new BrokerCommunicationException(
BrokerCommunicationException.Category.OPERATION_NOT_SUPPORTED_ON_SERVER_SIDE,
strategy.getType(),
errorMessage,
null
);
}
}

@Override
@NonNull
public BrokerOperationBundle getBundle() {
return new BrokerOperationBundle(
MSAL_IS_QR_PIN_AVAILABLE,
mActiveBrokerPackageName,
null
);
}

@Override
@NonNull
public Boolean extractResultBundle(final @Nullable Bundle resultBundle) throws BaseException {
return mResultAdapter.getIfQrPinIsAvailableFromResultBundle(resultBundle);
}

@Override
@NonNull
public String getMethodName() {
return ":isQrPinAvailable";
}

@Override
@NonNull
public String getTelemetryApiId() {
return TelemetryEventStrings.Api.IS_QR_PIN_AVAILABLE;
}

@Override
public void putValueInSuccessEvent(final @NonNull ApiEndEvent event, final @NonNull Boolean result) {
// Deprecated telemetry.
}
});
}

/**
* Get device mode from broker.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;

import com.microsoft.identity.common.internal.broker.BrokerData;
import com.microsoft.identity.common.java.configuration.LibraryConfiguration;
import com.microsoft.identity.common.java.controllers.CommandDispatcher;
import com.microsoft.identity.common.java.eststelemetry.PublicApiId;
import com.microsoft.identity.common.java.exception.ArgumentException;
import com.microsoft.identity.common.java.exception.BrokerRequiredException;
import com.microsoft.identity.common.java.exception.ClientException;
import com.microsoft.identity.common.java.exception.ErrorStrings;
import com.microsoft.identity.common.java.exception.ServiceException;
Expand Down Expand Up @@ -509,6 +511,24 @@ public boolean removeAccount(
return localRemoveAccountSuccess;
}

/**
* Checks if QR + PIN auth is available.
* LocalMSALController is not eligible to use the broker.
* Do not check if QR + PIN is available and return false immediately.
*
* @return true false
*/
@Override
public boolean isQrPinAvailable() throws BrokerRequiredException {
final String methodTag = TAG + ":isQrPinAvailable";
final BrokerRequiredException exception = new BrokerRequiredException(
BrokerData.getProdMicrosoftAuthenticator().getPackageName(),
null
);
Logger.error(methodTag, "QR + PIN not available. Requires broker.", exception);
throw exception;
}

@Override
public boolean getDeviceMode(CommandParameters parameters) throws Exception {
final String methodTag = TAG + ":getDeviceMode";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.BROKER_RESULT_V2_COMPRESSED;
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.HELLO_ERROR_CODE;
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.HELLO_ERROR_MESSAGE;
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.IS_QR_PIN_AVAILABLE;
import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.Broker.NEGOTIATED_BP_VERSION_KEY;
import static com.microsoft.identity.common.internal.util.GzipUtil.compressString;
import static com.microsoft.identity.common.java.exception.ClientException.INVALID_BROKER_BUNDLE;
Expand Down Expand Up @@ -816,4 +817,21 @@ public GenerateShrResult getGenerateShrResultFromResultBundle(@NonNull final Bun

return shrResult;
}

/**
* Checks if the provided {@link Bundle} contains a key indicating that the broker is capable of QR code + PIN auth.
*
* @param bundle The {@link Bundle} to check.
* @return True if the broker is capable of QR code + PIN auth, false otherwise.
* @throws BaseException If the bundle does not contain the key.
*/
public boolean getIfQrPinIsAvailableFromResultBundle(@Nullable final Bundle bundle) throws BaseException {
if (bundle == null) {
throw getExceptionForEmptyResultBundle();
}
if (!bundle.containsKey(IS_QR_PIN_AVAILABLE)) {
throw new MsalBrokerResultAdapter().getBaseExceptionFromBundle(bundle);
}
return bundle.getBoolean(IS_QR_PIN_AVAILABLE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,13 @@ abstract class BaseNativeAuthController : BaseController() {
override fun getCachedAccountRecordFromAllCaches(parameters: SilentTokenCommandParameters): AccountRecord? {
throw ClientException("getCachedAccountRecordFromAllCaches() not supported in NativeAuthController")
}

@Throws(ClientException::class)
@Deprecated(
level = DeprecationLevel.HIDDEN,
message = "isQrPinAvailable() not supported in NativeAuthController"
)
override fun isQrPinAvailable(): Boolean {
throw ClientException("isQrPinAvailable() not supported in NativeAuthController")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ public abstract boolean removeAccount(
public abstract boolean getDeviceMode(final CommandParameters parameters)
throws Exception;

/**
* This method is used to determine if the QR + PIN auth flow is available on the device.
*
* @return true if if QR + PIN authorization is available. False otherwise.
*/
public abstract boolean isQrPinAvailable() throws Exception;

public abstract List<ICacheRecord> getCurrentAccount(final CommandParameters parameters)
throws Exception;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public final class PublicApiId {

public static final String PCA_GET_DEVICE_MODE = "1200";

public static final String PCA_IS_QR_PIN_AVAILABLE = "1300";
//region NativeAuthPublicClientApplication
//==============================================================================================
public static final String NATIVE_AUTH_SIGN_IN_WITH_EMAIL = "210";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.exception


/**
* Representing exceptions that occur when broker is required but not installed.
*/
class BrokerRequiredException(
brokerPackageName: String? = null,
cause: Throwable? = null,
) : BaseException(ERROR_CODE, getErrorMessage(brokerPackageName), cause) {

companion object {
public const val ERROR_CODE = "broker_required"
private const val DEFAULT_ERROR_MESSAGE = "Broker is required but not installed."

private fun getErrorMessage(brokerPackageName: String?): String {
return if (brokerPackageName != null) {
"$DEFAULT_ERROR_MESSAGE Please install $brokerPackageName."
} else {
DEFAULT_ERROR_MESSAGE
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ public static final class Api {
public static final String BROKER_GET_ACCOUNTS = "206";
public static final String BROKER_REMOVE_ACCOUNT = "207";
public static final String BROKER_REMOVE_ACCOUNT_FROM_SHARED_DEVICE = "208";
public static final String IS_QR_PIN_AVAILABLE = "209";

public static final String LOCAL_ACQUIRE_TOKEN_INTERACTIVE = "101";
public static final String LOCAL_COMPLETE_ACQUIRE_TOKEN_INTERACTIVE = "1032";
Expand Down

0 comments on commit 389106f

Please sign in to comment.