Support AccessDeniedPath for Cookie 403 redirection
Fixes https://github.com/aspnet/Security/issues/166
This commit is contained in:
parent
08017f992a
commit
e2bb76280f
|
|
@ -323,6 +323,41 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
|
|
||||||
protected override void ApplyResponseChallenge()
|
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 )
|
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
|
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
|
||||||
using Microsoft.AspNet.Authentication.DataHandler;
|
using Microsoft.AspNet.Authentication.DataHandler;
|
||||||
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
|
|
||||||
using Microsoft.AspNet.Authentication.DataHandler;
|
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.DataProtection;
|
using Microsoft.AspNet.DataProtection;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,15 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")]
|
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")]
|
||||||
public PathString LogoutPath { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The ReturnUrlParameter determines the name of the query string parameter which is appended by the middleware
|
/// 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
|
/// when a 401 Unauthorized status code is changed to a 302 redirect onto the login path. This is also the query
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ using System.IdentityModel.Tokens;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Authentication.Notifications;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Authentication;
|
using Microsoft.AspNet.Http.Authentication;
|
||||||
using Microsoft.AspNet.Authentication;
|
|
||||||
using Microsoft.AspNet.Authentication.Notifications;
|
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.IdentityModel.Protocols;
|
using Microsoft.IdentityModel.Protocols;
|
||||||
|
|
||||||
|
|
@ -195,6 +194,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
|
||||||
if (ShouldConvertChallengeToForbidden())
|
if (ShouldConvertChallengeToForbidden())
|
||||||
{
|
{
|
||||||
Response.StatusCode = 403;
|
Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
||||||
|
|
|
||||||
|
|
@ -90,23 +90,5 @@ namespace Microsoft.AspNet.Authentication
|
||||||
|
|
||||||
return Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme);
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -420,6 +420,27 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
|
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]
|
[Fact]
|
||||||
public async Task CookieDoesNothingTo401IfNotAuthenticated()
|
public async Task CookieDoesNothingTo401IfNotAuthenticated()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue