blob: fd9360f00d4a15bc1078631220259f942d070767 [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 android.service.credentials;
import static java.util.Objects.requireNonNull;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PackagePolicy;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* {@link CredentialProviderInfo} generator.
*
* @hide
*/
public final class CredentialProviderInfoFactory {
private static final String TAG = "CredentialProviderInfoFactory";
/**
* Constructs an information instance of the credential provider.
*
* @param context the context object
* @param serviceComponent the serviceComponent of the provider service
* @param userId the android userId for which the current process is running
* @param isSystemProvider whether this provider is a system provider
* @throws PackageManager.NameNotFoundException If provider service is not found
* @throws SecurityException If provider does not require the relevant permission
*/
public static CredentialProviderInfo create(
@NonNull Context context,
@NonNull ComponentName serviceComponent,
int userId,
boolean isSystemProvider)
throws PackageManager.NameNotFoundException {
return create(
context,
getServiceInfoOrThrow(serviceComponent, userId),
isSystemProvider,
/* disableSystemAppVerificationForTests= */ false,
/* isEnabled= */ false);
}
/**
* Constructs an information instance of the credential provider.
*
* @param context the context object
* @param serviceInfo the service info for the provider app. This must be retrieved from the
* {@code PackageManager}
* @param isSystemProvider whether the provider app is a system provider
* @param disableSystemAppVerificationForTests whether to disable system app permission
* verification so that tests can install system providers
* @param isEnabled whether the user enabled this provider
* @throws SecurityException If provider does not require the relevant permission
*/
public static CredentialProviderInfo create(
@NonNull Context context,
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider,
boolean disableSystemAppVerificationForTests,
boolean isEnabled)
throws SecurityException {
verifyProviderPermission(serviceInfo);
if (isSystemProvider) {
if (!isValidSystemProvider(
context, serviceInfo, disableSystemAppVerificationForTests)) {
Slog.e(TAG, "Provider is not a valid system provider: " + serviceInfo);
throw new SecurityException(
"Provider is not a valid system provider: " + serviceInfo);
}
}
return populateMetadata(context, serviceInfo)
.setSystemProvider(isSystemProvider)
.setEnabled(isEnabled)
.build();
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
final String permission = Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE;
if (permission.equals(serviceInfo.permission)) {
return;
}
throw new SecurityException(
"Service does not require the expected permission : " + permission);
}
private static boolean isSystemProviderWithValidPermission(
ServiceInfo serviceInfo, Context context) {
requireNonNull(context, "context must not be null");
final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE;
try {
ApplicationInfo appInfo =
context.getPackageManager()
.getApplicationInfo(
serviceInfo.packageName,
PackageManager.ApplicationInfoFlags.of(
PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo != null
&& context.checkPermission(permission, /* pid= */ -1, appInfo.uid)
== PackageManager.PERMISSION_GRANTED) {
Slog.i(TAG, "SYS permission granted for: " + serviceInfo.packageName);
return true;
} else {
Slog.i(TAG, "SYS permission failed for: " + serviceInfo.packageName);
}
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
}
return false;
}
private static boolean isValidSystemProvider(
Context context,
ServiceInfo serviceInfo,
boolean disableSystemAppVerificationForTests) {
requireNonNull(context, "context must not be null");
if (disableSystemAppVerificationForTests) {
Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
return false;
}
return metadata.getBoolean(
CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
}
return isSystemProviderWithValidPermission(serviceInfo, context);
}
private static CredentialProviderInfo.Builder populateMetadata(
@NonNull Context context, ServiceInfo serviceInfo) {
requireNonNull(context, "context must not be null");
final CredentialProviderInfo.Builder builder =
new CredentialProviderInfo.Builder(serviceInfo);
final PackageManager pm = context.getPackageManager();
// 1. Get the metadata for the service.
final Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
Log.i(TAG, "populateMetadata - metadata is null");
return builder;
}
// 2. Extract the capabilities from the bundle.
try {
Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
if (metadata == null || resources == null) {
Log.i(TAG, "populateMetadata - resources is null");
return builder;
}
builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, e.getMessage());
}
return builder;
}
private static List<String> populateProviderCapabilities(
Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
List<String> output = new ArrayList<>();
String[] capabilities = new String[0];
try {
capabilities =
resources.getStringArray(
metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
} catch (Resources.NotFoundException e) {
Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
}
if (capabilities == null || capabilities.length == 0) {
Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
return output;
}
for (String capability : capabilities) {
if (capability.isEmpty()) {
Slog.e(TAG, "Skipping empty capability");
continue;
}
Slog.e(TAG, "Capabilities found for provider: " + capability);
output.add(capability);
}
return output;
}
private static ServiceInfo getServiceInfoOrThrow(
@NonNull ComponentName serviceComponent, int userId)
throws PackageManager.NameNotFoundException {
try {
ServiceInfo si =
AppGlobals.getPackageManager()
.getServiceInfo(serviceComponent, PackageManager.GET_META_DATA, userId);
if (si != null) {
return si;
}
} catch (RemoteException e) {
Slog.v(TAG, e.getMessage());
}
throw new PackageManager.NameNotFoundException(serviceComponent.toString());
}
/**
* Returns the valid credential provider services available for the user with the given {@code
* userId}.
*/
@NonNull
private static List<ServiceInfo> getAvailableSystemServiceInfos(
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests) {
requireNonNull(context, "context must not be null");
final List<ServiceInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos = new ArrayList<>();
resolveInfos.addAll(
context.getPackageManager()
.queryIntentServicesAsUser(
new Intent(CredentialProviderService.SYSTEM_SERVICE_INTERFACE),
PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
userId));
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (disableSystemAppVerificationForTests) {
if (serviceInfo != null) {
services.add(serviceInfo);
}
continue;
}
try {
ApplicationInfo appInfo =
context.getPackageManager()
.getApplicationInfo(
serviceInfo.packageName,
PackageManager.ApplicationInfoFlags.of(
PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo == null || serviceInfo == null) {
continue;
}
services.add(serviceInfo);
} catch (SecurityException e) {
Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
}
}
return services;
}
/**
* Returns the valid credential provider services available for the user with the given {@code
* userId}.
*/
@NonNull
public static List<CredentialProviderInfo> getAvailableSystemServices(
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests,
Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
final List<CredentialProviderInfo> providerInfos = new ArrayList<>();
for (ServiceInfo si :
getAvailableSystemServiceInfos(
context, userId, disableSystemAppVerificationForTests)) {
try {
CredentialProviderInfo cpi =
CredentialProviderInfoFactory.create(
context,
si,
/* isSystemProvider= */ true,
disableSystemAppVerificationForTests,
enabledServices.contains(si));
if (cpi.isSystemProvider()) {
providerInfos.add(cpi);
} else {
Slog.e(TAG, "Non system provider was in system provider list.");
}
} catch (SecurityException e) {
Slog.e(TAG, "Failed to create CredentialProviderInfo: " + e);
}
}
return providerInfos;
}
private static @Nullable PackagePolicy getDeviceManagerPolicy(
@NonNull Context context, int userId) {
Context newContext = context.createContextAsUser(UserHandle.of(userId), 0);
try {
DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
return dpm.getCredentialManagerPolicy();
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
Log.e(TAG, "Failed to get device policy: " + e);
}
return null;
}
/**
* Returns the valid credential provider services available for the user with the given {@code
* userId}.
*/
@NonNull
public static List<CredentialProviderInfo> getCredentialProviderServices(
@NonNull Context context,
int userId,
int providerFilter,
Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
PackagePolicy pp = getDeviceManagerPolicy(context, userId);
// Generate the provider list.
final boolean disableSystemAppVerificationForTests = false;
ProviderGenerator generator =
new ProviderGenerator(
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
getUserProviders(
context, userId, disableSystemAppVerificationForTests, enabledServices));
generator.addSystemProviders(
getAvailableSystemServices(
context, userId, disableSystemAppVerificationForTests, enabledServices));
return generator.getProviders();
}
/**
* Returns the valid credential provider services available for the user with the given {@code
* userId}. Includes test providers.
*/
@NonNull
public static List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
@NonNull Context context,
int userId,
int providerFilter,
Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
PackagePolicy pp = getDeviceManagerPolicy(context, userId);
// Generate the provider list.
final boolean disableSystemAppVerificationForTests = true;
ProviderGenerator generator =
new ProviderGenerator(
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
getUserProviders(
context, userId, disableSystemAppVerificationForTests, enabledServices));
generator.addSystemProviders(
getAvailableSystemServices(
context, userId, disableSystemAppVerificationForTests, enabledServices));
return generator.getProviders();
}
private static class ProviderGenerator {
private final Context mContext;
private final PackagePolicy mPp;
private final boolean mDisableSystemAppVerificationForTests;
private final Map<String, CredentialProviderInfo> mServices = new HashMap();
private final int mProviderFilter;
ProviderGenerator(
Context context,
PackagePolicy pp,
boolean disableSystemAppVerificationForTests,
int providerFilter) {
this.mContext = context;
this.mPp = pp;
this.mDisableSystemAppVerificationForTests = disableSystemAppVerificationForTests;
this.mProviderFilter = providerFilter;
}
private boolean isPackageAllowed(boolean isSystemProvider, String packageName) {
if (mPp == null) {
return true;
}
if (isSystemProvider) {
return mPp.getPolicyType() == PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM;
}
return mPp.isPackageAllowed(packageName, new HashSet<>());
}
public List<CredentialProviderInfo> getProviders() {
return new ArrayList<>(mServices.values());
}
public void addUserProviders(List<CredentialProviderInfo> providers) {
for (CredentialProviderInfo cpi : providers) {
if (!cpi.isSystemProvider()) {
addProvider(cpi);
}
}
}
public void addSystemProviders(List<CredentialProviderInfo> providers) {
for (CredentialProviderInfo cpi : providers) {
if (cpi.isSystemProvider()) {
addProvider(cpi);
}
}
}
private boolean isProviderAllowedWithFilter(CredentialProviderInfo cpi) {
if (mProviderFilter == CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS) {
return true;
}
if (cpi.isSystemProvider()) {
return mProviderFilter == CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY;
} else {
return mProviderFilter == CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY;
}
}
private void addProvider(CredentialProviderInfo cpi) {
final String componentNameString =
cpi.getServiceInfo().getComponentName().flattenToString();
if (!isProviderAllowedWithFilter(cpi)) {
return;
}
if (!isPackageAllowed(cpi.isSystemProvider(), cpi.getServiceInfo().packageName)) {
return;
}
mServices.put(componentNameString, cpi);
}
}
/**
* Returns the valid credential provider services available for the user with the given {@code
* userId}.
*/
@NonNull
private static List<CredentialProviderInfo> getUserProviders(
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests,
Set<ServiceInfo> enabledServices) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager()
.queryIntentServicesAsUser(
new Intent(CredentialProviderService.SERVICE_INTERFACE),
PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
userId);
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
CredentialProviderInfo cpi =
CredentialProviderInfoFactory.create(
context,
serviceInfo,
/* isSystemProvider= */ false,
disableSystemAppVerificationForTests,
enabledServices.contains(serviceInfo));
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
} catch (SecurityException e) {
Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
} catch (Exception e) {
Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
}
}
return services;
}
}