1 #region Copyright notice and license 2 3 // Copyright 2015-2016 gRPC authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 #endregion 18 19 using System; 20 using System.Collections.Generic; 21 using System.IO; 22 using System.Linq; 23 using System.Threading; 24 using System.Threading.Tasks; 25 using Grpc.Core; 26 using Grpc.Core.Utils; 27 using Grpc.Testing; 28 using NUnit.Framework; 29 30 namespace Grpc.IntegrationTesting 31 { 32 public class MetadataCredentialsTest 33 { 34 const string Host = "localhost"; 35 36 FakeTestService serviceImpl; 37 Server server; 38 Channel channel; 39 TestService.TestServiceClient client; 40 List<ChannelOption> options; 41 42 [SetUp] Init()43 public void Init() 44 { 45 serviceImpl = new FakeTestService(); 46 // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 47 server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) 48 { 49 Services = { TestService.BindService(serviceImpl) }, 50 Ports = { { Host, ServerPort.PickUnused, TestCredentials.CreateSslServerCredentials() } } 51 }; 52 server.Start(); 53 54 options = new List<ChannelOption> 55 { 56 new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride) 57 }; 58 } 59 60 [TearDown] Cleanup()61 public void Cleanup() 62 { 63 channel.ShutdownAsync().Wait(); 64 server.ShutdownAsync().Wait(); 65 } 66 67 [Test] MetadataCredentials_Channel()68 public void MetadataCredentials_Channel() 69 { 70 serviceImpl.UnaryCallHandler = (req, context) => 71 { 72 var authToken = context.RequestHeaders.First((entry) => entry.Key == "authorization").Value; 73 Assert.AreEqual("SECRET_TOKEN", authToken); 74 return Task.FromResult(new SimpleResponse()); 75 }; 76 77 var asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) => 78 { 79 await Task.Delay(100).ConfigureAwait(false); // make sure the operation is asynchronous. 80 metadata.Add("authorization", "SECRET_TOKEN"); 81 }); 82 83 var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(), 84 CallCredentials.FromInterceptor(asyncAuthInterceptor)); 85 channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options); 86 client = new TestService.TestServiceClient(channel); 87 88 client.UnaryCall(new SimpleRequest { }); 89 } 90 91 [Test] MetadataCredentials_PerCall()92 public void MetadataCredentials_PerCall() 93 { 94 serviceImpl.UnaryCallHandler = (req, context) => 95 { 96 var authToken = context.RequestHeaders.First((entry) => entry.Key == "authorization").Value; 97 Assert.AreEqual("SECRET_TOKEN", authToken); 98 return Task.FromResult(new SimpleResponse()); 99 }; 100 101 var asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) => 102 { 103 await Task.Delay(100).ConfigureAwait(false); // make sure the operation is asynchronous. 104 metadata.Add("authorization", "SECRET_TOKEN"); 105 }); 106 107 channel = new Channel(Host, server.Ports.Single().BoundPort, TestCredentials.CreateSslCredentials(), options); 108 client = new TestService.TestServiceClient(channel); 109 110 var callCredentials = CallCredentials.FromInterceptor(asyncAuthInterceptor); 111 client.UnaryCall(new SimpleRequest { }, new CallOptions(credentials: callCredentials)); 112 } 113 114 [Test] MetadataCredentials_BothChannelAndPerCall()115 public void MetadataCredentials_BothChannelAndPerCall() 116 { 117 serviceImpl.UnaryCallHandler = (req, context) => 118 { 119 var firstAuth = context.RequestHeaders.First((entry) => entry.Key == "first_authorization").Value; 120 Assert.AreEqual("FIRST_SECRET_TOKEN", firstAuth); 121 var secondAuth = context.RequestHeaders.First((entry) => entry.Key == "second_authorization").Value; 122 Assert.AreEqual("SECOND_SECRET_TOKEN", secondAuth); 123 // both values of "duplicate_authorization" are sent 124 Assert.AreEqual("value1", context.RequestHeaders.First((entry) => entry.Key == "duplicate_authorization").Value); 125 Assert.AreEqual("value2", context.RequestHeaders.Last((entry) => entry.Key == "duplicate_authorization").Value); 126 return Task.FromResult(new SimpleResponse()); 127 }; 128 129 var channelCallCredentials = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 130 metadata.Add("first_authorization", "FIRST_SECRET_TOKEN"); 131 metadata.Add("duplicate_authorization", "value1"); 132 return TaskUtils.CompletedTask; 133 })); 134 var perCallCredentials = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 135 metadata.Add("second_authorization", "SECOND_SECRET_TOKEN"); 136 metadata.Add("duplicate_authorization", "value2"); 137 return TaskUtils.CompletedTask; 138 })); 139 140 var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(), channelCallCredentials); 141 channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options); 142 client = new TestService.TestServiceClient(channel); 143 144 client.UnaryCall(new SimpleRequest { }, new CallOptions(credentials: perCallCredentials)); 145 } 146 147 [Test] MetadataCredentials_Composed()148 public async Task MetadataCredentials_Composed() 149 { 150 serviceImpl.StreamingOutputCallHandler = async (req, responseStream, context) => 151 { 152 var firstAuth = context.RequestHeaders.Last((entry) => entry.Key == "first_authorization").Value; 153 Assert.AreEqual("FIRST_SECRET_TOKEN", firstAuth); 154 var secondAuth = context.RequestHeaders.First((entry) => entry.Key == "second_authorization").Value; 155 Assert.AreEqual("SECOND_SECRET_TOKEN", secondAuth); 156 var thirdAuth = context.RequestHeaders.First((entry) => entry.Key == "third_authorization").Value; 157 Assert.AreEqual("THIRD_SECRET_TOKEN", thirdAuth); 158 await responseStream.WriteAsync(new StreamingOutputCallResponse()); 159 }; 160 161 var first = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 162 // Attempt to exercise the case where async callback is inlineable/synchronously-runnable. 163 metadata.Add("first_authorization", "FIRST_SECRET_TOKEN"); 164 return TaskUtils.CompletedTask; 165 })); 166 var second = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 167 metadata.Add("second_authorization", "SECOND_SECRET_TOKEN"); 168 return TaskUtils.CompletedTask; 169 })); 170 var third = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 171 metadata.Add("third_authorization", "THIRD_SECRET_TOKEN"); 172 return TaskUtils.CompletedTask; 173 })); 174 var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(), 175 CallCredentials.Compose(first, second, third)); 176 channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options); 177 var client = new TestService.TestServiceClient(channel); 178 var call = client.StreamingOutputCall(new StreamingOutputCallRequest { }); 179 Assert.IsTrue(await call.ResponseStream.MoveNext()); 180 Assert.IsFalse(await call.ResponseStream.MoveNext()); 181 } 182 183 [Test] MetadataCredentials_ComposedPerCall()184 public async Task MetadataCredentials_ComposedPerCall() 185 { 186 serviceImpl.StreamingOutputCallHandler = async (req, responseStream, context) => 187 { 188 var firstAuth = context.RequestHeaders.Last((entry) => entry.Key == "first_authorization").Value; 189 Assert.AreEqual("FIRST_SECRET_TOKEN", firstAuth); 190 var secondAuth = context.RequestHeaders.First((entry) => entry.Key == "second_authorization").Value; 191 Assert.AreEqual("SECOND_SECRET_TOKEN", secondAuth); 192 await responseStream.WriteAsync(new StreamingOutputCallResponse()); 193 }; 194 195 channel = new Channel(Host, server.Ports.Single().BoundPort, TestCredentials.CreateSslCredentials(), options); 196 var client = new TestService.TestServiceClient(channel); 197 var first = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 198 metadata.Add("first_authorization", "FIRST_SECRET_TOKEN"); 199 return TaskUtils.CompletedTask; 200 })); 201 var second = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => { 202 metadata.Add("second_authorization", "SECOND_SECRET_TOKEN"); 203 return TaskUtils.CompletedTask; 204 })); 205 var call = client.StreamingOutputCall(new StreamingOutputCallRequest{ }, 206 new CallOptions(credentials: CallCredentials.Compose(first, second))); 207 Assert.IsTrue(await call.ResponseStream.MoveNext()); 208 Assert.IsFalse(await call.ResponseStream.MoveNext()); 209 } 210 211 [Test] MetadataCredentials_InterceptorLeavesMetadataEmpty()212 public void MetadataCredentials_InterceptorLeavesMetadataEmpty() 213 { 214 serviceImpl.UnaryCallHandler = (req, context) => 215 { 216 var authHeaderCount = context.RequestHeaders.Count((entry) => entry.Key == "authorization"); 217 Assert.AreEqual(0, authHeaderCount); 218 return Task.FromResult(new SimpleResponse()); 219 }; 220 var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(), 221 CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => TaskUtils.CompletedTask))); 222 channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options); 223 client = new TestService.TestServiceClient(channel); 224 client.UnaryCall(new SimpleRequest { }); 225 } 226 227 [Test] MetadataCredentials_InterceptorThrows()228 public void MetadataCredentials_InterceptorThrows() 229 { 230 var authInterceptorExceptionMessage = "Auth interceptor throws"; 231 var callCredentials = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => 232 { 233 throw new Exception(authInterceptorExceptionMessage); 234 })); 235 var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(), callCredentials); 236 channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options); 237 client = new TestService.TestServiceClient(channel); 238 239 var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { })); 240 Assert.AreEqual(StatusCode.Unavailable, ex.Status.StatusCode); 241 StringAssert.Contains(authInterceptorExceptionMessage, ex.Status.Detail); 242 } 243 244 private class FakeTestService : TestService.TestServiceBase 245 { 246 public UnaryServerMethod<SimpleRequest, SimpleResponse> UnaryCallHandler; 247 248 public ServerStreamingServerMethod<StreamingOutputCallRequest, StreamingOutputCallResponse> StreamingOutputCallHandler; 249 UnaryCall(SimpleRequest request, ServerCallContext context)250 public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context) 251 { 252 if (UnaryCallHandler != null) 253 { 254 return UnaryCallHandler(request, context); 255 } 256 return base.UnaryCall(request, context); 257 } 258 StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)259 public override Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context) 260 { 261 if (StreamingOutputCallHandler != null) 262 { 263 return StreamingOutputCallHandler(request, responseStream, context); 264 } 265 return base.StreamingOutputCall(request, responseStream, context); 266 } 267 } 268 } 269 } 270