Add AllowAnonymous for resetting password navigation flow (#1744)
Addresses #1736 and aspnet/templating#448
This commit is contained in:
parent
e5d9efbd48
commit
ae9aa9ebec
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
public class ForgotPasswordConfirmation : PageModel
|
public class ForgotPasswordConfirmation : PageModel
|
||||||
{
|
{
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
public class LockoutModel : PageModel
|
public class LockoutModel : PageModel
|
||||||
{
|
{
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
[IdentityDefaultUI(typeof(ResetPasswordModel<>))]
|
[IdentityDefaultUI(typeof(ResetPasswordModel<>))]
|
||||||
public abstract class ResetPasswordModel : PageModel
|
public abstract class ResetPasswordModel : PageModel
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
public class ResetPasswordConfirmationModel : PageModel
|
public class ResetPasswordConfirmationModel : PageModel
|
||||||
{
|
{
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>ASP.NET Core Identity UI is the default Razor Pages built-in UI for the ASP.NET Core Identity framework.</Description>
|
<Description>ASP.NET Core Identity UI is the default Razor Pages built-in UI for the ASP.NET Core Identity framework.</Description>
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<EmbeddedResource Remove="wwwroot/**/LICENSE*" />
|
<EmbeddedResource Remove="wwwroot/**/LICENSE*" />
|
||||||
<None Remove="wwwroot/**/.bower.json" />
|
<None Remove="wwwroot/**/.bower.json" />
|
||||||
<None Remove="wwwroot/**/LICENSE*" />
|
<None Remove="wwwroot/**/LICENSE*" />
|
||||||
<None Include="THIRD-PARTY-NOTICES" Pack="true" />
|
<None Include="THIRD-PARTY-NOTICES" Pack="true" PackagePath="/"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -48,5 +48,8 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
||||||
config.Filters.Add(new AuthorizeFilter(policy));
|
config.Filters.Add(new AuthorizeFilter(policy));
|
||||||
})
|
})
|
||||||
.Services;
|
.Services;
|
||||||
|
|
||||||
|
public static IServiceCollection SetupMaxFailedAccessAttempts(this IServiceCollection services) =>
|
||||||
|
services.Configure<IdentityOptions>(o => o.Lockout.MaxFailedAccessAttempts = 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -228,5 +228,60 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
||||||
await UserStories.ResetPasswordAsync(resetPasswordClient, email, userName, newPassword);
|
await UserStories.ResetPasswordAsync(resetPasswordClient, email, userName, newPassword);
|
||||||
await UserStories.LoginExistingUserAsync(newClient, userName, newPassword);
|
await UserStories.LoginExistingUserAsync(newClient, userName, newPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanResetPassword_WithGlobalAuthorizeFilter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var emailSender = new ContosoEmailSender();
|
||||||
|
void ConfigureTestServices(IServiceCollection services) =>
|
||||||
|
services.SetupGlobalAuthorizeFilter().SetupTestEmailSender(emailSender);
|
||||||
|
|
||||||
|
var server = ServerFactory.WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices));
|
||||||
|
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var resetPasswordClient = server.CreateClient();
|
||||||
|
var newClient = server.CreateClient();
|
||||||
|
|
||||||
|
var userName = $"{Guid.NewGuid()}@example.com";
|
||||||
|
var password = $"!Test.Password1$";
|
||||||
|
var newPassword = $"!New.Password1$";
|
||||||
|
|
||||||
|
await UserStories.RegisterNewUserAsync(client, userName, password);
|
||||||
|
var registrationEmail = Assert.Single(emailSender.SentEmails);
|
||||||
|
await UserStories.ConfirmEmailAsync(registrationEmail, client);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await UserStories.ForgotPasswordAsync(resetPasswordClient, userName);
|
||||||
|
Assert.Equal(2, emailSender.SentEmails.Count);
|
||||||
|
var email = emailSender.SentEmails[1];
|
||||||
|
await UserStories.ResetPasswordAsync(resetPasswordClient, email, userName, newPassword);
|
||||||
|
await UserStories.LoginExistingUserAsync(newClient, userName, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UserLockedOut_AfterMaxFailedAccessAttempts_WithGlobalAuthorizeFilter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var emailSender = new ContosoEmailSender();
|
||||||
|
void ConfigureTestServices(IServiceCollection services) =>
|
||||||
|
services.SetupGlobalAuthorizeFilter().SetupMaxFailedAccessAttempts().SetupTestEmailSender(emailSender);
|
||||||
|
|
||||||
|
var server = ServerFactory.WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices));
|
||||||
|
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var newClient = server.CreateClient();
|
||||||
|
|
||||||
|
var userName = $"{Guid.NewGuid()}@example.com";
|
||||||
|
var password = $"!Test.Password1$";
|
||||||
|
var wrongPassword = $"!Wrong.Password1$";
|
||||||
|
|
||||||
|
await UserStories.RegisterNewUserAsync(client, userName, password);
|
||||||
|
var registrationEmail = Assert.Single(emailSender.SentEmails);
|
||||||
|
await UserStories.ConfirmEmailAsync(registrationEmail, client);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await UserStories.LockoutExistingUserAsync(newClient, userName, wrongPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,18 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
|
||||||
Context.WithAuthenticatedUser());
|
Context.WithAuthenticatedUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<DefaultUIPage> LockoutUserAsync(string userName, string password)
|
||||||
|
{
|
||||||
|
var loginAttempt = await SendLoginForm(userName, password);
|
||||||
|
|
||||||
|
var lockedOut = ResponseAssert.IsRedirect(loginAttempt);
|
||||||
|
Assert.Equal("/Identity/Account/Lockout", lockedOut.ToString());
|
||||||
|
|
||||||
|
var lockedOutResponse = await Client.GetAsync(lockedOut);
|
||||||
|
var lockout = await ResponseAssert.IsHtmlDocumentAsync(lockedOutResponse);
|
||||||
|
return new DefaultUIPage(Client, lockout, Context);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> SendLoginForm(string userName, string password)
|
private async Task<HttpResponseMessage> SendLoginForm(string userName, string password)
|
||||||
{
|
{
|
||||||
return await Client.SendAsync(_loginForm, new Dictionary<string, string>()
|
return await Client.SendAsync(_loginForm, new Dictionary<string, string>()
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,15 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
||||||
return await login.LoginValidUserAsync(userName, password);
|
return await login.LoginValidUserAsync(userName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static async Task<DefaultUIPage> LockoutExistingUserAsync(HttpClient client, string userName, string password)
|
||||||
|
{
|
||||||
|
var index = await Index.CreateAsync(client);
|
||||||
|
|
||||||
|
var login = await index.ClickLoginLinkAsync();
|
||||||
|
|
||||||
|
return await login.LockoutUserAsync(userName, password);
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<Index> RegisterNewUserWithSocialLoginAsync(HttpClient client, string userName, string email)
|
internal static async Task<Index> RegisterNewUserWithSocialLoginAsync(HttpClient client, string userName, string email)
|
||||||
{
|
{
|
||||||
var index = await Index.CreateAsync(client, new DefaultUIContext().WithSocialLoginEnabled());
|
var index = await Index.CreateAsync(client, new DefaultUIContext().WithSocialLoginEnabled());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue