Loading src/com/android/settings/accounts/AccountTypePreferenceLoader.java +55 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; Loading @@ -46,6 +50,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; import java.util.Set; /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. Loading Loading @@ -83,6 +89,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { Set<String> fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the Loading @@ -99,6 +106,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); // Ignore Fragments provided dynamically, as these are coming from external // applications which must not have access to internal Settings' fragments. // These preferences are rendered into Settings, so they also won't have access // to their own Fragments, meaning there is no acceptable usage of // android:fragment here. filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); Loading Loading @@ -186,6 +199,48 @@ public class AccountTypePreferenceLoader { } } // Build allowlist from existing Fragments in PreferenceGroup @VisibleForTesting Set<String> generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { Set<String> fragmentAllowList = new ArraySet<>(); if (prefs == null) { return fragmentAllowList; } for (int i = 0; i < prefs.getPreferenceCount(); i++) { Preference pref = prefs.getPreference(i); if (pref instanceof PreferenceGroup) { fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); } String fragmentName = pref.getFragment(); if (!TextUtils.isEmpty(fragmentName)) { fragmentAllowList.add(fragmentName); } } return fragmentAllowList; } // Block clicks on any Preference with android:fragment that is not contained in the allowlist @VisibleForTesting void filterBlockedFragments(@Nullable PreferenceGroup prefs, @NonNull Set<String> allowedFragments) { if (prefs == null) { return; } for (int i = 0; i < prefs.getPreferenceCount(); i++) { Preference pref = prefs.getPreference(i); if (pref instanceof PreferenceGroup) { filterBlockedFragments((PreferenceGroup) pref, allowedFragments); } String fragmentName = pref.getFragment(); if (fragmentName != null && !allowedFragments.contains(fragmentName)) { pref.setOnPreferenceClickListener(preference -> true); } } } /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the Loading tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java +124 −6 Original line number Diff line number Diff line Loading @@ -16,9 +16,13 @@ package com.android.settings.accounts; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -30,6 +34,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceGroup; Loading @@ -51,9 +56,13 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; import java.util.Set; @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAccountManager.class, ShadowContentResolver.class, }) public class AccountTypePreferenceLoaderTest { Loading @@ -63,6 +72,8 @@ public class AccountTypePreferenceLoaderTest { private PreferenceFragmentCompat mPreferenceFragment; @Mock private PackageManager mPackageManager; @Mock private PreferenceManager mManager; private Context mContext; private Account mAccount; Loading Loading @@ -91,18 +102,16 @@ public class AccountTypePreferenceLoaderTest { } @Test @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class}) public void updatePreferenceIntents_shouldRunRecursively() { final PreferenceManager preferenceManager = mock(PreferenceManager.class); // Top level PreferenceGroup prefRoot = spy(new PreferenceScreen(mContext, null)); when(prefRoot.getPreferenceManager()).thenReturn(preferenceManager); when(prefRoot.getPreferenceManager()).thenReturn(mManager); Preference pref1 = mock(Preference.class); PreferenceGroup prefGroup2 = spy(new PreferenceScreen(mContext, null)); when(prefGroup2.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup2.getPreferenceManager()).thenReturn(mManager); Preference pref3 = mock(Preference.class); PreferenceGroup prefGroup4 = spy(new PreferenceScreen(mContext, null)); when(prefGroup4.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup4.getPreferenceManager()).thenReturn(mManager); prefRoot.addPreference(pref1); prefRoot.addPreference(prefGroup2); prefRoot.addPreference(pref3); Loading @@ -114,7 +123,7 @@ public class AccountTypePreferenceLoaderTest { prefGroup2.addPreference(pref21); prefGroup2.addPreference(pref22); PreferenceGroup prefGroup41 = spy(new PreferenceScreen(mContext, null)); when(prefGroup41.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup41.getPreferenceManager()).thenReturn(mManager); Preference pref42 = mock(Preference.class); prefGroup4.addPreference(prefGroup41); prefGroup4.addPreference(pref42); Loading @@ -132,4 +141,113 @@ public class AccountTypePreferenceLoaderTest { verify(mPrefLoader).updatePreferenceIntents(prefGroup4, acctType, mAccount); verify(mPrefLoader).updatePreferenceIntents(prefGroup41, acctType, mAccount); } @Test public void generateFragmentAllowlist_nullPrefGroup_emptyList() { Set<String> allowed = mPrefLoader.generateFragmentAllowlist(null); assertThat(allowed).isEmpty(); } @Test public void generateFragmentAllowlist_simpleGroupNoFragment_emptyList() { Preference pref = new Preference(mContext); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(screen); assertThat(allowed).isEmpty(); } @Test public void generateFragmentAllowlist_simpleGroupOneFragment_populatedList() { Preference pref = new Preference(mContext); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(screen); assertThat(allowed).isNotEmpty(); } @Test public void generateFragmentAllowlist_nestedGroupWithFragments_populatedList() { Preference pref = new Preference(mContext); pref.setFragment("test"); PreferenceScreen nested = spy(new PreferenceScreen(mContext, null)); PreferenceScreen parent = spy(new PreferenceScreen(mContext, null)); when(nested.getPreferenceManager()).thenReturn(mManager); when(parent.getPreferenceManager()).thenReturn(mManager); parent.addPreference(nested); nested.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(parent); assertThat(allowed).isNotEmpty(); } @Test public void filterBlockedFragments_nullPrefGroup_noop() { // verify no NPE mPrefLoader.filterBlockedFragments(null, new ArraySet<>()); } @Test public void filterBlockedFragments_simplePrefGroupNoFragment_noop() { Preference pref = spy(new Preference(mContext)); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, new ArraySet<>()); verify(screen, never()).setOnPreferenceClickListener(any()); verify(pref, never()).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_simplePrefGroupWithAllowedFragment_noop() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, Set.of("test")); verify(screen, never()).setOnPreferenceClickListener(any()); verify(pref, never()).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_simplePrefGroupNoMatchFragment_overrideClick() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, new ArraySet<>()); verify(pref).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_nestedPrefGroupWithNoMatchFragment_overrideClick() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen nested = spy(new PreferenceScreen(mContext, null)); PreferenceScreen parent = spy(new PreferenceScreen(mContext, null)); when(nested.getPreferenceManager()).thenReturn(mManager); when(parent.getPreferenceManager()).thenReturn(mManager); parent.addPreference(nested); nested.addPreference(pref); mPrefLoader.filterBlockedFragments(parent, Set.of("nomatch", "other")); verify(pref).setOnPreferenceClickListener(any()); } } Loading
src/com/android/settings/accounts/AccountTypePreferenceLoader.java +55 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; Loading @@ -46,6 +50,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; import java.util.Set; /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. Loading Loading @@ -83,6 +89,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { Set<String> fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the Loading @@ -99,6 +106,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); // Ignore Fragments provided dynamically, as these are coming from external // applications which must not have access to internal Settings' fragments. // These preferences are rendered into Settings, so they also won't have access // to their own Fragments, meaning there is no acceptable usage of // android:fragment here. filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); Loading Loading @@ -186,6 +199,48 @@ public class AccountTypePreferenceLoader { } } // Build allowlist from existing Fragments in PreferenceGroup @VisibleForTesting Set<String> generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { Set<String> fragmentAllowList = new ArraySet<>(); if (prefs == null) { return fragmentAllowList; } for (int i = 0; i < prefs.getPreferenceCount(); i++) { Preference pref = prefs.getPreference(i); if (pref instanceof PreferenceGroup) { fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); } String fragmentName = pref.getFragment(); if (!TextUtils.isEmpty(fragmentName)) { fragmentAllowList.add(fragmentName); } } return fragmentAllowList; } // Block clicks on any Preference with android:fragment that is not contained in the allowlist @VisibleForTesting void filterBlockedFragments(@Nullable PreferenceGroup prefs, @NonNull Set<String> allowedFragments) { if (prefs == null) { return; } for (int i = 0; i < prefs.getPreferenceCount(); i++) { Preference pref = prefs.getPreference(i); if (pref instanceof PreferenceGroup) { filterBlockedFragments((PreferenceGroup) pref, allowedFragments); } String fragmentName = pref.getFragment(); if (fragmentName != null && !allowedFragments.contains(fragmentName)) { pref.setOnPreferenceClickListener(preference -> true); } } } /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the Loading
tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java +124 −6 Original line number Diff line number Diff line Loading @@ -16,9 +16,13 @@ package com.android.settings.accounts; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -30,6 +34,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceGroup; Loading @@ -51,9 +56,13 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; import java.util.Set; @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAccountManager.class, ShadowContentResolver.class, }) public class AccountTypePreferenceLoaderTest { Loading @@ -63,6 +72,8 @@ public class AccountTypePreferenceLoaderTest { private PreferenceFragmentCompat mPreferenceFragment; @Mock private PackageManager mPackageManager; @Mock private PreferenceManager mManager; private Context mContext; private Account mAccount; Loading Loading @@ -91,18 +102,16 @@ public class AccountTypePreferenceLoaderTest { } @Test @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class}) public void updatePreferenceIntents_shouldRunRecursively() { final PreferenceManager preferenceManager = mock(PreferenceManager.class); // Top level PreferenceGroup prefRoot = spy(new PreferenceScreen(mContext, null)); when(prefRoot.getPreferenceManager()).thenReturn(preferenceManager); when(prefRoot.getPreferenceManager()).thenReturn(mManager); Preference pref1 = mock(Preference.class); PreferenceGroup prefGroup2 = spy(new PreferenceScreen(mContext, null)); when(prefGroup2.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup2.getPreferenceManager()).thenReturn(mManager); Preference pref3 = mock(Preference.class); PreferenceGroup prefGroup4 = spy(new PreferenceScreen(mContext, null)); when(prefGroup4.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup4.getPreferenceManager()).thenReturn(mManager); prefRoot.addPreference(pref1); prefRoot.addPreference(prefGroup2); prefRoot.addPreference(pref3); Loading @@ -114,7 +123,7 @@ public class AccountTypePreferenceLoaderTest { prefGroup2.addPreference(pref21); prefGroup2.addPreference(pref22); PreferenceGroup prefGroup41 = spy(new PreferenceScreen(mContext, null)); when(prefGroup41.getPreferenceManager()).thenReturn(preferenceManager); when(prefGroup41.getPreferenceManager()).thenReturn(mManager); Preference pref42 = mock(Preference.class); prefGroup4.addPreference(prefGroup41); prefGroup4.addPreference(pref42); Loading @@ -132,4 +141,113 @@ public class AccountTypePreferenceLoaderTest { verify(mPrefLoader).updatePreferenceIntents(prefGroup4, acctType, mAccount); verify(mPrefLoader).updatePreferenceIntents(prefGroup41, acctType, mAccount); } @Test public void generateFragmentAllowlist_nullPrefGroup_emptyList() { Set<String> allowed = mPrefLoader.generateFragmentAllowlist(null); assertThat(allowed).isEmpty(); } @Test public void generateFragmentAllowlist_simpleGroupNoFragment_emptyList() { Preference pref = new Preference(mContext); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(screen); assertThat(allowed).isEmpty(); } @Test public void generateFragmentAllowlist_simpleGroupOneFragment_populatedList() { Preference pref = new Preference(mContext); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(screen); assertThat(allowed).isNotEmpty(); } @Test public void generateFragmentAllowlist_nestedGroupWithFragments_populatedList() { Preference pref = new Preference(mContext); pref.setFragment("test"); PreferenceScreen nested = spy(new PreferenceScreen(mContext, null)); PreferenceScreen parent = spy(new PreferenceScreen(mContext, null)); when(nested.getPreferenceManager()).thenReturn(mManager); when(parent.getPreferenceManager()).thenReturn(mManager); parent.addPreference(nested); nested.addPreference(pref); Set<String> allowed = mPrefLoader.generateFragmentAllowlist(parent); assertThat(allowed).isNotEmpty(); } @Test public void filterBlockedFragments_nullPrefGroup_noop() { // verify no NPE mPrefLoader.filterBlockedFragments(null, new ArraySet<>()); } @Test public void filterBlockedFragments_simplePrefGroupNoFragment_noop() { Preference pref = spy(new Preference(mContext)); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, new ArraySet<>()); verify(screen, never()).setOnPreferenceClickListener(any()); verify(pref, never()).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_simplePrefGroupWithAllowedFragment_noop() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, Set.of("test")); verify(screen, never()).setOnPreferenceClickListener(any()); verify(pref, never()).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_simplePrefGroupNoMatchFragment_overrideClick() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); when(screen.getPreferenceManager()).thenReturn(mManager); screen.addPreference(pref); mPrefLoader.filterBlockedFragments(screen, new ArraySet<>()); verify(pref).setOnPreferenceClickListener(any()); } @Test public void filterBlockedFragments_nestedPrefGroupWithNoMatchFragment_overrideClick() { Preference pref = spy(new Preference(mContext)); pref.setFragment("test"); PreferenceScreen nested = spy(new PreferenceScreen(mContext, null)); PreferenceScreen parent = spy(new PreferenceScreen(mContext, null)); when(nested.getPreferenceManager()).thenReturn(mManager); when(parent.getPreferenceManager()).thenReturn(mManager); parent.addPreference(nested); nested.addPreference(pref); mPrefLoader.filterBlockedFragments(parent, Set.of("nomatch", "other")); verify(pref).setOnPreferenceClickListener(any()); } }