diff --git a/binder/src/main/java/io/grpc/binder/PeerUids.java b/binder/src/main/java/io/grpc/binder/PeerUids.java
index 4c4143166a1..f9a22e98cc1 100644
--- a/binder/src/main/java/io/grpc/binder/PeerUids.java
+++ b/binder/src/main/java/io/grpc/binder/PeerUids.java
@@ -22,6 +22,7 @@
import android.os.Build.VERSION_CODES;
import android.os.UserHandle;
import androidx.annotation.RequiresApi;
+import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Context;
import io.grpc.Contexts;
import io.grpc.ExperimentalApi;
@@ -29,6 +30,7 @@
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
+import io.grpc.Status;
import io.grpc.binder.internal.BinderTransport;
import javax.annotation.Nullable;
@@ -74,6 +76,38 @@ public static UserHandle getUserHandleForUid(PeerUid who) {
return UserHandle.getUserHandleForUid(who.getUid());
}
+ /**
+ * Tests whether a {@link SecurityPolicy} authorizes the given {@link PeerUid}.
+ *
+ *
This method does not replace the transport layer {@link ServerSecurityPolicy} required by
+ * all servers. Rather, it lets an application implement additional, higher layer security policy
+ * that's checked after the call itself is authorized by the lower layer. This could be done with
+ * a {@link ServerInterceptor} or in the RPC method implementation itself.
+ *
+ *
This method is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
+ * the caller provides a {@link PeerUid} instead of the raw integer uid.
+ */
+ public static Status checkAuthorization(SecurityPolicy securityPolicy, PeerUid who) {
+ return securityPolicy.checkAuthorization(who.getUid());
+ }
+
+ /**
+ * Tests whether a {@link AsyncSecurityPolicy} authorizes the given {@link PeerUid}.
+ *
+ *
This method does not replace the transport layer {@link ServerSecurityPolicy} required by
+ * all servers. Rather, it lets an application implement additional, higher layer security policy
+ * that's checked after the call itself is authorized by the lower layer. This could be done with
+ * a {@link ServerInterceptor} or in the RPC method implementation itself.
+ *
+ *
This method is equivalent to calling {@link
+ * AsyncSecurityPolicy#checkAuthorizationAsync(int)}, except the caller provides a {@link PeerUid}
+ * instead of the raw integer uid.
+ */
+ public static ListenableFuture checkAuthorizationAsync(
+ AsyncSecurityPolicy securityPolicy, PeerUid who) {
+ return securityPolicy.checkAuthorizationAsync(who.getUid());
+ }
+
/**
* Creates an interceptor that exposes the client's identity in the {@link Context} under {@link
* #REMOTE_PEER}.
diff --git a/binder/src/test/java/io/grpc/binder/PeerUidsTest.java b/binder/src/test/java/io/grpc/binder/PeerUidsTest.java
index f87f7bd56f1..c3d68c9d723 100644
--- a/binder/src/test/java/io/grpc/binder/PeerUidsTest.java
+++ b/binder/src/test/java/io/grpc/binder/PeerUidsTest.java
@@ -19,9 +19,13 @@
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.ManagedChannel;
@@ -31,6 +35,8 @@
import io.grpc.ServerInterceptors;
import io.grpc.ServerServiceDefinition;
import io.grpc.ServerTransportFilter;
+import io.grpc.Status;
+import io.grpc.Status.Code;
import io.grpc.StatusRuntimeException;
import io.grpc.binder.internal.BinderTransport;
import io.grpc.inprocess.InProcessChannelBuilder;
@@ -41,6 +47,9 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
@@ -61,6 +70,28 @@ public class PeerUidsTest {
private final AtomicReference clientUidCapture = new AtomicReference<>();
+ @Test
+ public void checkAuthorizationIsForwardedToSecurityPolicy() {
+ SecurityPolicy mockPolicy = mock(SecurityPolicy.class);
+ when(mockPolicy.checkAuthorization(FAKE_UID))
+ .thenReturn(Status.PERMISSION_DENIED.augmentDescription("xyzzy"));
+ Status status = PeerUids.checkAuthorization(mockPolicy, new PeerUid(FAKE_UID));
+ assertThat(status.getCode()).isEqualTo(Code.PERMISSION_DENIED);
+ assertThat(status.getDescription()).contains("xyzzy");
+ }
+
+ @Test
+ public void checkAuthorizationAsyncIsForwardedToSecurityPolicy()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ AsyncSecurityPolicy mockPolicy = mock(AsyncSecurityPolicy.class);
+ when(mockPolicy.checkAuthorizationAsync(FAKE_UID))
+ .thenReturn(Futures.immediateFuture(Status.PERMISSION_DENIED.augmentDescription("xyzzy")));
+ ListenableFuture statusFuture =
+ PeerUids.checkAuthorizationAsync(mockPolicy, new PeerUid(FAKE_UID));
+ assertThat(statusFuture.get(10, TimeUnit.SECONDS).getCode()).isEqualTo(Code.PERMISSION_DENIED);
+ assertThat(statusFuture.get(10, TimeUnit.SECONDS).getDescription()).contains("xyzzy");
+ }
+
@Test
public void keyPopulatedWithInterceptor() throws Exception {
makeServiceCall(/* populateUid= */ true, /* includeInterceptor= */ true);