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);