summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-09-12 00:35:53 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-12 00:35:53 +0000
commit570e23fed99752e2a1eb46bbaf7c8da7efc94df8 (patch)
tree147f6d34f632af86f6682e5223186884788a1f8d
parentea031114de08b8ef86365152e0d46de15adfef02 (diff)
parentf4f5352f2750add51ce2f1e0b2b0fccae8dfc14a (diff)
Merge "Honor opting out from caller with only EXECUTE_APP_FUNCTIONS permissions." into main
-rw-r--r--core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java2
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java37
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java85
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java8
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java70
5 files changed, 153 insertions, 49 deletions
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
index 085e0a47d356..a23f842e6eeb 100644
--- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -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";
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
new file mode 100644
index 000000000000..c3b7087a44c3
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -0,0 +1,37 @@
+/*
+ * 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() {}
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index f04bd9f8ccc0..f83269f86cd2 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -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,39 +119,47 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
return;
}
- if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
- validatedCallingPackage, targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute the appfunction",
- /* extras= */ null));
- return;
- }
-
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
-
- final long token = Binder.clearCallingIdentity();
- try {
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE,
- /* timeoutInMillis= */ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ 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",
+ /* extras= */ null));
+ return;
+ }
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Cannot find the target service.",
+ /* extras= */ null));
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE,
+ /* timeoutInMillis= */ mServiceConfig
+ .getExecuteAppFunctionTimeoutMillis());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ });
}
private void bindAppFunctionServiceUnchecked(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index ca43dfaf87ac..e7a861e0a0dc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -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.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 618a5aedd9bb..99984384f455 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -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