Add API to check if credman is enabled

Adds an API that can be called by provider apps
to see if they are enabled.

Test: Make
Bug: 253157366
API-Coverage-Bug: 247549381
Change-Id: I61e64b21437165303ca16656b86c8814b27e26b7
This commit is contained in:
Becca Hughes
2023-01-04 22:49:38 +00:00
parent 27bd04e263
commit 1f33ec6df9
4 changed files with 183 additions and 106 deletions

View File

@@ -13507,6 +13507,7 @@ package android.credentials {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
}

View File

@@ -25,6 +25,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.os.CancellationSignal;
@@ -279,6 +280,25 @@ public final class CredentialManager {
}
}
/**
* Returns {@code true} if the calling application provides a CredentialProviderService that is
* enabled for the current user, or {@code false} otherwise. CredentialProviderServices are
* enabled on a per-service basis so the individual component name of the service should be
* passed in here.
*
* @param componentName the component name to check is enabled
*/
public boolean isEnabledCredentialProviderService(@NonNull ComponentName componentName) {
requireNonNull(componentName, "componentName must not be null");
try {
return mService.isEnabledCredentialProviderService(
componentName, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns whether the service is enabled.
*
@@ -286,7 +306,7 @@ public final class CredentialManager {
*/
public static boolean isServiceEnabled(Context context) {
if (context == null) {
return false;
return false;
}
CredentialManager credentialManager =
(CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
@@ -298,8 +318,7 @@ public final class CredentialManager {
private boolean isServiceEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
true);
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
}
/**
@@ -313,22 +332,19 @@ public final class CredentialManager {
}
/**
* Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
* a CredentialProvider has. This registry will then be used to determine where to
* fetch the requested {@link Credential} from. Not all credential types will be supported.
* The distinction will be made by the JetPack layer. For the types that are supported,
* JetPack will add a new key-value pair into {@link GetCredentialRequest}. These will not
* be persistent on the device. The Credential Providers will need to call this API again
* upon device reboot.
* Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} a
* CredentialProvider has. This registry will then be used to determine where to fetch the
* requested {@link Credential} from. Not all credential types will be supported. The
* distinction will be made by the JetPack layer. For the types that are supported, JetPack will
* add a new key-value pair into {@link GetCredentialRequest}. These will not be persistent on
* the device. The Credential Providers will need to call this API again upon device reboot.
*
* @param request the request data
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
* @throws {@link com.android.server.credentials.NonCredentialProviderCallerException}
* if the calling package name is not also listed as a Credential Provider.
* @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle
* one or more of the Credential Types that are sent for registration.
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
* @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} if the
* calling package name is not also listed as a Credential Provider.
* @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle
* one or more of the Credential Types that are sent for registration.
*/
public void registerCredentialDescription(
@NonNull RegisterCredentialDescriptionRequest request) {
@@ -346,16 +362,12 @@ public final class CredentialManager {
}
}
/**
* Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
* Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
* that has been registered previously.
*
*
* @param request the request data
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
*/
public void unregisterCredentialDescription(
@NonNull UnregisterCredentialDescriptionRequest request) {
@@ -371,7 +383,6 @@ public final class CredentialManager {
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {

View File

@@ -28,6 +28,7 @@ import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
/**
@@ -50,5 +51,7 @@ interface ICredentialManager {
void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
void unregisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, String callingPackage);
boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);
}

View File

@@ -84,14 +84,11 @@ public final class CredentialManagerService
private final Context mContext;
/**
* Cache of system service list per user id.
*/
/** Cache of system service list per user id. */
@GuardedBy("mLock")
private final SparseArray<List<CredentialManagerServiceImpl>> mSystemServicesCacheList =
new SparseArray<>();
public CredentialManagerService(@NonNull Context context) {
super(
context,
@@ -109,9 +106,11 @@ public final class CredentialManagerService
List<CredentialManagerServiceImpl> services = new ArrayList<>();
List<CredentialProviderInfo> credentialProviderInfos =
CredentialProviderInfo.getAvailableSystemServices(mContext, resolvedUserId);
credentialProviderInfos.forEach(info -> {
services.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
});
credentialProviderInfos.forEach(
info -> {
services.add(
new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
});
return services;
}
@@ -174,10 +173,9 @@ public final class CredentialManagerService
CredentialManagerServiceImpl serviceToBeRemoved = null;
for (CredentialManagerServiceImpl service : services) {
if (service != null) {
CredentialProviderInfo credentialProviderInfo =
service.getCredentialProviderInfo();
ComponentName componentName = credentialProviderInfo.getServiceInfo()
.getComponentName();
CredentialProviderInfo credentialProviderInfo = service.getCredentialProviderInfo();
ComponentName componentName =
credentialProviderInfo.getServiceInfo().getComponentName();
if (packageName.equals(componentName.getPackageName())) {
serviceToBeRemoved = service;
removeServiceFromMultiModeSettings(componentName.flattenToString(), userId);
@@ -193,7 +191,6 @@ public final class CredentialManagerService
// TODO("Iterate over system services and remove if needed")
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -222,8 +219,7 @@ public final class CredentialManagerService
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getAllCredentialProviderServicesLocked(
int userId) {
private List<CredentialManagerServiceImpl> getAllCredentialProviderServicesLocked(int userId) {
List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>();
List<CredentialManagerServiceImpl> userConfigurableServices =
getServiceListForUserLocked(userId);
@@ -243,16 +239,18 @@ public final class CredentialManagerService
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
GetRequestSession session,
List<String> requestOptions, Set<String> activeCredentialContainers) {
List<String> requestOptions,
Set<String> activeCredentialContainers) {
List<ProviderSession> providerSessions = new ArrayList<>();
// Invoke all services of a user to initiate a provider session
for (String packageName: activeCredentialContainers) {
providerSessions.add(ProviderRegistryGetSession.createNewSession(
mContext,
UserHandle.getCallingUserId(),
session,
packageName,
requestOptions));
for (String packageName : activeCredentialContainers) {
providerSessions.add(
ProviderRegistryGetSession.createNewSession(
mContext,
UserHandle.getCallingUserId(),
session,
packageName,
requestOptions));
}
return providerSessions;
}
@@ -260,16 +258,17 @@ public final class CredentialManagerService
@NonNull
private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
CredentialDescriptionRegistry registry =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
// All requested credential descriptions based on the given request.
Set<String> requestedCredentialDescriptions =
options.stream().map(
getCredentialOption -> getCredentialOption
.getCredentialRetrievalData()
.getString(CredentialOption
.FLATTENED_REQUEST))
options.stream()
.map(
getCredentialOption ->
getCredentialOption
.getCredentialRetrievalData()
.getString(CredentialOption.FLATTENED_REQUEST))
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
@@ -306,10 +305,14 @@ public final class CredentialManagerService
private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
final PackageInfo packageInfo;
try {
packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
packageName,
PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES),
userId);
packageInfo =
getContext()
.getPackageManager()
.getPackageInfoAsUser(
packageName,
PackageManager.PackageInfoFlags.of(
PackageManager.GET_SIGNING_CERTIFICATES),
userId);
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
return new CallingAppInfo(packageName, null);
@@ -345,37 +348,50 @@ public final class CredentialManagerService
if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
.filter(getCredentialOption ->
!TextUtils.isEmpty(getCredentialOption
.getCredentialRetrievalData().getString(
CredentialOption
.FLATTENED_REQUEST, null)))
.filter(
getCredentialOption ->
!TextUtils.isEmpty(
getCredentialOption
.getCredentialRetrievalData()
.getString(
CredentialOption
.FLATTENED_REQUEST,
null)))
.toList();
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
request.getCredentialOptions().stream()
.filter(getCredentialOption ->
TextUtils.isEmpty(getCredentialOption
.getCredentialRetrievalData().getString(
CredentialOption
.FLATTENED_REQUEST, null)))
.filter(
getCredentialOption ->
TextUtils.isEmpty(
getCredentialOption
.getCredentialRetrievalData()
.getString(
CredentialOption
.FLATTENED_REQUEST,
null)))
.toList();
List<ProviderSession> sessionsWithoutRemoteService =
initiateProviderSessionsWithActiveContainers(session,
optionsThatRequireActiveCredentials
.stream().map(getCredentialOption ->
getCredentialOption.getCredentialRetrievalData()
.getString(CredentialOption
.FLATTENED_REQUEST))
initiateProviderSessionsWithActiveContainers(
session,
optionsThatRequireActiveCredentials.stream()
.map(
getCredentialOption ->
getCredentialOption
.getCredentialRetrievalData()
.getString(
CredentialOption
.FLATTENED_REQUEST))
.collect(Collectors.toList()),
getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions(
session,
optionsThatDoNotRequireActiveCredentials.stream()
.map(CredentialOption::getType)
.collect(Collectors.toList()));
List<ProviderSession> sessionsWithRemoteService =
initiateProviderSessions(
session,
optionsThatDoNotRequireActiveCredentials.stream()
.map(CredentialOption::getType)
.collect(Collectors.toList()));
Set<ProviderSession> all = new LinkedHashSet<>();
all.addAll(sessionsWithRemoteService);
@@ -394,7 +410,8 @@ public final class CredentialManagerService
if (providerSessions.isEmpty()) {
try {
callback.onError(GetCredentialException.TYPE_NO_CREDENTIAL,
callback.onError(
GetCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
} catch (RemoteException e) {
Log.i(
@@ -437,7 +454,8 @@ public final class CredentialManagerService
if (providerSessions.isEmpty()) {
try {
callback.onError(CreateCredentialException.TYPE_NO_CREDENTIAL,
callback.onError(
CreateCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
} catch (RemoteException e) {
Log.i(
@@ -521,6 +539,37 @@ public final class CredentialManagerService
getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
}
@Override
public boolean isEnabledCredentialProviderService(
ComponentName componentName, String callingPackage) {
Log.i(TAG, "isEnabledCredentialProviderService");
// TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
for (CredentialManagerServiceImpl s : services) {
final ComponentName serviceComponentName = s.getServiceComponentName();
if (serviceComponentName.equals(componentName)) {
if (!s.getServicePackageName().equals(callingPackage)) {
// The component name and the package name do not match.
Log.w(
TAG,
"isEnabledCredentialProviderService: Component name does not"
+ " match package name.");
return false;
}
return true;
}
}
}
return false;
}
@Override
public ICancellationSignal clearCredentialState(
ClearCredentialStateRequest request,
@@ -550,8 +599,7 @@ public final class CredentialManagerService
if (providerSessions.isEmpty()) {
try {
// TODO("Replace with properly defined error type")
callback.onError("UNKNOWN", "No crdentials available on this "
+ "device");
callback.onError("UNKNOWN", "No crdentials available on this " + "device");
} catch (RemoteException e) {
Log.i(
TAG,
@@ -569,39 +617,50 @@ public final class CredentialManagerService
@Override
public void registerCredentialDescription(
RegisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException , NonCredentialProviderCallerException {
throws IllegalArgumentException, NonCredentialProviderCallerException {
Log.i(TAG, "registerCredentialDescription");
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(mContext,
UserHandle.getCallingUserId());
CredentialProviderInfo.getAvailableServices(
mContext, UserHandle.getCallingUserId());
List<String> providers = services.stream()
.map(credentialProviderInfo
-> credentialProviderInfo.getServiceInfo().packageName).toList();
List<String> providers =
services.stream()
.map(
credentialProviderInfo ->
credentialProviderInfo.getServiceInfo().packageName)
.toList();
if (!providers.contains(callingPackage)) {
throw new NonCredentialProviderCallerException(callingPackage);
}
List<CredentialProviderInfo> matchingService = services.stream().filter(
credentialProviderInfo ->
credentialProviderInfo.getServiceInfo()
.packageName.equals(callingPackage)).toList();
List<CredentialProviderInfo> matchingService =
services.stream()
.filter(
credentialProviderInfo ->
credentialProviderInfo
.getServiceInfo()
.packageName
.equals(callingPackage))
.toList();
CredentialProviderInfo credentialProviderInfo = matchingService.get(0);
Set<String> supportedTypes = request.getCredentialDescriptions()
.stream().map(CredentialDescription::getType).filter(
credentialProviderInfo::hasCapability).collect(Collectors.toSet());
Set<String> supportedTypes =
request.getCredentialDescriptions().stream()
.map(CredentialDescription::getType)
.filter(credentialProviderInfo::hasCapability)
.collect(Collectors.toSet());
if (supportedTypes.size() != request.getCredentialDescriptions().size()) {
throw new IllegalArgumentException("CredentialProvider does not support one or more"
+ "of the registered types. Check your XML entry.");
throw new IllegalArgumentException(
"CredentialProvider does not support one or more"
+ "of the registered types. Check your XML entry.");
}
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
CredentialDescriptionRegistry session =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
session.executeRegisterRequest(request, callingPackage);
}
@@ -614,19 +673,22 @@ public final class CredentialManagerService
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(mContext,
UserHandle.getCallingUserId());
CredentialProviderInfo.getAvailableServices(
mContext, UserHandle.getCallingUserId());
List<String> providers = services.stream()
.map(credentialProviderInfo
-> credentialProviderInfo.getServiceInfo().packageName).toList();
List<String> providers =
services.stream()
.map(
credentialProviderInfo ->
credentialProviderInfo.getServiceInfo().packageName)
.toList();
if (!providers.contains(callingPackage)) {
throw new NonCredentialProviderCallerException(callingPackage);
}
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
CredentialDescriptionRegistry session =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
session.executeUnregisterRequest(request, callingPackage);
}