|
36 | 36 | import static org.junit.Assert.assertNotNull; |
37 | 37 | import static org.junit.Assert.assertNull; |
38 | 38 | import static org.junit.Assert.assertTrue; |
| 39 | +import static org.junit.Assert.fail; |
39 | 40 | import static org.mockito.ArgumentMatchers.any; |
40 | 41 | import static org.mockito.ArgumentMatchers.eq; |
41 | 42 | import static org.mockito.ArgumentMatchers.same; |
|
64 | 65 | import io.grpc.internal.ClientStreamListener.RpcProgress; |
65 | 66 | import io.grpc.internal.ClientTransport; |
66 | 67 | import io.grpc.internal.ClientTransport.PingCallback; |
| 68 | +import io.grpc.internal.GrpcAttributes; |
67 | 69 | import io.grpc.internal.GrpcUtil; |
68 | 70 | import io.grpc.internal.KeepAliveManager; |
69 | 71 | import io.grpc.internal.ManagedClientTransport; |
|
90 | 92 | import io.netty.handler.codec.http2.Http2Stream; |
91 | 93 | import io.netty.util.AsciiString; |
92 | 94 | import java.io.InputStream; |
| 95 | +import java.security.cert.CertificateException; |
93 | 96 | import java.text.MessageFormat; |
94 | 97 | import java.util.LinkedList; |
95 | 98 | import java.util.List; |
96 | 99 | import java.util.Queue; |
| 100 | +import java.util.concurrent.ExecutionException; |
97 | 101 | import java.util.concurrent.atomic.AtomicBoolean; |
98 | 102 | import java.util.concurrent.atomic.AtomicReference; |
99 | 103 | import java.util.logging.Handler; |
|
108 | 112 | import org.mockito.ArgumentCaptor; |
109 | 113 | import org.mockito.ArgumentMatchers; |
110 | 114 | import org.mockito.Mock; |
| 115 | +import org.mockito.Mockito; |
111 | 116 | import org.mockito.invocation.InvocationOnMock; |
112 | 117 | import org.mockito.junit.MockitoJUnit; |
113 | 118 | import org.mockito.junit.MockitoRule; |
@@ -189,7 +194,11 @@ public Void answer(InvocationOnMock invocation) throws Throwable { |
189 | 194 | }) |
190 | 195 | .when(streamListener) |
191 | 196 | .messagesAvailable(ArgumentMatchers.<StreamListener.MessageProducer>any()); |
192 | | - |
| 197 | + doAnswer((attributes) -> Attributes.newBuilder().set( |
| 198 | + GrpcAttributes.ATTR_AUTHORITY_VERIFIER, |
| 199 | + (authority) -> Status.OK).build()) |
| 200 | + .when(listener) |
| 201 | + .filterTransport(ArgumentMatchers.any(Attributes.class)); |
193 | 202 | lifecycleManager = new ClientTransportLifecycleManager(listener); |
194 | 203 | // This mocks the keepalive manager only for there's in which we verify it. For other tests |
195 | 204 | // it'll be null which will be testing if we behave correctly when it's not present. |
@@ -919,6 +928,159 @@ public void exceptionCaughtShouldCloseConnection() throws Exception { |
919 | 928 | assertFalse(channel().isOpen()); |
920 | 929 | } |
921 | 930 |
|
| 931 | + @Test |
| 932 | + public void missingAuthorityHeader_streamCreationShouldFail() throws Exception { |
| 933 | + Http2Headers grpcHeadersWithoutAuthority = new DefaultHttp2Headers() |
| 934 | + .scheme(HTTPS) |
| 935 | + .path(as("/fakemethod")) |
| 936 | + .method(HTTP_METHOD) |
| 937 | + .add(as("auth"), as("sometoken")) |
| 938 | + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) |
| 939 | + .add(TE_HEADER, TE_TRAILERS); |
| 940 | + ChannelFuture channelFuture = enqueue(newCreateStreamCommand(grpcHeadersWithoutAuthority, streamTransportState)); |
| 941 | + try { |
| 942 | + channelFuture.get(); |
| 943 | + fail("Expected stream creation failure"); |
| 944 | + } catch (ExecutionException e) { |
| 945 | + assertThat(e.getCause().getMessage()).isEqualTo("UNAVAILABLE: Missing authority header"); |
| 946 | + } |
| 947 | + } |
| 948 | + |
| 949 | + @Test |
| 950 | + public void missingAuthorityVerifierInAttributes_streamCreationShouldFail() throws Exception { |
| 951 | + doAnswer( |
| 952 | + new Answer<Void>() { |
| 953 | + @Override |
| 954 | + public Void answer(InvocationOnMock invocation) throws Throwable { |
| 955 | + StreamListener.MessageProducer producer = |
| 956 | + (StreamListener.MessageProducer) invocation.getArguments()[0]; |
| 957 | + InputStream message; |
| 958 | + while ((message = producer.next()) != null) { |
| 959 | + streamListenerMessageQueue.add(message); |
| 960 | + } |
| 961 | + return null; |
| 962 | + } |
| 963 | + }) |
| 964 | + .when(streamListener) |
| 965 | + .messagesAvailable(ArgumentMatchers.<StreamListener.MessageProducer>any()); |
| 966 | + doAnswer((attributes) -> Attributes.EMPTY) |
| 967 | + .when(listener) |
| 968 | + .filterTransport(ArgumentMatchers.any(Attributes.class)); |
| 969 | + lifecycleManager = new ClientTransportLifecycleManager(listener); |
| 970 | + // This mocks the keepalive manager only for there's in which we verify it. For other tests |
| 971 | + // it'll be null which will be testing if we behave correctly when it's not present. |
| 972 | + if (setKeepaliveManagerFor.contains(testNameRule.getMethodName())) { |
| 973 | + mockKeepAliveManager = mock(KeepAliveManager.class); |
| 974 | + } |
| 975 | + |
| 976 | + initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE)); |
| 977 | + streamTransportState = new TransportStateImpl( |
| 978 | + handler(), |
| 979 | + channel().eventLoop(), |
| 980 | + DEFAULT_MAX_MESSAGE_SIZE, |
| 981 | + transportTracer); |
| 982 | + streamTransportState.setListener(streamListener); |
| 983 | + |
| 984 | + grpcHeaders = new DefaultHttp2Headers() |
| 985 | + .scheme(HTTPS) |
| 986 | + .authority(as("www.fake.com")) |
| 987 | + .path(as("/fakemethod")) |
| 988 | + .method(HTTP_METHOD) |
| 989 | + .add(as("auth"), as("sometoken")) |
| 990 | + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) |
| 991 | + .add(TE_HEADER, TE_TRAILERS); |
| 992 | + |
| 993 | + // Simulate receipt of initial remote settings. |
| 994 | + ByteBuf serializedSettings = serializeSettings(new Http2Settings()); |
| 995 | + channelRead(serializedSettings); |
| 996 | + channel().releaseOutbound(); |
| 997 | + |
| 998 | + ChannelFuture channelFuture = createStream(); |
| 999 | + try { |
| 1000 | + channelFuture.get(); |
| 1001 | + fail("Expected stream creation failure"); |
| 1002 | + } catch (ExecutionException e) { |
| 1003 | + assertThat(e.getCause().getMessage()).isEqualTo("UNAVAILABLE: Authority verifier not found to verify authority"); |
| 1004 | + } |
| 1005 | + } |
| 1006 | + |
| 1007 | + @Test |
| 1008 | + public void authorityVerificationSuccess_streamCreationSucceeds() throws Exception { |
| 1009 | + NettyClientHandler.enablePerRpcAuthorityCheck = true; |
| 1010 | + try { |
| 1011 | + ChannelFuture channelFuture = createStream(); |
| 1012 | + channelFuture.get(); |
| 1013 | + } finally { |
| 1014 | + NettyClientHandler.enablePerRpcAuthorityCheck = false; |
| 1015 | + } |
| 1016 | + } |
| 1017 | + |
| 1018 | + @Test |
| 1019 | + public void authorityVerificationFailure_streamCreationFails() throws Exception { |
| 1020 | + NettyClientHandler.enablePerRpcAuthorityCheck = true; |
| 1021 | + try { |
| 1022 | + doAnswer( |
| 1023 | + new Answer<Void>() { |
| 1024 | + @Override |
| 1025 | + public Void answer(InvocationOnMock invocation) throws Throwable { |
| 1026 | + StreamListener.MessageProducer producer = |
| 1027 | + (StreamListener.MessageProducer) invocation.getArguments()[0]; |
| 1028 | + InputStream message; |
| 1029 | + while ((message = producer.next()) != null) { |
| 1030 | + streamListenerMessageQueue.add(message); |
| 1031 | + } |
| 1032 | + return null; |
| 1033 | + } |
| 1034 | + }) |
| 1035 | + .when(streamListener) |
| 1036 | + .messagesAvailable(ArgumentMatchers.<StreamListener.MessageProducer>any()); |
| 1037 | + doAnswer((attributes) -> Attributes.newBuilder().set( |
| 1038 | + GrpcAttributes.ATTR_AUTHORITY_VERIFIER, |
| 1039 | + (authority) -> Status.UNAVAILABLE.withCause( |
| 1040 | + new CertificateException("Peer verification failed"))).build()) |
| 1041 | + .when(listener) |
| 1042 | + .filterTransport(ArgumentMatchers.any(Attributes.class)); |
| 1043 | + lifecycleManager = new ClientTransportLifecycleManager(listener); |
| 1044 | + // This mocks the keepalive manager only for there's in which we verify it. For other tests |
| 1045 | + // it'll be null which will be testing if we behave correctly when it's not present. |
| 1046 | + if (setKeepaliveManagerFor.contains(testNameRule.getMethodName())) { |
| 1047 | + mockKeepAliveManager = mock(KeepAliveManager.class); |
| 1048 | + } |
| 1049 | + |
| 1050 | + initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE)); |
| 1051 | + streamTransportState = new TransportStateImpl( |
| 1052 | + handler(), |
| 1053 | + channel().eventLoop(), |
| 1054 | + DEFAULT_MAX_MESSAGE_SIZE, |
| 1055 | + transportTracer); |
| 1056 | + streamTransportState.setListener(streamListener); |
| 1057 | + |
| 1058 | + grpcHeaders = new DefaultHttp2Headers() |
| 1059 | + .scheme(HTTPS) |
| 1060 | + .authority(as("www.fake.com")) |
| 1061 | + .path(as("/fakemethod")) |
| 1062 | + .method(HTTP_METHOD) |
| 1063 | + .add(as("auth"), as("sometoken")) |
| 1064 | + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) |
| 1065 | + .add(TE_HEADER, TE_TRAILERS); |
| 1066 | + |
| 1067 | + // Simulate receipt of initial remote settings. |
| 1068 | + ByteBuf serializedSettings = serializeSettings(new Http2Settings()); |
| 1069 | + channelRead(serializedSettings); |
| 1070 | + channel().releaseOutbound(); |
| 1071 | + |
| 1072 | + ChannelFuture channelFuture = createStream(); |
| 1073 | + try { |
| 1074 | + channelFuture.get(); |
| 1075 | + fail("Expected stream creation failure"); |
| 1076 | + } catch (ExecutionException e) { |
| 1077 | + assertThat(e.getMessage()).isEqualTo("io.grpc.InternalStatusRuntimeException: UNAVAILABLE"); |
| 1078 | + } |
| 1079 | + } finally { |
| 1080 | + NettyClientHandler.enablePerRpcAuthorityCheck = false; |
| 1081 | + } |
| 1082 | + } |
| 1083 | + |
922 | 1084 | @Override |
923 | 1085 | protected void makeStream() throws Exception { |
924 | 1086 | createStream(); |
|
0 commit comments