diff options
2 files changed, 288 insertions, 2 deletions
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index a4adf5866f3d..fd8f20cabac7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -65,6 +65,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; import com.android.server.infra.AbstractMasterSystemService; @@ -101,6 +102,13 @@ public final class CredentialManagerService private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = "enable_credential_description_api"; + /** + * Value stored in autofill pref when credential provider is primary. This is + * used as a placeholder since a credman only provider will not have an + * autofill service. + */ + public static final String AUTOFILL_PLACEHOLDER_VALUE = "credential-provider"; + private final Context mContext; /** Cache of system service list per user id. */ @@ -194,6 +202,8 @@ public final class CredentialManagerService @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same // this.mLock protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) { + updateProvidersWhenPackageRemoved(mContext, packageName); + List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId); if (services == null) { return; @@ -216,8 +226,6 @@ public final class CredentialManagerService for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) { removeServiceFromCache(serviceToBeRemoved, userId); removeServiceFromSystemServicesCache(serviceToBeRemoved, userId); - removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName() - .flattenToString(), userId); CredentialDescriptionRegistry.forUser(userId) .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName()); } @@ -1110,4 +1118,101 @@ public final class CredentialManagerService mRequestSessions.get(userId).put(token, requestSession); } } + + /** Updates the list of providers when an app is uninstalled. */ + public static void updateProvidersWhenPackageRemoved(Context context, String packageName) { + // Get the current providers. + String rawProviders = + Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + UserHandle.myUserId()); + if (rawProviders == null) { + Slog.w(TAG, "settings key is null"); + return; + } + + // Remove any providers from the primary setting that contain the package name + // being removed. + Set<String> primaryProviders = + getStoredProviders(rawProviders, packageName); + if (!Settings.Secure.putString( + context.getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + String.join(":", primaryProviders))) { + Slog.w(TAG, "Failed to remove primary package: " + packageName); + return; + } + + // Read the autofill provider so we don't accidentally erase it. + String autofillProvider = + Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, + UserHandle.myUserId()); + + // If there is an autofill provider and it is the placeholder indicating + // that the currently selected primary provider does not support autofill + // then we should wipe the setting to keep it in sync. + if (autofillProvider != null && primaryProviders.isEmpty()) { + if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) { + if (!Settings.Secure.putString( + context.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, + "")) { + Slog.w(TAG, "Failed to remove autofill package: " + packageName); + } + } else { + // If the existing autofill provider is from the app being removed + // then erase the autofill service setting. + ComponentName cn = ComponentName.unflattenFromString(autofillProvider); + if (cn != null && cn.getPackageName().equals(packageName)) { + if (!Settings.Secure.putString( + context.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, + "")) { + Slog.w(TAG, "Failed to remove autofill package: " + packageName); + } + } + } + } + + // Read the credential providers to remove any reference of the removed app. + String rawCredentialProviders = + Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE, + UserHandle.myUserId()); + + // Remove any providers that belong to the removed app. + Set<String> credentialProviders = + getStoredProviders(rawCredentialProviders, packageName); + if (!Settings.Secure.putString( + context.getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE, + String.join(":", credentialProviders))) { + Slog.w(TAG, "Failed to remove secondary package: " + packageName); + } + } + + /** Gets the list of stored providers from a string removing any mention of package name. */ + public static Set<String> getStoredProviders(String rawProviders, String packageName) { + // If the app being removed matches any of the package names from + // this list then don't add it in the output. + Set<String> providers = new HashSet<>(); + for (String rawComponentName : rawProviders.split(":")) { + if (TextUtils.isEmpty(rawComponentName) + || rawComponentName.equals("null")) { + Slog.d(TAG, "provider component name is empty or null"); + continue; + } + + ComponentName cn = ComponentName.unflattenFromString(rawComponentName); + if (cn != null && !cn.getPackageName().equals(packageName)) { + providers.add(cn.flattenToString()); + } + } + + return providers; + } } diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java new file mode 100644 index 000000000000..fd1abff8610b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2023 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 com.google.common.truth.Truth.assertThat; + +import android.content.ComponentName; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.security.cert.CertificateException; +import java.util.HashSet; +import java.util.Set; + +/** atest FrameworksServicesTests:com.android.server.credentials.CredentialManagerServiceTest */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class CredentialManagerServiceTest { + + Context mContext = null; + + @Before + public void setUp() throws CertificateException { + mContext = ApplicationProvider.getApplicationContext(); + } + + @Test + public void getStoredProviders_emptyValue_success() { + Set<String> providers = CredentialManagerService.getStoredProviders("", ""); + assertThat(providers.size()).isEqualTo(0); + } + + @Test + public void getStoredProviders_success() { + Set<String> providers = + CredentialManagerService.getStoredProviders( + "com.example.test/.TestActivity:com.example.test/.TestActivity2:" + + "com.example.test2/.TestActivity:blank", + "com.example.test"); + assertThat(providers.size()).isEqualTo(1); + assertThat(providers.contains("com.example.test2/com.example.test2.TestActivity")).isTrue(); + } + + @Test + public void onProviderRemoved_success() { + setSettingsKey( + Settings.Secure.AUTOFILL_SERVICE, + CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE); + setSettingsKey( + Settings.Secure.CREDENTIAL_SERVICE, + "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity"); + setSettingsKey( + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + "com.example.test/com.example.test.TestActivity"); + + CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); + + assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); + assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) + .isEqualTo("com.example.test2/com.example.test2.TestActivity"); + assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo(""); + } + + @Test + public void onProviderRemoved_notPrimaryRemoved_success() { + final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity"; + final String testCredentialValue = + "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity"; + + setSettingsKey( + Settings.Secure.AUTOFILL_SERVICE, + CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE); + setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); + setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); + + CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); + + // Since the provider removed was not a primary provider then we should do nothing. + assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)) + .isEqualTo(CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE); + assertCredentialPropertyEquals( + getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue); + assertCredentialPropertyEquals( + getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY), + testCredentialPrimaryValue); + } + + @Test + public void onProviderRemoved_isAlsoAutofillProvider_success() { + setSettingsKey( + Settings.Secure.AUTOFILL_SERVICE, + "com.example.test/com.example.test.AutofillProvider"); + setSettingsKey( + Settings.Secure.CREDENTIAL_SERVICE, + "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity"); + setSettingsKey( + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + "com.example.test/com.example.test.TestActivity"); + + CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); + + assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); + assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) + .isEqualTo("com.example.test2/com.example.test2.TestActivity"); + assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo(""); + } + + @Test + public void onProviderRemoved_notPrimaryRemoved_isAlsoAutofillProvider_success() { + final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity"; + final String testCredentialValue = + "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity"; + final String testAutofillValue = "com.example.test/com.example.test.TestAutofillActivity"; + + setSettingsKey(Settings.Secure.AUTOFILL_SERVICE, testAutofillValue); + setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); + setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); + + CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); + + // Since the provider removed was not a primary provider then we should do nothing. + assertCredentialPropertyEquals( + getSettingsKey(Settings.Secure.AUTOFILL_SERVICE), testAutofillValue); + assertCredentialPropertyEquals( + getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue); + assertCredentialPropertyEquals( + getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY), + testCredentialPrimaryValue); + } + + private void assertCredentialPropertyEquals(String actualValue, String newValue) { + Set<ComponentName> actualValueSet = new HashSet<>(); + for (String rawComponentName : actualValue.split(":")) { + ComponentName cn = ComponentName.unflattenFromString(rawComponentName); + if (cn != null) { + actualValueSet.add(cn); + } + } + + Set<ComponentName> newValueSet = new HashSet<>(); + for (String rawComponentName : newValue.split(":")) { + ComponentName cn = ComponentName.unflattenFromString(rawComponentName); + if (cn != null) { + newValueSet.add(cn); + } + } + + assertThat(actualValueSet).isEqualTo(newValueSet); + } + + private void setSettingsKey(String key, String value) { + assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue(); + } + + private String getSettingsKey(String key) { + return Settings.Secure.getStringForUser( + mContext.getContentResolver(), key, UserHandle.myUserId()); + } +} |