Support AccessDeniedPath for Cookie 403 redirection

Fixes https://github.com/aspnet/Security/issues/166
This commit is contained in:
Hao Kung 2015-03-05 15:01:44 -08:00
parent 08017f992a
commit e2bb76280f
6 changed files with 67 additions and 22 deletions

View File

@ -323,6 +323,41 @@ namespace Microsoft.AspNet.Authentication.Cookies
protected override void ApplyResponseChallenge()
{
if (ShouldConvertChallengeToForbidden())
{
// Handle 403 by redirecting to AccessDeniedPath if set
if (Options.AccessDeniedPath.HasValue)
{
try
{
var accessDeniedUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.AccessDeniedPath;
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
catch (Exception exception)
{
var exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
}
}
else
{
Response.StatusCode = 403;
}
return;
}
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
{
return;

View File

@ -4,8 +4,6 @@
using System;
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
using Microsoft.AspNet.Authentication.DataHandler;
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
using Microsoft.AspNet.Authentication.DataHandler;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.DataProtection;
using Microsoft.Framework.Logging;

View File

@ -105,6 +105,15 @@ namespace Microsoft.AspNet.Authentication.Cookies
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")]
public PathString LogoutPath { get; set; }
/// <summary>
/// The AccessDeniedPath property informs the middleware that it should change an outgoing 403 Forbidden status
/// code into a 302 redirection onto the given path.
///
/// If the AccessDeniedPath is null or empty, the middleware will not look for 403 Forbidden status codes, and it will
/// not redirect
/// </summary>
public PathString AccessDeniedPath { get; set; }
/// <summary>
/// The ReturnUrlParameter determines the name of the query string parameter which is appended by the middleware
/// when a 401 Unauthorized status code is changed to a 302 redirect onto the login path. This is also the query

View File

@ -7,10 +7,9 @@ using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Authentication;
using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols;
@ -195,6 +194,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return;
}
if ((Response.StatusCode != 401) || (ChallengeContext == null))

View File

@ -90,23 +90,5 @@ namespace Microsoft.AspNet.Authentication
return Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme);
}
/// <summary>
/// Override this method to deal with 401 challenge concerns, if an authentication scheme in question
/// deals an authentication interaction as part of it's request flow. (like adding a response header, or
/// changing the 401 result to 302 of a login page or external sign-in location.)
/// </summary>
/// <returns></returns>
protected override Task ApplyResponseChallengeAsync()
{
// If authenticate was called and the the status is still 401, authZ failed so set 403 and stop
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return Task.FromResult(0);
}
return base.ApplyResponseChallengeAsync();
}
}
}

View File

@ -420,6 +420,27 @@ namespace Microsoft.AspNet.Authentication.Cookies
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
}
[Fact]
public async Task CookieTurns401ToAccessDeniedWhenSetAndIfAuthenticated()
{
var clock = new TestClock();
TestServer server = CreateServer(options =>
{
options.SystemClock = clock;
options.AccessDeniedPath = new PathString("/accessdenied");
},
SignInAsAlice);
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
Transaction transaction2 = await SendAsync(server, "http://example.com/unauthorized", transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
Uri location = transaction2.Response.Headers.Location;
location.LocalPath.ShouldBe("/accessdenied");
}
[Fact]
public async Task CookieDoesNothingTo401IfNotAuthenticated()
{