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

Commit f4f5352f authored by Utkarsh Nigam's avatar Utkarsh Nigam
Browse files

Honor opting out from caller with only EXECUTE_APP_FUNCTIONS permissions.

Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: Verified by running locally. Will add CTS in next cl.
Bug: 360864791

Change-Id: I4dae65b5b9c978034252aa3db92c9f4a56eb3d43
parent ac60bc85
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import java.util.Objects;
public class AppFunctionStaticMetadataHelper {
    public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
    public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
    public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
        "restrictCallersWithExecuteAppFunctions";

    public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
    public static final String PROPERTY_FUNCTION_ID = "functionId";
+37 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.appfunctions;

import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** Executors for App function operations. */
public final class AppFunctionExecutors {

    /** Executor for operations that do not need to block. */
    public static final Executor THREAD_POOL_EXECUTOR =
            new ThreadPoolExecutor(
                    /* corePoolSize= */ Runtime.getRuntime().availableProcessors(),
                    /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
                    /* keepAliveTime= */ 0L,
                    /* unit= */ TimeUnit.SECONDS,
                    /* workQueue= */ new LinkedBlockingQueue<>());

    private AppFunctionExecutors() {}
}
+44 −41
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.appfunctions;

import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;

import android.annotation.NonNull;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
@@ -51,14 +53,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    public AppFunctionManagerServiceImpl(@NonNull Context context) {
        this(
                new RemoteServiceCallerImpl<>(
                        context,
                        IAppFunctionService.Stub::asInterface,
                        new ThreadPoolExecutor(
                                /* corePoolSize= */ Runtime.getRuntime().availableProcessors(),
                                /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
                                /* keepAliveTime= */ 0L,
                                /* unit= */ TimeUnit.SECONDS,
                                /* workQueue= */ new LinkedBlockingQueue<>())),
                        context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
                new CallerValidatorImpl(context),
                new ServiceHelperImpl(context),
                new ServiceConfigImpl());
@@ -124,18 +119,25 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
            return;
        }

        if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
                validatedCallingPackage, targetPackageName)) {
        mCallerValidator
                .verifyCallerCanExecuteAppFunction(
                        validatedCallingPackage,
                        targetPackageName,
                        requestInternal.getClientRequest().getFunctionIdentifier())
                .thenAccept(
                        canExecute -> {
                            if (!canExecute) {
                                safeExecuteAppFunctionCallback.onResult(
                                        ExecuteAppFunctionResponse.newFailure(
                                                ExecuteAppFunctionResponse.RESULT_DENIED,
                            "Caller does not have permission to execute the appfunction",
                                                "Caller does not have permission to execute the"
                                                        + " appfunction",
                                                /* extras= */ null));
                                return;
                            }

                            Intent serviceIntent =
                mInternalServiceHelper.resolveAppFunctionService(targetPackageName, targetUser);
                                    mInternalServiceHelper.resolveAppFunctionService(
                                            targetPackageName, targetUser);
                            if (serviceIntent == null) {
                                safeExecuteAppFunctionCallback.onResult(
                                        ExecuteAppFunctionResponse.newFailure(
@@ -144,7 +146,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                                                /* extras= */ null));
                                return;
                            }

                            final long token = Binder.clearCallingIdentity();
                            try {
                                bindAppFunctionServiceUnchecked(
@@ -153,10 +154,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                                        targetUser,
                                        safeExecuteAppFunctionCallback,
                                        /* bindFlags= */ Context.BIND_AUTO_CREATE,
                    /* timeoutInMillis= */ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
                                        /* timeoutInMillis= */ mServiceConfig
                                                .getExecuteAppFunctionTimeoutMillis());
                            } finally {
                                Binder.restoreCallingIdentity(token);
                            }
                        });
    }

    private void bindAppFunctionServiceUnchecked(
+6 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.os.UserHandle;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;

/**
 * Interface for validating that the caller has the correct privilege to call an AppFunctionManager
@@ -65,10 +66,13 @@ public interface CallerValidator {
     *
     * @param callerPackageName The calling package (as previously validated).
     * @param targetPackageName The package that owns the app function to execute.
     * @param functionId The id of the app function to execute.
     * @return Whether the caller can execute the specified app function.
     */
    boolean verifyCallerCanExecuteAppFunction(
            @NonNull String callerPackageName, @NonNull String targetPackageName);
    AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
            @NonNull String callerPackageName,
            @NonNull String targetPackageName,
            @NonNull String functionId);

    /**
     * Checks if the user is organization managed.
+64 −6
Original line number Diff line number Diff line
@@ -16,19 +16,30 @@

package com.android.server.appfunctions;

import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;

import android.Manifest;
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByDocumentIdRequest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;

import java.util.Objects;
import com.android.internal.infra.AndroidFuture;

/* Validates that caller has the correct privilege to call an AppFunctionManager Api. */
class CallerValidatorImpl implements CallerValidator {
@@ -76,11 +87,12 @@ class CallerValidatorImpl implements CallerValidator {
                Manifest.permission.EXECUTE_APP_FUNCTIONS
            },
            conditional = true)
    // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller.
    public boolean verifyCallerCanExecuteAppFunction(
            @NonNull String callerPackageName, @NonNull String targetPackageName) {
    public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
            @NonNull String callerPackageName,
            @NonNull String targetPackageName,
            @NonNull String functionId) {
        if (callerPackageName.equals(targetPackageName)) {
            return true;
            return AndroidFuture.completedFuture(true);
        }

        int pid = Binder.getCallingPid();
@@ -89,10 +101,56 @@ class CallerValidatorImpl implements CallerValidator {
                mContext.checkPermission(
                                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid)
                        == PackageManager.PERMISSION_GRANTED;

        if (hasTrustedExecutionPermission) {
            return AndroidFuture.completedFuture(true);
        }

        boolean hasExecutionPermission =
                mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid)
                        == PackageManager.PERMISSION_GRANTED;
        return hasExecutionPermission || hasTrustedExecutionPermission;

        final long token = Binder.clearCallingIdentity();
        try {
            FutureAppSearchSession futureAppSearchSession =
                    new FutureAppSearchSessionImpl(
                            mContext.getSystemService(AppSearchManager.class),
                            THREAD_POOL_EXECUTOR,
                            new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());

            String documentId = getDocumentIdForAppFunction(targetPackageName, functionId);

            return futureAppSearchSession
                    .getByDocumentId(
                            new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE)
                                    .addIds(documentId)
                                    .build())
                    .thenApply(
                            batchResult ->
                                    getGenericDocumentFromBatchResult(batchResult, documentId))
                    .thenApply(
                            CallerValidatorImpl::getRestrictCallersWithExecuteAppFunctionsProperty)
                    .thenApply(
                            restrictCallersWithExecuteAppFunctions ->
                                    !restrictCallersWithExecuteAppFunctions
                                            && hasExecutionPermission);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private static GenericDocument getGenericDocumentFromBatchResult(
            AppSearchBatchResult<String, GenericDocument> result, String documentId) {
        if (result.isSuccess()) {
            return result.getSuccesses().get(documentId);
        }
        throw new IllegalArgumentException("No document in the result for id: " + documentId);
    }

    private static boolean getRestrictCallersWithExecuteAppFunctionsProperty(
            GenericDocument genericDocument) {
        return genericDocument.getPropertyBoolean(
                STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS);
    }

    @Override