Skip to content
Snippets Groups Projects
Commit ad6b4bb8 authored by Michael Lumish's avatar Michael Lumish
Browse files

Merge pull request #3468 from jtattermusch/csharp_metadata_plugin

New C# auth API
parents b53b36e2 706a010e
No related branches found
No related tags found
No related merge requests found
Showing
with 705 additions and 65 deletions
...@@ -41,8 +41,8 @@ using Grpc.Core.Utils; ...@@ -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 ...@@ -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 @@ ...@@ -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>
......
#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 @@ ...@@ -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; ...@@ -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())
{
}
} }
} }
} }
#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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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());
} }
......
#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 @@ ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
} }
......
#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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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)
{ {
} }
......
...@@ -32,22 +32,26 @@ ...@@ -32,22 +32,26 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Grpc.Core.Internal; using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core namespace Grpc.Core
{ {
/// <summary> /// <summary>
/// Client-side credentials. Used for creation of a secure channel. /// Client-side channel credentials. Used for creation of a secure channel.
/// </summary> /// </summary>
public abstract class Credentials public abstract class ChannelCredentials
{ {
static readonly Credentials InsecureInstance = new InsecureCredentialsImpl(); static readonly ChannelCredentials InsecureInstance = new InsecureCredentialsImpl();
/// <summary> /// <summary>
/// Returns instance of credential that provides no security and /// Returns instance of credentials that provides no security and
/// will result in creating an unsecure channel with no encryption whatsoever. /// will result in creating an unsecure channel with no encryption whatsoever.
/// </summary> /// </summary>
public static Credentials Insecure public static ChannelCredentials Insecure
{ {
get get
{ {
...@@ -55,6 +59,29 @@ namespace Grpc.Core ...@@ -55,6 +59,29 @@ namespace Grpc.Core
} }
} }
/// <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> /// <summary>
/// Creates native object for the credentials. May return null if insecure channel /// Creates native object for the credentials. May return null if insecure channel
/// should be created. /// should be created.
...@@ -62,7 +89,15 @@ namespace Grpc.Core ...@@ -62,7 +89,15 @@ namespace Grpc.Core
/// <returns>The native credentials.</returns> /// <returns>The native credentials.</returns>
internal abstract CredentialsSafeHandle ToNativeCredentials(); internal abstract CredentialsSafeHandle ToNativeCredentials();
private sealed class InsecureCredentialsImpl : Credentials /// <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() internal override CredentialsSafeHandle ToNativeCredentials()
{ {
...@@ -74,7 +109,7 @@ namespace Grpc.Core ...@@ -74,7 +109,7 @@ namespace Grpc.Core
/// <summary> /// <summary>
/// Client-side SSL credentials. /// Client-side SSL credentials.
/// </summary> /// </summary>
public sealed class SslCredentials : Credentials public sealed class SslCredentials : ChannelCredentials
{ {
readonly string rootCertificates; readonly string rootCertificates;
readonly KeyCertificatePair keyCertificatePair; readonly KeyCertificatePair keyCertificatePair;
...@@ -130,9 +165,74 @@ namespace Grpc.Core ...@@ -130,9 +165,74 @@ namespace Grpc.Core
} }
} }
// Composing composite makes no sense.
internal override bool IsComposable
{
get { return true; }
}
internal override CredentialsSafeHandle ToNativeCredentials() internal override CredentialsSafeHandle ToNativeCredentials()
{ {
return CredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair); 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 ...@@ -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 ...@@ -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 ...@@ -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 + "/";
}
} }
} }
...@@ -48,7 +48,9 @@ ...@@ -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 @@ ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment