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

Commit 570e23fe authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Honor opting out from caller with only EXECUTE_APP_FUNCTIONS permissions." into main

parents ea031114 f4f5352f
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