This commit is contained in:
Mateus Azis 2025-07-24 08:04:58 -07:00 committed by GitHub
commit 8677177cf7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 7 deletions

View File

@ -16,6 +16,9 @@
package io.grpc.binder; package io.grpc.binder;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
@ -32,6 +35,9 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.CheckReturnValue;
import io.grpc.ExperimentalApi; import io.grpc.ExperimentalApi;
import io.grpc.Status; import io.grpc.Status;
@ -333,6 +339,9 @@ public final class SecurityPolicies {
* Creates a {@link SecurityPolicy} that allows access if and only if *all* of the specified * Creates a {@link SecurityPolicy} that allows access if and only if *all* of the specified
* {@code securityPolicies} allow access. * {@code securityPolicies} allow access.
* *
* <p>If any of the policies is an {@link AsyncSecurityPolicy}, then all policies may be evaluated
* concurrently to speed up the success scenario.
*
* @param securityPolicies the security policies that all must allow access. * @param securityPolicies the security policies that all must allow access.
* @throws NullPointerException if any of the inputs are {@code null}. * @throws NullPointerException if any of the inputs are {@code null}.
* @throws IllegalArgumentException if {@code securityPolicies} is empty. * @throws IllegalArgumentException if {@code securityPolicies} is empty.
@ -341,10 +350,17 @@ public final class SecurityPolicies {
Preconditions.checkNotNull(securityPolicies, "securityPolicies"); Preconditions.checkNotNull(securityPolicies, "securityPolicies");
Preconditions.checkArgument(securityPolicies.length > 0, "securityPolicies must not be empty"); Preconditions.checkArgument(securityPolicies.length > 0, "securityPolicies must not be empty");
return allOfSecurityPolicy(securityPolicies); boolean anyAsync =
Arrays
.stream(securityPolicies)
.anyMatch(policy -> policy instanceof AsyncSecurityPolicy);
return anyAsync
? allOfSecurityPolicyAsync(securityPolicies)
: allOfSecurityPolicySync(securityPolicies);
} }
private static SecurityPolicy allOfSecurityPolicy(SecurityPolicy... securityPolicies) { private static SecurityPolicy allOfSecurityPolicySync(SecurityPolicy... securityPolicies) {
return new SecurityPolicy() { return new SecurityPolicy() {
@Override @Override
public Status checkAuthorization(int uid) { public Status checkAuthorization(int uid) {
@ -360,6 +376,33 @@ public final class SecurityPolicies {
}; };
} }
private static SecurityPolicy allOfSecurityPolicyAsync(SecurityPolicy... securityPolicies) {
return new AsyncSecurityPolicy() {
@Override
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
ImmutableList<ListenableFuture<Status>> allStatuses =
Arrays.stream(securityPolicies).map(policy -> {
AsyncSecurityPolicy asyncPolicy =
policy instanceof AsyncSecurityPolicy ? (AsyncSecurityPolicy) policy :
new AsyncSecurityPolicy() {
@Override
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
return immediateFuture(policy.checkAuthorization(uid));
}
};
return asyncPolicy.checkAuthorizationAsync(uid);
}).collect(toImmutableList());
ListenableFuture<List<Status>> futureStatuses = Futures.allAsList(allStatuses);
return Futures
.transform(
futureStatuses,statuses ->
statuses.stream().filter(status -> !status.isOk()).findFirst().orElse(Status.OK),
MoreExecutors.directExecutor());
}
};
}
/** /**
* Creates a {@link SecurityPolicy} that allows access if *any* of the specified {@code * Creates a {@link SecurityPolicy} that allows access if *any* of the specified {@code
* securityPolicies} allow access. * securityPolicies} allow access.

View File

@ -22,6 +22,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static org.robolectric.Shadows.shadowOf; import static org.robolectric.Shadows.shadowOf;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
@ -35,9 +36,11 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Status; import io.grpc.Status;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -534,6 +537,29 @@ public final class SecurityPoliciesTest {
.contains("Not allowed SecurityPolicy"); .contains("Not allowed SecurityPolicy");
} }
@Test
public void testAllOfAsync_succeedsIfAllSecurityPoliciesAllowed() {
policy =
SecurityPolicies.allOf(
SecurityPolicies.internalOnly(),
makeAsyncPolicy(uid -> immediateFuture(Status.OK)));
assertThat(policy.checkAuthorization(MY_UID).getCode()).isEqualTo(Status.OK.getCode());
}
@Test
public void testAllOfAsync_failsIfOneSecurityPoliciesNotAllowed() {
policy =
SecurityPolicies.allOf(
SecurityPolicies.internalOnly(),
makeAsyncPolicy(uid -> immediateFuture(Status.OK)),
makeAsyncPolicy(uid -> immediateFuture(Status.ABORTED)),
makeAsyncPolicy(uid -> immediateFuture(Status.INVALID_ARGUMENT)));
assertThat(policy.checkAuthorization(MY_UID).getCode())
.isEqualTo(Status.Code.ABORTED);
}
@Test @Test
public void testAnyOf_succeedsIfAnySecurityPoliciesAllowed() throws Exception { public void testAnyOf_succeedsIfAnySecurityPoliciesAllowed() throws Exception {
RecordingPolicy recordingPolicy = new RecordingPolicy(); RecordingPolicy recordingPolicy = new RecordingPolicy();
@ -703,4 +729,13 @@ public final class SecurityPoliciesTest {
private static byte[] getSha256Hash(Signature signature) { private static byte[] getSha256Hash(Signature signature) {
return Hashing.sha256().hashBytes(signature.toByteArray()).asBytes(); return Hashing.sha256().hashBytes(signature.toByteArray()).asBytes();
} }
private static AsyncSecurityPolicy makeAsyncPolicy(Function<Integer, ListenableFuture<Status>> statusProvider) {
return new AsyncSecurityPolicy() {
@Override
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
return statusProvider.apply(uid);
}
};
}
} }