mirror of https://github.com/grpc/grpc-java.git
Don't block an offload thread when the client's SecurityPolicy is async (#11272)
https://github.com/grpc/grpc-java/issues/10566
This commit is contained in:
parent
47249c5f00
commit
7fee6a3fea
|
|
@ -24,6 +24,8 @@ import android.os.Parcel;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import com.google.protobuf.Empty;
|
import com.google.protobuf.Empty;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
|
|
@ -35,6 +37,7 @@ import io.grpc.ServerServiceDefinition;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.AsyncSecurityPolicy;
|
||||||
import io.grpc.binder.BinderServerBuilder;
|
import io.grpc.binder.BinderServerBuilder;
|
||||||
import io.grpc.binder.HostServices;
|
import io.grpc.binder.HostServices;
|
||||||
import io.grpc.binder.SecurityPolicy;
|
import io.grpc.binder.SecurityPolicy;
|
||||||
|
|
@ -381,6 +384,34 @@ public final class BinderClientTransportTest {
|
||||||
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
|
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicyFailure() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport = new BinderClientTransportBuilder()
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
RuntimeException exception = new NullPointerException();
|
||||||
|
securityPolicy.setAuthorizationException(exception);
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
|
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicySuccess() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport = new BinderClientTransportBuilder()
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
|
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
}
|
||||||
|
|
||||||
private static void startAndAwaitReady(
|
private static void startAndAwaitReady(
|
||||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
@ -540,4 +571,27 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AsyncSecurityPolicy that lets a test specify the outcome of checkAuthorizationAsync().
|
||||||
|
*/
|
||||||
|
static class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
|
||||||
|
private SettableFuture<Status> result = SettableFuture.create();
|
||||||
|
|
||||||
|
public void clearAuthorizationResult() {
|
||||||
|
result = SettableFuture.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setAuthorizationResult(Status status) {
|
||||||
|
return result.set(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setAuthorizationException(Throwable t) {
|
||||||
|
return result.setException(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
||||||
|
return Futures.nonCancellationPropagating(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ import android.os.TransactionTooLargeException;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Ticker;
|
import com.google.common.base.Ticker;
|
||||||
import com.google.common.base.Verify;
|
import com.google.common.base.Verify;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
|
|
@ -47,6 +49,7 @@ import io.grpc.ServerStreamTracer;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.AsyncSecurityPolicy;
|
||||||
import io.grpc.binder.InboundParcelablePolicy;
|
import io.grpc.binder.InboundParcelablePolicy;
|
||||||
import io.grpc.binder.SecurityPolicy;
|
import io.grpc.binder.SecurityPolicy;
|
||||||
import io.grpc.internal.ClientStream;
|
import io.grpc.internal.ClientStream;
|
||||||
|
|
@ -743,8 +746,8 @@ public abstract class BinderTransport
|
||||||
@Override
|
@Override
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
protected void handleSetupTransport(Parcel parcel) {
|
protected void handleSetupTransport(Parcel parcel) {
|
||||||
// Add the remote uid to our attributes.
|
int remoteUid = Binder.getCallingUid();
|
||||||
attributes = setSecurityAttrs(attributes, Binder.getCallingUid());
|
attributes = setSecurityAttrs(attributes, remoteUid);
|
||||||
if (inState(TransportState.SETUP)) {
|
if (inState(TransportState.SETUP)) {
|
||||||
int version = parcel.readInt();
|
int version = parcel.readInt();
|
||||||
IBinder binder = parcel.readStrongBinder();
|
IBinder binder = parcel.readStrongBinder();
|
||||||
|
|
@ -755,46 +758,54 @@ public abstract class BinderTransport
|
||||||
shutdownInternal(
|
shutdownInternal(
|
||||||
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
|
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
|
||||||
} else {
|
} else {
|
||||||
offloadExecutor.execute(() -> checkSecurityPolicy(binder));
|
ListenableFuture<Status> authFuture = (securityPolicy instanceof AsyncSecurityPolicy) ?
|
||||||
|
((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid) :
|
||||||
|
Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
|
||||||
|
Futures.addCallback(
|
||||||
|
authFuture,
|
||||||
|
new FutureCallback<Status>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status result) { handleAuthResult(binder, result); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) { handleAuthResult(t); }
|
||||||
|
},
|
||||||
|
offloadExecutor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSecurityPolicy(IBinder binder) {
|
private synchronized void handleAuthResult(IBinder binder, Status authorization) {
|
||||||
Status authorization;
|
if (inState(TransportState.SETUP)) {
|
||||||
Integer remoteUid;
|
if (!authorization.isOk()) {
|
||||||
synchronized (this) {
|
shutdownInternal(authorization, true);
|
||||||
remoteUid = attributes.get(REMOTE_UID);
|
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
||||||
}
|
shutdownInternal(
|
||||||
if (remoteUid == null) {
|
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
||||||
authorization = Status.UNAUTHENTICATED.withDescription("No remote UID available");
|
} else {
|
||||||
} else {
|
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
||||||
authorization = securityPolicy.checkAuthorization(remoteUid);
|
// triggers), could have shut us down.
|
||||||
}
|
if (!isShutdown()) {
|
||||||
synchronized (this) {
|
setState(TransportState.READY);
|
||||||
if (inState(TransportState.SETUP)) {
|
attributes = clientTransportListener.filterTransport(attributes);
|
||||||
if (!authorization.isOk()) {
|
clientTransportListener.transportReady();
|
||||||
shutdownInternal(authorization, true);
|
if (readyTimeoutFuture != null) {
|
||||||
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
readyTimeoutFuture.cancel(false);
|
||||||
shutdownInternal(
|
readyTimeoutFuture = null;
|
||||||
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
|
||||||
} else {
|
|
||||||
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
|
||||||
// triggers), could have shut us down.
|
|
||||||
if (!isShutdown()) {
|
|
||||||
setState(TransportState.READY);
|
|
||||||
attributes = clientTransportListener.filterTransport(attributes);
|
|
||||||
clientTransportListener.transportReady();
|
|
||||||
if (readyTimeoutFuture != null) {
|
|
||||||
readyTimeoutFuture.cancel(false);
|
|
||||||
readyTimeoutFuture = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void handleAuthResult(Throwable t) {
|
||||||
|
shutdownInternal(
|
||||||
|
Status.INTERNAL
|
||||||
|
.withDescription("Could not evaluate SecurityPolicy")
|
||||||
|
.withCause(t),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
@Override
|
@Override
|
||||||
protected void handlePingResponse(Parcel parcel) {
|
protected void handlePingResponse(Parcel parcel) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue