Add system service support to credential manager

Test: Built locally

Change-Id: Idb3e3983ed3194349fdf91dc0d43f40054cb40c9
This commit is contained in:
Reema Bajwa
2022-12-21 02:17:39 +00:00
parent 3af2f31845
commit fee24eaa74
5 changed files with 155 additions and 23 deletions

View File

@@ -24,6 +24,7 @@ import android.app.AppGlobals;
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;
@@ -58,6 +59,7 @@ public final class CredentialProviderInfo {
private final Drawable mIcon;
@Nullable
private final CharSequence mLabel;
private final boolean mIsSystemProvider;
/**
* Constructs an information instance of the credential provider.
@@ -65,13 +67,14 @@ public final class CredentialProviderInfo {
* @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 CredentialProviderInfo(@NonNull Context context,
@NonNull ComponentName serviceComponent, int userId)
@NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider)
throws PackageManager.NameNotFoundException {
this(context, getServiceInfoOrThrow(serviceComponent, userId));
this(context, getServiceInfoOrThrow(serviceComponent, userId), isSystemProvider);
}
/**
@@ -79,8 +82,11 @@ public final class CredentialProviderInfo {
* @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 is a system app or not
*/
public CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
public CredentialProviderInfo(@NonNull Context context,
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider) {
if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
+ "does not require permission"
@@ -95,6 +101,7 @@ public final class CredentialProviderInfo {
mLabel = mServiceInfo.loadSafeLabel(
mContext.getPackageManager(), 0 /* do not ellipsize */,
TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
mIsSystemProvider = isSystemProvider;
Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
.flattenToString());
populateProviderCapabilities(context, serviceInfo);
@@ -146,6 +153,42 @@ public final class CredentialProviderInfo {
throw new PackageManager.NameNotFoundException(serviceComponent.toString());
}
/**
* 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) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
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;
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
serviceInfo.packageName,
PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo != null
&& context.checkPermission(Manifest.permission.SYSTEM_CREDENTIAL_PROVIDER,
/*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
services.add(new CredentialProviderInfo(context, serviceInfo,
/*isSystemProvider=*/true));
}
} catch (SecurityException e) {
Log.i(TAG, "Error getting info for " + serviceInfo + ": " + e);
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Error getting info for " + serviceInfo + ": " + e);
}
}
return services;
}
/**
* Returns true if the service supports the given {@code credentialType}, false otherwise.
*/
@@ -160,6 +203,10 @@ public final class CredentialProviderInfo {
return mServiceInfo;
}
public boolean isSystemProvider() {
return mIsSystemProvider;
}
/** Returns the service icon. */
@Nullable
public Drawable getServiceIcon() {
@@ -195,7 +242,8 @@ public final class CredentialProviderInfo {
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
services.add(new CredentialProviderInfo(context, serviceInfo));
services.add(new CredentialProviderInfo(context,
serviceInfo, false));
} catch (SecurityException e) {
Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
}

View File

@@ -140,6 +140,20 @@ public abstract class CredentialProviderService extends Service {
public static final String SERVICE_INTERFACE =
"android.service.credentials.CredentialProviderService";
/**
* The {@link Intent} that must be declared as handled by a system credential provider
* service.
*
* <p>The service must also require the
* {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission
* so that only the system can bind to it.
*
* @hide
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SYSTEM_SERVICE_INTERFACE =
"android.service.credentials.system.CredentialProviderService";
@CallSuper
@Override
public void onCreate() {

View File

@@ -3906,6 +3906,18 @@
<permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"
android:protectionLevel="signature|privileged" />
<!-- Allows a system application to be registered with credential manager without
having to be enabled by the user.
@hide -->
<permission android:name="android.permission.SYSTEM_CREDENTIAL_PROVIDER"
android:protectionLevel="signature|privileged" />
<!-- Allows an application to be able to store and retrieve credentials from a remote
device.
@hide -->
<permission android:name="android.permission.HYBRID_CREDENTIAL_PROVIDER"
android:protectionLevel="signature|privileged" />
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->

View File

@@ -48,6 +48,7 @@ import android.service.credentials.CredentialProviderInfo;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.infra.AbstractMasterSystemService;
@@ -71,6 +72,16 @@ public final class CredentialManagerService
private static final String TAG = "CredManSysService";
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,
@@ -78,6 +89,20 @@ public final class CredentialManagerService
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> credentialProviderInfos =
CredentialProviderInfo.getAvailableSystemServices(mContext, resolvedUserId);
credentialProviderInfos.forEach(info -> {
services.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
});
return services;
}
@Override
@@ -103,8 +128,10 @@ public final class CredentialManagerService
}
@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<>();
@@ -153,13 +180,24 @@ public final class CredentialManagerService
// 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 void runForUser(@NonNull final Consumer<CredentialManagerServiceImpl> c) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
getAllCredentialProviderServicesLocked(userId);
for (CredentialManagerServiceImpl s : services) {
c.accept(s);
}
@@ -169,6 +207,19 @@ public final class CredentialManagerService
}
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getAllCredentialProviderServicesLocked(
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;
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessions(
@@ -231,14 +282,10 @@ public final class CredentialManagerService
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
providerGetSession -> {
providerGetSession
.getRemoteCredentialService()
.onBeginGetCredential(
(BeginGetCredentialRequest)
providerGetSession.getProviderRequest(),
/* callback= */ providerGetSession);
});
providerGetSession -> providerGetSession
.getRemoteCredentialService().onBeginGetCredential(
(BeginGetCredentialRequest) providerGetSession.getProviderRequest(),
/*callback=*/providerGetSession));
return cancelTransport;
}
@@ -279,14 +326,12 @@ public final class CredentialManagerService
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
providerCreateSession -> {
providerCreateSession
.getRemoteCredentialService()
.onCreateCredential(
(BeginCreateCredentialRequest)
providerCreateSession.getProviderRequest(),
/* callback= */ providerCreateSession);
});
providerCreateSession -> providerCreateSession
.getRemoteCredentialService()
.onCreateCredential(
(BeginCreateCredentialRequest)
providerCreateSession.getProviderRequest(),
/* callback= */ providerCreateSession));
return cancelTransport;
}

View File

@@ -58,7 +58,18 @@ public final class CredentialManagerServiceImpl extends
return mInfo.getServiceInfo().getComponentName();
}
@Override // from PerUserSystemService
CredentialManagerServiceImpl(
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId, CredentialProviderInfo providerInfo) {
super(master, lock, userId);
Log.i(TAG, "in CredentialManagerServiceImpl constructed with system constructor: "
+ providerInfo.isSystemProvider()
+ " , " + providerInfo.getServiceInfo() == null ? "" :
providerInfo.getServiceInfo().getComponentName().flattenToString());
mInfo = providerInfo;
}
@Override // from PerUserSystemService when a new setting based service is to be created
@GuardedBy("mLock")
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws PackageManager.NameNotFoundException {
@@ -71,7 +82,9 @@ public final class CredentialManagerServiceImpl extends
Log.i(TAG, "newServiceInfoLocked with null mInfo , "
+ serviceComponent.getPackageName());
}
mInfo = new CredentialProviderInfo(getContext(), serviceComponent, mUserId);
mInfo = new CredentialProviderInfo(
getContext(), serviceComponent,
mUserId, /*isSystemProvider=*/false);
return mInfo.getServiceInfo();
}