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

Commit f762496b authored by Jason Monk's avatar Jason Monk
Browse files

Slices permission model

 - Launcher/assistant get access to all slices
 - Apps with uri access to access a specific slice
 - Apps without access get a permission granting slice
 - If the user authorizes access to the slice for the app
   then the app will be granted access to the app's slices
   (this happens through a temp grant in the service, and a
   full uri grant from the app the next time it binds)
 - Add a hint that apps to add to allow them to return different
   slices depending on the caller, this allows custom permission
   checks.

Test: runtest --path frameworks/base/services/tests/uiservices
Bug: 68751119
Change-Id: I8f8cd0182cfcbfba3f307e2eaba5aae6f6fbe214
parent 69995cdd
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -29,4 +29,6 @@ interface ISliceManager {
    void unpinSlice(String pkg, in Uri uri);
    boolean hasSliceAccess(String pkg);
    SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
    int checkSlicePermission(in Uri uri, String pkg, int pid, int uid);
    void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
}
+7 −0
Original line number Diff line number Diff line
@@ -155,6 +155,13 @@ public final class Slice implements Parcelable {
     * content associated with this slice.
     */
    public static final String HINT_SEE_MORE = "see_more";
    /**
     * A hint to tell the system that this slice cares about the return value of
     * {@link SliceProvider#getBindingPackage} and should not cache the result
     * for multiple apps.
     * @hide
     */
    public static final String HINT_CALLER_NEEDED = "caller_needed";
    /**
     * Key to retrieve an extra added to an intent when a control is changed.
     */
+63 −2
Original line number Diff line number Diff line
@@ -53,11 +53,33 @@ public class SliceManager {

    private static final String TAG = "SliceManager";

    /**
     * @hide
     */
    public static final String ACTION_REQUEST_SLICE_PERMISSION =
            "android.intent.action.REQUEST_SLICE_PERMISSION";

    private final ISliceManager mService;
    private final Context mContext;
    private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup =
            new ArrayMap<>();

    /**
     * Permission denied.
     * @hide
     */
    public static final int PERMISSION_DENIED = -1;
    /**
     * Permission granted.
     * @hide
     */
    public static final int PERMISSION_GRANTED = 0;
    /**
     * Permission just granted by the user, and should be granted uri permission as well.
     * @hide
     */
    public static final int PERMISSION_USER_GRANTED = 1;

    /**
     * @hide
     */
@@ -284,7 +306,7 @@ public class SliceManager {
            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                    new ArrayList<>(supportedSpecs));
            final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
            final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE,
                    null, extras);
            Bundle.setDefusable(res, true);
            if (res == null) {
@@ -342,7 +364,7 @@ public class SliceManager {
            extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                    new ArrayList<>(supportedSpecs));
            final Bundle res = provider.call(resolver.getPackageName(),
            final Bundle res = provider.call(mContext.getPackageName(),
                    SliceProvider.METHOD_MAP_INTENT, null, extras);
            if (res == null) {
                return null;
@@ -357,6 +379,45 @@ public class SliceManager {
        }
    }

    /**
     * Does the permission check to see if a caller has access to a specific slice.
     * @hide
     */
    public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) {
        try {
            if (pkg == null) {
                throw new SecurityException("No pkg specified");
            }
            int result = mService.checkSlicePermission(uri, pkg, pid, uid);
            if (result == PERMISSION_DENIED) {
                throw new SecurityException("User " + uid + " does not have slice permission for "
                        + uri + ".");
            }
            if (result == PERMISSION_USER_GRANTED) {
                // We just had a user grant of this permission and need to grant this to the app
                // permanently.
                mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
                        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Called by SystemUI to grant a slice permission after a dialog is shown.
     * @hide
     */
    public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) {
        try {
            mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Class that listens to changes in {@link Slice}s.
     */
+120 −25
Original line number Diff line number Diff line
@@ -15,13 +15,19 @@
 */
package android.app.slice;

import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -129,9 +135,41 @@ public abstract class SliceProvider extends ContentProvider {
     * @hide
     */
    public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
    /**
     * @hide
     */
    public static final String EXTRA_PKG = "pkg";
    /**
     * @hide
     */
    public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
    /**
     * @hide
     */
    public static final String EXTRA_OVERRIDE_PKG = "override_pkg";

    private static final boolean DEBUG = false;

    private String mBindingPkg;
    private SliceManager mSliceManager;

    /**
     * Return the package name of the caller that initiated the binding request
     * currently happening. The returned package will have been
     * verified to belong to the calling UID. Returns {@code null} if not
     * currently performing an {@link #onBindSlice(Uri, List)}.
     * @hide
     */
    public final @Nullable String getBindingPackage() {
        return mBindingPkg;
    }

    @Override
    public void attachInfo(Context context, ProviderInfo info) {
        super.attachInfo(context, info);
        mSliceManager = context.getSystemService(SliceManager.class);
    }

    /**
     * Implemented to create a slice. Will be called on the main thread.
     * <p>
@@ -262,28 +300,27 @@ public abstract class SliceProvider extends ContentProvider {
    public Bundle call(String method, String arg, Bundle extras) {
        if (method.equals(METHOD_SLICE)) {
            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                        "Slice binding requires the permission BIND_SLICE");
            }
            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);

            Slice s = handleBindSlice(uri, supportedSpecs);
            String callingPackage = getCallingPackage();
            if (extras.containsKey(EXTRA_OVERRIDE_PKG)) {
                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                    throw new SecurityException("Only the system can override calling pkg");
                }
                callingPackage = extras.getString(EXTRA_OVERRIDE_PKG);
            }
            Slice s = handleBindSlice(uri, supportedSpecs, callingPackage);
            Bundle b = new Bundle();
            b.putParcelable(EXTRA_SLICE, s);
            return b;
        } else if (method.equals(METHOD_MAP_INTENT)) {
            getContext().enforceCallingPermission(permission.BIND_SLICE,
                    "Slice binding requires the permission BIND_SLICE");
            Intent intent = extras.getParcelable(EXTRA_INTENT);
            if (intent == null) return null;
            Uri uri = onMapIntentToUri(intent);
            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
            Bundle b = new Bundle();
            if (uri != null) {
                Slice s = handleBindSlice(uri, supportedSpecs);
                Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage());
                b.putParcelable(EXTRA_SLICE, s);
            } else {
                b.putParcelable(EXTRA_SLICE, null);
@@ -291,20 +328,14 @@ public abstract class SliceProvider extends ContentProvider {
            return b;
        } else if (method.equals(METHOD_PIN)) {
            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                        "Slice binding requires the permission BIND_SLICE");
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Only the system can pin/unpin slices");
            }
            handlePinSlice(uri);
        } else if (method.equals(METHOD_UNPIN)) {
            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                        "Slice binding requires the permission BIND_SLICE");
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Only the system can pin/unpin slices");
            }
            handleUnpinSlice(uri);
        } else if (method.equals(METHOD_GET_DESCENDANTS)) {
@@ -370,14 +401,27 @@ public abstract class SliceProvider extends ContentProvider {
        }
    }

    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
            String callingPkg) {
        // This can be removed once Slice#bindSlice is removed and everyone is using
        // SliceManager#bindSlice.
        String pkg = callingPkg != null ? callingPkg
                : getContext().getPackageManager().getNameForUid(Binder.getCallingUid());
        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
            try {
                mSliceManager.enforceSlicePermission(sliceUri, pkg,
                        Binder.getCallingPid(), Binder.getCallingUid());
            } catch (SecurityException e) {
                return createPermissionSlice(getContext(), sliceUri, pkg);
            }
        }
        if (Looper.myLooper() == Looper.getMainLooper()) {
            return onBindSliceStrict(sliceUri, supportedSpecs);
            return onBindSliceStrict(sliceUri, supportedSpecs, pkg);
        } else {
            CountDownLatch latch = new CountDownLatch(1);
            Slice[] output = new Slice[1];
            Handler.getMain().post(() -> {
                output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
                output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg);
                latch.countDown();
            });
            try {
@@ -389,15 +433,66 @@ public abstract class SliceProvider extends ContentProvider {
        }
    }

    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
    /**
     * @hide
     */
    public static Slice createPermissionSlice(Context context, Uri sliceUri,
            String callingPackage) {
        return new Slice.Builder(sliceUri)
                .addAction(createPermissionIntent(context, sliceUri, callingPackage),
                        new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build())
                                .addText(getPermissionString(context, callingPackage), null)
                                .build())
                .addHints(Slice.HINT_LIST_ITEM)
                .build();
    }

    /**
     * @hide
     */
    public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
            String callingPackage) {
        Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
        intent.setComponent(new ComponentName("com.android.systemui",
                "com.android.systemui.SlicePermissionActivity"));
        intent.putExtra(EXTRA_BIND_URI, sliceUri);
        intent.putExtra(EXTRA_PKG, callingPackage);
        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
        // Unique pending intent.
        intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                .build());

        return PendingIntent.getActivity(context, 0, intent, 0);
    }

    /**
     * @hide
     */
    public static CharSequence getPermissionString(Context context, String callingPackage) {
        PackageManager pm = context.getPackageManager();
        try {
            return context.getString(
                    com.android.internal.R.string.slices_permission_request,
                    pm.getApplicationInfo(callingPackage, 0).loadLabel(pm),
                    context.getApplicationInfo().loadLabel(pm));
        } catch (NameNotFoundException e) {
            // This shouldn't be possible since the caller is verified.
            throw new RuntimeException("Unknown calling app", e);
        }
    }

    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs,
            String callingPackage) {
        ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
        try {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build());
            mBindingPkg = callingPackage;
            return onBindSlice(sliceUri, supportedSpecs);
        } finally {
            mBindingPkg = null;
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }
+5 −1
Original line number Diff line number Diff line
@@ -3192,6 +3192,10 @@
    <permission android:name="android.permission.BIND_APPWIDGET"
        android:protectionLevel="signature|privileged" />

    <!-- @hide Allows sysui to manage user grants of slice permissions. -->
    <permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS"
        android:protectionLevel="signature" />

    <!-- Allows an application to bind app's slices and get their
         content. This content will be surfaced to the
         user and not to leave the device.
Loading