Improve the auth infrastructure. Expose the user.

This commit is contained in:
Chris Ross 2014-06-20 10:10:13 -07:00
Родитель bb0fb639ae
Коммит 6f550d67a4
15 изменённых файлов: 448 добавлений и 197 удалений

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

@ -32,7 +32,7 @@ namespace SelfHostServer
public void Configure(IBuilder app)
{
var info = (ServerInformation)app.Server;
info.Listener.AuthenticationManager.AuthenticationTypes = AuthenticationType.None;
info.Listener.AuthenticationManager.AuthenticationTypes = AuthenticationTypes.AllowAnonymous;
app.Run(async context =>
{

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

@ -20,11 +20,14 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.HttpFeature;
using Microsoft.AspNet.HttpFeature.Security;
using Microsoft.Net.Server;
using Microsoft.Net.WebSockets;
@ -38,6 +41,7 @@ namespace Microsoft.AspNet.Server.WebListener
IHttpClientCertificateFeature,
IHttpRequestLifetimeFeature,
IHttpWebSocketFeature,
IHttpAuthenticationFeature,
IHttpOpaqueUpgradeFeature
{
private RequestContext _requestContext;
@ -57,6 +61,8 @@ namespace Microsoft.AspNet.Server.WebListener
private int? _localPort;
private bool? _isLocal;
private X509Certificate _clientCert;
private ClaimsPrincipal _user;
private IAuthenticationHandler _authHandler;
private Stream _responseStream;
private IDictionary<string, string[]> _responseHeaders;
@ -94,6 +100,7 @@ namespace Microsoft.AspNet.Server.WebListener
_features.Add(typeof(IHttpResponseFeature), this);
_features.Add(typeof(IHttpSendFileFeature), this);
_features.Add(typeof(IHttpRequestLifetimeFeature), this);
_features.Add(typeof(IHttpAuthenticationFeature), this);
// Win8+
if (WebSocketHelpers.AreWebSocketsSupported)
@ -403,5 +410,25 @@ namespace Microsoft.AspNet.Server.WebListener
}
return _requestContext.AcceptWebSocketAsync(subProtocol);
}
ClaimsPrincipal IHttpAuthenticationFeature.User
{
get
{
if (_user == null)
{
_user = _requestContext.User;
}
return _user;
}
set { _user = value; }
}
// TODO: Hook this server up as the default handler, have it issue challenges for configured auth types by name.
IAuthenticationHandler IHttpAuthenticationFeature.Handler
{
get { return _authHandler; }
set { _authHandler = value; }
}
}
}

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

@ -33,6 +33,7 @@
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.Handles": "4.0.0.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Claims": "0.1-alpha-*",
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
"System.Security.Principal": "4.0.0.0",
"System.Text.Encoding": "4.0.20.0",

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

@ -91,7 +91,7 @@ namespace Microsoft.Net.Server
bool stoleBlob = false;
try
{
if (server.ValidateRequest(asyncResult._nativeRequestContext))
if (server.ValidateRequest(asyncResult._nativeRequestContext) && server.ValidateAuth(asyncResult._nativeRequestContext))
{
stoleBlob = true;
RequestContext requestContext = new RequestContext(server, asyncResult._nativeRequestContext);

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

@ -25,6 +25,8 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Principal;
namespace Microsoft.Net.Server
{
@ -45,17 +47,17 @@ namespace Microsoft.Net.Server
#endif
private WebListener _server;
AuthenticationType _authTypes;
private AuthenticationTypes _authTypes;
internal AuthenticationManager(WebListener listener)
{
_server = listener;
_authTypes = AuthenticationType.None;
_authTypes = AuthenticationTypes.AllowAnonymous;
}
#region Properties
public AuthenticationType AuthenticationTypes
public AuthenticationTypes AuthenticationTypes
{
get
{
@ -68,6 +70,14 @@ namespace Microsoft.Net.Server
}
}
internal bool AllowAnonymous
{
get
{
return ((_authTypes & AuthenticationTypes.AllowAnonymous) == AuthenticationTypes.AllowAnonymous);
}
}
#endregion Properties
private unsafe void SetServerSecurity()
@ -76,71 +86,108 @@ namespace Microsoft.Net.Server
new UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_AUTHENTICATION_INFO();
authInfo.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
authInfo.AuthSchemes = (UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_TYPES)_authTypes;
var authTypes = (UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_TYPES)(_authTypes & ~AuthenticationTypes.AllowAnonymous);
if (authTypes != UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_TYPES.NONE)
{
authInfo.AuthSchemes = authTypes;
// TODO:
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
// Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING
// Mutual Auth - ReceiveMutualAuth
// Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
// Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
// TODO:
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
// Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING
// Mutual Auth - ReceiveMutualAuth
// Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
// Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
IntPtr infoptr = new IntPtr(&authInfo);
IntPtr infoptr = new IntPtr(&authInfo);
_server.SetUrlGroupProperty(
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
infoptr, (uint)AuthInfoSize);
_server.SetUrlGroupProperty(
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
infoptr, (uint)AuthInfoSize);
}
}
internal void SetAuthenticationChallenge(Response response)
// TODO: If we're not going to support Digest then this whole list can be pre-computed and cached.
// consider even pre-serialzing and caching the bytes for the !AllowAnonymous scenario.
internal IList<string> GenerateChallenges()
{
if (_authTypes == AuthenticationType.None)
{
return;
}
IList<string> challenges = new List<string>();
// Order by strength.
if ((_authTypes & AuthenticationType.Kerberos) == AuthenticationType.Kerberos)
if ((_authTypes & AuthenticationTypes.Kerberos) == AuthenticationTypes.Kerberos)
{
challenges.Add("Kerberos");
}
if ((_authTypes & AuthenticationType.Negotiate) == AuthenticationType.Negotiate)
if ((_authTypes & AuthenticationTypes.Negotiate) == AuthenticationTypes.Negotiate)
{
challenges.Add("Negotiate");
}
if ((_authTypes & AuthenticationType.Ntlm) == AuthenticationType.Ntlm)
if ((_authTypes & AuthenticationTypes.Ntlm) == AuthenticationTypes.Ntlm)
{
challenges.Add("NTLM");
}
if ((_authTypes & AuthenticationType.Digest) == AuthenticationType.Digest)
/*if ((_authTypes & AuthenticationTypes.Digest) == AuthenticationTypes.Digest)
{
// TODO:
throw new NotImplementedException("Digest challenge generation has not been implemented.");
// challenges.Add("Digest");
}
if ((_authTypes & AuthenticationType.Basic) == AuthenticationType.Basic)
}*/
if ((_authTypes & AuthenticationTypes.Basic) == AuthenticationTypes.Basic)
{
// TODO: Realm
challenges.Add("Basic");
}
return challenges;
}
// Append to the existing header, if any. Some clients (IE, Chrome) require each challenges to be sent on their own line/header.
string[] oldValues;
string[] newValues;
if (response.Headers.TryGetValue(HttpKnownHeaderNames.WWWAuthenticate, out oldValues))
internal void SetAuthenticationChallenge(Response response)
{
IList<string> challenges = GenerateChallenges();
if (challenges.Count > 0)
{
newValues = new string[oldValues.Length + challenges.Count];
Array.Copy(oldValues, newValues, oldValues.Length);
challenges.CopyTo(newValues, oldValues.Length);
// TODO: We need a better header API that just lets us append values.
// Append to the existing header, if any. Some clients (IE, Chrome) require each challenges to be sent on their own line/header.
string[] oldValues;
string[] newValues;
if (response.Headers.TryGetValue(HttpKnownHeaderNames.WWWAuthenticate, out oldValues))
{
newValues = new string[oldValues.Length + challenges.Count];
Array.Copy(oldValues, newValues, oldValues.Length);
challenges.CopyTo(newValues, oldValues.Length);
}
else
{
newValues = new string[challenges.Count];
challenges.CopyTo(newValues, 0);
}
response.Headers[HttpKnownHeaderNames.WWWAuthenticate] = newValues;
}
else
}
internal static unsafe bool CheckAuthenticated(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO* requestInfo)
{
if (requestInfo != null
&& requestInfo->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
&& requestInfo->pInfo->AuthStatus == UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
{
newValues = new string[challenges.Count];
challenges.CopyTo(newValues, 0);
#if NET45
return true;
#endif
}
response.Headers[HttpKnownHeaderNames.WWWAuthenticate] = newValues;
return false;
}
internal static unsafe ClaimsPrincipal GetUser(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO* requestInfo)
{
if (requestInfo != null
&& requestInfo->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
&& requestInfo->pInfo->AuthStatus == UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
{
#if NET45
return new WindowsPrincipal(new WindowsIdentity(requestInfo->pInfo->AccessToken));
#endif
}
return new ClaimsPrincipal(new ClaimsIdentity(string.Empty)); // Anonymous / !IsAuthenticated
}
}
}

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

@ -16,21 +16,18 @@
// permissions and limitations under the License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Net.Server
{
[Flags]
public enum AuthenticationType
public enum AuthenticationTypes
{
None = 0x0,
// None = 0x0, // None is invalid, use AllowAnonymous (which must have a non-zero value).
Basic = 0x1,
Digest = 0x2,
// Digest = 0x2, // TODO: Verify this is no longer supported by Http.Sys
Ntlm = 0x4,
Negotiate = 0x8,
Kerberos = 0x10,
AllowAnonymous = 0x1000
}
}

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

@ -27,9 +27,11 @@ using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
#if NET45
using System.Security.Principal;
using System.Threading;
#endif
using System.Threading.Tasks;
namespace Microsoft.Net.Server
@ -66,7 +68,7 @@ namespace Microsoft.Net.Server
private SocketAddress _localEndPoint;
private SocketAddress _remoteEndPoint;
private IPrincipal _user;
private ClaimsPrincipal _user;
private bool _isDisposed = false;
@ -140,7 +142,7 @@ namespace Microsoft.Net.Server
_headers = new RequestHeaders(_nativeRequestContext);
UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2* requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob;
_user = GetUser(requestV2->pRequestInfo);
_user = AuthenticationManager.GetUser(requestV2->pRequestInfo);
// TODO: Verbose log parameters
@ -411,31 +413,11 @@ namespace Microsoft.Net.Server
}
}
internal IPrincipal User
internal ClaimsPrincipal User
{
get { return _user; }
}
private unsafe IPrincipal GetUser(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO* requestInfo)
{
if (requestInfo == null
|| requestInfo->InfoType != UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth)
{
return null;
}
if (requestInfo->pInfo->AuthStatus != UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
{
return null;
}
#if NET45
return new WindowsPrincipal(new WindowsIdentity(requestInfo->pInfo->AccessToken));
#else
return null;
#endif
}
internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB GetKnownMethod()
{
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);

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

@ -22,13 +22,12 @@
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Framework.Logging;
@ -72,7 +71,7 @@ namespace Microsoft.Net.Server
}
}
public IPrincipal User
public ClaimsPrincipal User
{
get { return _request.User; }
}

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

@ -708,7 +708,12 @@ namespace Microsoft.Net.Server
}
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length = (uint)Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS));
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length =
#if NET45
(uint)Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS));
#else
(uint)Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>();
#endif
UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS();

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

@ -78,6 +78,7 @@ namespace Microsoft.Net.Server
private SafeHandle _requestQueueHandle;
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
private bool _ignoreWriteExceptions;
private HttpServerSessionHandle _serverSessionHandle;
private ulong _urlGroupId;
@ -649,7 +650,18 @@ namespace Microsoft.Net.Server
// Block potential DOS attacks
if (requestMemory.RequestBlob->Headers.UnknownHeaderCount > UnknownHeaderLimit)
{
SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.BadRequest);
SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.BadRequest, authChallenges: null);
return false;
}
return true;
}
internal unsafe bool ValidateAuth(NativeRequestContext requestMemory)
{
var requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)requestMemory.RequestBlob;
if (!AuthenticationManager.AllowAnonymous && !AuthenticationManager.CheckAuthenticated(requestV2->pRequestInfo))
{
SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.Unauthorized, AuthenticationManager.GenerateChallenges());
return false;
}
return true;
@ -781,47 +793,116 @@ namespace Microsoft.Net.Server
return cts.Token;
}
private unsafe void SendError(ulong requestId, HttpStatusCode httpStatusCode)
private unsafe void SendError(ulong requestId, HttpStatusCode httpStatusCode, IList<string> authChallenges)
{
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2 httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2();
httpResponse.Response_V1.Version = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION();
httpResponse.Response_V1.Version.MajorVersion = (ushort)1;
httpResponse.Response_V1.Version.MinorVersion = (ushort)1;
httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode;
string statusDescription = HttpReasonPhrase.Get(httpStatusCode);
uint dataWritten = 0;
uint statusCode;
byte[] byteReason = HeaderEncoding.GetBytes(statusDescription);
fixed (byte* pReason = byteReason)
List<GCHandle> pinnedHeaders = null;
GCHandle gcHandle;
try
{
httpResponse.Response_V1.pReason = (sbyte*)pReason;
httpResponse.Response_V1.ReasonLength = (ushort)byteReason.Length;
byte[] byteContentLength = new byte[] { (byte)'0' };
fixed (byte* pContentLength = byteContentLength)
// Copied from the multi-value headers section of SerializeHeaders
if (authChallenges != null && authChallenges.Count > 0)
{
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength;
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Response_V1.Headers.UnknownHeaderCount = 0;
pinnedHeaders = new List<GCHandle>();
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
_requestQueueHandle,
requestId,
0,
&httpResponse,
null,
&dataWritten,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null;
knownHeaderInfo = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[1];
gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
httpResponse.pResponseInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
knownHeaderInfo[httpResponse.ResponseInfoCount].Type = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[httpResponse.ResponseInfoCount].Length =
#if NET45
(uint)Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS));
#else
(uint)Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>();
#endif
UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS();
header.HeaderId = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderWwwAuthenticate;
header.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // The docs say this is for www-auth only.
UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[authChallenges.Count];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
for (int headerValueIndex = 0; headerValueIndex < authChallenges.Count; headerValueIndex++)
{
// Add Value
string headerValue = authChallenges[headerValueIndex];
byte[] bytes = new byte[HeaderEncoding.GetByteCount(headerValue)];
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject();
header.KnownHeaderCount++;
}
// This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set.
gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
knownHeaderInfo[0].pInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();
httpResponse.ResponseInfoCount = 1;
}
httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode;
string statusDescription = HttpReasonPhrase.Get(httpStatusCode);
uint dataWritten = 0;
uint statusCode;
byte[] byteReason = HeaderEncoding.GetBytes(statusDescription);
fixed (byte* pReason = byteReason)
{
httpResponse.Response_V1.pReason = (sbyte*)pReason;
httpResponse.Response_V1.ReasonLength = (ushort)byteReason.Length;
byte[] byteContentLength = new byte[] { (byte)'0' };
fixed (byte* pContentLength = byteContentLength)
{
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength;
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Response_V1.Headers.UnknownHeaderCount = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
_requestQueueHandle,
requestId,
0,
&httpResponse,
null,
&dataWritten,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// if we fail to send a 401 something's seriously wrong, abort the request
RequestContext.CancelRequest(_requestQueueHandle, requestId);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
finally
{
// if we fail to send a 401 something's seriously wrong, abort the request
RequestContext.CancelRequest(_requestQueueHandle, requestId);
if (pinnedHeaders != null)
{
foreach (GCHandle handle in pinnedHeaders)
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
}
}

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

@ -29,6 +29,7 @@
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.Handles": "4.0.0.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Claims": "0.1-alpha-*",
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
"System.Security.Principal": "4.0.0.0",
"System.Text.Encoding": "4.0.20.0",

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

@ -31,35 +31,61 @@ namespace Microsoft.AspNet.Server.WebListener
private const string Address = "http://localhost:8080/";
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
[InlineData(AuthenticationType.Digest)]
[InlineData(AuthenticationType.Basic)]
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | AuthenticationType.Digest | AuthenticationType.Basic)]
public async Task AuthTypes_EnabledButNotChalleneged_PassThrough(AuthenticationType authType)
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)]
[InlineData(AuthenticationTypes.Basic)]
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_AllowAnonymous_NoChallenge(AuthenticationTypes authType)
{
using (Utilities.CreateAuthServer(authType, env =>
using (Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous, env =>
{
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(Address);
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
}
}
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
[InlineData(AuthenticationType.Basic)]
public async Task AuthType_Specify401_ChallengesAdded(AuthenticationType authType)
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationTypes.Basic)]
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationTypes authType)
{
using (Utilities.CreateAuthServer(authType, env =>
{
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
throw new NotImplementedException();
}))
{
var response = await SendRequestAsync(Address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[Theory]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationTypes.Basic)]
public async Task AuthType_AllowAnonymousButSpecify401_ChallengesAdded(AuthenticationTypes authType)
{
using (Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous, env =>
{
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
@ -70,17 +96,21 @@ namespace Microsoft.AspNet.Server.WebListener
}
[Fact]
public async Task MultipleAuthTypes_Specify401_ChallengesAdded()
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
{
using (Utilities.CreateAuthServer(
AuthenticationType.Kerberos
| AuthenticationType.Negotiate
| AuthenticationType.Ntlm
/* | AuthenticationType.Digest TODO: Not implemented */
| AuthenticationType.Basic,
AuthenticationTypes.Kerberos
| AuthenticationTypes.Negotiate
| AuthenticationTypes.Ntlm
/* | AuthenticationTypes.Digest TODO: Not implemented */
| AuthenticationTypes.Basic
| AuthenticationTypes.AllowAnonymous,
env =>
{
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
@ -89,35 +119,64 @@ namespace Microsoft.AspNet.Server.WebListener
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
/* TODO: User
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationType.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | / *AuthenticationType.Digest |* / AuthenticationType.Basic)]
public async Task AuthTypes_Login_Success(AuthenticationType authType)
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /* AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationTypes authType)
{
int requestCount = 0;
using (Utilities.CreateAuthServer(authType, env =>
int requestId = 0;
using (Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous, env =>
{
requestCount++;
/ * // TODO: Expose user as feature.
object obj;
if (env.TryGetValue("server.User", out obj) && obj != null)
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.NotNull(context.User);
if (requestId == 0)
{
return Task.FromResult(0);
}* /
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
}
else if (requestId == 1)
{
Assert.True(context.User.Identity.IsAuthenticated);
}
else
{
throw new NotImplementedException();
}
requestId++;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[Theory]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /* AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_RequireAuth_Success(AuthenticationTypes authType)
{
using (Utilities.CreateAuthServer(authType, env =>
{
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
*/
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
{

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

@ -35,17 +35,17 @@ namespace Microsoft.AspNet.Server.WebListener
return CreateServer("https", "localhost", "9090", string.Empty, app);
}
internal static IDisposable CreateAuthServer(AuthenticationType authType, AppFunc app)
internal static IDisposable CreateAuthServer(AuthenticationTypes authType, AppFunc app)
{
return CreateServer("http", "localhost", "8080", string.Empty, authType, app);
}
internal static IDisposable CreateServer(string scheme, string host, string port, string path, AppFunc app)
{
return CreateServer(scheme, host, port, path, AuthenticationType.None, app);
return CreateServer(scheme, host, port, path, AuthenticationTypes.AllowAnonymous, app);
}
internal static IDisposable CreateServer(string scheme, string host, string port, string path, AuthenticationType authType, AppFunc app)
internal static IDisposable CreateServer(string scheme, string host, string port, string path, AuthenticationTypes authType, AppFunc app)
{
var factory = new ServerFactory(loggerFactory: null);
var serverInfo = (ServerInformation)factory.Initialize(configuration: null);

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

@ -13,39 +13,65 @@ namespace Microsoft.Net.Server
private const string Address = "http://localhost:8080/";
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
[InlineData(AuthenticationType.Digest)]
[InlineData(AuthenticationType.Basic)]
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | AuthenticationType.Digest | AuthenticationType.Basic)]
public async Task AuthTypes_EnabledButNotChalleneged_PassThrough(AuthenticationType authType)
[InlineData(AuthenticationTypes.AllowAnonymous)]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)]
[InlineData(AuthenticationTypes.Basic)]
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_AllowAnonymous_NoChallenge(AuthenticationTypes authType)
{
using (var server = Utilities.CreateAuthServer(authType))
using (var server = Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Dispose();
var response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
}
}
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
[InlineData(AuthenticationType.Basic)]
public async Task AuthType_Specify401_ChallengesAdded(AuthenticationType authType)
[InlineData(AuthenticationTypes.Basic)]
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationTypes authType)
{
using (var server = Utilities.CreateAuthServer(authType))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var contextTask = server.GetContextAsync(); // Fails when the server shuts down, the challenge happens internally.
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[Theory]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationTypes.Basic)]
public async Task AuthType_AllowAnonymousButSpecify401_ChallengesAdded(AuthenticationTypes authType)
{
using (var server = Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
context.Dispose();
@ -56,18 +82,21 @@ namespace Microsoft.Net.Server
}
[Fact]
public async Task MultipleAuthTypes_Specify401_ChallengesAdded()
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
{
using (var server = Utilities.CreateAuthServer(
AuthenticationType.Kerberos
| AuthenticationType.Negotiate
| AuthenticationType.Ntlm
/* | AuthenticationType.Digest TODO: Not implemented */
| AuthenticationType.Basic))
AuthenticationTypes.Kerberos
| AuthenticationTypes.Negotiate
| AuthenticationTypes.Ntlm
/* | AuthenticationTypes.Digest TODO: Not implemented */
| AuthenticationTypes.Basic
| AuthenticationTypes.AllowAnonymous))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
context.Dispose();
@ -76,35 +105,58 @@ namespace Microsoft.Net.Server
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
/* TODO: User
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationType.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | / *AuthenticationType.Digest |* / AuthenticationType.Basic)]
public async Task AuthTypes_Login_Success(AuthenticationType authType)
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationType.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationTypes authType)
{
int requestCount = 0;
using (Utilities.CreateAuthServer(authType, env =>
using (var server = Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous))
{
requestCount++;
/ * // TODO: Expose user as feature.
object obj;
if (env.TryGetValue("server.User", out obj) && obj != null)
{
return Task.FromResult(0);
}* /
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
response.EnsureSuccessStatusCode();
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address, useDefaultCredentials: true);
var context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
context.Response.StatusCode = 401;
context.Dispose();
context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[Theory]
[InlineData(AuthenticationTypes.Kerberos)]
[InlineData(AuthenticationTypes.Negotiate)]
[InlineData(AuthenticationTypes.Ntlm)]
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationType.Digest |*/ AuthenticationTypes.Basic)]
public async Task AuthTypes_RequireAuth_Success(AuthenticationTypes authType)
{
using (var server = Utilities.CreateAuthServer(authType))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address, useDefaultCredentials: true);
var context = await server.GetContextAsync();
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
*/
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
{

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

@ -16,17 +16,17 @@ namespace Microsoft.Net.Server
return CreateServer("https", "localhost", "9090", string.Empty);
}
internal static WebListener CreateAuthServer(AuthenticationType authType)
internal static WebListener CreateAuthServer(AuthenticationTypes authType)
{
return CreateServer("http", "localhost", "8080", string.Empty, authType);
}
internal static WebListener CreateServer(string scheme, string host, string port, string path)
{
return CreateServer(scheme, host, port, path, AuthenticationType.None);
return CreateServer(scheme, host, port, path, AuthenticationTypes.AllowAnonymous);
}
internal static WebListener CreateServer(string scheme, string host, string port, string path, AuthenticationType authType)
internal static WebListener CreateServer(string scheme, string host, string port, string path, AuthenticationTypes authType)
{
WebListener listener = new WebListener();
listener.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));