From 1f33ec6df91d80f502b572cd2f1de4e011ecda21 Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Wed, 4 Jan 2023 22:49:38 +0000 Subject: [PATCH] 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 --- core/api/current.txt | 1 + .../credentials/CredentialManager.java | 59 +++-- .../credentials/ICredentialManager.aidl | 3 + .../credentials/CredentialManagerService.java | 226 +++++++++++------- 4 files changed, 183 insertions(+), 106 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index de540bf93e4bf..7364a7f453093 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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); 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); 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); + method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName); method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest); method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest); } diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 1db14a3dda164..7fcb55548264c 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -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 { diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index 4ca3124f5c810..885acd4f712b3 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -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); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index ea63c30a911c2..3c7438b0b708f 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -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> mSystemServicesCacheList = new SparseArray<>(); - public CredentialManagerService(@NonNull Context context) { super( context, @@ -109,9 +106,11 @@ public final class CredentialManagerService List services = new ArrayList<>(); List 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 getOrConstructSystemServiceListLock( int resolvedUserId) { @@ -222,8 +219,7 @@ public final class CredentialManagerService } @GuardedBy("mLock") - private List getAllCredentialProviderServicesLocked( - int userId) { + private List getAllCredentialProviderServicesLocked(int userId) { List concatenatedServices = new ArrayList<>(); List 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 initiateProviderSessionsWithActiveContainers( GetRequestSession session, - List requestOptions, Set activeCredentialContainers) { + List requestOptions, + Set activeCredentialContainers) { List 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 getFilteredResultFromRegistry(List 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 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 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 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 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 sessionsWithRemoteService = initiateProviderSessions( - session, - optionsThatDoNotRequireActiveCredentials.stream() - .map(CredentialOption::getType) - .collect(Collectors.toList())); + List sessionsWithRemoteService = + initiateProviderSessions( + session, + optionsThatDoNotRequireActiveCredentials.stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); Set 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 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 services = - CredentialProviderInfo.getAvailableServices(mContext, - UserHandle.getCallingUserId()); + CredentialProviderInfo.getAvailableServices( + mContext, UserHandle.getCallingUserId()); - List providers = services.stream() - .map(credentialProviderInfo - -> credentialProviderInfo.getServiceInfo().packageName).toList(); + List providers = + services.stream() + .map( + credentialProviderInfo -> + credentialProviderInfo.getServiceInfo().packageName) + .toList(); if (!providers.contains(callingPackage)) { throw new NonCredentialProviderCallerException(callingPackage); } - List matchingService = services.stream().filter( - credentialProviderInfo -> - credentialProviderInfo.getServiceInfo() - .packageName.equals(callingPackage)).toList(); + List matchingService = + services.stream() + .filter( + credentialProviderInfo -> + credentialProviderInfo + .getServiceInfo() + .packageName + .equals(callingPackage)) + .toList(); CredentialProviderInfo credentialProviderInfo = matchingService.get(0); - Set supportedTypes = request.getCredentialDescriptions() - .stream().map(CredentialDescription::getType).filter( - credentialProviderInfo::hasCapability).collect(Collectors.toSet()); + Set 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 services = - CredentialProviderInfo.getAvailableServices(mContext, - UserHandle.getCallingUserId()); + CredentialProviderInfo.getAvailableServices( + mContext, UserHandle.getCallingUserId()); - List providers = services.stream() - .map(credentialProviderInfo - -> credentialProviderInfo.getServiceInfo().packageName).toList(); + List 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); }