diff options
| author | 2024-09-12 00:35:53 +0000 | |
|---|---|---|
| committer | 2024-09-12 00:35:53 +0000 | |
| commit | 570e23fed99752e2a1eb46bbaf7c8da7efc94df8 (patch) | |
| tree | 147f6d34f632af86f6682e5223186884788a1f8d | |
| parent | ea031114de08b8ef86365152e0d46de15adfef02 (diff) | |
| parent | f4f5352f2750add51ce2f1e0b2b0fccae8dfc14a (diff) | |
Merge "Honor opting out from caller with only EXECUTE_APP_FUNCTIONS permissions." into main
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 |