Improve the auth infrastructure. Expose the user.
This commit is contained in:
Родитель
bb0fb639ae
Коммит
6f550d67a4
|
@ -32,7 +32,7 @@ namespace SelfHostServer
|
||||||
public void Configure(IBuilder app)
|
public void Configure(IBuilder app)
|
||||||
{
|
{
|
||||||
var info = (ServerInformation)app.Server;
|
var info = (ServerInformation)app.Server;
|
||||||
info.Listener.AuthenticationManager.AuthenticationTypes = AuthenticationType.None;
|
info.Listener.AuthenticationManager.AuthenticationTypes = AuthenticationTypes.AllowAnonymous;
|
||||||
|
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,11 +20,14 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FeatureModel;
|
using Microsoft.AspNet.FeatureModel;
|
||||||
using Microsoft.AspNet.HttpFeature;
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
using Microsoft.AspNet.HttpFeature.Security;
|
||||||
using Microsoft.Net.Server;
|
using Microsoft.Net.Server;
|
||||||
using Microsoft.Net.WebSockets;
|
using Microsoft.Net.WebSockets;
|
||||||
|
|
||||||
|
@ -38,6 +41,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
IHttpClientCertificateFeature,
|
IHttpClientCertificateFeature,
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpWebSocketFeature,
|
IHttpWebSocketFeature,
|
||||||
|
IHttpAuthenticationFeature,
|
||||||
IHttpOpaqueUpgradeFeature
|
IHttpOpaqueUpgradeFeature
|
||||||
{
|
{
|
||||||
private RequestContext _requestContext;
|
private RequestContext _requestContext;
|
||||||
|
@ -57,6 +61,8 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
private int? _localPort;
|
private int? _localPort;
|
||||||
private bool? _isLocal;
|
private bool? _isLocal;
|
||||||
private X509Certificate _clientCert;
|
private X509Certificate _clientCert;
|
||||||
|
private ClaimsPrincipal _user;
|
||||||
|
private IAuthenticationHandler _authHandler;
|
||||||
private Stream _responseStream;
|
private Stream _responseStream;
|
||||||
private IDictionary<string, string[]> _responseHeaders;
|
private IDictionary<string, string[]> _responseHeaders;
|
||||||
|
|
||||||
|
@ -94,6 +100,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
_features.Add(typeof(IHttpResponseFeature), this);
|
_features.Add(typeof(IHttpResponseFeature), this);
|
||||||
_features.Add(typeof(IHttpSendFileFeature), this);
|
_features.Add(typeof(IHttpSendFileFeature), this);
|
||||||
_features.Add(typeof(IHttpRequestLifetimeFeature), this);
|
_features.Add(typeof(IHttpRequestLifetimeFeature), this);
|
||||||
|
_features.Add(typeof(IHttpAuthenticationFeature), this);
|
||||||
|
|
||||||
// Win8+
|
// Win8+
|
||||||
if (WebSocketHelpers.AreWebSocketsSupported)
|
if (WebSocketHelpers.AreWebSocketsSupported)
|
||||||
|
@ -403,5 +410,25 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
return _requestContext.AcceptWebSocketAsync(subProtocol);
|
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.Extensions": "4.0.10.0",
|
||||||
"System.Runtime.Handles": "4.0.0.0",
|
"System.Runtime.Handles": "4.0.0.0",
|
||||||
"System.Runtime.InteropServices": "4.0.20.0",
|
"System.Runtime.InteropServices": "4.0.20.0",
|
||||||
|
"System.Security.Claims": "0.1-alpha-*",
|
||||||
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
|
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
|
||||||
"System.Security.Principal": "4.0.0.0",
|
"System.Security.Principal": "4.0.0.0",
|
||||||
"System.Text.Encoding": "4.0.20.0",
|
"System.Text.Encoding": "4.0.20.0",
|
||||||
|
|
|
@ -91,7 +91,7 @@ namespace Microsoft.Net.Server
|
||||||
bool stoleBlob = false;
|
bool stoleBlob = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (server.ValidateRequest(asyncResult._nativeRequestContext))
|
if (server.ValidateRequest(asyncResult._nativeRequestContext) && server.ValidateAuth(asyncResult._nativeRequestContext))
|
||||||
{
|
{
|
||||||
stoleBlob = true;
|
stoleBlob = true;
|
||||||
RequestContext requestContext = new RequestContext(server, asyncResult._nativeRequestContext);
|
RequestContext requestContext = new RequestContext(server, asyncResult._nativeRequestContext);
|
||||||
|
|
|
@ -25,6 +25,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
namespace Microsoft.Net.Server
|
namespace Microsoft.Net.Server
|
||||||
{
|
{
|
||||||
|
@ -45,17 +47,17 @@ namespace Microsoft.Net.Server
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private WebListener _server;
|
private WebListener _server;
|
||||||
AuthenticationType _authTypes;
|
private AuthenticationTypes _authTypes;
|
||||||
|
|
||||||
internal AuthenticationManager(WebListener listener)
|
internal AuthenticationManager(WebListener listener)
|
||||||
{
|
{
|
||||||
_server = listener;
|
_server = listener;
|
||||||
_authTypes = AuthenticationType.None;
|
_authTypes = AuthenticationTypes.AllowAnonymous;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
public AuthenticationType AuthenticationTypes
|
public AuthenticationTypes AuthenticationTypes
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
@ -68,6 +70,14 @@ namespace Microsoft.Net.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool AllowAnonymous
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ((_authTypes & AuthenticationTypes.AllowAnonymous) == AuthenticationTypes.AllowAnonymous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Properties
|
#endregion Properties
|
||||||
|
|
||||||
private unsafe void SetServerSecurity()
|
private unsafe void SetServerSecurity()
|
||||||
|
@ -76,7 +86,10 @@ namespace Microsoft.Net.Server
|
||||||
new UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_AUTHENTICATION_INFO();
|
new UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_AUTHENTICATION_INFO();
|
||||||
|
|
||||||
authInfo.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
|
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:
|
// TODO:
|
||||||
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
|
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
|
||||||
|
@ -91,41 +104,48 @@ namespace Microsoft.Net.Server
|
||||||
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
|
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
|
||||||
infoptr, (uint)AuthInfoSize);
|
infoptr, (uint)AuthInfoSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetAuthenticationChallenge(Response response)
|
|
||||||
{
|
|
||||||
if (_authTypes == AuthenticationType.None)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
IList<string> challenges = new List<string>();
|
IList<string> challenges = new List<string>();
|
||||||
|
|
||||||
// Order by strength.
|
// Order by strength.
|
||||||
if ((_authTypes & AuthenticationType.Kerberos) == AuthenticationType.Kerberos)
|
if ((_authTypes & AuthenticationTypes.Kerberos) == AuthenticationTypes.Kerberos)
|
||||||
{
|
{
|
||||||
challenges.Add("Kerberos");
|
challenges.Add("Kerberos");
|
||||||
}
|
}
|
||||||
if ((_authTypes & AuthenticationType.Negotiate) == AuthenticationType.Negotiate)
|
if ((_authTypes & AuthenticationTypes.Negotiate) == AuthenticationTypes.Negotiate)
|
||||||
{
|
{
|
||||||
challenges.Add("Negotiate");
|
challenges.Add("Negotiate");
|
||||||
}
|
}
|
||||||
if ((_authTypes & AuthenticationType.Ntlm) == AuthenticationType.Ntlm)
|
if ((_authTypes & AuthenticationTypes.Ntlm) == AuthenticationTypes.Ntlm)
|
||||||
{
|
{
|
||||||
challenges.Add("NTLM");
|
challenges.Add("NTLM");
|
||||||
}
|
}
|
||||||
if ((_authTypes & AuthenticationType.Digest) == AuthenticationType.Digest)
|
/*if ((_authTypes & AuthenticationTypes.Digest) == AuthenticationTypes.Digest)
|
||||||
{
|
{
|
||||||
// TODO:
|
// TODO:
|
||||||
throw new NotImplementedException("Digest challenge generation has not been implemented.");
|
throw new NotImplementedException("Digest challenge generation has not been implemented.");
|
||||||
// challenges.Add("Digest");
|
// challenges.Add("Digest");
|
||||||
}
|
}*/
|
||||||
if ((_authTypes & AuthenticationType.Basic) == AuthenticationType.Basic)
|
if ((_authTypes & AuthenticationTypes.Basic) == AuthenticationTypes.Basic)
|
||||||
{
|
{
|
||||||
// TODO: Realm
|
// TODO: Realm
|
||||||
challenges.Add("Basic");
|
challenges.Add("Basic");
|
||||||
}
|
}
|
||||||
|
return challenges;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetAuthenticationChallenge(Response response)
|
||||||
|
{
|
||||||
|
IList<string> challenges = GenerateChallenges();
|
||||||
|
|
||||||
|
if (challenges.Count > 0)
|
||||||
|
{
|
||||||
|
// 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.
|
// 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[] oldValues;
|
||||||
string[] newValues;
|
string[] newValues;
|
||||||
|
@ -143,4 +163,31 @@ namespace Microsoft.Net.Server
|
||||||
response.Headers[HttpKnownHeaderNames.WWWAuthenticate] = newValues;
|
response.Headers[HttpKnownHeaderNames.WWWAuthenticate] = newValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
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.
|
// permissions and limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.Net.Server
|
namespace Microsoft.Net.Server
|
||||||
{
|
{
|
||||||
[Flags]
|
[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,
|
Basic = 0x1,
|
||||||
Digest = 0x2,
|
// Digest = 0x2, // TODO: Verify this is no longer supported by Http.Sys
|
||||||
Ntlm = 0x4,
|
Ntlm = 0x4,
|
||||||
Negotiate = 0x8,
|
Negotiate = 0x8,
|
||||||
Kerberos = 0x10,
|
Kerberos = 0x10,
|
||||||
|
AllowAnonymous = 0x1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,11 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
#if NET45
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
#endif
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.Net.Server
|
namespace Microsoft.Net.Server
|
||||||
|
@ -66,7 +68,7 @@ namespace Microsoft.Net.Server
|
||||||
private SocketAddress _localEndPoint;
|
private SocketAddress _localEndPoint;
|
||||||
private SocketAddress _remoteEndPoint;
|
private SocketAddress _remoteEndPoint;
|
||||||
|
|
||||||
private IPrincipal _user;
|
private ClaimsPrincipal _user;
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ namespace Microsoft.Net.Server
|
||||||
_headers = new RequestHeaders(_nativeRequestContext);
|
_headers = new RequestHeaders(_nativeRequestContext);
|
||||||
|
|
||||||
UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2* requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob;
|
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
|
// TODO: Verbose log parameters
|
||||||
|
|
||||||
|
@ -411,31 +413,11 @@ namespace Microsoft.Net.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IPrincipal User
|
internal ClaimsPrincipal User
|
||||||
{
|
{
|
||||||
get { return _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()
|
internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB GetKnownMethod()
|
||||||
{
|
{
|
||||||
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);
|
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);
|
||||||
|
|
|
@ -22,13 +22,12 @@
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Principal;
|
using System.Security.Claims;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
|
@ -72,7 +71,7 @@ namespace Microsoft.Net.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPrincipal User
|
public ClaimsPrincipal User
|
||||||
{
|
{
|
||||||
get { return _request.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].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();
|
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 SafeHandle _requestQueueHandle;
|
||||||
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
|
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
|
||||||
|
|
||||||
private bool _ignoreWriteExceptions;
|
private bool _ignoreWriteExceptions;
|
||||||
private HttpServerSessionHandle _serverSessionHandle;
|
private HttpServerSessionHandle _serverSessionHandle;
|
||||||
private ulong _urlGroupId;
|
private ulong _urlGroupId;
|
||||||
|
@ -649,7 +650,18 @@ namespace Microsoft.Net.Server
|
||||||
// Block potential DOS attacks
|
// Block potential DOS attacks
|
||||||
if (requestMemory.RequestBlob->Headers.UnknownHeaderCount > UnknownHeaderLimit)
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -781,12 +793,67 @@ namespace Microsoft.Net.Server
|
||||||
return cts.Token;
|
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();
|
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 = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION();
|
||||||
httpResponse.Response_V1.Version.MajorVersion = (ushort)1;
|
httpResponse.Response_V1.Version.MajorVersion = (ushort)1;
|
||||||
httpResponse.Response_V1.Version.MinorVersion = (ushort)1;
|
httpResponse.Response_V1.Version.MinorVersion = (ushort)1;
|
||||||
|
|
||||||
|
List<GCHandle> pinnedHeaders = null;
|
||||||
|
GCHandle gcHandle;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Copied from the multi-value headers section of SerializeHeaders
|
||||||
|
if (authChallenges != null && authChallenges.Count > 0)
|
||||||
|
{
|
||||||
|
pinnedHeaders = new List<GCHandle>();
|
||||||
|
|
||||||
|
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;
|
httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode;
|
||||||
string statusDescription = HttpReasonPhrase.Get(httpStatusCode);
|
string statusDescription = HttpReasonPhrase.Get(httpStatusCode);
|
||||||
uint dataWritten = 0;
|
uint dataWritten = 0;
|
||||||
|
@ -824,6 +891,20 @@ namespace Microsoft.Net.Server
|
||||||
RequestContext.CancelRequest(_requestQueueHandle, requestId);
|
RequestContext.CancelRequest(_requestQueueHandle, requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (pinnedHeaders != null)
|
||||||
|
{
|
||||||
|
foreach (GCHandle handle in pinnedHeaders)
|
||||||
|
{
|
||||||
|
if (handle.IsAllocated)
|
||||||
|
{
|
||||||
|
handle.Free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int GetTokenOffsetFromBlob(IntPtr blob)
|
private static int GetTokenOffsetFromBlob(IntPtr blob)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"System.Runtime.Extensions": "4.0.10.0",
|
"System.Runtime.Extensions": "4.0.10.0",
|
||||||
"System.Runtime.Handles": "4.0.0.0",
|
"System.Runtime.Handles": "4.0.0.0",
|
||||||
"System.Runtime.InteropServices": "4.0.20.0",
|
"System.Runtime.InteropServices": "4.0.20.0",
|
||||||
|
"System.Security.Claims": "0.1-alpha-*",
|
||||||
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
|
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
|
||||||
"System.Security.Principal": "4.0.0.0",
|
"System.Security.Principal": "4.0.0.0",
|
||||||
"System.Text.Encoding": "4.0.20.0",
|
"System.Text.Encoding": "4.0.20.0",
|
||||||
|
|
|
@ -31,35 +31,61 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
private const string Address = "http://localhost:8080/";
|
private const string Address = "http://localhost:8080/";
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
[InlineData(AuthenticationType.Digest)]
|
// [InlineData(AuthenticationTypes.Digest)]
|
||||||
[InlineData(AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Basic)]
|
||||||
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | AuthenticationType.Digest | AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
|
||||||
public async Task AuthTypes_EnabledButNotChalleneged_PassThrough(AuthenticationType authType)
|
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);
|
return Task.FromResult(0);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
var response = await SendRequestAsync(Address);
|
var response = await SendRequestAsync(Address);
|
||||||
response.EnsureSuccessStatusCode();
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
|
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
|
||||||
[InlineData(AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Basic)]
|
||||||
public async Task AuthType_Specify401_ChallengesAdded(AuthenticationType authType)
|
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationTypes authType)
|
||||||
{
|
{
|
||||||
using (Utilities.CreateAuthServer(authType, env =>
|
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);
|
return Task.FromResult(0);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
|
@ -70,17 +96,21 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MultipleAuthTypes_Specify401_ChallengesAdded()
|
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
|
||||||
{
|
{
|
||||||
using (Utilities.CreateAuthServer(
|
using (Utilities.CreateAuthServer(
|
||||||
AuthenticationType.Kerberos
|
AuthenticationTypes.Kerberos
|
||||||
| AuthenticationType.Negotiate
|
| AuthenticationTypes.Negotiate
|
||||||
| AuthenticationType.Ntlm
|
| AuthenticationTypes.Ntlm
|
||||||
/* | AuthenticationType.Digest TODO: Not implemented */
|
/* | AuthenticationTypes.Digest TODO: Not implemented */
|
||||||
| AuthenticationType.Basic,
|
| AuthenticationTypes.Basic
|
||||||
|
| AuthenticationTypes.AllowAnonymous,
|
||||||
env =>
|
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);
|
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);
|
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* TODO: User
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
|
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
|
||||||
// [InlineData(AuthenticationType.Basic)] // Doesn't work with default creds
|
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
|
||||||
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | / *AuthenticationType.Digest |* / AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /* AuthenticationTypes.Digest |*/ AuthenticationTypes.Basic)]
|
||||||
public async Task AuthTypes_Login_Success(AuthenticationType authType)
|
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationTypes authType)
|
||||||
{
|
{
|
||||||
int requestCount = 0;
|
int requestId = 0;
|
||||||
using (Utilities.CreateAuthServer(authType, env =>
|
using (Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous, env =>
|
||||||
{
|
{
|
||||||
requestCount++;
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
/ * // TODO: Expose user as feature.
|
Assert.NotNull(context.User);
|
||||||
object obj;
|
if (requestId == 0)
|
||||||
if (env.TryGetValue("server.User", out obj) && obj != null)
|
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
}* /
|
context.Response.StatusCode = 401;
|
||||||
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
|
}
|
||||||
|
else if (requestId == 1)
|
||||||
|
{
|
||||||
|
Assert.True(context.User.Identity.IsAuthenticated);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
requestId++;
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
|
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)
|
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);
|
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);
|
return CreateServer("http", "localhost", "8080", string.Empty, authType, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IDisposable CreateServer(string scheme, string host, string port, string path, AppFunc 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 factory = new ServerFactory(loggerFactory: null);
|
||||||
var serverInfo = (ServerInformation)factory.Initialize(configuration: null);
|
var serverInfo = (ServerInformation)factory.Initialize(configuration: null);
|
||||||
|
|
|
@ -13,39 +13,65 @@ namespace Microsoft.Net.Server
|
||||||
private const string Address = "http://localhost:8080/";
|
private const string Address = "http://localhost:8080/";
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.AllowAnonymous)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Digest)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
[InlineData(AuthenticationType.Basic)]
|
// [InlineData(AuthenticationTypes.Digest)]
|
||||||
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | AuthenticationType.Digest | AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Basic)]
|
||||||
public async Task AuthTypes_EnabledButNotChalleneged_PassThrough(AuthenticationType authType)
|
[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);
|
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||||
|
|
||||||
var context = await server.GetContextAsync();
|
var context = await server.GetContextAsync();
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
context.Dispose();
|
context.Dispose();
|
||||||
|
|
||||||
var response = await responseTask;
|
var response = await responseTask;
|
||||||
response.EnsureSuccessStatusCode();
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
|
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
|
||||||
[InlineData(AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Basic)]
|
||||||
public async Task AuthType_Specify401_ChallengesAdded(AuthenticationType authType)
|
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationTypes authType)
|
||||||
{
|
{
|
||||||
using (var server = Utilities.CreateAuthServer(authType))
|
using (var server = Utilities.CreateAuthServer(authType))
|
||||||
{
|
{
|
||||||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
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();
|
var context = await server.GetContextAsync();
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
context.Response.StatusCode = 401;
|
context.Response.StatusCode = 401;
|
||||||
context.Dispose();
|
context.Dispose();
|
||||||
|
|
||||||
|
@ -56,18 +82,21 @@ namespace Microsoft.Net.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MultipleAuthTypes_Specify401_ChallengesAdded()
|
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
|
||||||
{
|
{
|
||||||
using (var server = Utilities.CreateAuthServer(
|
using (var server = Utilities.CreateAuthServer(
|
||||||
AuthenticationType.Kerberos
|
AuthenticationTypes.Kerberos
|
||||||
| AuthenticationType.Negotiate
|
| AuthenticationTypes.Negotiate
|
||||||
| AuthenticationType.Ntlm
|
| AuthenticationTypes.Ntlm
|
||||||
/* | AuthenticationType.Digest TODO: Not implemented */
|
/* | AuthenticationTypes.Digest TODO: Not implemented */
|
||||||
| AuthenticationType.Basic))
|
| AuthenticationTypes.Basic
|
||||||
|
| AuthenticationTypes.AllowAnonymous))
|
||||||
{
|
{
|
||||||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||||
|
|
||||||
var context = await server.GetContextAsync();
|
var context = await server.GetContextAsync();
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
context.Response.StatusCode = 401;
|
context.Response.StatusCode = 401;
|
||||||
context.Dispose();
|
context.Dispose();
|
||||||
|
|
||||||
|
@ -76,35 +105,58 @@ namespace Microsoft.Net.Server
|
||||||
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
|
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* TODO: User
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AuthenticationType.Kerberos)]
|
[InlineData(AuthenticationTypes.Kerberos)]
|
||||||
[InlineData(AuthenticationType.Negotiate)]
|
[InlineData(AuthenticationTypes.Negotiate)]
|
||||||
[InlineData(AuthenticationType.Ntlm)]
|
[InlineData(AuthenticationTypes.Ntlm)]
|
||||||
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
|
// [InlineData(AuthenticationTypes.Digest)] // TODO: Not implemented
|
||||||
// [InlineData(AuthenticationType.Basic)] // Doesn't work with default creds
|
// [InlineData(AuthenticationTypes.Basic)] // Doesn't work with default creds
|
||||||
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | / *AuthenticationType.Digest |* / AuthenticationType.Basic)]
|
[InlineData(AuthenticationTypes.Kerberos | AuthenticationTypes.Negotiate | AuthenticationTypes.Ntlm | /*AuthenticationType.Digest |*/ AuthenticationTypes.Basic)]
|
||||||
public async Task AuthTypes_Login_Success(AuthenticationType authType)
|
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationTypes authType)
|
||||||
{
|
{
|
||||||
int requestCount = 0;
|
using (var server = Utilities.CreateAuthServer(authType | AuthenticationTypes.AllowAnonymous))
|
||||||
using (Utilities.CreateAuthServer(authType, env =>
|
|
||||||
{
|
{
|
||||||
requestCount++;
|
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address, useDefaultCredentials: true);
|
||||||
/ * // TODO: Expose user as feature.
|
|
||||||
object obj;
|
var context = await server.GetContextAsync();
|
||||||
if (env.TryGetValue("server.User", out obj) && obj != null)
|
Assert.NotNull(context.User);
|
||||||
{
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
return Task.FromResult(0);
|
context.Response.StatusCode = 401;
|
||||||
}* /
|
context.Dispose();
|
||||||
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
|
|
||||||
return Task.FromResult(0);
|
context = await server.GetContextAsync();
|
||||||
}))
|
Assert.NotNull(context.User);
|
||||||
{
|
Assert.True(context.User.Identity.IsAuthenticated);
|
||||||
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
|
context.Dispose();
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
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)
|
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);
|
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);
|
return CreateServer("http", "localhost", "8080", string.Empty, authType);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static WebListener CreateServer(string scheme, string host, string port, string path)
|
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();
|
WebListener listener = new WebListener();
|
||||||
listener.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));
|
listener.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));
|
||||||
|
|
Загрузка…
Ссылка в новой задаче