diff --git a/src/csharp/Grpc.Auth/AuthInterceptors.cs b/src/csharp/Grpc.Auth/AuthInterceptors.cs new file mode 100644 index 00000000000..61338f7f0e2 --- /dev/null +++ b/src/csharp/Grpc.Auth/AuthInterceptors.cs @@ -0,0 +1,84 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Threading; + +using Google.Apis.Auth.OAuth2; +using Grpc.Core; +using Grpc.Core.Utils; + +namespace Grpc.Auth +{ + /// + /// Factory methods to create authorization interceptors. + /// + public static class AuthInterceptors + { + private const string AuthorizationHeader = "Authorization"; + private const string Schema = "Bearer"; + + /// + /// Creates interceptor that will obtain access token from any credential type that implements + /// ITokenAccess. (e.g. GoogleCredential). + /// + public static HeaderInterceptor FromCredential(ITokenAccess credential) + { + return new HeaderInterceptor((method, authUri, metadata) => + { + // TODO(jtattermusch): Rethink synchronous wait to obtain the result. + var accessToken = credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None) + .ConfigureAwait(false).GetAwaiter().GetResult(); + metadata.Add(CreateBearerTokenHeader(accessToken)); + }); + } + + /// + /// Creates OAuth2 interceptor that will use given access token as authorization. + /// + /// OAuth2 access token. + public static HeaderInterceptor FromAccessToken(string accessToken) + { + Preconditions.CheckNotNull(accessToken); + return new HeaderInterceptor((method, authUri, metadata) => + { + metadata.Add(CreateBearerTokenHeader(accessToken)); + }); + } + + private static Metadata.Entry CreateBearerTokenHeader(string accessToken) + { + return new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken); + } + } +} diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj index 930a34b0c33..4fb087d4a34 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj +++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj @@ -3,8 +3,6 @@ Debug AnyCPU - 10.0.0 - 2.0 {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA} Library Grpc.Auth @@ -41,57 +39,47 @@ C:\keys\Grpc.snk - - False + + + + + ..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll - - False + ..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll - - False + ..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll - - False + ..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll - - False + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll - - False + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll - - False + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll - - False + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - - - - - False + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll - - False + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll - Version.cs - + diff --git a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs deleted file mode 100644 index d628a83246d..00000000000 --- a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs +++ /dev/null @@ -1,115 +0,0 @@ -#region Copyright notice and license - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#endregion - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Security.Cryptography.X509Certificates; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -using Google.Apis.Auth.OAuth2; -using Google.Apis.Util; -using Grpc.Core; -using Grpc.Core.Utils; - -namespace Grpc.Auth -{ - public static class OAuth2Interceptors - { - /// - /// Creates OAuth2 interceptor that will obtain access token from GoogleCredentials. - /// - public static MetadataInterceptorDelegate FromCredential(GoogleCredential googleCredential) - { - var interceptor = new OAuth2Interceptor(googleCredential, SystemClock.Default); - return new MetadataInterceptorDelegate(interceptor.InterceptHeaders); - } - - /// - /// Creates OAuth2 interceptor that will use given OAuth2 token. - /// - /// - /// - public static MetadataInterceptorDelegate FromAccessToken(string oauth2Token) - { - Preconditions.CheckNotNull(oauth2Token); - return new MetadataInterceptorDelegate((authUri, metadata) => - { - metadata.Add(OAuth2Interceptor.CreateBearerTokenHeader(oauth2Token)); - }); - } - - /// - /// Injects OAuth2 authorization header into initial metadata (= request headers). - /// - private class OAuth2Interceptor - { - private const string AuthorizationHeader = "Authorization"; - private const string Schema = "Bearer"; - - private ITokenAccess credential; - private IClock clock; - - public OAuth2Interceptor(ITokenAccess credential, IClock clock) - { - this.credential = credential; - this.clock = clock; - } - - /// - /// Gets access token and requests refreshing it if is going to expire soon. - /// - /// - /// - public string GetAccessToken(string authUri, CancellationToken cancellationToken) - { - // TODO(jtattermusch): Rethink synchronous wait to obtain the result. - return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken: cancellationToken).GetAwaiter().GetResult(); - } - - public void InterceptHeaders(string authUri, Metadata metadata) - { - var accessToken = GetAccessToken(authUri, CancellationToken.None); - metadata.Add(CreateBearerTokenHeader(accessToken)); - } - - public static Metadata.Entry CreateBearerTokenHeader(string accessToken) - { - return new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken); - } - } - } -} diff --git a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs index bf020cd6274..fb9b562c77b 100644 --- a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs @@ -88,6 +88,24 @@ namespace Grpc.Core return responseAsync.GetAwaiter(); } + /// + /// Gets the call status if the call has already finished. + /// Throws InvalidOperationException otherwise. + /// + public Status GetStatus() + { + return getStatusFunc(); + } + + /// + /// Gets the call trailing metadata if the call has already finished. + /// Throws InvalidOperationException otherwise. + /// + public Metadata GetTrailers() + { + return getTrailersFunc(); + } + /// /// Provides means to cleanup after the call. /// If the call has already finished normally (request stream has been completed and call result has been received), doesn't do anything. diff --git a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs index 0979de606f7..183c84216a0 100644 --- a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs @@ -32,8 +32,6 @@ #endregion using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; namespace Grpc.Core { diff --git a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs index 380efcdb0e2..ab2049f2695 100644 --- a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs @@ -32,8 +32,6 @@ #endregion using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; namespace Grpc.Core { diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs index 0cb2953f2c2..ad54b46ad59 100644 --- a/src/csharp/Grpc.Core/ChannelOptions.cs +++ b/src/csharp/Grpc.Core/ChannelOptions.cs @@ -71,7 +71,7 @@ namespace Grpc.Core /// Creates a channel option with an integer value. /// /// Name. - /// String value. + /// Integer value. public ChannelOption(string name, int intValue) { this.type = OptionType.Integer; diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index f240d777b9c..7bc100ca603 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -32,15 +32,15 @@ #endregion using System; -using System.Collections.Generic; using System.Text.RegularExpressions; - -using Grpc.Core.Internal; -using Grpc.Core.Utils; +using System.Threading.Tasks; namespace Grpc.Core { - public delegate void MetadataInterceptorDelegate(string authUri, Metadata metadata); + /// + /// Interceptor for call headers. + /// + public delegate void HeaderInterceptor(IMethod method, string authUri, Metadata metadata); /// /// Base class for client-side stubs. @@ -60,10 +60,10 @@ namespace Grpc.Core } /// - /// Can be used to register a custom header (initial metadata) interceptor. - /// The delegate each time before a new call on this client is started. + /// Can be used to register a custom header (request metadata) interceptor. + /// The interceptor is invoked each time a new call on this client is started. /// - public MetadataInterceptorDelegate HeaderInterceptor + public HeaderInterceptor HeaderInterceptor { get; set; @@ -107,7 +107,7 @@ namespace Grpc.Core options = options.WithHeaders(new Metadata()); } var authUri = authUriBase != null ? authUriBase + method.ServiceName : null; - interceptor(authUri, options.Headers); + interceptor(method, authUri, options.Headers); } return new CallInvocationDetails(channel, method, Host, options); } diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs index 4c208b4a263..4c53285893e 100644 --- a/src/csharp/Grpc.Core/Method.cs +++ b/src/csharp/Grpc.Core/Method.cs @@ -54,10 +54,37 @@ namespace Grpc.Core DuplexStreaming } + /// + /// A non-generic representation of a remote method. + /// + public interface IMethod + { + /// + /// Gets the type of the method. + /// + MethodType Type { get; } + + /// + /// Gets the name of the service to which this method belongs. + /// + string ServiceName { get; } + + /// + /// Gets the unqualified name of the method. + /// + string Name { get; } + + /// + /// Gets the fully qualified name of the method. On the server side, methods are dispatched + /// based on this name. + /// + string FullName { get; } + } + /// /// A description of a remote method. /// - public class Method + public class Method : IMethod { readonly MethodType type; readonly string serviceName; diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs index 385ca920862..f4b0a1028f9 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs @@ -308,7 +308,7 @@ namespace Grpc.IntegrationTesting Console.WriteLine("running service_account_creds"); var credential = await GoogleCredential.GetApplicationDefaultAsync(); credential = credential.CreateScoped(new[] { AuthScope }); - client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + client.HeaderInterceptor = AuthInterceptors.FromCredential(credential); var request = SimpleRequest.CreateBuilder() .SetResponseType(PayloadType.COMPRESSABLE) @@ -332,7 +332,7 @@ namespace Grpc.IntegrationTesting Console.WriteLine("running compute_engine_creds"); var credential = await GoogleCredential.GetApplicationDefaultAsync(); Assert.IsFalse(credential.IsCreateScopedRequired); - client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + client.HeaderInterceptor = AuthInterceptors.FromCredential(credential); var request = SimpleRequest.CreateBuilder() .SetResponseType(PayloadType.COMPRESSABLE) @@ -357,7 +357,7 @@ namespace Grpc.IntegrationTesting var credential = await GoogleCredential.GetApplicationDefaultAsync(); // check this a credential with scope support, but don't add the scope. Assert.IsTrue(credential.IsCreateScopedRequired); - client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + client.HeaderInterceptor = AuthInterceptors.FromCredential(credential); var request = SimpleRequest.CreateBuilder() .SetResponseType(PayloadType.COMPRESSABLE) @@ -381,7 +381,7 @@ namespace Grpc.IntegrationTesting ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope }); string oauth2Token = await credential.GetAccessTokenForRequestAsync(); - client.HeaderInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token); + client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token); var request = SimpleRequest.CreateBuilder() .SetFillUsername(true) @@ -401,7 +401,7 @@ namespace Grpc.IntegrationTesting ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope }); string oauth2Token = await credential.GetAccessTokenForRequestAsync(); - var headerInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token); + var headerInterceptor = AuthInterceptors.FromAccessToken(oauth2Token); var request = SimpleRequest.CreateBuilder() .SetFillUsername(true) @@ -409,7 +409,7 @@ namespace Grpc.IntegrationTesting .Build(); var headers = new Metadata(); - headerInterceptor("", headers); + headerInterceptor(null, "", headers); var response = client.UnaryCall(request, headers: headers); Assert.AreEqual(AuthScopeResponse, response.OauthScope);