#109 expose IPrincipal on connection when identify is established through SASL or transport

This commit is contained in:
xinchen 2016-03-11 09:26:57 -08:00
Родитель 96d009ee96
Коммит 79af04dccc
14 изменённых файлов: 301 добавлений и 39 удалений

Просмотреть файл

@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Amqp.Micro.nuspec = Amqp.Micro.nuspec
Amqp.Net.nuspec = Amqp.Net.nuspec
build.cmd = build.cmd
dnx\Amqp.DotNet\project.json = dnx\Amqp.DotNet\project.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amqp.Net", "src\Amqp.Net.csproj", "{92153715-1D99-43B1-B291-470CF91A156D}"

Просмотреть файл

@ -64,7 +64,9 @@
"System.Reflection.Emit.Lightweight": "4.0.1-beta-23409",
"System.Reflection.Extensions": "4.0.1-beta-23409",
"System.Reflection.TypeExtensions": "4.0.1-beta-23409",
"System.Runtime.Serialization.Primitives": "4.0.11-beta-23409"
"System.Runtime.Serialization.Primitives": "4.0.11-beta-23409",
"System.Security.Claims": "4.0.1-beta-23509",
"System.Security.Principal": "4.0.1-beta-23509"
}
}
}

Просмотреть файл

@ -49,6 +49,8 @@
<Compile Include="Listener\AttachContext.cs" />
<Compile Include="Listener\ContextState.cs" />
<Compile Include="Listener\FlowContext.cs" />
<Compile Include="Listener\IAuthenticated.cs" />
<Compile Include="Listener\X509Identity.cs" />
<Compile Include="Listener\LinkCollection.cs" />
<Compile Include="Listener\ILinkProcessor.cs" />
<Compile Include="Listener\LinkEndpoint.cs" />
@ -71,8 +73,8 @@
<Compile Include="Net\ConnectionFactoryBase.cs" />
<Compile Include="Properties\Version.cs" />
<Compile Include="Sasl\SaslExternalProfile.cs" />
<Compile Include="Sasl\SaslPlainMechanism.cs" />
<Compile Include="Sasl\SaslMechanism.cs" />
<Compile Include="Listener\SaslPlainMechanism.cs" />
<Compile Include="Listener\SaslMechanism.cs" />
<Compile Include="Listener\IContainer.cs" />
<Compile Include="Listener\ListenerSession.cs" />
<Compile Include="Listener\ListenerConnection.cs" />

Просмотреть файл

@ -51,8 +51,6 @@
<Compile Include="Net\TypeExtensions.cs" />
<Compile Include="Properties\Version.cs" />
<Compile Include="Sasl\SaslExternalProfile.cs" />
<Compile Include="Sasl\SaslPlainMechanism.cs" />
<Compile Include="Sasl\SaslMechanism.cs" />
<Compile Include="Delivery.cs" />
<Compile Include="Framing\Accepted.cs" />
<Compile Include="Framing\ApplicationProperties.cs" />

Просмотреть файл

@ -48,6 +48,7 @@
<Compile Include="Listener\AttachContext.cs" />
<Compile Include="Listener\ContextState.cs" />
<Compile Include="Listener\FlowContext.cs" />
<Compile Include="Listener\IAuthenticated.cs" />
<Compile Include="Listener\LinkCollection.cs" />
<Compile Include="Listener\ILinkProcessor.cs" />
<Compile Include="Listener\LinkEndpoint.cs" />
@ -58,6 +59,9 @@
<Compile Include="Listener\IMessageProcessor.cs" />
<Compile Include="Listener\Context.cs" />
<Compile Include="Listener\ListenerLink.cs" />
<Compile Include="Listener\SaslMechanism.cs" />
<Compile Include="Listener\SaslPlainMechanism.cs" />
<Compile Include="Listener\X509Identity.cs" />
<Compile Include="Net\ResourceManager.cs" />
<Compile Include="Net\BufferManager.cs" />
<Compile Include="Net\SocketExtensions.cs" />
@ -70,8 +74,6 @@
<Compile Include="Net\ConnectionFactoryBase.cs" />
<Compile Include="Properties\Version.cs" />
<Compile Include="Sasl\SaslExternalProfile.cs" />
<Compile Include="Sasl\SaslPlainMechanism.cs" />
<Compile Include="Sasl\SaslMechanism.cs" />
<Compile Include="Listener\IContainer.cs" />
<Compile Include="Listener\ListenerSession.cs" />
<Compile Include="Listener\ListenerConnection.cs" />

Просмотреть файл

@ -27,6 +27,7 @@ namespace Amqp.Listener
#endif
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading.Tasks;
using Amqp.Framing;
using Amqp.Sasl;
@ -190,13 +191,27 @@ namespace Amqp.Listener
async Task HandleTransportAsync(IAsyncTransport transport)
{
IPrincipal principal = null;
if (this.saslSettings != null)
{
ListenerSaslProfile profile = new ListenerSaslProfile(this);
transport = await profile.OpenAsync(null, this.BufferManager, transport);
principal = profile.GetPrincipal();
}
Connection connection = new ListenerConnection(this, this.address, transport);
var connection = new ListenerConnection(this, this.address, transport);
if (principal == null)
{
// SASL principal preferred. If not present, check transport.
IAuthenticated authenticated = transport as IAuthenticated;
if (authenticated != null)
{
principal = authenticated.Principal;
}
}
connection.Principal = principal;
bool shouldClose = false;
lock (this.connections)
{
@ -355,9 +370,15 @@ namespace Amqp.Listener
this.listener = listener;
}
public SaslProfile InnerProfile
public IPrincipal GetPrincipal()
{
get { return this.innerProfile; }
IAuthenticated authenticated = this.innerProfile as IAuthenticated;
if (authenticated != null)
{
return authenticated.Principal;
}
return null;
}
protected override ITransport UpgradeTransport(ITransport transport)
@ -499,7 +520,7 @@ namespace Amqp.Listener
protected virtual Task<IAsyncTransport> CreateTransportAsync(Socket socket)
{
var tcs = new TaskCompletionSource<IAsyncTransport>();
tcs.SetResult(new TcpTransport(socket, this.Listener.BufferManager));
tcs.SetResult(new ListenerTcpTransport(socket, this.Listener.BufferManager));
return tcs.Task;
}
@ -558,7 +579,36 @@ namespace Amqp.Listener
this.Listener.sslSettings.Protocols, this.Listener.sslSettings.CheckCertificateRevocation);
}
return new TcpTransport(sslStream, this.Listener.BufferManager);
return new ListenerTcpTransport(sslStream, this.Listener.BufferManager);
}
}
class ListenerTcpTransport : TcpTransport, IAuthenticated
{
public ListenerTcpTransport(Socket socket, IBufferManager bufferManager)
: base(bufferManager)
{
this.socketTransport = new TcpSocket(this, socket);
this.writer = new Writer(this, this.socketTransport);
}
public ListenerTcpTransport(SslStream sslStream, IBufferManager bufferManager)
: base(bufferManager)
{
this.socketTransport = new SslSocket(this, sslStream);
this.writer = new Writer(this, this.socketTransport);
if (sslStream.RemoteCertificate != null)
{
this.Principal = new GenericPrincipal(
new X509Identity(sslStream.RemoteCertificate),
new string[0]);
}
}
public IPrincipal Principal
{
get;
private set;
}
}
@ -597,7 +647,7 @@ namespace Amqp.Listener
try
{
var wsContext = await context.AcceptWebSocketAsync(WebSocketTransport.WebSocketSubProtocol);
var wsTransport = new WebSocketTransport(wsContext.WebSocket);
var wsTransport = new ListenerWebSocketTransport(wsContext);
await this.listener.HandleTransportAsync(wsTransport);
}
catch(Exception exception)
@ -627,6 +677,21 @@ namespace Amqp.Listener
}
}
}
class ListenerWebSocketTransport : WebSocketTransport, IAuthenticated
{
public ListenerWebSocketTransport(HttpListenerWebSocketContext context)
: base(context.WebSocket)
{
this.Principal = context.User;
}
public IPrincipal Principal
{
get;
private set;
}
}
#endif
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// ------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this
// file except in compliance with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR
// CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------------------
namespace Amqp.Listener
{
using System.Security.Principal;
interface IAuthenticated
{
IPrincipal Principal { get; }
}
}

Просмотреть файл

@ -18,6 +18,7 @@
namespace Amqp.Listener
{
using System;
using System.Security.Principal;
using System.Threading;
using Amqp.Framing;
@ -35,6 +36,16 @@ namespace Amqp.Listener
this.listener = listener;
}
/// <summary>
/// Gets a IPrincipal object for the connection. If the value is null,
/// the connection is not authenticated.
/// </summary>
public IPrincipal Principal
{
get;
internal set;
}
internal ConnectionListener Listener
{
get { return this.listener; }

Просмотреть файл

@ -15,8 +15,10 @@
// limitations under the License.
// ------------------------------------------------------------------------------------
namespace Amqp.Sasl
namespace Amqp.Listener
{
using Amqp.Sasl;
abstract class SaslMechanism
{
public abstract string Name { get; }

Просмотреть файл

@ -15,12 +15,14 @@
// limitations under the License.
// ------------------------------------------------------------------------------------
namespace Amqp.Sasl
namespace Amqp.Listener
{
using System;
using System.Text;
using Amqp.Framing;
using Amqp.Types;
using Amqp.Sasl;
using System.Security.Principal;
class SaslPlainMechanism : SaslMechanism
{
@ -43,7 +45,7 @@ namespace Amqp.Sasl
return new SaslPlainProfile(this);
}
class SaslPlainProfile : SaslProfile
class SaslPlainProfile : SaslProfile, IAuthenticated
{
readonly SaslPlainMechanism mechanism;
@ -52,6 +54,12 @@ namespace Amqp.Sasl
this.mechanism = mechanism;
}
public IPrincipal Principal
{
get;
private set;
}
protected override ITransport UpgradeTransport(ITransport transport)
{
return transport;
@ -85,6 +93,10 @@ namespace Amqp.Sasl
string.Equals(this.mechanism.user, items[1], StringComparison.OrdinalIgnoreCase) &&
string.Equals(this.mechanism.password, items[2], StringComparison.Ordinal))
{
this.Principal = new GenericPrincipal(
new GenericIdentity(string.IsNullOrEmpty(items[2]) ? items[0] : items[2], this.mechanism.Name),
new string[0]);
return SaslCode.Ok;
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// ------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this
// file except in compliance with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR
// CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------------------
namespace Amqp.Listener
{
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
/// <summary>
/// Represents an client identity established by SSL client certificate authentication.
/// </summary>
public class X509Identity : IIdentity
{
internal X509Identity(X509Certificate certificate)
{
this.Certificate = certificate;
}
/// <summary>
/// Gets the client certificate.
/// </summary>
public X509Certificate Certificate
{
get;
private set;
}
/// <summary>
/// Gets the type of authentication used. ("X509").
/// </summary>
public string AuthenticationType
{
get { return "X509"; }
}
/// <summary>
/// Gets a value that indicates whether the user has been authenticated.
/// </summary>
public bool IsAuthenticated
{
get { return true; }
}
/// <summary>
/// Gets the name of the identity.
/// </summary>
public string Name
{
get { return this.Certificate.Subject; }
}
}
}

Просмотреть файл

@ -24,13 +24,13 @@ namespace Amqp
using System.Net.Sockets;
using System.Threading.Tasks;
sealed class TcpTransport : IAsyncTransport
class TcpTransport : IAsyncTransport
{
static readonly RemoteCertificateValidationCallback noneCertValidator = (a, b, c, d) => true;
readonly IBufferManager bufferManager;
Connection connection;
Writer writer;
IAsyncTransport socketTransport;
protected Writer writer;
protected IAsyncTransport socketTransport;
public TcpTransport()
: this(null)
@ -42,22 +42,6 @@ namespace Amqp
this.bufferManager = bufferManager;
}
// called by listener
public TcpTransport(Socket socket, IBufferManager bufferManager)
: this(bufferManager)
{
this.socketTransport = new TcpSocket(this, socket);
this.writer = new Writer(this, this.socketTransport);
}
// called by listener
public TcpTransport(SslStream sslStream, IBufferManager bufferManager)
: this(bufferManager)
{
this.socketTransport = new SslSocket(this, sslStream);
this.writer = new Writer(this, this.socketTransport);
}
public void Connect(Connection connection, Address address, bool noVerification)
{
this.connection = connection;
@ -203,7 +187,7 @@ namespace Amqp
this.writer.DisposeQueuedBuffers();
}
class TcpSocket : IAsyncTransport
protected class TcpSocket : IAsyncTransport
{
readonly static EventHandler<SocketAsyncEventArgs> onWriteComplete = OnWriteComplete;
readonly TcpTransport transport;
@ -353,7 +337,7 @@ namespace Amqp
}
}
class SslSocket : IAsyncTransport
protected class SslSocket : IAsyncTransport
{
readonly TcpTransport transport;
readonly SslStream sslStream;
@ -439,7 +423,7 @@ namespace Amqp
}
}
sealed class Writer
protected class Writer
{
readonly TcpTransport owner;
readonly IAsyncTransport transport;

Просмотреть файл

@ -25,7 +25,7 @@ namespace Amqp
using System.Threading;
using System.Threading.Tasks;
sealed class WebSocketTransport : IAsyncTransport
class WebSocketTransport : IAsyncTransport
{
public const string WebSocketSubProtocol = "AMQPWSB10";
public const string WebSockets = "WS";

Просмотреть файл

@ -18,10 +18,12 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Amqp;
using Amqp.Framing;
using Amqp.Listener;
using Amqp.Sasl;
using Amqp.Types;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -58,6 +60,7 @@ namespace Test.Amqp
this.Uri = new Uri("amqp://guest:guest@localhost:5765");
this.host = new ContainerHost(new List<Uri>() { this.Uri }, null, this.Uri.UserInfo);
this.host.Listeners[0].SASL.EnableExternalMechanism = true;
this.host.Open();
}
@ -476,6 +479,70 @@ namespace Test.Amqp
connection.Close();
}
[TestMethod]
public void ContainerHostPlainPrincipalTest()
{
string name = MethodInfo.GetCurrentMethod().Name;
ListenerLink link = null;
var linkProcessor = new TestLinkProcessor();
linkProcessor.OnLinkAttached += a => link = a;
this.host.RegisterLinkProcessor(linkProcessor);
var connection = new Connection(Address);
var session = new Session(connection);
var sender = new SenderLink(session, name, name);
sender.Send(new Message("msg1"), SendTimeout);
connection.Close();
Assert.IsTrue(link != null, "link is null");
var listenerConnection = (ListenerConnection)link.Session.Connection;
Assert.IsTrue(listenerConnection.Principal != null, "principal is null");
Assert.IsTrue(listenerConnection.Principal.Identity.AuthenticationType == "PLAIN", "wrong auth type");
}
[TestMethod]
public void ContainerHostX509PrincipalTest()
{
string name = MethodInfo.GetCurrentMethod().Name;
string address = "amqps://localhost:5676";
X509Certificate2 cert = GetCertificate(StoreLocation.LocalMachine, StoreName.My, "localhost");
ContainerHost sslHost = new ContainerHost(new Uri(address));
sslHost.Listeners[0].SSL.Certificate = cert;
sslHost.Listeners[0].SSL.ClientCertificateRequired = true;
sslHost.Listeners[0].SSL.RemoteCertificateValidationCallback = (a, b, c, d) => true;
sslHost.Listeners[0].SASL.EnableExternalMechanism = true;
ListenerLink link = null;
var linkProcessor = new TestLinkProcessor();
linkProcessor.OnLinkAttached += a => link = a;
sslHost.RegisterLinkProcessor(linkProcessor);
sslHost.Open();
try
{
var factory = new ConnectionFactory();
factory.SSL.RemoteCertificateValidationCallback = (a, b, c, d) => true;
factory.SSL.ClientCertificates.Add(cert);
factory.SASL.Profile = SaslProfile.External;
var connection = factory.CreateAsync(new Address(address)).Result;
var session = new Session(connection);
var sender = new SenderLink(session, name, name);
sender.Send(new Message("msg1"), SendTimeout);
connection.Close();
Assert.IsTrue(link != null, "link is null");
var listenerConnection = (ListenerConnection)link.Session.Connection;
Assert.IsTrue(listenerConnection.Principal != null, "principal is null");
Assert.IsTrue(listenerConnection.Principal.Identity.AuthenticationType == "X509", "wrong auth type");
X509Identity identity = (X509Identity)listenerConnection.Principal.Identity;
Assert.IsTrue(identity.Certificate != null, "certificate is null");
}
finally
{
sslHost.Close();
}
}
[TestMethod]
public void InvalidAddresses()
{
@ -509,6 +576,23 @@ namespace Test.Amqp
connection.Close();
}
}
static X509Certificate2 GetCertificate(StoreLocation storeLocation, StoreName storeName, string certFindValue)
{
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = store.Certificates.Find(
X509FindType.FindBySubjectName,
certFindValue,
false);
if (collection.Count == 0)
{
throw new ArgumentException("No certificate can be found using the find value " + certFindValue);
}
store.Close();
return collection[0];
}
}
class TestMessageProcessor : IMessageProcessor
@ -569,8 +653,15 @@ namespace Test.Amqp
class TestLinkProcessor : ILinkProcessor
{
public Action<ListenerLink> OnLinkAttached;
public void Process(AttachContext attachContext)
{
if (this.OnLinkAttached != null)
{
this.OnLinkAttached(attachContext.Link);
}
attachContext.Complete(new TestLinkEndpoint(), attachContext.Attach.Role ? 0 : 30);
}