Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 03e59a08 authored by Chris Antol's avatar Chris Antol Committed by Android (Google) Code Review
Browse files

Merge "Ignore fragment attr from ext authenticator resource" into main

parents b7415373 2cb9b10e
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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
@@ -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);
@@ -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
+124 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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 {

@@ -63,6 +72,8 @@ public class AccountTypePreferenceLoaderTest {
    private PreferenceFragmentCompat mPreferenceFragment;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private PreferenceManager mManager;

    private Context mContext;
    private Account mAccount;
@@ -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);
@@ -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);
@@ -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());
    }
}