[Fixes #1638] Send confirmation email doesn't generate the callback URI correctly
This commit is contained in:
parent
d1aa7d527b
commit
2511862a77
|
|
@ -8,7 +8,7 @@
|
|||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post">
|
||||
<form id="profile-form" method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Username"></label>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
else
|
||||
{
|
||||
<input asp-for="Input.Email" class="form-control" />
|
||||
<button asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
|
||||
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
|
||||
}
|
||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
|
|||
var callbackUrl = Url.Page(
|
||||
"/Account/ConfirmEmail",
|
||||
pageHandler: null,
|
||||
values: new { user.Id, code },
|
||||
values: new { userId = user.Id, code = code },
|
||||
protocol: Request.Scheme);
|
||||
await _emailSender.SendEmailAsync(
|
||||
user.Email,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -43,7 +44,13 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
}
|
||||
|
||||
var submit = form.GetSubmission(submitButton);
|
||||
var submision = new HttpRequestMessage(new HttpMethod(submit.Method.ToString()), submit.Target)
|
||||
var target = (Uri)submit.Target;
|
||||
if (submitButton.HasAttribute("formaction"))
|
||||
{
|
||||
var formaction = submitButton.GetAttribute("formaction");
|
||||
target = new Uri(formaction, UriKind.Relative);
|
||||
}
|
||||
var submision = new HttpRequestMessage(new HttpMethod(submit.Method.ToString()), target)
|
||||
{
|
||||
Content = new StreamContent(submit.Body)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
public DefaultUIContext WithExistingUser() =>
|
||||
new DefaultUIContext(this) { ExistingUser = true };
|
||||
|
||||
public DefaultUIContext WithConfirmedEmail() =>
|
||||
new DefaultUIContext(this) { EmailConfirmed = true };
|
||||
|
||||
public string AuthenticatorKey
|
||||
{
|
||||
get => GetValue<string>(nameof(AuthenticatorKey));
|
||||
|
|
@ -58,5 +61,11 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
get => GetValue<bool>(nameof(ExistingUser));
|
||||
set => SetValue(nameof(ExistingUser), value);
|
||||
}
|
||||
|
||||
public bool EmailConfirmed
|
||||
{
|
||||
get => GetValue<bool>(nameof(ExistingUser));
|
||||
set => SetValue(nameof(ExistingUser), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Identity.DefaultUI.WebSite;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
@ -18,5 +19,11 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
services.AddAuthentication()
|
||||
.AddContosoAuthentication(o => o.SignInScheme = IdentityConstants.ExternalScheme)
|
||||
.Services;
|
||||
|
||||
public static IServiceCollection SetupTestEmailSender(this IServiceCollection services, IEmailSender sender) =>
|
||||
services.AddSingleton(sender);
|
||||
|
||||
public static IServiceCollection SetupEmailRequired(this IServiceCollection services) =>
|
||||
services.Configure<IdentityOptions>(o => o.SignIn.RequireConfirmedEmail = true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom.Html;
|
||||
using Identity.DefaultUI.WebSite.Services;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
|
@ -78,12 +79,12 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
public async Task CannotLogInWithoutRequiredEmailConfirmation()
|
||||
{
|
||||
// Arrange
|
||||
var testEmailSender = new TestEmailSender();
|
||||
var emailSender = new ContosoEmailSender();
|
||||
var server = ServerFactory.CreateServer(builder =>
|
||||
{
|
||||
builder.ConfigureServices(services => services
|
||||
.AddSingleton<IEmailSender>(testEmailSender)
|
||||
.Configure<IdentityOptions>(opt => opt.SignIn.RequireConfirmedEmail = true));
|
||||
.SetupTestEmailSender(emailSender)
|
||||
.SetupEmailRequired());
|
||||
});
|
||||
|
||||
var client = ServerFactory.CreateDefaultClient(server);
|
||||
|
|
@ -103,12 +104,12 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
public async Task CanLogInAfterConfirmingEmail()
|
||||
{
|
||||
// Arrange
|
||||
TestEmailSender testEmailSender = new TestEmailSender();
|
||||
var emailSender = new ContosoEmailSender();
|
||||
var server = ServerFactory.CreateServer(builder =>
|
||||
{
|
||||
builder.ConfigureServices(services => services
|
||||
.AddSingleton<IEmailSender>(testEmailSender)
|
||||
.Configure<IdentityOptions>(opt => opt.SignIn.RequireConfirmedEmail = true));
|
||||
.SetupTestEmailSender(emailSender)
|
||||
.SetupEmailRequired());
|
||||
});
|
||||
|
||||
var client = ServerFactory.CreateDefaultClient(server);
|
||||
|
|
@ -121,10 +122,8 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
|
||||
// Act & Assert
|
||||
// Use a new client to simulate a new browser session.
|
||||
var emailBody = HtmlAssert.IsHtmlFragment(testEmailSender.HtmlMessage);
|
||||
var linkElement = HtmlAssert.HasElement("a", emailBody);
|
||||
var link = Assert.IsAssignableFrom<IHtmlAnchorElement>(linkElement);
|
||||
var response = await newClient.GetAsync(link.Href);
|
||||
var email = Assert.Single(emailSender.SentEmails);
|
||||
await UserStories.ConfirmEmailAsync(email, newClient);
|
||||
|
||||
await UserStories.LoginExistingUserAsync(newClient, userName, password);
|
||||
}
|
||||
|
|
@ -147,20 +146,4 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
await UserStories.LoginWithSocialLoginAsync(newClient, userName);
|
||||
}
|
||||
}
|
||||
|
||||
class TestEmailSender : IEmailSender
|
||||
{
|
||||
public string Email { get; private set; }
|
||||
public string Subject { get; private set; }
|
||||
public string HtmlMessage { get; private set; }
|
||||
|
||||
public Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
Email = email;
|
||||
Subject = subject;
|
||||
HtmlMessage = htmlMessage;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Identity.DefaultUI.WebSite.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
||||
|
|
@ -23,5 +24,26 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
// Act & Assert
|
||||
await UserStories.EnableTwoFactorAuthentication(index);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanConfirmEmail()
|
||||
{
|
||||
// Arrange
|
||||
var emails = new ContosoEmailSender();
|
||||
var server = ServerFactory.CreateServer(builder =>
|
||||
builder.ConfigureServices(s => s.SetupTestEmailSender(emails)));
|
||||
var client = ServerFactory.CreateDefaultClient(server);
|
||||
|
||||
var userName = $"{Guid.NewGuid()}@example.com";
|
||||
var password = $"!Test.Password1$";
|
||||
|
||||
var index = await UserStories.RegisterNewUserAsync(client, userName, password);
|
||||
var manageIndex = await UserStories.SendEmailConfirmationLinkAsync(index);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(2, emails.SentEmails.Count);
|
||||
var email = emails.SentEmails[1];
|
||||
await UserStories.ConfirmEmailAsync(email, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom.Html;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account
|
||||
{
|
||||
public class ConfirmEmail : DefaultUIPage
|
||||
{
|
||||
public ConfirmEmail(
|
||||
HttpClient client,
|
||||
IHtmlDocument document,
|
||||
DefaultUIContext context) : base(client, document, context)
|
||||
{
|
||||
}
|
||||
|
||||
public static async Task<ConfirmEmail> Create(IHtmlAnchorElement link, HttpClient client, DefaultUIContext context)
|
||||
{
|
||||
var response = await client.GetAsync(link.Href);
|
||||
var confirmEmail = await ResponseAssert.IsHtmlDocumentAsync(response);
|
||||
|
||||
return new ConfirmEmail(client, confirmEmail, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom.Html;
|
||||
|
|
@ -14,6 +15,8 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
|
|||
private readonly IHtmlAnchorElement _changePasswordLink;
|
||||
private readonly IHtmlAnchorElement _twoFactorLink;
|
||||
private readonly IHtmlAnchorElement _personalDataLink;
|
||||
private readonly IHtmlFormElement _updateProfileForm;
|
||||
private readonly IHtmlElement _confirmEmailButton;
|
||||
|
||||
public Index(HttpClient client, IHtmlDocument manage, DefaultUIContext context)
|
||||
: base(client, manage, context)
|
||||
|
|
@ -24,6 +27,11 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
|
|||
_changePasswordLink = HtmlAssert.HasLink("#change-password", manage);
|
||||
_twoFactorLink = HtmlAssert.HasLink("#two-factor", manage);
|
||||
_personalDataLink = HtmlAssert.HasLink("#personal-data", manage);
|
||||
_updateProfileForm = HtmlAssert.HasForm("#profile-form", manage);
|
||||
if (!Context.EmailConfirmed)
|
||||
{
|
||||
_confirmEmailButton = HtmlAssert.HasElement("button#email-verification", manage);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TwoFactorAuthentication> ClickTwoFactorLinkAsync()
|
||||
|
|
@ -33,5 +41,17 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
|
|||
|
||||
return new TwoFactorAuthentication(Client, twoFactor, Context);
|
||||
}
|
||||
|
||||
internal async Task<Index> SendConfirmationEmailAsync()
|
||||
{
|
||||
Assert.False(Context.EmailConfirmed);
|
||||
|
||||
var response = await Client.SendAsync(_updateProfileForm, _confirmEmailButton);
|
||||
var goToManage = ResponseAssert.IsRedirect(response);
|
||||
var manageResponse = await Client.GetAsync(goToManage);
|
||||
var manage = await ResponseAssert.IsHtmlDocumentAsync(manageResponse);
|
||||
|
||||
return new Index(Client, manage, Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom.Html;
|
||||
using Identity.DefaultUI.WebSite.Services;
|
||||
using Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage;
|
||||
using Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
||||
{
|
||||
|
|
@ -43,6 +47,12 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
return await externalLogin.SendEmailAsync(email);
|
||||
}
|
||||
|
||||
internal static async Task<Account.Manage.Index> SendEmailConfirmationLinkAsync(Index index)
|
||||
{
|
||||
var manage = await index.ClickManageLinkAsync();
|
||||
return await manage.SendConfirmationEmailAsync();
|
||||
}
|
||||
|
||||
internal static async Task<Index> LoginWithSocialLoginAsync(HttpClient client, string userName)
|
||||
{
|
||||
var index = await Index.CreateAsync(
|
||||
|
|
@ -93,5 +103,16 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
|
||||
return await loginRecoveryCode.SendRecoveryCodeAsync(recoveryCode);
|
||||
}
|
||||
|
||||
internal static async Task<ConfirmEmail> ConfirmEmailAsync(IdentityEmail email, HttpClient client)
|
||||
{
|
||||
var emailBody = HtmlAssert.IsHtmlFragment(email.Body);
|
||||
var linkElement = HtmlAssert.HasElement("a", emailBody);
|
||||
var link = Assert.IsAssignableFrom<IHtmlAnchorElement>(linkElement);
|
||||
return await ConfirmEmail.Create(link, client, new DefaultUIContext()
|
||||
.WithAuthenticatedUser()
|
||||
.WithExistingUser()
|
||||
.WithConfirmedEmail());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
|
||||
namespace Identity.DefaultUI.WebSite.Services
|
||||
{
|
||||
public class ContosoEmailSender : IEmailSender
|
||||
{
|
||||
public IList<IdentityEmail> SentEmails { get; set; } = new List<IdentityEmail>();
|
||||
|
||||
public Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
SentEmails.Add(new IdentityEmail(email, subject, htmlMessage));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Identity.DefaultUI.WebSite.Services
|
||||
{
|
||||
public class IdentityEmail
|
||||
{
|
||||
public IdentityEmail(string to, string subject, string body)
|
||||
{
|
||||
To = to;
|
||||
Subject = subject;
|
||||
Body = body;
|
||||
}
|
||||
|
||||
public string To { get; }
|
||||
public string Subject { get; }
|
||||
public string Body { get; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue