blob: 8170dc30d09d66f4c9299472887939542ef12bf4 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
import android.credentials.CredentialDescription;
import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfoFactory;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Entry point service for credential management.
*
* <p>This service provides the {@link ICredentialManager} implementation and keeps a list of {@link
* CredentialManagerServiceImpl} per user; the real work is done by {@link
* CredentialManagerServiceImpl} itself.
*/
public final class CredentialManagerService
extends AbstractMasterSystemService<
CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
"enable_credential_description_api";
private static final String PERMISSION_DENIED_ERROR = "permission_denied";
private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR =
"Caller is missing WRITE_SECURE_SETTINGS permission";
private final Context mContext;
/** 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,
new SecureSettingsServiceNameResolver(
context, Settings.Secure.CREDENTIAL_SERVICE, /* isMultipleMode= */ true),
null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
mContext = context;
}
@NonNull
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> constructSystemServiceListLocked(
int resolvedUserId) {
List<CredentialManagerServiceImpl> services = new ArrayList<>();
List<CredentialProviderInfo> serviceInfos =
CredentialProviderInfoFactory.getAvailableSystemServices(
mContext,
resolvedUserId,
/* disableSystemAppVerificationForTests= */ false,
new HashSet<>());
serviceInfos.forEach(
info -> {
services.add(
new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
});
return services;
}
@Override
protected String getServiceSettingsProperty() {
return Settings.Secure.CREDENTIAL_SERVICE;
}
@Override // from AbstractMasterSystemService
protected CredentialManagerServiceImpl newServiceLocked(
@UserIdInt int resolvedUserId, boolean disabled) {
// This method should not be called for CredentialManagerService as it is configured to use
// multiple services.
Slog.w(
TAG,
"Should not be here - CredentialManagerService is configured to use "
+ "multiple services");
return null;
}
@Override // from SystemService
public void onStart() {
publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
}
@Override // from AbstractMasterSystemService
@GuardedBy("mLock")
protected List<CredentialManagerServiceImpl> newServiceListLocked(
int resolvedUserId, boolean disabled, String[] serviceNames) {
getOrConstructSystemServiceListLock(resolvedUserId);
if (serviceNames == null || serviceNames.length == 0) {
Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
return new ArrayList<>();
}
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
for (String serviceName : serviceNames) {
Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
if (TextUtils.isEmpty(serviceName)) {
continue;
}
try {
serviceList.add(
new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName));
} catch (PackageManager.NameNotFoundException | SecurityException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
}
}
return serviceList;
}
@GuardedBy("mLock")
@SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
// this.mLock
protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
if (services == null) {
return;
}
CredentialManagerServiceImpl serviceToBeRemoved = null;
for (CredentialManagerServiceImpl service : services) {
if (service != null) {
CredentialProviderInfo credentialProviderInfo = service.getCredentialProviderInfo();
ComponentName componentName =
credentialProviderInfo.getServiceInfo().getComponentName();
if (packageName.equals(componentName.getPackageName())) {
serviceToBeRemoved = service;
removeServiceFromMultiModeSettings(componentName.flattenToString(), userId);
break;
}
}
}
if (serviceToBeRemoved != null) {
removeServiceFromCache(serviceToBeRemoved, userId);
CredentialDescriptionRegistry.forUser(userId)
.evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
}
// TODO("Iterate over system services and remove if needed")
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
List<CredentialManagerServiceImpl> services = mSystemServicesCacheList.get(resolvedUserId);
if (services == null || services.size() == 0) {
services = constructSystemServiceListLocked(resolvedUserId);
mSystemServicesCacheList.put(resolvedUserId, services);
}
return services;
}
private boolean hasWriteSecureSettingsPermission() {
return hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
private void verifyGetProvidersPermission() throws SecurityException {
if (hasPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)) {
return;
}
if (hasPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)) {
return;
}
throw new SecurityException("Caller is missing permission: QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS");
}
private boolean hasPermission(String permission) {
final boolean result =
mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
if (!result) {
Slog.e(TAG, "Caller does not have permission: " + permission);
}
return result;
}
private void runForUser(@NonNull final Consumer<CredentialManagerServiceImpl> c) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getCredentialProviderServicesLocked(userId);
for (CredentialManagerServiceImpl s : services) {
c.accept(s);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getCredentialProviderServicesLocked(int userId) {
List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>();
List<CredentialManagerServiceImpl> userConfigurableServices =
getServiceListForUserLocked(userId);
if (userConfigurableServices != null && !userConfigurableServices.isEmpty()) {
concatenatedServices.addAll(userConfigurableServices);
}
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
return concatenatedServices;
}
public static boolean isCredentialDescriptionApiEnabled() {
final long origId = Binder.clearCallingIdentity();
try {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
false);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
GetRequestSession session,
Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
activeCredentialContainers) {
List<ProviderSession> providerSessions = new ArrayList<>();
for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
activeCredentialContainers) {
providerSessions.add(
ProviderRegistryGetSession.createNewSession(
mContext,
UserHandle.getCallingUserId(),
session,
result.second.mPackageName,
result.first));
}
return providerSessions;
}
@NonNull
private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
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))
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
Set<CredentialDescriptionRegistry.FilterResult> filterResults =
registry.getMatchingProviders(requestedCredentialDescriptions);
Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> result =
new HashSet<>();
for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) {
for (CredentialOption credentialOption : options) {
if (filterResult.mFlattenedRequest.equals(credentialOption
.getCredentialRetrievalData()
.getString(CredentialOption.FLATTENED_REQUEST))) {
result.add(new Pair<>(credentialOption, filterResult));
}
}
}
return result;
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessions(
RequestSession session, List<String> requestOptions) {
List<ProviderSession> providerSessions = new ArrayList<>();
// Invoke all services of a user to initiate a provider session
runForUser(
(service) -> {
synchronized (mLock) {
ProviderSession providerSession =
service.initiateProviderSessionForRequestLocked(
session, requestOptions);
if (providerSession != null) {
providerSessions.add(providerSession);
}
}
});
return providerSessions;
}
@Override
@GuardedBy("CredentialDescriptionRegistry.sLock")
public void onUserStopped(@NonNull TargetUser user) {
super.onUserStopped(user);
CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier());
}
private CallingAppInfo constructCallingAppInfo(
String realPackageName,
int userId,
@Nullable String origin) {
final PackageInfo packageInfo;
CallingAppInfo callingAppInfo;
try {
packageInfo =
getContext()
.getPackageManager()
.getPackageInfoAsUser(
realPackageName,
PackageManager.PackageInfoFlags.of(
PackageManager.GET_SIGNING_CERTIFICATES),
userId);
callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin);
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
callingAppInfo = new CallingAppInfo(realPackageName, null, origin);
}
return callingAppInfo;
}
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
public ICancellationSignal executeGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
final String callingPackage) {
Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
if (request.getOrigin() != null) {
// Check privileged permissions
mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
}
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
// New request session, scoped for this request only.
final GetRequestSession session =
new GetRequestSession(
getContext(),
userId,
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processGetCredential(request, callback, session);
return cancelTransport;
}
private void processGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
GetRequestSession session) {
List<ProviderSession> providerSessions;
if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
.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)))
.toList();
List<ProviderSession> sessionsWithoutRemoteService =
initiateProviderSessionsWithActiveContainers(
session,
getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
List<ProviderSession> sessionsWithRemoteService =
initiateProviderSessions(
session,
optionsThatDoNotRequireActiveCredentials.stream()
.map(CredentialOption::getType)
.collect(Collectors.toList()));
Set<ProviderSession> all = new LinkedHashSet<>();
all.addAll(sessionsWithRemoteService);
all.addAll(sessionsWithoutRemoteService);
providerSessions = new ArrayList<>(all);
} else {
// Initiate all provider sessions
providerSessions =
initiateProviderSessions(
session,
request.getCredentialOptions().stream()
.map(CredentialOption::getType)
.collect(Collectors.toList()));
}
if (providerSessions.isEmpty()) {
try {
callback.onError(
GetCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
} catch (RemoteException e) {
Log.i(
TAG,
"Issue invoking onError on IGetCredentialCallback "
+ "callback: "
+ e.getMessage());
}
}
providerSessions.forEach(ProviderSession::invokeSession);
}
@Override
public ICancellationSignal executeCreateCredential(
CreateCredentialRequest request,
ICreateCredentialCallback callback,
String callingPackage) {
Log.i(TAG, "starting executeCreateCredential with callingPackage: "
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
if (request.getOrigin() != null) {
// Check privileged permissions
mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
}
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
// New request session, scoped for this request only.
final CreateRequestSession session =
new CreateRequestSession(
getContext(),
userId,
callingUid,
request,
callback,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processCreateCredential(request, callback, session);
return cancelTransport;
}
private void processCreateCredential(
CreateCredentialRequest request,
ICreateCredentialCallback callback,
CreateRequestSession session) {
// Initiate all provider sessions
List<ProviderSession> providerSessions =
initiateProviderSessions(session, List.of(request.getType()));
if (providerSessions.isEmpty()) {
try {
callback.onError(
CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"No create options available.");
} catch (RemoteException e) {
Log.i(
TAG,
"Issue invoking onError on ICreateCredentialCallback "
+ "callback: "
+ e.getMessage());
}
}
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(ProviderSession::invokeSession);
}
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
Log.i(TAG, "setEnabledProviders");
if (!hasWriteSecureSettingsPermission()) {
try {
callback.onError(
PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
} catch (RemoteException e) {
Log.e(TAG, "Issue with invoking response: " + e.getMessage());
}
return;
}
userId =
ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
Binder.getCallingUid(),
userId,
false,
false,
"setEnabledProviders",
null);
String storedValue = String.join(":", providers);
if (!Settings.Secure.putStringForUser(
getContext().getContentResolver(),
Settings.Secure.CREDENTIAL_SERVICE,
storedValue,
userId)) {
Log.e(TAG, "Failed to store setting containing enabled providers");
try {
callback.onError(
"failed_setting_store",
"Failed to store setting containing enabled providers");
} catch (RemoteException e) {
Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
return;
}
}
// Call the callback.
try {
callback.onResponse();
} catch (RemoteException e) {
Log.i(TAG, "Issue with invoking response: " + e.getMessage());
// TODO: Propagate failure
}
// Send an intent to the UI that we have new enabled providers.
getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
LAUNCH_CREDENTIAL_SELECTOR);
}
@Override
public boolean isEnabledCredentialProviderService(
ComponentName componentName, String callingPackage) {
Log.i(TAG, "isEnabledCredentialProviderService");
// TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
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.
MetricUtilities.logApiCalled(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
ApiStatus.METRICS_API_STATUS_FAILURE, callingUid);
Log.w(
TAG,
"isEnabledCredentialProviderService: Component name does not"
+ " match package name.");
return false;
}
MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid);
return true;
}
}
}
return false;
}
@Override
public List<CredentialProviderInfo> getCredentialProviderServices(
int userId, int providerFilter) {
Log.i(TAG, "getCredentialProviderServices");
verifyGetProvidersPermission();
return CredentialProviderInfoFactory.getCredentialProviderServices(
mContext, userId, providerFilter, getEnabledProviders());
}
@Override
public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
int providerFilter) {
Log.i(TAG, "getCredentialProviderServicesForTesting");
verifyGetProvidersPermission();
final int userId = UserHandle.getCallingUserId();
return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
mContext, userId, providerFilter, getEnabledProviders());
}
private Set<ServiceInfo> getEnabledProviders() {
Set<ServiceInfo> enabledProviders = new HashSet<>();
synchronized (mLock) {
runForUser(
(service) -> {
enabledProviders.add(service.getCredentialProviderInfo().getServiceInfo());
});
}
return enabledProviders;
}
@Override
public ICancellationSignal clearCredentialState(
ClearCredentialStateRequest request,
IClearCredentialStateCallback callback,
String callingPackage) {
Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
final int userId = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
// TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
final ClearRequestSession session =
new ClearRequestSession(
getContext(),
userId,
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId, null),
CancellationSignal.fromTransport(cancelTransport));
// Initiate all provider sessions
// TODO: Determine if provider needs to have clear capability in their manifest
List<ProviderSession> providerSessions = initiateProviderSessions(session, List.of());
if (providerSessions.isEmpty()) {
try {
// TODO("Replace with properly defined error type")
callback.onError("UNKNOWN", "No crdentials available on this " + "device");
} catch (RemoteException e) {
Log.i(
TAG,
"Issue invoking onError on IClearCredentialStateCallback "
+ "callback: "
+ e.getMessage());
}
}
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@Override
public void registerCredentialDescription(
RegisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException, NonCredentialProviderCallerException {
Log.i(TAG, "registerCredentialDescription");
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException();
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
getServicesForCredentialDescription(UserHandle.getCallingUserId());
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();
CredentialProviderInfo credentialProviderInfo = matchingService.get(0);
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.");
}
CredentialDescriptionRegistry session =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
session.executeRegisterRequest(request, callingPackage);
}
@Override
public void unregisterCredentialDescription(
UnregisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException {
Log.i(TAG, "registerCredentialDescription");
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException();
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
getServicesForCredentialDescription(UserHandle.getCallingUserId());
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());
session.executeUnregisterRequest(request, callingPackage);
}
private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
return CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
userId,
CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
new HashSet<>());
}
}
private void enforceCallingPackage(String callingPackage, int callingUid) {
int packageUid;
PackageManager pm = mContext.createContextAsUser(
UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
try {
packageUid = pm.getPackageUid(callingPackage,
PackageManager.PackageInfoFlags.of(0));
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException(callingPackage + " not found");
}
if (packageUid != callingUid) {
throw new SecurityException(callingPackage + " does not belong to uid " + callingUid);
}
}
}