#131 Handle behavioral challenges (403 vs 401).
This commit is contained in:
parent
e825da0910
commit
2b5785c2c6
|
|
@ -45,8 +45,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
|
|
||||||
public Task AuthenticateAsync(AuthenticateContext context)
|
public Task AuthenticateAsync(AuthenticateContext context)
|
||||||
{
|
{
|
||||||
var user = _requestContext.User;
|
var identity = (ClaimsIdentity)_requestContext.User?.Identity;
|
||||||
var identity = user == null ? null : (ClaimsIdentity)user.Identity;
|
|
||||||
|
|
||||||
foreach (var authType in ListEnabledAuthSchemes())
|
foreach (var authType in ListEnabledAuthSchemes())
|
||||||
{
|
{
|
||||||
|
|
@ -56,7 +55,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
if (identity != null && identity.IsAuthenticated
|
if (identity != null && identity.IsAuthenticated
|
||||||
&& string.Equals(authScheme, identity.AuthenticationType, StringComparison.Ordinal))
|
&& string.Equals(authScheme, identity.AuthenticationType, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
context.Authenticated(new ClaimsPrincipal(user.Identity), properties: null, description: GetDescription(authScheme));
|
context.Authenticated(new ClaimsPrincipal(identity), properties: null, description: GetDescription(authScheme));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -73,12 +72,38 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
var authScheme = scheme.ToString();
|
var authScheme = scheme.ToString();
|
||||||
// Not including any auth types means it's a blanket challenge for any auth type.
|
// Not including any auth types means it's a blanket challenge for any auth type.
|
||||||
if (context.AuthenticationScheme == string.Empty ||
|
if (string.IsNullOrEmpty(context.AuthenticationScheme) ||
|
||||||
string.Equals(context.AuthenticationScheme, authScheme, StringComparison.Ordinal))
|
string.Equals(context.AuthenticationScheme, authScheme, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_requestContext.Response.StatusCode = 401;
|
switch (context.Behavior)
|
||||||
_customChallenges |= scheme;
|
{
|
||||||
context.Accept();
|
case ChallengeBehavior.Forbidden:
|
||||||
|
_requestContext.Response.StatusCode = 403;
|
||||||
|
context.Accept();
|
||||||
|
break;
|
||||||
|
case ChallengeBehavior.Unauthorized:
|
||||||
|
_requestContext.Response.StatusCode = 401;
|
||||||
|
_customChallenges |= scheme;
|
||||||
|
context.Accept();
|
||||||
|
break;
|
||||||
|
case ChallengeBehavior.Automatic:
|
||||||
|
var identity = (ClaimsIdentity)_requestContext.User?.Identity;
|
||||||
|
if (identity != null && identity.IsAuthenticated
|
||||||
|
&& string.Equals(identity.AuthenticationType, context.AuthenticationScheme, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_requestContext.Response.StatusCode = 403;
|
||||||
|
context.Accept();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_requestContext.Response.StatusCode = 401;
|
||||||
|
_customChallenges |= scheme;
|
||||||
|
context.Accept();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(context.Behavior.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A challenge was issued, it overrides any pre-set auth types.
|
// A challenge was issued, it overrides any pre-set auth types.
|
||||||
|
|
@ -97,13 +122,13 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
|
|
||||||
public Task SignInAsync(SignInContext context)
|
public Task SignInAsync(SignInContext context)
|
||||||
{
|
{
|
||||||
// Not supported
|
// Not supported. AuthenticationManager will throw if !Accepted.
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SignOutAsync(SignOutContext context)
|
public Task SignOutAsync(SignOutContext context)
|
||||||
{
|
{
|
||||||
// Not supported
|
// Not supported. AuthenticationManager will throw if !Accepted.
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FeatureModel;
|
using Microsoft.AspNet.FeatureModel;
|
||||||
|
using Microsoft.AspNet.Http.Features.Authentication;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using AuthenticationSchemes = Microsoft.Net.Http.Server.AuthenticationSchemes;
|
using AuthenticationSchemes = Microsoft.Net.Http.Server.AuthenticationSchemes;
|
||||||
|
|
@ -411,6 +412,78 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(AuthenticationSchemes.Kerberos)]
|
||||||
|
[InlineData(AuthenticationSchemes.Negotiate)]
|
||||||
|
[InlineData(AuthenticationSchemes.NTLM)]
|
||||||
|
// [InlineData(AuthenticationSchemes.Digest)]
|
||||||
|
[InlineData(AuthenticationSchemes.Basic)]
|
||||||
|
public async Task AuthTypes_Forbid_Forbidden(AuthenticationSchemes authType)
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
var authTypes = AuthenticationSchemes.AllowAnonymous | AuthenticationSchemes.Kerberos | AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic;
|
||||||
|
using (Utilities.CreateHttpAuthServer(authTypes, out address, env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.False(context.User.Identity.IsAuthenticated);
|
||||||
|
return context.Authentication.ForbidAsync(authType.ToString());
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address);
|
||||||
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||||
|
Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(AuthenticationSchemes.Kerberos)]
|
||||||
|
[InlineData(AuthenticationSchemes.Negotiate)]
|
||||||
|
[InlineData(AuthenticationSchemes.NTLM)]
|
||||||
|
// [InlineData(AuthenticationSchemes.Digest)] // Not implemented
|
||||||
|
// [InlineData(AuthenticationSchemes.Basic)] // Can't log in with UseDefaultCredentials
|
||||||
|
public async Task AuthTypes_ChallengeAuthenticatedAuthType_Forbidden(AuthenticationSchemes authType)
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpAuthServer(authType, out address, env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.True(context.User.Identity.IsAuthenticated);
|
||||||
|
return context.Authentication.ChallengeAsync(authType.ToString());
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, useDefaultCredentials: true);
|
||||||
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||||
|
// for some reason Kerberos and Negotiate include a 2nd stage challenge.
|
||||||
|
// Assert.Equal(0, response.Headers.WwwAuthenticate.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(AuthenticationSchemes.Kerberos)]
|
||||||
|
[InlineData(AuthenticationSchemes.Negotiate)]
|
||||||
|
[InlineData(AuthenticationSchemes.NTLM)]
|
||||||
|
// [InlineData(AuthenticationSchemes.Digest)] // Not implemented
|
||||||
|
// [InlineData(AuthenticationSchemes.Basic)] // Can't log in with UseDefaultCredentials
|
||||||
|
public async Task AuthTypes_UnathorizedAuthenticatedAuthType_Unauthorized(AuthenticationSchemes authType)
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpAuthServer(authType, out address, env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
Assert.NotNull(context.User);
|
||||||
|
Assert.True(context.User.Identity.IsAuthenticated);
|
||||||
|
return context.Authentication.ChallengeAsync(authType.ToString(), null, ChallengeBehavior.Unauthorized);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, useDefaultCredentials: true);
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||||
|
Assert.Equal(1, response.Headers.WwwAuthenticate.Count);
|
||||||
|
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.First().Scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
|
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
|
||||||
{
|
{
|
||||||
HttpClientHandler handler = new HttpClientHandler();
|
HttpClientHandler handler = new HttpClientHandler();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue