diff --git a/src/Grpc.Net.ClientFactory/Internal/DefaultGrpcClientFactory.cs b/src/Grpc.Net.ClientFactory/Internal/DefaultGrpcClientFactory.cs index 37c4348d5..db368e940 100644 --- a/src/Grpc.Net.ClientFactory/Internal/DefaultGrpcClientFactory.cs +++ b/src/Grpc.Net.ClientFactory/Internal/DefaultGrpcClientFactory.cs @@ -19,6 +19,7 @@ using System; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; namespace Grpc.Net.ClientFactory.Internal @@ -27,17 +28,20 @@ internal class DefaultGrpcClientFactory : GrpcClientFactory { private readonly IServiceProvider _serviceProvider; private readonly GrpcCallInvokerFactory _callInvokerFactory; - private readonly IOptionsMonitor _clientFactoryOptionsMonitor; + private readonly IOptionsMonitor _grpcClientFactoryOptionsMonitor; + private readonly IOptionsMonitor _httpClientFactoryOptionsMonitor; private readonly IHttpMessageHandlerFactory _messageHandlerFactory; public DefaultGrpcClientFactory(IServiceProvider serviceProvider, GrpcCallInvokerFactory callInvokerFactory, - IOptionsMonitor clientFactoryOptionsMonitor, + IOptionsMonitor grpcClientFactoryOptionsMonitor, + IOptionsMonitor httpClientFactoryOptionsMonitor, IHttpMessageHandlerFactory messageHandlerFactory) { _serviceProvider = serviceProvider; _callInvokerFactory = callInvokerFactory; - _clientFactoryOptionsMonitor = clientFactoryOptionsMonitor; + _grpcClientFactoryOptionsMonitor = grpcClientFactoryOptionsMonitor; + _httpClientFactoryOptionsMonitor = httpClientFactoryOptionsMonitor; _messageHandlerFactory = messageHandlerFactory; } @@ -49,7 +53,13 @@ public override TClient CreateClient(string name) where TClient : class throw new InvalidOperationException($"No gRPC client configured with name '{name}'."); } - var clientFactoryOptions = _clientFactoryOptionsMonitor.Get(name); + var httpClientFactoryOptions = _httpClientFactoryOptionsMonitor.Get(name); + if (httpClientFactoryOptions.HttpClientActions.Count > 0) + { + throw new InvalidOperationException($"The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name '{name}'."); + } + + var clientFactoryOptions = _grpcClientFactoryOptionsMonitor.Get(name); var httpHandler = _messageHandlerFactory.CreateHandler(name); var callInvoker = _callInvokerFactory.CreateCallInvoker(httpHandler, name, typeof(TClient), clientFactoryOptions); diff --git a/test/FunctionalTests/Client/AuthorizationTests.cs b/test/FunctionalTests/Client/AuthorizationTests.cs new file mode 100644 index 000000000..a03e98960 --- /dev/null +++ b/test/FunctionalTests/Client/AuthorizationTests.cs @@ -0,0 +1,139 @@ +#region Copyright notice and license + +// Copyright 2019 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. + +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; +using Greet; +using Grpc.AspNetCore.FunctionalTests.Infrastructure; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Grpc.Net.Client; +using Grpc.Tests.Shared; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace Grpc.AspNetCore.FunctionalTests.Client +{ + [TestFixture] + public class AuthorizationTests : FunctionalTestBase + { + [Test] + public async Task Client_CallCredentials_RoundtripToken() + { + // Arrange + string? authorization = null; + Task UnaryTelemetryHeader(HelloRequest request, ServerCallContext context) + { + authorization = context.RequestHeaders.GetValue("authorization"); + + return Task.FromResult(new HelloReply()); + } + var method = Fixture.DynamicGrpc.AddUnaryMethod(UnaryTelemetryHeader); + + var token = "token!"; + var credentials = CallCredentials.FromInterceptor((context, metadata) => + { + if (!string.IsNullOrEmpty(token)) + { + metadata.Add("Authorization", $"Bearer {token}"); + } + return Task.CompletedTask; + }); + + var options = new GrpcChannelOptions + { + LoggerFactory = LoggerFactory, + Credentials = ChannelCredentials.Create(new SslCredentials(), credentials), + HttpHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + } + }; + + var channel = GrpcChannel.ForAddress(Fixture.GetUrl(TestServerEndpointName.Http2WithTls), options); + var client = TestClientFactory.Create(channel, method); + + var call = client.UnaryCall(new HelloRequest { Name = "world" }); + + // Act + await call.ResponseAsync.DefaultTimeout(); + + + Assert.AreEqual("Bearer token!", authorization); + } + + [Test] + public async Task ClientFactory_CallCredentials_RoundtripToken() + { + string? authorization = null; + Task UnaryTelemetryHeader(HelloRequest request, ServerCallContext context) + { + authorization = context.RequestHeaders.GetValue("authorization"); + + return Task.FromResult(new HelloReply()); + } + var method = Fixture.DynamicGrpc.AddUnaryMethod(UnaryTelemetryHeader); + + var token = "token!"; + var credentials = CallCredentials.FromInterceptor((context, metadata) => + { + if (!string.IsNullOrEmpty(token)) + { + metadata.Add("Authorization", $"Bearer {token}"); + } + return Task.CompletedTask; + }); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(LoggerFactory); + serviceCollection + .AddGrpcClient>(options => + { + options.Address = Fixture.GetUrl(TestServerEndpointName.Http2WithTls); + }) + .ConfigureChannel(channel => + { + channel.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials); + }) + .ConfigureGrpcClientCreator(invoker => + { + return TestClientFactory.Create(invoker, method); + }) + .ConfigurePrimaryHttpMessageHandler(() => + { + return new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + }); + var services = serviceCollection.BuildServiceProvider(); + + var client = services.GetRequiredService>(); + + var call = client.UnaryCall(new HelloRequest { Name = "world" }); + + await call.ResponseAsync.DefaultTimeout(); + + Assert.AreEqual("Bearer token!", authorization); + } + } +} diff --git a/test/Grpc.AspNetCore.Server.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs b/test/Grpc.AspNetCore.Server.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs index 00f9c7160..6d4c8d924 100644 --- a/test/Grpc.AspNetCore.Server.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs +++ b/test/Grpc.AspNetCore.Server.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs @@ -30,6 +30,7 @@ using Grpc.Tests.Shared; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; @@ -334,6 +335,7 @@ private static DefaultGrpcClientFactory CreateGrpcClientFactory(ServiceProvider return new DefaultGrpcClientFactory(serviceProvider, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService>(), serviceProvider.GetRequiredService()); } } diff --git a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs index 8bc95c151..e6caaed12 100644 --- a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs +++ b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs @@ -19,6 +19,7 @@ using System; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Greet; @@ -151,13 +152,12 @@ public void CreateClient_NoAddress_ThrowError() } [Test] - public void CreateClient_AddressSpecifiedOnHttpClientFactory_ThrowError() + public void CreateClient_ConfigureHttpClient_ThrowError() { // Arrange var services = new ServiceCollection(); services .AddGrpcClient() - // The underlying handler is used directly so no longer look for address on HttpClient .ConfigureHttpClient(options => options.BaseAddress = new Uri("http://contoso")) .ConfigurePrimaryHttpMessageHandler(() => { @@ -172,7 +172,7 @@ public void CreateClient_AddressSpecifiedOnHttpClientFactory_ThrowError() var ex = Assert.Throws(() => clientFactory.CreateClient(nameof(TestGreeterClient)))!; // Assert - Assert.AreEqual(@"Could not resolve the address for gRPC client 'TestGreeterClient'. Set an address when registering the client: services.AddGrpcClient(o => o.Address = new Uri(""https://localhost:5001""))", ex.Message); + Assert.AreEqual("The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'TestGreeterClient'.", ex.Message); } #if NET472 @@ -346,6 +346,7 @@ private static DefaultGrpcClientFactory CreateGrpcClientFactory(ServiceProvider return new DefaultGrpcClientFactory(serviceProvider, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService>(), serviceProvider.GetRequiredService()); } }