Merge pull request #3468 from jtattermusch/csharp_metadata_plugin

New C# auth API
pull/3739/head
Michael Lumish 9 years ago
commit ad6b4bb81a
  1. 24
      src/csharp/Grpc.Auth/AuthInterceptors.cs
  2. 1
      src/csharp/Grpc.Auth/Grpc.Auth.csproj
  3. 93
      src/csharp/Grpc.Auth/GrpcCredentials.cs
  4. 25
      src/csharp/Grpc.Core.Tests/CallCredentialsTest.cs
  5. 73
      src/csharp/Grpc.Core.Tests/ChannelCredentialsTest.cs
  6. 10
      src/csharp/Grpc.Core.Tests/ChannelTest.cs
  7. 73
      src/csharp/Grpc.Core.Tests/FakeCredentials.cs
  8. 4
      src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
  9. 2
      src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
  10. 4
      src/csharp/Grpc.Core.Tests/MarshallingErrorsTest.cs
  11. 2
      src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
  12. 142
      src/csharp/Grpc.Core/CallCredentials.cs
  13. 16
      src/csharp/Grpc.Core/CallOptions.cs
  14. 4
      src/csharp/Grpc.Core/Channel.cs
  15. 238
      src/csharp/Grpc.Core/ChannelCredentials.cs
  16. 31
      src/csharp/Grpc.Core/ClientBase.cs
  17. 138
      src/csharp/Grpc.Core/Credentials.cs
  18. 4
      src/csharp/Grpc.Core/Grpc.Core.csproj
  19. 10
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  20. 8
      src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
  21. 6
      src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
  22. 8
      src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs
  23. 112
      src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
  24. 2
      src/csharp/Grpc.Examples.MathClient/MathClient.cs
  25. 2
      src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
  26. 2
      src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
  27. 49
      src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj
  28. 11
      src/csharp/Grpc.IntegrationTesting.Client/packages.config
  29. 49
      src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj
  30. 11
      src/csharp/Grpc.IntegrationTesting.Server/packages.config
  31. 1
      src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
  32. 56
      src/csharp/Grpc.IntegrationTesting/InteropClient.cs
  33. 97
      src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
  34. 66
      src/csharp/ext/grpc_csharp_ext.c

@ -41,8 +41,8 @@ using Grpc.Core.Utils;
namespace Grpc.Auth namespace Grpc.Auth
{ {
/// <summary> /// <summary>
/// Factory methods to create authorization interceptors. Interceptors created can be registered with gRPC client classes (autogenerated client stubs that /// Factory methods to create authorization interceptors.
/// inherit from <see cref="Grpc.Core.ClientBase"/>). /// <seealso cref="GrpcCredentials"/>
/// </summary> /// </summary>
public static class AuthInterceptors public static class AuthInterceptors
{ {
@ -50,31 +50,29 @@ namespace Grpc.Auth
private const string Schema = "Bearer"; private const string Schema = "Bearer";
/// <summary> /// <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>). /// <c>ITokenAccess</c>. (e.g. <c>GoogleCredential</c>).
/// </summary> /// </summary>
/// <param name="credential">The credential to use to obtain access tokens.</param> /// <param name="credential">The credential to use to obtain access tokens.</param>
/// <returns>The header interceptor.</returns> /// <returns>The interceptor.</returns>
public static HeaderInterceptor FromCredential(ITokenAccess credential) 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 = await credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None).ConfigureAwait(false);
var accessToken = credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None)
.ConfigureAwait(false).GetAwaiter().GetResult();
metadata.Add(CreateBearerTokenHeader(accessToken)); metadata.Add(CreateBearerTokenHeader(accessToken));
}); });
} }
/// <summary> /// <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> /// </summary>
/// <param name="accessToken">OAuth2 access token.</param> /// <param name="accessToken">OAuth2 access token.</param>
/// <returns>The header interceptor.</returns> /// <returns>The interceptor.</returns>
public static HeaderInterceptor FromAccessToken(string accessToken) public static AsyncAuthInterceptor FromAccessToken(string accessToken)
{ {
Preconditions.CheckNotNull(accessToken); Preconditions.CheckNotNull(accessToken);
return new HeaderInterceptor((method, authUri, metadata) => return new AsyncAuthInterceptor(async (authUri, metadata) =>
{ {
metadata.Add(CreateBearerTokenHeader(accessToken)); metadata.Add(CreateBearerTokenHeader(accessToken));
}); });

@ -78,6 +78,7 @@
<Compile Include="..\Grpc.Core\Version.cs"> <Compile Include="..\Grpc.Core\Version.cs">
<Link>Version.cs</Link> <Link>Version.cs</Link>
</Compile> </Compile>
<Compile Include="GrpcCredentials.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="AuthInterceptors.cs" /> <Compile Include="AuthInterceptors.cs" />
</ItemGroup> </ItemGroup>

@ -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="ChannelCredentials"/> and <see cref="CallCredentials"/> classes.
/// </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="ChannelCredentials"/> 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 channel credentials for access token based auth over a secure channel.</returns>
public static ChannelCredentials Create(ITokenAccess credential, SslCredentials sslCredentials)
{
return ChannelCredentials.Create(sslCredentials, Create(credential));
}
/// <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);
}
}
}

@ -32,6 +32,10 @@
#endregion #endregion
using System; using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core; using Grpc.Core;
using Grpc.Core.Internal; using Grpc.Core.Internal;
using Grpc.Core.Utils; using Grpc.Core.Utils;
@ -39,24 +43,23 @@ using NUnit.Framework;
namespace Grpc.Core.Tests namespace Grpc.Core.Tests
{ {
public class ClientBaseTest public class CallCredentialsTest
{ {
[Test] [Test]
public void GetAuthUriBase_Valid() public void CallCredentials_ComposeAtLeastTwo()
{ {
Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com")); Assert.Throws(typeof(ArgumentException), () => CallCredentials.Compose(new FakeCallCredentials()));
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] [Test]
public void GetAuthUriBase_Invalid() public void CallCredentials_ToNativeCredentials()
{ {
Assert.IsNull(ClientBase.GetAuthUriBase("some.googleapi.com:")); var composite = CallCredentials.Compose(
Assert.IsNull(ClientBase.GetAuthUriBase("https://some.googleapi.com/")); new MetadataCredentials(async (uri, m) => { await Task.Delay(1); }),
Assert.IsNull(ClientBase.GetAuthUriBase("dns://some.googleapi.com:443")); // just two slashes new MetadataCredentials(async (uri, m) => { await Task.Delay(2); }));
Assert.IsNull(ClientBase.GetAuthUriBase("")); using (var nativeComposite = composite.ToNativeCredentials())
{
}
} }
} }
} }

@ -0,0 +1,73 @@
#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 ChannelCredentialsTest
{
[Test]
public void InsecureCredentials_IsNonComposable()
{
Assert.IsFalse(ChannelCredentials.Insecure.IsComposable);
}
[Test]
public void ChannelCredentials_CreateComposite()
{
var composite = ChannelCredentials.Create(new FakeChannelCredentials(true), new FakeCallCredentials());
Assert.IsFalse(composite.IsComposable);
Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(null, new FakeCallCredentials()));
Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(new FakeChannelCredentials(true), null));
// forbid composing non-composable
Assert.Throws(typeof(ArgumentException), () => ChannelCredentials.Create(new FakeChannelCredentials(false), new FakeCallCredentials()));
}
[Test]
public void ChannelCredentials_CreateWrapped()
{
ChannelCredentials.Create(new FakeCallCredentials());
}
}
}

@ -44,13 +44,13 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void Constructor_RejectsInvalidParams() public void Constructor_RejectsInvalidParams()
{ {
Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, Credentials.Insecure)); Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, ChannelCredentials.Insecure));
} }
[Test] [Test]
public void State_IdleAfterCreation() public void State_IdleAfterCreation()
{ {
var channel = new Channel("localhost", Credentials.Insecure); var channel = new Channel("localhost", ChannelCredentials.Insecure);
Assert.AreEqual(ChannelState.Idle, channel.State); Assert.AreEqual(ChannelState.Idle, channel.State);
channel.ShutdownAsync().Wait(); channel.ShutdownAsync().Wait();
} }
@ -58,7 +58,7 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void WaitForStateChangedAsync_InvalidArgument() public void WaitForStateChangedAsync_InvalidArgument()
{ {
var channel = new Channel("localhost", Credentials.Insecure); var channel = new Channel("localhost", ChannelCredentials.Insecure);
Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure)); Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure));
channel.ShutdownAsync().Wait(); channel.ShutdownAsync().Wait();
} }
@ -66,7 +66,7 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void ResolvedTarget() public void ResolvedTarget()
{ {
var channel = new Channel("127.0.0.1", Credentials.Insecure); var channel = new Channel("127.0.0.1", ChannelCredentials.Insecure);
Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1")); Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
channel.ShutdownAsync().Wait(); channel.ShutdownAsync().Wait();
} }
@ -74,7 +74,7 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void Shutdown_AllowedOnlyOnce() public void Shutdown_AllowedOnlyOnce()
{ {
var channel = new Channel("localhost", Credentials.Insecure); var channel = new Channel("localhost", ChannelCredentials.Insecure);
channel.ShutdownAsync().Wait(); channel.ShutdownAsync().Wait();
Assert.Throws(typeof(InvalidOperationException), () => channel.ShutdownAsync().GetAwaiter().GetResult()); Assert.Throws(typeof(InvalidOperationException), () => channel.ShutdownAsync().GetAwaiter().GetResult());
} }

@ -0,0 +1,73 @@
#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
{
internal class FakeChannelCredentials : ChannelCredentials
{
readonly bool composable;
public FakeChannelCredentials(bool composable)
{
this.composable = composable;
}
internal override bool IsComposable
{
get { return composable; }
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
return null;
}
}
internal class FakeCallCredentials : CallCredentials
{
internal override CredentialsSafeHandle ToNativeCredentials()
{
return null;
}
}
}

@ -63,8 +63,10 @@
<Compile Include="..\Grpc.Core\Version.cs"> <Compile Include="..\Grpc.Core\Version.cs">
<Link>Version.cs</Link> <Link>Version.cs</Link>
</Compile> </Compile>
<Compile Include="ClientBaseTest.cs" /> <Compile Include="CallCredentialsTest.cs" />
<Compile Include="FakeCredentials.cs" />
<Compile Include="MarshallingErrorsTest.cs" /> <Compile Include="MarshallingErrorsTest.cs" />
<Compile Include="ChannelCredentialsTest.cs" />
<Compile Include="ShutdownTest.cs" /> <Compile Include="ShutdownTest.cs" />
<Compile Include="Internal\AsyncCallTest.cs" /> <Compile Include="Internal\AsyncCallTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

@ -49,7 +49,7 @@ namespace Grpc.Core.Internal.Tests
[SetUp] [SetUp]
public void Init() public void Init()
{ {
channel = new Channel("localhost", Credentials.Insecure); channel = new Channel("localhost", ChannelCredentials.Insecure);
fakeCall = new FakeNativeCall(); fakeCall = new FakeNativeCall();

@ -119,7 +119,7 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void RequestParsingError_UnaryRequest() public void RequestParsingError_UnaryRequest()
{ {
helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
{ {
return Task.FromResult("RESPONSE"); return Task.FromResult("RESPONSE");
}); });
@ -161,7 +161,7 @@ namespace Grpc.Core.Tests
{ {
helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => 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"; return "RESPONSE";
}); });
var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall()); var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());

@ -154,7 +154,7 @@ namespace Grpc.Core.Tests
{ {
if (channel == null) if (channel == null)
{ {
channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure); channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure);
} }
return channel; return channel;
} }

@ -0,0 +1,142 @@
#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.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
/// Client-side call credentials. Provide authorization with per-call granularity.
/// </summary>
public abstract class CallCredentials
{
/// <summary>
/// Composes multiple multiple <c>CallCredentials</c> objects into
/// a single <c>CallCredentials</c> object.
/// </summary>
/// <param name="credentials">credentials to compose</param>
/// <returns>The new <c>CompositeCallCredentials</c></returns>
public static CallCredentials Compose(params CallCredentials[] credentials)
{
return new CompositeCallCredentials(credentials);
}
/// <summary>
/// Creates native object for the credentials.
/// </summary>
/// <returns>The native credentials.</returns>
internal abstract CredentialsSafeHandle ToNativeCredentials();
}
/// <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 class MetadataCredentials : CallCredentials
{
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;
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
NativeMetadataCredentialsPlugin plugin = new NativeMetadataCredentialsPlugin(interceptor);
return plugin.Credentials;
}
}
/// <summary>
/// Credentials that allow composing multiple credentials objects into one <see cref="CallCredentials"/> object.
/// </summary>
internal sealed class CompositeCallCredentials : CallCredentials
{
readonly List<CallCredentials> credentials;
/// <summary>
/// Initializes a new instance of <c>CompositeCallCredentials</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 CompositeCallCredentials(params CallCredentials[] credentials)
{
Preconditions.CheckArgument(credentials.Length >= 2, "Composite credentials object can only be created from 2 or more credentials.");
this.credentials = new List<CallCredentials>(credentials);
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
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))
{
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;
}
}
}
}

@ -49,6 +49,7 @@ namespace Grpc.Core
CancellationToken cancellationToken; CancellationToken cancellationToken;
WriteOptions writeOptions; WriteOptions writeOptions;
ContextPropagationToken propagationToken; ContextPropagationToken propagationToken;
CallCredentials credentials;
/// <summary> /// <summary>
/// Creates a new instance of <c>CallOptions</c> struct. /// 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="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="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="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), public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken),
WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null) WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null, CallCredentials credentials = null)
{ {
this.headers = headers; this.headers = headers;
this.deadline = deadline; this.deadline = deadline;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.writeOptions = writeOptions; this.writeOptions = writeOptions;
this.propagationToken = propagationToken; this.propagationToken = propagationToken;
this.credentials = credentials;
} }
/// <summary> /// <summary>
@ -114,6 +117,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Credentials to use for this call.
/// </summary>
public CallCredentials Credentials
{
get
{
return this.credentials;
}
}
/// <summary> /// <summary>
/// Returns new instance of <see cref="CallOptions"/> with /// Returns new instance of <see cref="CallOptions"/> with
/// <c>Headers</c> set to the value provided. Values of all other fields are preserved. /// <c>Headers</c> set to the value provided. Values of all other fields are preserved.

@ -68,7 +68,7 @@ namespace Grpc.Core
/// <param name="target">Target of the channel.</param> /// <param name="target">Target of the channel.</param>
/// <param name="credentials">Credentials to secure the channel.</param> /// <param name="credentials">Credentials to secure the channel.</param>
/// <param name="options">Channel options.</param> /// <param name="options">Channel options.</param>
public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null) public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null)
{ {
this.target = Preconditions.CheckNotNull(target, "target"); this.target = Preconditions.CheckNotNull(target, "target");
this.environment = GrpcEnvironment.AddRef(); this.environment = GrpcEnvironment.AddRef();
@ -96,7 +96,7 @@ namespace Grpc.Core
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <param name="credentials">Credentials to secure the channel.</param> /// <param name="credentials">Credentials to secure the channel.</param>
/// <param name="options">Channel options.</param> /// <param name="options">Channel options.</param>
public Channel(string host, int port, Credentials credentials, IEnumerable<ChannelOption> options = null) : public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null) :
this(string.Format("{0}:{1}", host, port), credentials, options) this(string.Format("{0}:{1}", host, port), credentials, options)
{ {
} }

@ -0,0 +1,238 @@
#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.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
/// Client-side channel credentials. Used for creation of a secure channel.
/// </summary>
public abstract class ChannelCredentials
{
static readonly ChannelCredentials InsecureInstance = new InsecureCredentialsImpl();
/// <summary>
/// Returns instance of credentials that provides no security and
/// will result in creating an unsecure channel with no encryption whatsoever.
/// </summary>
public static ChannelCredentials Insecure
{
get
{
return InsecureInstance;
}
}
/// <summary>
/// Creates a new instance of <c>ChannelCredentials</c> class by composing
/// given channel credentials with call credentials.
/// </summary>
/// <param name="channelCredentials">Channel credentials.</param>
/// <param name="callCredentials">Call credentials.</param>
/// <returns>The new composite <c>ChannelCredentials</c></returns>
public static ChannelCredentials Create(ChannelCredentials channelCredentials, CallCredentials callCredentials)
{
return new CompositeChannelCredentials(channelCredentials, callCredentials);
}
/// <summary>
/// Creates a new instance of <c>ChannelCredentials</c> by wrapping
/// an instance of <c>CallCredentials</c>.
/// </summary>
/// <param name="callCredentials">Call credentials.</param>
/// <returns>The <c>ChannelCredentials</c> wrapping given call credentials.</returns>
public static ChannelCredentials Create(CallCredentials callCredentials)
{
return new WrappedCallCredentials(callCredentials);
}
/// <summary>
/// Creates native object for the credentials. May return null if insecure channel
/// should be created.
/// </summary>
/// <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 false; }
}
private sealed class InsecureCredentialsImpl : ChannelCredentials
{
internal override CredentialsSafeHandle ToNativeCredentials()
{
return null;
}
}
}
/// <summary>
/// Client-side SSL credentials.
/// </summary>
public sealed class SslCredentials : ChannelCredentials
{
readonly string rootCertificates;
readonly KeyCertificatePair keyCertificatePair;
/// <summary>
/// Creates client-side SSL credentials loaded from
/// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
/// If that fails, gets the roots certificates from a well known place on disk.
/// </summary>
public SslCredentials() : this(null, null)
{
}
/// <summary>
/// Creates client-side SSL credentials from
/// a string containing PEM encoded root certificates.
/// </summary>
public SslCredentials(string rootCertificates) : this(rootCertificates, null)
{
}
/// <summary>
/// Creates client-side SSL credentials.
/// </summary>
/// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
/// <param name="keyCertificatePair">a key certificate pair.</param>
public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
{
this.rootCertificates = rootCertificates;
this.keyCertificatePair = keyCertificatePair;
}
/// <summary>
/// PEM encoding of the server root certificates.
/// </summary>
public string RootCertificates
{
get
{
return this.rootCertificates;
}
}
/// <summary>
/// Client side key and certificate pair.
/// If null, client will not use key and certificate pair.
/// </summary>
public KeyCertificatePair KeyCertificatePair
{
get
{
return this.keyCertificatePair;
}
}
// Composing composite makes no sense.
internal override bool IsComposable
{
get { return true; }
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
return CredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
}
}
/// <summary>
/// Credentials that allow composing one <see cref="ChannelCredentials"/> object and
/// one or more <see cref="CallCredentials"/> objects into a single <see cref="ChannelCredentials"/>.
/// </summary>
internal sealed class CompositeChannelCredentials : ChannelCredentials
{
readonly ChannelCredentials channelCredentials;
readonly CallCredentials callCredentials;
/// <summary>
/// Initializes a new instance of <c>CompositeChannelCredentials</c> class.
/// The resulting credentials object will be composite of all the credentials specified as parameters.
/// </summary>
/// <param name="channelCredentials">channelCredentials to compose</param>
/// <param name="callCredentials">channelCredentials to compose</param>
public CompositeChannelCredentials(ChannelCredentials channelCredentials, CallCredentials callCredentials)
{
this.channelCredentials = Preconditions.CheckNotNull(channelCredentials);
this.callCredentials = Preconditions.CheckNotNull(callCredentials);
Preconditions.CheckArgument(channelCredentials.IsComposable, "Supplied channel credentials do not allow composition.");
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
using (var cred1 = channelCredentials.ToNativeCredentials())
using (var cred2 = callCredentials.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;
}
}
}
/// <summary>
/// Credentials wrapping <see cref="CallCredentials"/> as <see cref="ChannelCredentials"/>.
/// </summary>
internal sealed class WrappedCallCredentials : ChannelCredentials
{
readonly CallCredentials callCredentials;
/// <summary>
/// Wraps instance of <c>CallCredentials</c> as <c>ChannelCredentials</c>.
/// </summary>
/// <param name="callCredentials">credentials to wrap</param>
public WrappedCallCredentials(CallCredentials callCredentials)
{
this.callCredentials = Preconditions.CheckNotNull(callCredentials);
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
return callCredentials.ToNativeCredentials();
}
}
}

@ -40,18 +40,17 @@ namespace Grpc.Core
/// <summary> /// <summary>
/// Interceptor for call headers. /// Interceptor for call headers.
/// </summary> /// </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> /// <summary>
/// Base class for client-side stubs. /// Base class for client-side stubs.
/// </summary> /// </summary>
public abstract class ClientBase 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 Channel channel;
readonly string authUriBase;
/// <summary> /// <summary>
/// Initializes a new instance of <c>ClientBase</c> class. /// Initializes a new instance of <c>ClientBase</c> class.
@ -60,13 +59,14 @@ namespace Grpc.Core
public ClientBase(Channel channel) public ClientBase(Channel channel)
{ {
this.channel = channel; this.channel = channel;
this.authUriBase = GetAuthUriBase(channel.Target);
} }
/// <summary> /// <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. /// 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> /// </summary>
/// <seealso cref="HeaderInterceptor"/>
public HeaderInterceptor HeaderInterceptor public HeaderInterceptor HeaderInterceptor
{ {
get; get;
@ -115,24 +115,9 @@ namespace Grpc.Core
{ {
options = options.WithHeaders(new Metadata()); options = options.WithHeaders(new Metadata());
} }
var authUri = authUriBase != null ? authUriBase + method.ServiceName : null; interceptor(method, options.Headers);
interceptor(method, authUri, options.Headers);
} }
return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options); 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 + "/";
}
} }
} }

@ -1,138 +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.Internal;
namespace Grpc.Core
{
/// <summary>
/// Client-side credentials. Used for creation of a secure channel.
/// </summary>
public abstract class Credentials
{
static readonly Credentials InsecureInstance = new InsecureCredentialsImpl();
/// <summary>
/// Returns instance of credential that provides no security and
/// will result in creating an unsecure channel with no encryption whatsoever.
/// </summary>
public static Credentials Insecure
{
get
{
return InsecureInstance;
}
}
/// <summary>
/// Creates native object for the credentials. May return null if insecure channel
/// should be created.
/// </summary>
/// <returns>The native credentials.</returns>
internal abstract CredentialsSafeHandle ToNativeCredentials();
private sealed class InsecureCredentialsImpl : Credentials
{
internal override CredentialsSafeHandle ToNativeCredentials()
{
return null;
}
}
}
/// <summary>
/// Client-side SSL credentials.
/// </summary>
public sealed class SslCredentials : Credentials
{
readonly string rootCertificates;
readonly KeyCertificatePair keyCertificatePair;
/// <summary>
/// Creates client-side SSL credentials loaded from
/// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
/// If that fails, gets the roots certificates from a well known place on disk.
/// </summary>
public SslCredentials() : this(null, null)
{
}
/// <summary>
/// Creates client-side SSL credentials from
/// a string containing PEM encoded root certificates.
/// </summary>
public SslCredentials(string rootCertificates) : this(rootCertificates, null)
{
}
/// <summary>
/// Creates client-side SSL credentials.
/// </summary>
/// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
/// <param name="keyCertificatePair">a key certificate pair.</param>
public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
{
this.rootCertificates = rootCertificates;
this.keyCertificatePair = keyCertificatePair;
}
/// <summary>
/// PEM encoding of the server root certificates.
/// </summary>
public string RootCertificates
{
get
{
return this.rootCertificates;
}
}
/// <summary>
/// Client side key and certificate pair.
/// If null, client will not use key and certificate pair.
/// </summary>
public KeyCertificatePair KeyCertificatePair
{
get
{
return this.keyCertificatePair;
}
}
internal override CredentialsSafeHandle ToNativeCredentials()
{
return CredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
}
}
}

@ -48,7 +48,9 @@
<ItemGroup> <ItemGroup>
<Compile Include="AsyncDuplexStreamingCall.cs" /> <Compile Include="AsyncDuplexStreamingCall.cs" />
<Compile Include="AsyncServerStreamingCall.cs" /> <Compile Include="AsyncServerStreamingCall.cs" />
<Compile Include="CallCredentials.cs" />
<Compile Include="IClientStreamWriter.cs" /> <Compile Include="IClientStreamWriter.cs" />
<Compile Include="Internal\NativeMetadataCredentialsPlugin.cs" />
<Compile Include="Internal\INativeCall.cs" /> <Compile Include="Internal\INativeCall.cs" />
<Compile Include="IServerStreamWriter.cs" /> <Compile Include="IServerStreamWriter.cs" />
<Compile Include="IAsyncStreamWriter.cs" /> <Compile Include="IAsyncStreamWriter.cs" />
@ -79,7 +81,7 @@
<Compile Include="Utils\AsyncStreamExtensions.cs" /> <Compile Include="Utils\AsyncStreamExtensions.cs" />
<Compile Include="Utils\BenchmarkUtil.cs" /> <Compile Include="Utils\BenchmarkUtil.cs" />
<Compile Include="Internal\CredentialsSafeHandle.cs" /> <Compile Include="Internal\CredentialsSafeHandle.cs" />
<Compile Include="Credentials.cs" /> <Compile Include="ChannelCredentials.cs" />
<Compile Include="Internal\ChannelArgsSafeHandle.cs" /> <Compile Include="Internal\ChannelArgsSafeHandle.cs" />
<Compile Include="Internal\AsyncCompletion.cs" /> <Compile Include="Internal\AsyncCompletion.cs" />
<Compile Include="Internal\AsyncCallBase.cs" /> <Compile Include="Internal\AsyncCallBase.cs" />

@ -344,9 +344,13 @@ namespace Grpc.Core.Internal
var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
return details.Channel.Handle.CreateCall(environment.CompletionRegistry, var credentials = details.Options.Credentials;
parentCall, ContextPropagationToken.DefaultMask, cq, using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null)
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); {
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. // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.

@ -98,6 +98,9 @@ namespace Grpc.Core.Internal
static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call, static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call,
BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray); 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")] [DllImport("grpc_csharp_ext.dll")]
static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call); static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call);
@ -113,6 +116,11 @@ namespace Grpc.Core.Internal
this.completionRegistry = completionRegistry; 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) public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();

@ -82,9 +82,13 @@ namespace Grpc.Core.Internal
return grpcsharp_secure_channel_create(credentials, target, channelArgs); 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); var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline);
if (credentials != null)
{
result.SetCredentials(credentials);
}
result.SetCompletionRegistry(registry); result.SetCompletionRegistry(registry);
return result; return result;
} }

@ -43,6 +43,9 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)] [DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)]
static extern CredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey); static extern CredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
[DllImport("grpc_csharp_ext.dll")]
static extern CredentialsSafeHandle grpcsharp_composite_credentials_create(CredentialsSafeHandle creds1, CredentialsSafeHandle creds2);
[DllImport("grpc_csharp_ext.dll")] [DllImport("grpc_csharp_ext.dll")]
static extern void grpcsharp_credentials_release(IntPtr credentials); static extern void grpcsharp_credentials_release(IntPtr credentials);
@ -69,6 +72,11 @@ namespace Grpc.Core.Internal
} }
} }
public static CredentialsSafeHandle CreateComposite(CredentialsSafeHandle creds1, CredentialsSafeHandle creds2)
{
return grpcsharp_composite_credentials_create(creds1, creds2);
}
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
grpcsharp_credentials_release(handle); grpcsharp_credentials_release(handle);

@ -0,0 +1,112 @@
#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.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core.Logging;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
{
internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy);
internal class NativeMetadataCredentialsPlugin
{
const string GetMetadataExceptionMsg = "Exception occured in metadata credentials plugin.";
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<NativeMetadataCredentialsPlugin>();
[DllImport("grpc_csharp_ext.dll")]
static extern CredentialsSafeHandle grpcsharp_metadata_credentials_create_from_plugin(NativeMetadataInterceptor interceptor);
[DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)]
static extern void grpcsharp_metadata_credentials_notify_from_plugin(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails);
AsyncAuthInterceptor interceptor;
GCHandle gcHandle;
NativeMetadataInterceptor nativeInterceptor;
CredentialsSafeHandle credentials;
public NativeMetadataCredentialsPlugin(AsyncAuthInterceptor interceptor)
{
this.interceptor = Preconditions.CheckNotNull(interceptor, "interceptor");
this.nativeInterceptor = NativeMetadataInterceptorHandler;
// Make sure the callback doesn't get garbage collected until it is destroyed.
this.gcHandle = GCHandle.Alloc(this.nativeInterceptor, GCHandleType.Normal);
this.credentials = grpcsharp_metadata_credentials_create_from_plugin(nativeInterceptor);
}
public CredentialsSafeHandle Credentials
{
get { return credentials; }
}
private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)
{
if (isDestroy)
{
gcHandle.Free();
return;
}
try
{
string serviceUrl = Marshal.PtrToStringAnsi(serviceUrlPtr);
StartGetMetadata(serviceUrl, callbackPtr, userDataPtr);
}
catch (Exception e)
{
grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, null, StatusCode.Unknown, GetMetadataExceptionMsg);
Logger.Error(e, GetMetadataExceptionMsg);
}
}
private async void StartGetMetadata(string serviceUrl, IntPtr callbackPtr, IntPtr userDataPtr)
{
try
{
var metadata = new Metadata();
await interceptor(serviceUrl, metadata);
using (var metadataArray = MetadataArraySafeHandle.Create(metadata))
{
grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, metadataArray, StatusCode.OK, null);
}
}
catch (Exception e)
{
grpcsharp_metadata_credentials_notify_from_plugin(callbackPtr, userDataPtr, null, StatusCode.Unknown, GetMetadataExceptionMsg);
Logger.Error(e, GetMetadataExceptionMsg);
}
}
}
}

@ -39,7 +39,7 @@ namespace Math
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var channel = new Channel("127.0.0.1", 23456, Credentials.Insecure); var channel = new Channel("127.0.0.1", 23456, ChannelCredentials.Insecure);
Math.IMathClient client = new Math.MathClient(channel); Math.IMathClient client = new Math.MathClient(channel);
MathExamples.DivExample(client); MathExamples.DivExample(client);

@ -61,7 +61,7 @@ namespace Math.Tests
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
}; };
server.Start(); server.Start();
channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure); channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
client = Math.NewClient(channel); client = Math.NewClient(channel);
} }

@ -63,7 +63,7 @@ namespace Grpc.HealthCheck.Tests
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
}; };
server.Start(); server.Start();
channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure); channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
client = Grpc.Health.V1Alpha.Health.NewClient(channel); client = Grpc.Health.V1Alpha.Health.NewClient(channel);
} }

@ -9,6 +9,7 @@
<AssemblyName>Grpc.IntegrationTesting.Client</AssemblyName> <AssemblyName>Grpc.IntegrationTesting.Client</AssemblyName>
<StartupObject>Grpc.IntegrationTesting.Client.Program</StartupObject> <StartupObject>Grpc.IntegrationTesting.Client.Program</StartupObject>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<NuGetPackageImportStamp>6d22e68f</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -38,7 +39,47 @@
<AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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" />
<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>
<ItemGroup> <ItemGroup>
<Compile Include="..\Grpc.Core\Version.cs"> <Compile Include="..\Grpc.Core\Version.cs">
@ -60,5 +101,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="packages.config" />
</ItemGroup> </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> </Project>

@ -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>

@ -9,6 +9,7 @@
<AssemblyName>Grpc.IntegrationTesting.Server</AssemblyName> <AssemblyName>Grpc.IntegrationTesting.Server</AssemblyName>
<StartupObject>Grpc.IntegrationTesting.Server.Program</StartupObject> <StartupObject>Grpc.IntegrationTesting.Server.Program</StartupObject>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<NuGetPackageImportStamp>d9ee8e52</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -38,7 +39,47 @@
<AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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" />
<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>
<ItemGroup> <ItemGroup>
<Compile Include="..\Grpc.Core\Version.cs"> <Compile Include="..\Grpc.Core\Version.cs">
@ -60,5 +101,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="packages.config" />
</ItemGroup> </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> </Project>

@ -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>

@ -96,6 +96,7 @@
<Compile Include="Empty.cs" /> <Compile Include="Empty.cs" />
<Compile Include="Messages.cs" /> <Compile Include="Messages.cs" />
<Compile Include="InteropClientServerTest.cs" /> <Compile Include="InteropClientServerTest.cs" />
<Compile Include="MetadataCredentialsTest.cs" />
<Compile Include="TestServiceImpl.cs" /> <Compile Include="TestServiceImpl.cs" />
<Compile Include="InteropServer.cs" /> <Compile Include="InteropServer.cs" />
<Compile Include="InteropClient.cs" /> <Compile Include="InteropClient.cs" />

@ -33,11 +33,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommandLine; using CommandLine;
using CommandLine.Text;
using Google.Apis.Auth.OAuth2; using Google.Apis.Auth.OAuth2;
using Google.Protobuf; using Google.Protobuf;
using Grpc.Auth; using Grpc.Auth;
@ -45,8 +47,6 @@ using Grpc.Core;
using Grpc.Core.Utils; using Grpc.Core.Utils;
using Grpc.Testing; using Grpc.Testing;
using NUnit.Framework; using NUnit.Framework;
using CommandLine.Text;
using System.IO;
namespace Grpc.IntegrationTesting namespace Grpc.IntegrationTesting
{ {
@ -116,7 +116,7 @@ namespace Grpc.IntegrationTesting
private async Task Run() private async Task Run()
{ {
var credentials = options.UseTls ? TestCredentials.CreateTestClientCredentials(options.UseTestCa) : Credentials.Insecure; var credentials = await CreateCredentialsAsync();
List<ChannelOption> channelOptions = null; List<ChannelOption> channelOptions = null;
if (!string.IsNullOrEmpty(options.ServerHostOverride)) if (!string.IsNullOrEmpty(options.ServerHostOverride))
@ -132,6 +132,26 @@ namespace Grpc.IntegrationTesting
await channel.ShutdownAsync(); await channel.ShutdownAsync();
} }
private async Task<ChannelCredentials> CreateCredentialsAsync()
{
var credentials = options.UseTls ? TestCredentials.CreateTestClientCredentials(options.UseTestCa) : ChannelCredentials.Insecure;
if (options.TestCase == "jwt_token_creds")
{
var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
Assert.IsTrue(googleCredential.IsCreateScopedRequired);
credentials = ChannelCredentials.Create(credentials, googleCredential.ToGrpcCredentials());
}
if (options.TestCase == "compute_engine_creds")
{
var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
Assert.IsFalse(googleCredential.IsCreateScopedRequired);
credentials = ChannelCredentials.Create(credentials, googleCredential.ToGrpcCredentials());
}
return credentials;
}
private async Task RunTestCaseAsync(TestService.TestServiceClient client, ClientOptions options) private async Task RunTestCaseAsync(TestService.TestServiceClient client, ClientOptions options)
{ {
switch (options.TestCase) switch (options.TestCase)
@ -155,10 +175,10 @@ namespace Grpc.IntegrationTesting
await RunEmptyStreamAsync(client); await RunEmptyStreamAsync(client);
break; break;
case "compute_engine_creds": case "compute_engine_creds":
await RunComputeEngineCredsAsync(client, options.DefaultServiceAccount, options.OAuthScope); RunComputeEngineCreds(client, options.DefaultServiceAccount, options.OAuthScope);
break; break;
case "jwt_token_creds": case "jwt_token_creds":
await RunJwtTokenCredsAsync(client, options.DefaultServiceAccount); RunJwtTokenCreds(client, options.DefaultServiceAccount);
break; break;
case "oauth2_auth_token": case "oauth2_auth_token":
await RunOAuth2AuthTokenAsync(client, options.DefaultServiceAccount, options.OAuthScope); await RunOAuth2AuthTokenAsync(client, options.DefaultServiceAccount, options.OAuthScope);
@ -318,12 +338,9 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!"); 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"); Console.WriteLine("running compute_engine_creds");
var credential = await GoogleCredential.GetApplicationDefaultAsync();
Assert.IsFalse(credential.IsCreateScopedRequired);
client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
var request = new SimpleRequest var request = new SimpleRequest
{ {
@ -334,6 +351,7 @@ namespace Grpc.IntegrationTesting
FillOauthScope = true FillOauthScope = true
}; };
// not setting credentials here because they were set on channel already
var response = client.UnaryCall(request); var response = client.UnaryCall(request);
Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
@ -344,12 +362,9 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!"); 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"); Console.WriteLine("running jwt_token_creds");
var credential = await GoogleCredential.GetApplicationDefaultAsync();
Assert.IsTrue(credential.IsCreateScopedRequired);
client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
var request = new SimpleRequest var request = new SimpleRequest
{ {
@ -359,6 +374,7 @@ namespace Grpc.IntegrationTesting
FillUsername = true, FillUsername = true,
}; };
// not setting credentials here because they were set on channel already
var response = client.UnaryCall(request); var response = client.UnaryCall(request);
Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
@ -373,15 +389,14 @@ namespace Grpc.IntegrationTesting
ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope });
string oauth2Token = await credential.GetAccessTokenForRequestAsync(); string oauth2Token = await credential.GetAccessTokenForRequestAsync();
client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token); var credentials = GrpcCredentials.FromAccessToken(oauth2Token);
var request = new SimpleRequest var request = new SimpleRequest
{ {
FillUsername = true, FillUsername = true,
FillOauthScope = true FillOauthScope = true
}; };
var response = client.UnaryCall(request); var response = client.UnaryCall(request, new CallOptions(credentials: credentials));
Assert.False(string.IsNullOrEmpty(response.OauthScope)); Assert.False(string.IsNullOrEmpty(response.OauthScope));
Assert.True(oauthScope.Contains(response.OauthScope)); Assert.True(oauthScope.Contains(response.OauthScope));
@ -392,18 +407,15 @@ namespace Grpc.IntegrationTesting
public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope) public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope)
{ {
Console.WriteLine("running per_rpc_creds"); Console.WriteLine("running per_rpc_creds");
ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); ITokenAccess googleCredential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope });
string accessToken = await credential.GetAccessTokenForRequestAsync();
var headerInterceptor = AuthInterceptors.FromAccessToken(accessToken);
var credentials = GrpcCredentials.Create(googleCredential);
var request = new SimpleRequest var request = new SimpleRequest
{ {
FillUsername = true, FillUsername = true,
}; };
var headers = new Metadata(); var response = client.UnaryCall(request, new CallOptions(credentials: credentials));
headerInterceptor(null, "", headers);
var response = client.UnaryCall(request, headers: headers);
Assert.AreEqual(defaultServiceAccount, response.Username); Assert.AreEqual(defaultServiceAccount, response.Username);
Console.WriteLine("Passed!"); Console.WriteLine("Passed!");

@ -0,0 +1,97 @@
#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.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Utils;
using Grpc.Testing;
using NUnit.Framework;
namespace Grpc.IntegrationTesting
{
public class MetadataCredentialsTest
{
const string Host = "localhost";
Server server;
Channel channel;
TestService.ITestServiceClient client;
[TestFixtureSetUp]
public void Init()
{
var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair(File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) });
server = new Server
{
Services = { TestService.BindService(new TestServiceImpl()) },
Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
};
server.Start();
var options = new List<ChannelOption>
{
new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
};
var asyncAuthInterceptor = new AsyncAuthInterceptor(async (authUri, metadata) =>
{
await Task.Delay(100); // make sure the operation is asynchronous.
metadata.Add("authorization", "SECRET_TOKEN");
});
var clientCredentials = ChannelCredentials.Create(
new SslCredentials(File.ReadAllText(TestCredentials.ClientCertAuthorityPath)),
new MetadataCredentials(asyncAuthInterceptor));
channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
client = TestService.NewClient(channel);
}
[TestFixtureTearDown]
public void Cleanup()
{
channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
[Test]
public void MetadataCredentials()
{
var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
Assert.AreEqual(10, response.Payload.Body.Length);
}
}
}

@ -68,7 +68,7 @@ grpc_byte_buffer *string_to_byte_buffer(const char *buffer, size_t len) {
/* /*
* Helper to maintain lifetime of batch op inputs and store batch op outputs. * Helper to maintain lifetime of batch op inputs and store batch op outputs.
*/ */
typedef struct gprcsharp_batch_context { typedef struct grpcsharp_batch_context {
grpc_metadata_array send_initial_metadata; grpc_metadata_array send_initial_metadata;
grpc_byte_buffer *send_message; grpc_byte_buffer *send_message;
struct { struct {
@ -665,16 +665,16 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call,
} }
GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_recv_initial_metadata( GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_recv_initial_metadata(
grpc_call *call, grpcsharp_batch_context *ctx) { grpc_call *call, grpcsharp_batch_context *ctx) {
/* TODO: don't use magic number */ /* TODO: don't use magic number */
grpc_op ops[1]; grpc_op ops[1];
ops[0].op = GRPC_OP_RECV_INITIAL_METADATA; ops[0].op = GRPC_OP_RECV_INITIAL_METADATA;
ops[0].data.recv_initial_metadata = &(ctx->recv_initial_metadata); ops[0].data.recv_initial_metadata = &(ctx->recv_initial_metadata);
ops[0].flags = 0; ops[0].flags = 0;
ops[0].reserved = NULL; ops[0].reserved = NULL;
return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx, return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
NULL); NULL);
} }
GPR_EXPORT grpc_call_error GPR_CALLTYPE GPR_EXPORT grpc_call_error GPR_CALLTYPE
@ -785,6 +785,11 @@ grpcsharp_call_send_initial_metadata(grpc_call *call,
NULL); 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 */ /* Server */
GPR_EXPORT grpc_server *GPR_CALLTYPE GPR_EXPORT grpc_server *GPR_CALLTYPE
@ -892,6 +897,47 @@ grpcsharp_server_add_secure_http2_port(grpc_server *server, const char *addr,
return grpc_server_add_secure_http2_port(server, addr, creds); return grpc_server_add_secure_http2_port(server, addr, creds);
} }
GPR_EXPORT grpc_credentials *GPR_CALLTYPE grpcsharp_composite_credentials_create(
grpc_credentials *creds1,
grpc_credentials *creds2) {
return grpc_composite_credentials_create(creds1, creds2, NULL);
}
/* Metadata credentials plugin */
GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin(
grpc_credentials_plugin_metadata_cb cb,
void *user_data, grpc_metadata_array *metadata,
grpc_status_code status, const char *error_details) {
cb(user_data, metadata->metadata, metadata->count, status, error_details);
}
typedef void(GPR_CALLTYPE *grpcsharp_metadata_interceptor_func)(
void *state, const char *service_url, grpc_credentials_plugin_metadata_cb cb,
void *user_data, gpr_int32 is_destroy);
static void grpcsharp_get_metadata_handler(void *state, const char *service_url,
grpc_credentials_plugin_metadata_cb cb, void *user_data) {
grpcsharp_metadata_interceptor_func interceptor =
(grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
interceptor(state, service_url, cb, user_data, 0);
}
static void grpcsharp_metadata_credentials_destroy_handler(void *state) {
grpcsharp_metadata_interceptor_func interceptor =
(grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
interceptor(state, NULL, NULL, NULL, 1);
}
GPR_EXPORT grpc_credentials *GPR_CALLTYPE grpcsharp_metadata_credentials_create_from_plugin(
grpcsharp_metadata_interceptor_func metadata_interceptor) {
grpc_metadata_credentials_plugin plugin;
plugin.get_metadata = grpcsharp_get_metadata_handler;
plugin.destroy = grpcsharp_metadata_credentials_destroy_handler;
plugin.state = (void*)(gpr_intptr)metadata_interceptor;
return grpc_metadata_credentials_create_from_plugin(plugin, NULL);
}
/* Logging */ /* Logging */
typedef void(GPR_CALLTYPE *grpcsharp_log_func)(const char *file, gpr_int32 line, typedef void(GPR_CALLTYPE *grpcsharp_log_func)(const char *file, gpr_int32 line,

Loading…
Cancel
Save