diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index c258db1bc39..32855e3bde5 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -22,8 +22,7 @@ import com.google.auth.RequestMetadataCallback; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.BaseEncoding; -import io.grpc.Attributes; -import io.grpc.CallCredentials; +import io.grpc.CallCredentials2; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; @@ -47,7 +46,7 @@ /** * Wraps {@link Credentials} as a {@link CallCredentials}. */ -final class GoogleAuthLibraryCallCredentials implements CallCredentials { +final class GoogleAuthLibraryCallCredentials extends CallCredentials2 { private static final Logger log = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName()); private static final JwtHelper jwtHelper @@ -88,15 +87,9 @@ public GoogleAuthLibraryCallCredentials(Credentials creds) { public void thisUsesUnstableApi() {} @Override - public void applyRequestMetadata(MethodDescriptor method, Attributes attrs, - Executor appExecutor, final MetadataApplier applier) { - SecurityLevel security = attrs.get(ATTR_SECURITY_LEVEL); - if (security == null) { - // Although the API says ATTR_SECURITY_LEVEL is required, no one was really looking at it thus - // there may be transports that got away without setting it. Now we start to check it, it'd - // be less disruptive to tolerate nulls. - security = SecurityLevel.NONE; - } + public void applyRequestMetadata( + RequestInfo info, Executor appExecutor, final MetadataApplier applier) { + SecurityLevel security = info.getSecurityLevel(); if (requirePrivacy && security != SecurityLevel.PRIVACY_AND_INTEGRITY) { applier.fail(Status.UNAUTHENTICATED .withDescription("Credentials require channel with PRIVACY_AND_INTEGRITY security level. " @@ -104,10 +97,10 @@ public void applyRequestMetadata(MethodDescriptor method, Attributes attrs return; } - String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority"); + String authority = checkNotNull(info.getAuthority(), "authority"); final URI uri; try { - uri = serviceUri(authority, method); + uri = serviceUri(authority, info.getMethodDescriptor()); } catch (StatusException e) { applier.fail(e.getStatus()); return; diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java index 438dab0ffc9..a74074110dd 100644 --- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java +++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java @@ -39,8 +39,8 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.CallCredentials.MetadataApplier; +import io.grpc.CallCredentials2; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; @@ -105,11 +105,8 @@ public class GoogleAuthLibraryCallCredentialsTest { .build(); private URI expectedUri = URI.create("https://testauthority/a.service"); - private final String authority = "testauthority"; - private final Attributes attrs = Attributes.newBuilder() - .set(CallCredentials.ATTR_AUTHORITY, authority) - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) - .build(); + private static final String AUTHORITY = "testauthority"; + private static final SecurityLevel SECURITY_LEVEL = SecurityLevel.PRIVACY_AND_INTEGRITY; private ArrayList pendingRunnables = new ArrayList<>(); @@ -155,7 +152,7 @@ public void copyCredentialsToHeaders() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); verify(credentials).getRequestMetadata(eq(expectedUri)); verify(applier).apply(headersCaptor.capture()); @@ -177,7 +174,7 @@ public void invalidBase64() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); verify(credentials).getRequestMetadata(eq(expectedUri)); verify(applier).fail(statusCaptor.capture()); @@ -193,7 +190,7 @@ public void credentialsFailsWithIoException() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); verify(credentials).getRequestMetadata(eq(expectedUri)); verify(applier).fail(statusCaptor.capture()); @@ -209,7 +206,7 @@ public void credentialsFailsWithRuntimeException() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); verify(credentials).getRequestMetadata(eq(expectedUri)); verify(applier).fail(statusCaptor.capture()); @@ -229,7 +226,7 @@ public void credentialsReturnNullMetadata() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); for (int i = 0; i < 3; i++) { - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); } verify(credentials, times(3)).getRequestMetadata(eq(expectedUri)); @@ -255,14 +252,11 @@ public AccessToken refreshAccessToken() throws IOException { return token; } }; - // Security level should not impact non-GoogleCredentials - Attributes securityNone = attrs.toBuilder() - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) - .build(); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, securityNone, executor, applier); + callCredentials.applyRequestMetadata( + new RequestInfoImpl(SecurityLevel.NONE), executor, applier); assertEquals(1, runPendingRunnables()); verify(applier).apply(headersCaptor.capture()); @@ -276,13 +270,11 @@ public AccessToken refreshAccessToken() throws IOException { public void googleCredential_privacyAndIntegrityAllowed() { final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); final Credentials credentials = GoogleCredentials.create(token); - Attributes privacy = attrs.toBuilder() - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) - .build(); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, privacy, executor, applier); + callCredentials.applyRequestMetadata( + new RequestInfoImpl(SecurityLevel.PRIVACY_AND_INTEGRITY), executor, applier); runPendingRunnables(); verify(applier).apply(headersCaptor.capture()); @@ -297,33 +289,11 @@ public void googleCredential_integrityDenied() { final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); final Credentials credentials = GoogleCredentials.create(token); // Anything less than PRIVACY_AND_INTEGRITY should fail - Attributes integrity = attrs.toBuilder() - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY) - .build(); - - GoogleAuthLibraryCallCredentials callCredentials = - new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, integrity, executor, applier); - runPendingRunnables(); - - verify(applier).fail(statusCaptor.capture()); - Status status = statusCaptor.getValue(); - assertEquals(Status.Code.UNAUTHENTICATED, status.getCode()); - } - - @Test - public void googleCredential_nullSecurityDenied() { - final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); - final Credentials credentials = GoogleCredentials.create(token); - // Null should not (for the moment) crash in horrible ways. In the future this could be changed, - // since it technically isn't allowed per the API. - Attributes integrity = attrs.toBuilder() - .set(CallCredentials.ATTR_SECURITY_LEVEL, null) - .build(); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, integrity, executor, applier); + callCredentials.applyRequestMetadata( + new RequestInfoImpl(SecurityLevel.INTEGRITY), executor, applier); runPendingRunnables(); verify(applier).fail(statusCaptor.capture()); @@ -335,20 +305,12 @@ public void googleCredential_nullSecurityDenied() { public void serviceUri() throws Exception { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, - Attributes.newBuilder() - .setAll(attrs) - .set(CallCredentials.ATTR_AUTHORITY, "example.com:443") - .build(), - executor, applier); + callCredentials.applyRequestMetadata( + new RequestInfoImpl("example.com:443"), executor, applier); verify(credentials).getRequestMetadata(eq(new URI("https://example.com/a.service"))); - callCredentials.applyRequestMetadata(method, - Attributes.newBuilder() - .setAll(attrs) - .set(CallCredentials.ATTR_AUTHORITY, "example.com:123") - .build(), - executor, applier); + callCredentials.applyRequestMetadata( + new RequestInfoImpl("example.com:123"), executor, applier); verify(credentials).getRequestMetadata(eq(new URI("https://example.com:123/a.service"))); } @@ -366,7 +328,7 @@ public AccessToken refreshAccessToken() { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); assertEquals(0, runPendingRunnables()); verify(applier).apply(headersCaptor.capture()); @@ -393,7 +355,7 @@ public AccessToken refreshAccessToken() { GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); assertEquals(1, runPendingRunnables()); verify(applier).apply(headersCaptor.capture()); @@ -412,7 +374,7 @@ public void oauthClassesNotInClassPath() throws Exception { assertNull(GoogleAuthLibraryCallCredentials.createJwtHelperOrNull(null)); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials, null); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier); verify(credentials).getRequestMetadata(eq(expectedUri)); verify(applier).apply(headersCaptor.capture()); @@ -430,4 +392,46 @@ private int runPendingRunnables() { } return savedPendingRunnables.size(); } + + private final class RequestInfoImpl extends CallCredentials2.RequestInfo { + final String authority; + final SecurityLevel securityLevel; + + RequestInfoImpl() { + this(AUTHORITY, SECURITY_LEVEL); + } + + RequestInfoImpl(SecurityLevel securityLevel) { + this(AUTHORITY, securityLevel); + } + + RequestInfoImpl(String authority) { + this(authority, SECURITY_LEVEL); + } + + RequestInfoImpl(String authority, SecurityLevel securityLevel) { + this.authority = authority; + this.securityLevel = securityLevel; + } + + @Override + public MethodDescriptor getMethodDescriptor() { + return method; + } + + @Override + public SecurityLevel getSecurityLevel() { + return securityLevel; + } + + @Override + public String getAuthority() { + return authority; + } + + @Override + public Attributes getTransportAttrs() { + return Attributes.EMPTY; + } + } } diff --git a/core/src/main/java/io/grpc/CallCredentials.java b/core/src/main/java/io/grpc/CallCredentials.java index 0d87bb61003..2bc4b2876c6 100644 --- a/core/src/main/java/io/grpc/CallCredentials.java +++ b/core/src/main/java/io/grpc/CallCredentials.java @@ -40,11 +40,15 @@ public interface CallCredentials { * The security level of the transport. It is guaranteed to be present in the {@code attrs} passed * to {@link #applyRequestMetadata}. It is by default {@link SecurityLevel#NONE} but can be * overridden by the transport. + * + * @deprecated transport implementations should use {@code + * io.grpc.internal.GrpcAttributes.ATTR_SECURITY_LEVEL} instead. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") @Grpc.TransportAttr + @Deprecated public static final Key ATTR_SECURITY_LEVEL = - Key.create("io.grpc.CallCredentials.securityLevel"); + Key.create("io.grpc.internal.GrpcAttributes.securityLevel"); /** * The authority string used to authenticate the server. Usually it's the server's host name. It @@ -54,6 +58,7 @@ public interface CallCredentials { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") @Grpc.TransportAttr + @Deprecated public static final Key ATTR_AUTHORITY = Key.create("io.grpc.CallCredentials.authority"); /** @@ -73,8 +78,11 @@ public interface CallCredentials { * needs to perform blocking operations. * @param applier The outlet of the produced headers. It can be called either before or after this * method returns. + * + * @deprecated implement {@link CallCredentials2} instead. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") + @Deprecated void applyRequestMetadata( MethodDescriptor method, Attributes attrs, Executor appExecutor, MetadataApplier applier); @@ -92,16 +100,43 @@ void applyRequestMetadata( *

Exactly one of its methods must be called to make the RPC proceed. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") - public interface MetadataApplier { + public abstract static class MetadataApplier { /** * Called when headers are successfully generated. They will be merged into the original * headers. */ - void apply(Metadata headers); + public abstract void apply(Metadata headers); /** * Called when there has been an error when preparing the headers. This will fail the RPC. */ - void fail(Status status); + public abstract void fail(Status status); + } + + /** + * The request-related information passed to {@code CallCredentials2.applyRequestMetadata()}. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") + public abstract static class RequestInfo { + /** + * The method descriptor of this RPC. + */ + public abstract MethodDescriptor getMethodDescriptor(); + + /** + * The security level on the transport. + */ + public abstract SecurityLevel getSecurityLevel(); + + /** + * Returns the authority string used to authenticate the server for this call. + */ + public abstract String getAuthority(); + + /** + * Returns the transport attributes. + */ + @Grpc.TransportAttr + public abstract Attributes getTransportAttrs(); } } diff --git a/core/src/main/java/io/grpc/CallCredentials2.java b/core/src/main/java/io/grpc/CallCredentials2.java new file mode 100644 index 00000000000..458a04c9f86 --- /dev/null +++ b/core/src/main/java/io/grpc/CallCredentials2.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 The gRPC Authors + * + * 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 io.grpc; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.concurrent.Executor; + +/** + * The new interface for {@link CallCredentials}. + * + *

THIS CLASS NAME IS TEMPORARY and is part of a migration. This class will BE DELETED as it + * replaces {@link CallCredentials} in short-term. THIS CLASS SHOULD ONLY BE REFERENCED BY + * IMPLEMENTIONS. All consumers should still reference {@link CallCredentials}. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4901") +public abstract class CallCredentials2 implements CallCredentials { + /** + * Pass the credential data to the given {@link CallCredentials.MetadataApplier}, which will + * propagate it to the request metadata. + * + *

It is called for each individual RPC, within the {@link Context} of the call, before the + * stream is about to be created on a transport. Implementations should not block in this + * method. If metadata is not immediately available, e.g., needs to be fetched from network, the + * implementation may give the {@code applier} to an asynchronous task which will eventually call + * the {@code applier}. The RPC proceeds only after the {@code applier} is called. + * + * @param requestInfo request-related information + * @param appExecutor The application thread-pool. It is provided to the implementation in case it + * needs to perform blocking operations. + * @param applier The outlet of the produced headers. It can be called either before or after this + * method returns. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914") + public abstract void applyRequestMetadata( + RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier); + + @Override + @SuppressWarnings("deprecation") + public final void applyRequestMetadata( + final MethodDescriptor method, final Attributes attrs, + Executor appExecutor, CallCredentials.MetadataApplier applier) { + final String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority"); + final SecurityLevel securityLevel = + firstNonNull(attrs.get(ATTR_SECURITY_LEVEL), SecurityLevel.NONE); + RequestInfo requestInfo = new RequestInfo() { + @Override + public MethodDescriptor getMethodDescriptor() { + return method; + } + + @Override + public SecurityLevel getSecurityLevel() { + return securityLevel; + } + + @Override + public String getAuthority() { + return authority; + } + + @Override + public Attributes getTransportAttrs() { + return attrs; + } + }; + applyRequestMetadata(requestInfo, appExecutor, applier); + } +} diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java index b10a46a2574..03e2e1a197d 100644 --- a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java +++ b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java @@ -24,7 +24,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.CallOptions; import io.grpc.Compressor; import io.grpc.Deadline; @@ -41,6 +40,7 @@ import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ManagedClientTransport; import io.grpc.internal.NoopClientStream; @@ -91,7 +91,7 @@ final class InProcessTransport implements ServerTransport, ConnectionClientTrans @GuardedBy("this") private List serverStreamTracerFactories; private final Attributes attributes = Attributes.newBuilder() - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) .build(); public InProcessTransport(String name, String authority, String userAgent) { diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java index df2e0a0bd33..e5141cf1ef1 100644 --- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java +++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java @@ -72,6 +72,7 @@ protected ConnectionClientTransport delegate() { } @Override + @SuppressWarnings("deprecation") public ClientStream newStream( MethodDescriptor method, Metadata headers, CallOptions callOptions) { CallCredentials creds = callOptions.getCredentials(); diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java index e620bb6d714..6a63864f6bc 100644 --- a/core/src/main/java/io/grpc/internal/GrpcAttributes.java +++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java @@ -18,7 +18,9 @@ import io.grpc.Attributes; import io.grpc.EquivalentAddressGroup; +import io.grpc.Grpc; import io.grpc.NameResolver; +import io.grpc.SecurityLevel; import java.util.Map; /** @@ -48,5 +50,14 @@ public final class GrpcAttributes { public static final Attributes.Key ATTR_LB_PROVIDED_BACKEND = Attributes.Key.create("io.grpc.grpclb.lbProvidedBackend"); + /** + * The security level of the transport. If it's not present, {@link SecurityLevel#NONE} should be + * assumed. + */ + @SuppressWarnings("deprecation") + @Grpc.TransportAttr + public static final Attributes.Key ATTR_SECURITY_LEVEL = + io.grpc.CallCredentials.ATTR_SECURITY_LEVEL; + private GrpcAttributes() {} } diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java index 48e00665cd1..c3196ddd107 100644 --- a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java +++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java @@ -29,7 +29,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; -final class MetadataApplierImpl implements MetadataApplier { +final class MetadataApplierImpl extends MetadataApplier { private final ClientTransport transport; private final MethodDescriptor method; private final Metadata origHeaders; diff --git a/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java new file mode 100644 index 00000000000..f87e22810a9 --- /dev/null +++ b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java @@ -0,0 +1,291 @@ +/* + * Copyright 2016 The gRPC Authors + * + * 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 io.grpc.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.Attributes; +import io.grpc.CallCredentials.MetadataApplier; +import io.grpc.CallCredentials.RequestInfo; +import io.grpc.CallCredentials2; +import io.grpc.CallOptions; +import io.grpc.IntegerMarshaller; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.SecurityLevel; +import io.grpc.Status; +import io.grpc.StringMarshaller; +import java.net.SocketAddress; +import java.util.concurrent.Executor; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * Unit test for {@link CallCredentials2} applying functionality implemented by {@link + * CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}. + */ +@RunWith(JUnit4.class) +public class CallCredentials2ApplyingTest { + @Mock + private ClientTransportFactory mockTransportFactory; + + @Mock + private ConnectionClientTransport mockTransport; + + @Mock + private ClientStream mockStream; + + @Mock + private CallCredentials2 mockCreds; + + @Mock + private Executor mockExecutor; + + @Mock + private SocketAddress address; + + private static final String AUTHORITY = "testauthority"; + private static final String USER_AGENT = "testuseragent"; + private static final Attributes.Key ATTR_KEY = Attributes.Key.create("somekey"); + private static final String ATTR_VALUE = "somevalue"; + private static final MethodDescriptor method = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNKNOWN) + .setFullMethodName("service/method") + .setRequestMarshaller(new StringMarshaller()) + .setResponseMarshaller(new IntegerMarshaller()) + .build(); + private static final Metadata.Key ORIG_HEADER_KEY = + Metadata.Key.of("header1", Metadata.ASCII_STRING_MARSHALLER); + private static final String ORIG_HEADER_VALUE = "some original header value"; + private static final Metadata.Key CREDS_KEY = + Metadata.Key.of("test-creds", Metadata.ASCII_STRING_MARSHALLER); + private static final String CREDS_VALUE = "some credentials"; + + private final Metadata origHeaders = new Metadata(); + private ForwardingConnectionClientTransport transport; + private CallOptions callOptions; + + @Before + public void setUp() { + ClientTransportFactory.ClientTransportOptions clientTransportOptions = + new ClientTransportFactory.ClientTransportOptions() + .setAuthority(AUTHORITY) + .setUserAgent(USER_AGENT); + + MockitoAnnotations.initMocks(this); + origHeaders.put(ORIG_HEADER_KEY, ORIG_HEADER_VALUE); + when(mockTransportFactory.newClientTransport(address, clientTransportOptions)) + .thenReturn(mockTransport); + when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) + .thenReturn(mockStream); + ClientTransportFactory transportFactory = new CallCredentialsApplyingTransportFactory( + mockTransportFactory, mockExecutor); + transport = (ForwardingConnectionClientTransport) + transportFactory.newClientTransport(address, clientTransportOptions); + callOptions = CallOptions.DEFAULT.withCallCredentials(mockCreds); + verify(mockTransportFactory).newClientTransport(address, clientTransportOptions); + assertSame(mockTransport, transport.delegate()); + } + + @Test + public void parameterPropagation_base() { + Attributes transportAttrs = Attributes.newBuilder().set(ATTR_KEY, ATTR_VALUE).build(); + when(mockTransport.getAttributes()).thenReturn(transportAttrs); + + transport.newStream(method, origHeaders, callOptions); + + ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null); + verify(mockCreds).applyRequestMetadata( + infoCaptor.capture(), same(mockExecutor), any(MetadataApplier.class)); + RequestInfo info = infoCaptor.getValue(); + assertSame(method, info.getMethodDescriptor()); + Attributes attrs = info.getTransportAttrs(); + assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY)); + assertSame(AUTHORITY, info.getAuthority()); + assertSame(SecurityLevel.NONE, info.getSecurityLevel()); + } + + @Test + public void parameterPropagation_transportSetSecurityLevel() { + Attributes transportAttrs = Attributes.newBuilder() + .set(ATTR_KEY, ATTR_VALUE) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY) + .build(); + when(mockTransport.getAttributes()).thenReturn(transportAttrs); + + transport.newStream(method, origHeaders, callOptions); + + ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null); + verify(mockCreds).applyRequestMetadata( + infoCaptor.capture(), same(mockExecutor), any(MetadataApplier.class)); + RequestInfo info = infoCaptor.getValue(); + assertSame(method, info.getMethodDescriptor()); + assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY)); + assertSame(AUTHORITY, info.getAuthority()); + assertSame(SecurityLevel.INTEGRITY, info.getSecurityLevel()); + } + + @Test + public void parameterPropagation_callOptionsSetAuthority() { + Attributes transportAttrs = Attributes.newBuilder() + .set(ATTR_KEY, ATTR_VALUE) + .build(); + when(mockTransport.getAttributes()).thenReturn(transportAttrs); + Executor anotherExecutor = mock(Executor.class); + + transport.newStream(method, origHeaders, + callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor)); + + ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null); + verify(mockCreds).applyRequestMetadata( + infoCaptor.capture(), same(anotherExecutor), any(MetadataApplier.class)); + RequestInfo info = infoCaptor.getValue(); + assertSame(method, info.getMethodDescriptor()); + assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY)); + assertEquals("calloptions-authority", info.getAuthority()); + assertSame(SecurityLevel.NONE, info.getSecurityLevel()); + } + + @Test + public void credentialThrows() { + final RuntimeException ex = new RuntimeException(); + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + doThrow(ex).when(mockCreds).applyRequestMetadata( + any(RequestInfo.class), same(mockExecutor), any(MetadataApplier.class)); + + FailingClientStream stream = + (FailingClientStream) transport.newStream(method, origHeaders, callOptions); + + verify(mockTransport, never()).newStream(method, origHeaders, callOptions); + assertEquals(Status.Code.UNAUTHENTICATED, stream.getError().getCode()); + assertSame(ex, stream.getError().getCause()); + } + + @Test + public void applyMetadata_inline() { + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + MetadataApplier applier = (MetadataApplier) invocation.getArguments()[2]; + Metadata headers = new Metadata(); + headers.put(CREDS_KEY, CREDS_VALUE); + applier.apply(headers); + return null; + } + }).when(mockCreds).applyRequestMetadata( + any(RequestInfo.class), same(mockExecutor), any(MetadataApplier.class)); + + ClientStream stream = transport.newStream(method, origHeaders, callOptions); + + verify(mockTransport).newStream(method, origHeaders, callOptions); + assertSame(mockStream, stream); + assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY)); + assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY)); + } + + @Test + public void fail_inline() { + final Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds"); + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + MetadataApplier applier = (MetadataApplier) invocation.getArguments()[2]; + applier.fail(error); + return null; + } + }).when(mockCreds).applyRequestMetadata( + any(RequestInfo.class), same(mockExecutor), any(MetadataApplier.class)); + + FailingClientStream stream = + (FailingClientStream) transport.newStream(method, origHeaders, callOptions); + + verify(mockTransport, never()).newStream(method, origHeaders, callOptions); + assertSame(error, stream.getError()); + } + + @Test + public void applyMetadata_delayed() { + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + + // Will call applyRequestMetadata(), which is no-op. + DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions); + + ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null); + verify(mockCreds).applyRequestMetadata( + any(RequestInfo.class), same(mockExecutor), applierCaptor.capture()); + verify(mockTransport, never()).newStream(method, origHeaders, callOptions); + + Metadata headers = new Metadata(); + headers.put(CREDS_KEY, CREDS_VALUE); + applierCaptor.getValue().apply(headers); + + verify(mockTransport).newStream(method, origHeaders, callOptions); + assertSame(mockStream, stream.getRealStream()); + assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY)); + assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY)); + } + + @Test + public void fail_delayed() { + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + + // Will call applyRequestMetadata(), which is no-op. + DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions); + + ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null); + verify(mockCreds).applyRequestMetadata( + any(RequestInfo.class), same(mockExecutor), applierCaptor.capture()); + + Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds"); + applierCaptor.getValue().fail(error); + + verify(mockTransport, never()).newStream(method, origHeaders, callOptions); + FailingClientStream failingStream = (FailingClientStream) stream.getRealStream(); + assertSame(error, failingStream.getError()); + } + + @Test + public void noCreds() { + callOptions = callOptions.withCallCredentials(null); + ClientStream stream = transport.newStream(method, origHeaders, callOptions); + + verify(mockTransport).newStream(method, origHeaders, callOptions); + assertSame(mockStream, stream); + assertNull(origHeaders.get(CREDS_KEY)); + assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY)); + } +} diff --git a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java index c6a9bfb4301..51df406b5ed 100644 --- a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java +++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java @@ -55,6 +55,7 @@ * CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}. */ @RunWith(JUnit4.class) +@Deprecated public class CallCredentialsApplyingTest { @Mock private ClientTransportFactory mockTransportFactory; diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 248eb66f018..863834a0f3c 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -1388,6 +1388,7 @@ public void uriPattern() { * propagated to newStream() and applyRequestMetadata(). */ @Test + @SuppressWarnings("deprecation") public void informationPropagatedToNewStreamAndCallCredentials() { createChannel(); CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds); @@ -1401,7 +1402,7 @@ public Void answer(InvocationOnMock in) throws Throwable { credsApplyContexts.add(Context.current()); return null; } - }).when(creds).applyRequestMetadata( + }).when(creds).applyRequestMetadata( // TODO(zhangkun83): remove suppression of deprecations any(MethodDescriptor.class), any(Attributes.class), any(Executor.class), any(MetadataApplier.class)); diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java index 38b871d33ad..102aad92337 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java @@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.CallOptions; import io.grpc.InternalChannelz.SocketStats; import io.grpc.InternalLogId; @@ -31,6 +30,7 @@ import io.grpc.Status.Code; import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory; import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; @@ -95,8 +95,7 @@ class CronetClientTransport implements ConnectionClientTransport { this.streamFactory = Preconditions.checkNotNull(streamFactory, "streamFactory"); this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer"); this.attrs = Attributes.newBuilder() - .set(CallCredentials.ATTR_AUTHORITY, authority) - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) .build(); } diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java index 539ba1ccdb2..194e4c9edf4 100644 --- a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java +++ b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java @@ -33,6 +33,7 @@ import io.grpc.Status; import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory; import io.grpc.internal.ClientStreamListener; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.ManagedClientTransport; import io.grpc.internal.TransportTracer; import io.grpc.testing.TestMethodDescriptors; @@ -82,9 +83,8 @@ public void setUp() { @Test public void transportAttributes() { Attributes attrs = transport.getAttributes(); - assertEquals(AUTHORITY, attrs.get(CallCredentials.ATTR_AUTHORITY)); assertEquals( - SecurityLevel.PRIVACY_AND_INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL)); + SecurityLevel.PRIVACY_AND_INTEGRITY, attrs.get(GrpcAttributes.ATTR_SECURITY_LEVEL)); } @Test diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 684a67f3eff..06180170506 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -22,12 +22,12 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.Grpc; import io.grpc.Internal; import io.grpc.InternalChannelz; import io.grpc.SecurityLevel; import io.grpc.Status; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; @@ -676,7 +676,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session) .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress()) - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) .build(), new InternalChannelz.Security(new InternalChannelz.Tls(session))); writeBufferedAndRemove(ctx); @@ -725,7 +725,7 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { .newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress()) - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) .build(), /*securityInfo=*/ null); super.channelActive(ctx); @@ -769,7 +769,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc .newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress()) - .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) .build(), /*securityInfo=*/ null); } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_REJECTED) { diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 73added7312..aa9e07499e8 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -31,7 +31,6 @@ import com.squareup.okhttp.Request; import com.squareup.okhttp.internal.http.StatusLine; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.CallOptions; import io.grpc.Grpc; import io.grpc.InternalChannelz; @@ -46,6 +45,7 @@ import io.grpc.StatusException; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.Http2Ping; import io.grpc.internal.KeepAliveManager; @@ -486,7 +486,7 @@ sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, sock.getRemoteSocketAddress()) .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, sock.getLocalSocketAddress()) .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession) - .set(CallCredentials.ATTR_SECURITY_LEVEL, + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, sslSession == null ? SecurityLevel.NONE : SecurityLevel.PRIVACY_AND_INTEGRITY) .build(); } catch (StatusException e) { diff --git a/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java index 4a37729f649..19f5e1f6894 100644 --- a/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java +++ b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java @@ -44,7 +44,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; -import io.grpc.CallCredentials; import io.grpc.CallOptions; import io.grpc.ClientStreamTracer; import io.grpc.Grpc; @@ -59,6 +58,7 @@ import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.InternalServer; import io.grpc.internal.IoUtils; import io.grpc.internal.ManagedClientTransport; @@ -346,7 +346,7 @@ public void checkClientAttributes() throws Exception { verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady(); assertNotNull("security level should be set in client attributes", - connectionClient.getAttributes().get(CallCredentials.ATTR_SECURITY_LEVEL)); + connectionClient.getAttributes().get(GrpcAttributes.ATTR_SECURITY_LEVEL)); } @Test