diff --git a/src/Microsoft.AspNet.Authentication.Cookies/Events/CookieAuthenticationEvents.cs b/src/Microsoft.AspNet.Authentication.Cookies/Events/CookieAuthenticationEvents.cs index 43ad5b0e70..615eb2b0c7 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/Events/CookieAuthenticationEvents.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/Events/CookieAuthenticationEvents.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Authentication.Cookies { @@ -36,12 +37,75 @@ namespace Microsoft.AspNet.Authentication.Cookies /// /// A delegate assigned to this property will be invoked when the related method is called. /// - public Func OnRedirect { get; set; } = context => + public Func OnRedirectToLogin { get; set; } = context => { - context.Response.Redirect(context.RedirectUri); + if (IsAjaxRequest(context.Request)) + { + context.Response.Headers["Location"] = context.RedirectUri; + context.Response.StatusCode = 401; + } + else + { + context.Response.Redirect(context.RedirectUri); + } return Task.FromResult(0); }; + /// + /// A delegate assigned to this property will be invoked when the related method is called. + /// + public Func OnRedirectToAccessDenied { get; set; } = context => + { + if (IsAjaxRequest(context.Request)) + { + context.Response.Headers["Location"] = context.RedirectUri; + context.Response.StatusCode = 403; + } + else + { + context.Response.Redirect(context.RedirectUri); + } + return Task.FromResult(0); + }; + + /// + /// A delegate assigned to this property will be invoked when the related method is called. + /// + public Func OnRedirectToLogout { get; set; } = context => + { + if (IsAjaxRequest(context.Request)) + { + context.Response.Headers["Location"] = context.RedirectUri; + } + else + { + context.Response.Redirect(context.RedirectUri); + } + return Task.FromResult(0); + }; + + /// + /// A delegate assigned to this property will be invoked when the related method is called. + /// + public Func OnRedirectToReturnUrl { get; set; } = context => + { + if (IsAjaxRequest(context.Request)) + { + context.Response.Headers["Location"] = context.RedirectUri; + } + else + { + context.Response.Redirect(context.RedirectUri); + } + return Task.FromResult(0); + }; + + private static bool IsAjaxRequest(HttpRequest request) + { + return request.Query["X-Requested-With"] == "XMLHttpRequest" || + request.Headers["X-Requested-With"] == "XMLHttpRequest"; + } + /// /// Implements the interface method by invoking the related delegate method. /// @@ -71,24 +135,24 @@ namespace Microsoft.AspNet.Authentication.Cookies /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToLogout(CookieRedirectContext context) => OnRedirect(context); + public virtual Task RedirectToLogout(CookieRedirectContext context) => OnRedirectToLogout(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToLogin(CookieRedirectContext context) => OnRedirect(context); + public virtual Task RedirectToLogin(CookieRedirectContext context) => OnRedirectToLogin(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToReturnUrl(CookieRedirectContext context) => OnRedirect(context); + public virtual Task RedirectToReturnUrl(CookieRedirectContext context) => OnRedirectToReturnUrl(context); /// /// Implements the interface method by invoking the related delegate method. /// /// Contains information about the event - public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirect(context); + public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirectToAccessDenied(context); } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs index 3f40b1cebb..646375b880 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs @@ -33,6 +33,67 @@ namespace Microsoft.AspNet.Authentication.Cookies Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + [Fact] + public async Task AjaxLoginRedirectToReturnUrlTurnsInto200WithLocationHeader() + { + var server = CreateServer(options => + { + options.AutomaticChallenge = true; + options.LoginPath = "/login"; + }); + + var transaction = await SendAsync(server, "http://example.com/protected?X-Requested-With=XMLHttpRequest"); + Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Equal(1, responded.Count()); + Assert.True(responded.Single().StartsWith("http://example.com/login")); + } + + [Fact] + public async Task AjaxForbidTurnsInto403WithLocationHeader() + { + var server = CreateServer(options => + { + options.AccessDeniedPath = "/denied"; + }); + + var transaction = await SendAsync(server, "http://example.com/forbid?X-Requested-With=XMLHttpRequest"); + Assert.Equal(HttpStatusCode.Forbidden, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Equal(1, responded.Count()); + Assert.True(responded.Single().StartsWith("http://example.com/denied")); + } + + [Fact] + public async Task AjaxLogoutRedirectToReturnUrlTurnsInto200WithLocationHeader() + { + var server = CreateServer(options => + { + options.LogoutPath = "/signout"; + }); + + var transaction = await SendAsync(server, "http://example.com/signout?X-Requested-With=XMLHttpRequest&ReturnUrl=/"); + Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Equal(1, responded.Count()); + Assert.True(responded.Single().StartsWith("/")); + } + + [Fact] + public async Task AjaxChallengeRedirectTurnsInto200WithLocationHeader() + { + var server = CreateServer(options => + { + }); + + var transaction = await SendAsync(server, "http://example.com/challenge?X-Requested-With=XMLHttpRequest&ReturnUrl=/"); + Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Equal(1, responded.Count()); + Assert.True(responded.Single().StartsWith("http://example.com/Account/Login")); + } + + [Theory] [InlineData(true)] [InlineData(false)] @@ -225,9 +286,9 @@ namespace Microsoft.AspNet.Authentication.Cookies var server = CreateServer(options => { options.SystemClock = clock; - }, - SignInAsAlice, - baseAddress: null, + }, + SignInAsAlice, + baseAddress: null, claimsTransform: o => o.Transformer = new ClaimsTransformer { OnTransform = p => @@ -401,7 +462,7 @@ namespace Microsoft.AspNet.Authentication.Cookies new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))))); var transaction1 = await SendAsync(server, "http://example.com/testpath"); - + var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); Assert.NotNull(transaction2.SetCookie); Assert.Equal("Alice", FindClaimValue(transaction2, ClaimTypes.Name)); @@ -554,7 +615,7 @@ namespace Microsoft.AspNet.Authentication.Cookies context => { Assert.Equal(new PathString("/base"), context.Request.PathBase); - return context.Authentication.SignInAsync("Cookies", + return context.Authentication.SignInAsync("Cookies", new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))); }, new Uri("http://example.com/base")); @@ -573,7 +634,7 @@ namespace Microsoft.AspNet.Authentication.Cookies { options.AutomaticAuthenticate = automatic; options.SystemClock = clock; - }, + }, SignInAsAlice); var transaction1 = await SendAsync(server, "http://example.com/testpath"); @@ -708,7 +769,8 @@ namespace Microsoft.AspNet.Authentication.Cookies var server = TestServer.Create(app => { app.UseCookieAuthentication(); - app.Run(async context => { + app.Run(async context => + { await Assert.ThrowsAsync(() => context.Authentication.ChallengeAsync()); }); }, services => services.AddAuthentication()); @@ -739,7 +801,7 @@ namespace Microsoft.AspNet.Authentication.Cookies var server = TestServer.Create(app => { app.UseCookieAuthentication(options => options.LoginPath = new PathString("/login")); - app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies", + app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies", new ClaimsPrincipal()))); }, services => services.AddAuthentication()); @@ -970,6 +1032,10 @@ namespace Microsoft.AspNet.Authentication.Cookies { await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme); } + else if (req.Path == new PathString("/signout")) + { + await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + } else if (req.Path == new PathString("/unauthorized")) { await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties(), ChallengeBehavior.Unauthorized); diff --git a/test/Microsoft.Owin.Security.Cookies.Interop.Test/TicketInteropTests.cs b/test/Microsoft.Owin.Security.Cookies.Interop.Test/TicketInteropTests.cs index 63ab2d36cd..725c5b1f2f 100644 --- a/test/Microsoft.Owin.Security.Cookies.Interop.Test/TicketInteropTests.cs +++ b/test/Microsoft.Owin.Security.Cookies.Interop.Test/TicketInteropTests.cs @@ -106,8 +106,7 @@ namespace Microsoft.AspNet.CookiePolicy.Test var identity = new ClaimsIdentity("Cookies"); identity.AddClaim(new Claim(ClaimTypes.Name, "Alice")); - var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo(".")); - + var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("..\\..\\artifacts")); var interopServer = TestServer.Create(app => { app.Properties["host.AppName"] = "Microsoft.Owin.Security.Tests"; @@ -146,8 +145,7 @@ namespace Microsoft.AspNet.CookiePolicy.Test identity.AddClaim(new Claim(ClaimTypes.Name, "Alice")); user.AddIdentity(identity); - var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo(".")); - + var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("..\\..\\artifacts")); var newServer = TestHost.TestServer.Create(app => { app.UseCookieAuthentication(options => options.DataProtectionProvider = dataProtection);