diff options
Diffstat (limited to 'src/csharp')
20 files changed, 481 insertions, 141 deletions
diff --git a/src/csharp/Grpc.Auth/AuthInterceptors.cs b/src/csharp/Grpc.Auth/AuthInterceptors.cs index c8ab4d9af6..fa92566775 100644 --- a/src/csharp/Grpc.Auth/AuthInterceptors.cs +++ b/src/csharp/Grpc.Auth/AuthInterceptors.cs @@ -41,8 +41,8 @@ using Grpc.Core.Utils; namespace Grpc.Auth { /// <summary> - /// Factory methods to create authorization interceptors. Interceptors created can be registered with gRPC client classes (autogenerated client stubs that - /// inherit from <see cref="Grpc.Core.ClientBase"/>). + /// Factory methods to create authorization interceptors. + /// <seealso cref="GrpcCredentials"/> /// </summary> public static class AuthInterceptors { @@ -50,31 +50,29 @@ namespace Grpc.Auth private const string Schema = "Bearer"; /// <summary> - /// Creates interceptor that will obtain access token from any credential type that implements + /// Creates an <see cref="AsyncAuthInterceptor"/> that will obtain access token from any credential type that implements /// <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>). /// </summary> /// <param name="credential">The credential to use to obtain access tokens.</param> - /// <returns>The header interceptor.</returns> - public static HeaderInterceptor FromCredential(ITokenAccess credential) + /// <returns>The interceptor.</returns> + public static AsyncAuthInterceptor FromCredential(ITokenAccess credential) { - return new HeaderInterceptor((method, authUri, metadata) => + return new AsyncAuthInterceptor(async (authUri, metadata) => { - // TODO(jtattermusch): Rethink synchronous wait to obtain the result. - var accessToken = credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None) - .ConfigureAwait(false).GetAwaiter().GetResult(); + var accessToken = await credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None).ConfigureAwait(false); metadata.Add(CreateBearerTokenHeader(accessToken)); }); } /// <summary> - /// Creates OAuth2 interceptor that will use given access token as authorization. + /// Creates an <see cref="AsyncAuthInterceptor"/> that will use given access token as authorization. /// </summary> /// <param name="accessToken">OAuth2 access token.</param> - /// <returns>The header interceptor.</returns> - public static HeaderInterceptor FromAccessToken(string accessToken) + /// <returns>The interceptor.</returns> + public static AsyncAuthInterceptor FromAccessToken(string accessToken) { Preconditions.CheckNotNull(accessToken); - return new HeaderInterceptor((method, authUri, metadata) => + return new AsyncAuthInterceptor(async (authUri, metadata) => { metadata.Add(CreateBearerTokenHeader(accessToken)); }); diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj index 4fb087d4a3..80ab07d2ae 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj +++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj @@ -78,6 +78,7 @@ <Compile Include="..\Grpc.Core\Version.cs"> <Link>Version.cs</Link> </Compile> + <Compile Include="GrpcCredentials.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="AuthInterceptors.cs" /> </ItemGroup> diff --git a/src/csharp/Grpc.Auth/GrpcCredentials.cs b/src/csharp/Grpc.Auth/GrpcCredentials.cs new file mode 100644 index 0000000000..496d8e1416 --- /dev/null +++ b/src/csharp/Grpc.Auth/GrpcCredentials.cs @@ -0,0 +1,93 @@ +#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 +{ + /// <summary> + /// Factory methods to create instances of <see cref="Credentials"/> class. + /// </summary> + public static class GrpcCredentials + { + /// <summary> + /// Creates a <see cref="MetadataCredentials"/> instance that will obtain access tokens + /// from any credential that implements <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>). + /// </summary> + /// <param name="credential">The credential to use to obtain access tokens.</param> + /// <returns>The <c>MetadataCredentials</c> instance.</returns> + public static MetadataCredentials Create(ITokenAccess credential) + { + return new MetadataCredentials(AuthInterceptors.FromCredential(credential)); + } + + /// <summary> + /// Convenience method to create a <see cref="CompositeCredentials"/> instance from + /// <c>ITokenAccess</c> credential and <c>SslCredentials</c> instance. + /// </summary> + /// <param name="credential">The credential to use to obtain access tokens.</param> + /// <param name="sslCredentials">The <c>SslCredentials</c> instance.</param> + /// <returns>The composite credential for access token based auth over a secure channel.</returns> + public static CompositeCredentials Create(ITokenAccess credential, SslCredentials sslCredentials) + { + return CompositeCredentials.Create(Create(credential), sslCredentials); + } + + /// <summary> + /// Creates an instance of <see cref="MetadataCredentials"/> that will use given access token to authenticate + /// with a gRPC service. + /// </summary> + /// <param name="accessToken">OAuth2 access token.</param> + /// /// <returns>The <c>MetadataCredentials</c> instance.</returns> + public static MetadataCredentials FromAccessToken(string accessToken) + { + return new MetadataCredentials(AuthInterceptors.FromAccessToken(accessToken)); + } + + /// <summary> + /// Converts a <c>ITokenAccess</c> object into a <see cref="MetadataCredentials"/> object supported + /// by gRPC. + /// </summary> + /// <param name="credential"></param> + /// <returns></returns> + public static MetadataCredentials ToGrpcCredentials(this ITokenAccess credential) + { + return GrpcCredentials.Create(credential); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs b/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs deleted file mode 100644 index 2dc10ebe97..0000000000 --- a/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs +++ /dev/null @@ -1,62 +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 Grpc.Core; -using Grpc.Core.Internal; -using Grpc.Core.Utils; -using NUnit.Framework; - -namespace Grpc.Core.Tests -{ - public class ClientBaseTest - { - [Test] - public void GetAuthUriBase_Valid() - { - Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com")); - Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com/")); - Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com:443/")); - Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com:443/")); - } - - [Test] - public void GetAuthUriBase_Invalid() - { - Assert.IsNull(ClientBase.GetAuthUriBase("some.googleapi.com:")); - Assert.IsNull(ClientBase.GetAuthUriBase("https://some.googleapi.com/")); - Assert.IsNull(ClientBase.GetAuthUriBase("dns://some.googleapi.com:443")); // just two slashes - Assert.IsNull(ClientBase.GetAuthUriBase("")); - } - } -} diff --git a/src/csharp/Grpc.Core.Tests/CredentialsTest.cs b/src/csharp/Grpc.Core.Tests/CredentialsTest.cs new file mode 100644 index 0000000000..5048144f98 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/CredentialsTest.cs @@ -0,0 +1,109 @@ +#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.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class CredentialsTest + { + [Test] + public void InsecureCredentials_IsNonComposable() + { + Assert.IsFalse(Credentials.Insecure.IsComposable); + } + + [Test] + public void CompositeCredentials_Create() + { + new CompositeCredentials(new FakeCredentials(true), new FakeCredentials(true), new FakeCredentials(true)); + } + + [Test] + public void CompositeCredentials_ComposeAtLeastTwo() + { + Assert.Throws(typeof(ArgumentException), () => new CompositeCredentials(new FakeCredentials(true))); + } + + [Test] + public void CompositeCredentials_ForbidsNonComposable() + { + Assert.Throws(typeof(ArgumentException), () => new CompositeCredentials(new FakeCredentials(true), new FakeCredentials(false))); + } + + [Test] + public void CompositeCredentials_ToNativeCredentials() + { + var composite = new CompositeCredentials(new MetadataCredentials(async (uri, m) => { await Task.Delay(1); }), new SslCredentials()); + using (var nativeComposite = composite.ToNativeCredentials()) + { + } + } + + [Test] + public void CompositeCredentials_OnlyOneConnectorCredentialAllowed() + { + var composite = new CompositeCredentials(new SslCredentials(), new SslCredentials()); + // it makes no sense to compose multiple ssl credentials. + Assert.Throws(typeof(ArgumentException), () => composite.ToNativeCredentials()); + } + + private class FakeCredentials : Credentials + { + readonly bool composable; + + public FakeCredentials(bool composable) + { + this.composable = composable; + } + + internal override bool IsComposable + { + get { return composable; } + } + + internal override CredentialsSafeHandle ToNativeCredentials() + { + return null; + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index f730936062..0ebfaa0a62 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -63,8 +63,8 @@ <Compile Include="..\Grpc.Core\Version.cs"> <Link>Version.cs</Link> </Compile> - <Compile Include="ClientBaseTest.cs" /> <Compile Include="MarshallingErrorsTest.cs" /> + <Compile Include="CredentialsTest.cs" /> <Compile Include="ShutdownTest.cs" /> <Compile Include="Internal\AsyncCallTest.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> diff --git a/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs index 83707e0c6d..37fb36946a 100644 --- a/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs +++ b/src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs @@ -119,7 +119,7 @@ namespace Grpc.Core.Tests [Test] public void RequestParsingError_UnaryRequest() { - helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => { return Task.FromResult("RESPONSE"); }); @@ -161,7 +161,7 @@ namespace Grpc.Core.Tests { helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => { - CollectionAssert.AreEqual(new [] {"A", "B"}, await requestStream.ToListAsync()); + CollectionAssert.AreEqual(new[] { "A", "B" }, await requestStream.ToListAsync()); return "RESPONSE"; }); var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall()); diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs index c3bc9c3156..c708fcdfc4 100644 --- a/src/csharp/Grpc.Core/CallOptions.cs +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -49,6 +49,7 @@ namespace Grpc.Core CancellationToken cancellationToken; WriteOptions writeOptions; ContextPropagationToken propagationToken; + Credentials credentials; /// <summary> /// Creates a new instance of <c>CallOptions</c> struct. @@ -58,14 +59,16 @@ namespace Grpc.Core /// <param name="cancellationToken">Can be used to request cancellation of the call.</param> /// <param name="writeOptions">Write options that will be used for this call.</param> /// <param name="propagationToken">Context propagation token obtained from <see cref="ServerCallContext"/>.</param> + /// <param name="credentials">Credentials to use for this call.</param> public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken), - WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null) + WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null, Credentials credentials = null) { this.headers = headers; this.deadline = deadline; this.cancellationToken = cancellationToken; this.writeOptions = writeOptions; this.propagationToken = propagationToken; + this.credentials = credentials; } /// <summary> @@ -115,6 +118,17 @@ namespace Grpc.Core } /// <summary> + /// Credentials to use for this call. + /// </summary> + public Credentials Credentials + { + get + { + return this.credentials; + } + } + + /// <summary> /// Returns new instance of <see cref="CallOptions"/> with /// <c>Headers</c> set to the value provided. Values of all other fields are preserved. /// </summary> diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index f4533e735c..e5b398062b 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -40,18 +40,17 @@ namespace Grpc.Core /// <summary> /// Interceptor for call headers. /// </summary> - public delegate void HeaderInterceptor(IMethod method, string authUri, Metadata metadata); + /// <remarks>Header interceptor is no longer to recommented way to perform authentication. + /// For header (initial metadata) based auth such as OAuth2 or JWT access token, use <see cref="MetadataCredentials"/>. + /// </remarks> + public delegate void HeaderInterceptor(IMethod method, Metadata metadata); /// <summary> /// Base class for client-side stubs. /// </summary> public abstract class ClientBase { - // Regex for removal of the optional DNS scheme, trailing port, and trailing backslash - static readonly Regex ChannelTargetPattern = new Regex(@"^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$"); - readonly Channel channel; - readonly string authUriBase; /// <summary> /// Initializes a new instance of <c>ClientBase</c> class. @@ -60,13 +59,14 @@ namespace Grpc.Core public ClientBase(Channel channel) { this.channel = channel; - this.authUriBase = GetAuthUriBase(channel.Target); } /// <summary> - /// Can be used to register a custom header (request metadata) interceptor. + /// Can be used to register a custom header interceptor. /// The interceptor is invoked each time a new call on this client is started. + /// It is not recommented to use header interceptor to add auth headers to RPC calls. /// </summary> + /// <seealso cref="HeaderInterceptor"/> public HeaderInterceptor HeaderInterceptor { get; @@ -115,24 +115,9 @@ namespace Grpc.Core { options = options.WithHeaders(new Metadata()); } - var authUri = authUriBase != null ? authUriBase + method.ServiceName : null; - interceptor(method, authUri, options.Headers); + interceptor(method, options.Headers); } return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options); } - - /// <summary> - /// Creates Auth URI base from channel's target (the one passed at channel creation). - /// Fully-qualified service name is to be appended to this. - /// </summary> - internal static string GetAuthUriBase(string target) - { - var match = ChannelTargetPattern.Match(target); - if (!match.Success) - { - return null; - } - return "https://" + match.Groups[2].Value + "/"; - } } } diff --git a/src/csharp/Grpc.Core/Credentials.cs b/src/csharp/Grpc.Core/Credentials.cs index e653d3688c..fcef627b25 100644 --- a/src/csharp/Grpc.Core/Credentials.cs +++ b/src/csharp/Grpc.Core/Credentials.cs @@ -40,9 +40,6 @@ using Grpc.Core.Utils; namespace Grpc.Core { - // TODO: rename - public delegate Task AsyncAuthInterceptor(string authUri, Metadata metadata); - /// <summary> /// Client-side credentials. Used for creation of a secure channel. /// </summary> @@ -69,12 +66,26 @@ namespace Grpc.Core /// <returns>The native credentials.</returns> internal abstract CredentialsSafeHandle ToNativeCredentials(); + /// <summary> + /// Returns <c>true</c> if this credential type allows being composed by <c>CompositeCredentials</c>. + /// </summary> + internal virtual bool IsComposable + { + get { return true; } + } + private sealed class InsecureCredentialsImpl : Credentials { internal override CredentialsSafeHandle ToNativeCredentials() { return null; } + + // Composing insecure credentials makes no sense. + internal override bool IsComposable + { + get { return false; } + } } } @@ -144,12 +155,25 @@ namespace Grpc.Core } /// <summary> + /// Asynchronous authentication interceptor for <see cref="MetadataCredentials"/>. + /// </summary> + /// <param name="authUri">URL of a service to which current remote call needs to authenticate</param> + /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param> + /// <returns></returns> + public delegate Task AsyncAuthInterceptor(string authUri, Metadata metadata); + + /// <summary> /// Client-side credentials that delegate metadata based auth to an interceptor. + /// The interceptor is automatically invoked for each remote call that uses <c>MetadataCredentials.</c> /// </summary> public partial class MetadataCredentials : Credentials { readonly AsyncAuthInterceptor interceptor; + /// <summary> + /// Initializes a new instance of <c>MetadataCredentials</c> class. + /// </summary> + /// <param name="interceptor">authentication interceptor</param> public MetadataCredentials(AsyncAuthInterceptor interceptor) { this.interceptor = interceptor; @@ -162,16 +186,34 @@ namespace Grpc.Core } } + /// <summary> + /// Credentials that allow composing multiple credentials objects into one <see cref="Credentials"/> object. + /// </summary> public sealed class CompositeCredentials : Credentials { readonly List<Credentials> credentials; + /// <summary> + /// Initializes a new instance of <c>CompositeCredentials</c> class. + /// The resulting credentials object will be composite of all the credentials specified as parameters. + /// </summary> + /// <param name="credentials">credentials to compose</param> public CompositeCredentials(params Credentials[] credentials) { Preconditions.CheckArgument(credentials.Length >= 2, "Composite credentials object can only be created from 2 or more credentials."); + foreach (var cred in credentials) + { + Preconditions.CheckArgument(cred.IsComposable, "Cannot create composite credentials: one or more credential objects do not allow composition."); + } this.credentials = new List<Credentials>(credentials); } + /// <summary> + /// Creates a new instance of <c>CompositeCredentials</c> class by composing + /// multiple <c>Credentials</c> objects. + /// </summary> + /// <param name="credentials">credentials to compose</param> + /// <returns>The new <c>CompositeCredentials</c></returns> public static CompositeCredentials Create(params Credentials[] credentials) { return new CompositeCredentials(credentials); @@ -179,12 +221,28 @@ namespace Grpc.Core internal override CredentialsSafeHandle ToNativeCredentials() { - var nativeComposite = credentials[0].ToNativeCredentials(); - for (int i = 1; i < credentials.Count; i++) + return ToNativeRecursive(0); + } + + // Recursive descent makes managing lifetime of intermediate CredentialSafeHandle instances easier. + // In practice, we won't usually see composites from more than two credentials anyway. + private CredentialsSafeHandle ToNativeRecursive(int startIndex) + { + if (startIndex == credentials.Count - 1) + { + return credentials[startIndex].ToNativeCredentials(); + } + + using (var cred1 = credentials[startIndex].ToNativeCredentials()) + using (var cred2 = ToNativeRecursive(startIndex + 1)) { - nativeComposite = CredentialsSafeHandle.CreateComposite(nativeComposite, credentials[i].ToNativeCredentials()); + var nativeComposite = CredentialsSafeHandle.CreateComposite(cred1, cred2); + if (nativeComposite.IsInvalid) + { + throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials."); + } + return nativeComposite; } - return nativeComposite; } } } diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index e3b00781c6..800462c854 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -344,9 +344,13 @@ namespace Grpc.Core.Internal var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; - return details.Channel.Handle.CreateCall(environment.CompletionRegistry, - parentCall, ContextPropagationToken.DefaultMask, cq, - details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); + var credentials = details.Options.Credentials; + using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null) + { + return details.Channel.Handle.CreateCall(environment.CompletionRegistry, + parentCall, ContextPropagationToken.DefaultMask, cq, + details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value), nativeCredentials); + } } // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called. diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs index c3611a7761..0be7a4dd3a 100644 --- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs @@ -99,6 +99,9 @@ namespace Grpc.Core.Internal BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray); [DllImport("grpc_csharp_ext.dll")] + static extern GRPCCallError grpcsharp_call_set_credentials(CallSafeHandle call, CredentialsSafeHandle credentials); + + [DllImport("grpc_csharp_ext.dll")] static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call); [DllImport("grpc_csharp_ext.dll")] @@ -113,6 +116,11 @@ namespace Grpc.Core.Internal this.completionRegistry = completionRegistry; } + public void SetCredentials(CredentialsSafeHandle credentials) + { + grpcsharp_call_set_credentials(this, credentials).CheckOk(); + } + public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { var ctx = BatchContextSafeHandle.Create(); diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs index 7a1c6e3dac..d270d77526 100644 --- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs @@ -82,9 +82,13 @@ namespace Grpc.Core.Internal return grpcsharp_secure_channel_create(credentials, target, channelArgs); } - public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline) + public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline, CredentialsSafeHandle credentials) { var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline); + if (credentials != null) + { + result.SetCredentials(credentials); + } result.SetCompletionRegistry(registry); return result; } diff --git a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj index 2c38c9645c..8bc2082a1d 100644 --- a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj @@ -9,6 +9,7 @@ <AssemblyName>Grpc.IntegrationTesting.Client</AssemblyName> <StartupObject>Grpc.IntegrationTesting.Client.Program</StartupObject> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <NuGetPackageImportStamp>6d22e68f</NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -38,7 +39,47 @@ <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> + <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks.Extensions"> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> + </Reference> + <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> <Reference Include="System" /> + <Reference Include="System.Net" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.Extensions"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http.Primitives"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http.WebRequest" /> </ItemGroup> <ItemGroup> <Compile Include="..\Grpc.Core\Version.cs"> @@ -60,5 +101,13 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> + <None Include="packages.config" /> </ItemGroup> + <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> + </Target> </Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.Client/packages.config b/src/csharp/Grpc.IntegrationTesting.Client/packages.config new file mode 100644 index 0000000000..7a02c95db9 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.Client/packages.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="BouncyCastle" version="1.7.0" targetFramework="net45" /> + <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" /> + <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj index 949ad61375..1eadbeb920 100644 --- a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj @@ -9,6 +9,7 @@ <AssemblyName>Grpc.IntegrationTesting.Server</AssemblyName> <StartupObject>Grpc.IntegrationTesting.Server.Program</StartupObject> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <NuGetPackageImportStamp>d9ee8e52</NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -38,7 +39,47 @@ <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> + <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks.Extensions"> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> + </Reference> + <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> <Reference Include="System" /> + <Reference Include="System.Net" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.Extensions"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http.Primitives"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http.WebRequest" /> </ItemGroup> <ItemGroup> <Compile Include="..\Grpc.Core\Version.cs"> @@ -60,5 +101,13 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> + <None Include="packages.config" /> </ItemGroup> + <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> + </Target> </Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.Server/packages.config b/src/csharp/Grpc.IntegrationTesting.Server/packages.config new file mode 100644 index 0000000000..7a02c95db9 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.Server/packages.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="BouncyCastle" version="1.7.0" targetFramework="net45" /> + <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" /> + <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" /> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> + <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" /> + <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs index 504d798b89..d3b7fe87f3 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs @@ -33,11 +33,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommandLine; +using CommandLine.Text; using Google.Apis.Auth.OAuth2; using Google.Protobuf; using Grpc.Auth; @@ -45,8 +47,6 @@ using Grpc.Core; using Grpc.Core.Utils; using Grpc.Testing; using NUnit.Framework; -using CommandLine.Text; -using System.IO; namespace Grpc.IntegrationTesting { @@ -117,6 +117,20 @@ namespace Grpc.IntegrationTesting private async Task Run() { var credentials = options.UseTls ? TestCredentials.CreateTestClientCredentials(options.UseTestCa) : Credentials.Insecure; + + if (options.TestCase == "jwt_token_creds") + { + var googleCredential = await GoogleCredential.GetApplicationDefaultAsync(); + Assert.IsTrue(googleCredential.IsCreateScopedRequired); + credentials = CompositeCredentials.Create(googleCredential.ToGrpcCredentials(), credentials); + } + + if (options.TestCase == "compute_engine_creds") + { + var googleCredential = await GoogleCredential.GetApplicationDefaultAsync(); + Assert.IsFalse(googleCredential.IsCreateScopedRequired); + credentials = CompositeCredentials.Create(googleCredential.ToGrpcCredentials(), credentials); + } List<ChannelOption> channelOptions = null; if (!string.IsNullOrEmpty(options.ServerHostOverride)) @@ -155,10 +169,10 @@ namespace Grpc.IntegrationTesting await RunEmptyStreamAsync(client); break; case "compute_engine_creds": - await RunComputeEngineCredsAsync(client, options.DefaultServiceAccount, options.OAuthScope); + RunComputeEngineCreds(client, options.DefaultServiceAccount, options.OAuthScope); break; case "jwt_token_creds": - await RunJwtTokenCredsAsync(client, options.DefaultServiceAccount); + RunJwtTokenCreds(client, options.DefaultServiceAccount); break; case "oauth2_auth_token": await RunOAuth2AuthTokenAsync(client, options.DefaultServiceAccount, options.OAuthScope); @@ -318,13 +332,10 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - public static async Task RunComputeEngineCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope) + public static void RunComputeEngineCreds(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope) { Console.WriteLine("running compute_engine_creds"); - var credential = await GoogleCredential.GetApplicationDefaultAsync(); - Assert.IsFalse(credential.IsCreateScopedRequired); - client.HeaderInterceptor = AuthInterceptors.FromCredential(credential); - + var request = new SimpleRequest { ResponseType = PayloadType.COMPRESSABLE, @@ -334,6 +345,7 @@ namespace Grpc.IntegrationTesting FillOauthScope = true }; + // not setting credentials here because they were set on channel already var response = client.UnaryCall(request); Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); @@ -344,13 +356,10 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - public static async Task RunJwtTokenCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount) + public static void RunJwtTokenCreds(TestService.TestServiceClient client, string defaultServiceAccount) { Console.WriteLine("running jwt_token_creds"); - var credential = await GoogleCredential.GetApplicationDefaultAsync(); - Assert.IsTrue(credential.IsCreateScopedRequired); - client.HeaderInterceptor = AuthInterceptors.FromCredential(credential); - + var request = new SimpleRequest { ResponseType = PayloadType.COMPRESSABLE, @@ -359,6 +368,7 @@ namespace Grpc.IntegrationTesting FillUsername = true, }; + // not setting credentials here because they were set on channel already var response = client.UnaryCall(request); Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); @@ -373,15 +383,14 @@ namespace Grpc.IntegrationTesting ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); string oauth2Token = await credential.GetAccessTokenForRequestAsync(); - client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token); - + var credentials = GrpcCredentials.FromAccessToken(oauth2Token); var request = new SimpleRequest { FillUsername = true, FillOauthScope = true }; - var response = client.UnaryCall(request); + var response = client.UnaryCall(request, new CallOptions(credentials: credentials)); Assert.False(string.IsNullOrEmpty(response.OauthScope)); Assert.True(oauthScope.Contains(response.OauthScope)); @@ -392,18 +401,15 @@ namespace Grpc.IntegrationTesting public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope) { Console.WriteLine("running per_rpc_creds"); - ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); - string accessToken = await credential.GetAccessTokenForRequestAsync(); - var headerInterceptor = AuthInterceptors.FromAccessToken(accessToken); + ITokenAccess googleCredential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); + var credentials = GrpcCredentials.Create(googleCredential); var request = new SimpleRequest { FillUsername = true, }; - var headers = new Metadata(); - headerInterceptor(null, "", headers); - var response = client.UnaryCall(request, headers: headers); + var response = client.UnaryCall(request, new CallOptions(credentials: credentials)); Assert.AreEqual(defaultServiceAccount, response.Username); Console.WriteLine("Passed!"); diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs index 76991dfc20..790bade89d 100644 --- a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs @@ -54,9 +54,7 @@ namespace Grpc.IntegrationTesting [TestFixtureSetUp] public void Init() { - var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair( - File.ReadAllText(TestCredentials.ServerCertChainPath), - File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) }); + var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair(File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) }); server = new Server { Services = { TestService.BindService(new TestServiceImpl()) }, @@ -77,8 +75,7 @@ namespace Grpc.IntegrationTesting var clientCredentials = CompositeCredentials.Create( new SslCredentials(File.ReadAllText(TestCredentials.ClientCertAuthorityPath)), - new MetadataCredentials(asyncAuthInterceptor) - ); + new MetadataCredentials(asyncAuthInterceptor)); channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options); client = TestService.NewClient(channel); } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 657f999ad4..0f05347dc5 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -785,6 +785,11 @@ grpcsharp_call_send_initial_metadata(grpc_call *call, NULL); } +GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_set_credentials(grpc_call *call, + grpc_credentials *creds) { + return grpc_call_set_credentials(call, creds); +} + /* Server */ GPR_EXPORT grpc_server *GPR_CALLTYPE |