@foreach (var provider in Model.ExternalLogins)
diff --git a/test/Identity.FunctionalTests/Extensions/HttpClientExtensions.cs b/test/Identity.FunctionalTests/Extensions/HttpClientExtensions.cs
index 61ac948ae2..08bb305fb7 100644
--- a/test/Identity.FunctionalTests/Extensions/HttpClientExtensions.cs
+++ b/test/Identity.FunctionalTests/Extensions/HttpClientExtensions.cs
@@ -1,10 +1,8 @@
// 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.Text;
using System.Threading.Tasks;
using AngleSharp.Dom.Html;
using Xunit;
@@ -16,7 +14,27 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
public static Task SendAsync(
this HttpClient client,
IHtmlFormElement form,
- IEnumerable> formValues)
+ IHtmlElement submitButton)
+ {
+ return client.SendAsync(form, submitButton, new Dictionary());
+ }
+
+ public static Task SendAsync(
+ this HttpClient client,
+ IHtmlFormElement form,
+ IEnumerable> formValues)
+ {
+ var submitElement = Assert.Single(form.QuerySelectorAll("[type=submit]"));
+ var submitButton = Assert.IsAssignableFrom(submitElement);
+
+ return client.SendAsync(form, submitButton, formValues);
+ }
+
+ public static Task SendAsync(
+ this HttpClient client,
+ IHtmlFormElement form,
+ IHtmlElement submitButton,
+ IEnumerable> formValues)
{
foreach (var kvp in formValues)
{
@@ -24,9 +42,6 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
element.Value = kvp.Value;
}
- var submitElement = Assert.Single(form.QuerySelectorAll("[type=submit]"));
- var submitButton = Assert.IsAssignableFrom(submitElement);
-
var submit = form.GetSubmission(submitButton);
var submision = new HttpRequestMessage(new HttpMethod(submit.Method.ToString()), submit.Target)
{
diff --git a/test/Identity.FunctionalTests/Infrastructure/DefaultUIContext.cs b/test/Identity.FunctionalTests/Infrastructure/DefaultUIContext.cs
new file mode 100644
index 0000000000..de4484ba75
--- /dev/null
+++ b/test/Identity.FunctionalTests/Infrastructure/DefaultUIContext.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Identity.FunctionalTests
+{
+ public class DefaultUIContext : HtmlPageContext
+ {
+ public DefaultUIContext()
+ {
+ }
+
+ public DefaultUIContext(DefaultUIContext currentContext)
+ : base(currentContext)
+ {
+
+ }
+
+ public DefaultUIContext WithAuthenticatedUser() =>
+ new DefaultUIContext(this) { UserAuthenticated = true };
+
+ public DefaultUIContext WithSocialLoginEnabled() =>
+ new DefaultUIContext(this) { ContosoLoginEnabled = true };
+
+ public DefaultUIContext WithExistingUser() =>
+ new DefaultUIContext(this) { ExistingUser = true };
+
+ public string AuthenticatorKey
+ {
+ get => GetValue(nameof(AuthenticatorKey));
+ set => SetValue(nameof(AuthenticatorKey), value);
+ }
+
+ public string[] RecoveryCodes
+ {
+ get => GetValue(nameof(RecoveryCodes));
+ set => SetValue(nameof(RecoveryCodes), value);
+ }
+
+ public bool TwoFactorEnabled
+ {
+ get => GetValue(nameof(TwoFactorEnabled));
+ set => SetValue(nameof(TwoFactorEnabled), value);
+ }
+ public bool ContosoLoginEnabled
+ {
+ get => GetValue(nameof(ContosoLoginEnabled));
+ set => SetValue(nameof(ContosoLoginEnabled), value);
+ }
+
+ public bool UserAuthenticated
+ {
+ get => GetValue(nameof(UserAuthenticated));
+ set => SetValue(nameof(UserAuthenticated), value);
+ }
+ public bool ExistingUser
+ {
+ get => GetValue(nameof(ExistingUser));
+ set => SetValue(nameof(ExistingUser), value);
+ }
+ }
+}
diff --git a/test/Identity.FunctionalTests/Infrastructure/DefaultUIPage.cs b/test/Identity.FunctionalTests/Infrastructure/DefaultUIPage.cs
new file mode 100644
index 0000000000..6c6c4d3288
--- /dev/null
+++ b/test/Identity.FunctionalTests/Infrastructure/DefaultUIPage.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using AngleSharp.Dom.Html;
+
+namespace Microsoft.AspNetCore.Identity.FunctionalTests
+{
+ public class DefaultUIPage : HtmlPage
+ {
+ public DefaultUIPage(HttpClient client, IHtmlDocument document, DefaultUIContext context)
+ : base(client, document, context)
+ {
+ }
+ }
+}
diff --git a/test/Identity.FunctionalTests/Infrastructure/FunctionalTestsServiceCollectionExtensions.cs b/test/Identity.FunctionalTests/Infrastructure/FunctionalTestsServiceCollectionExtensions.cs
index 15b5cbe4c3..ec2c97ce81 100644
--- a/test/Identity.FunctionalTests/Infrastructure/FunctionalTestsServiceCollectionExtensions.cs
+++ b/test/Identity.FunctionalTests/Infrastructure/FunctionalTestsServiceCollectionExtensions.cs
@@ -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 Identity.DefaultUI.WebSite;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -9,12 +10,13 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
{
public static class FunctionalTestsServiceCollectionExtensions
{
- public static IServiceCollection SetupTestDatabase(this IServiceCollection services, string databaseName)
- {
+ public static IServiceCollection SetupTestDatabase(this IServiceCollection services, string databaseName) =>
services.AddDbContext(options =>
options.UseInMemoryDatabase(databaseName, memoryOptions => { }));
- return services;
- }
+ public static IServiceCollection SetupTestThirdPartyLogin(this IServiceCollection services) =>
+ services.AddAuthentication()
+ .AddContosoAuthentication(o => o.SignInScheme = IdentityConstants.ExternalScheme)
+ .Services;
}
}
diff --git a/test/Identity.FunctionalTests/Infrastructure/HtmlPage.cs b/test/Identity.FunctionalTests/Infrastructure/HtmlPage.cs
index 8a2b1c5f45..7c5ac23757 100644
--- a/test/Identity.FunctionalTests/Infrastructure/HtmlPage.cs
+++ b/test/Identity.FunctionalTests/Infrastructure/HtmlPage.cs
@@ -6,9 +6,9 @@ using AngleSharp.Dom.Html;
namespace Microsoft.AspNetCore.Identity.FunctionalTests
{
- public class HtmlPage
+ public class HtmlPage
{
- public HtmlPage(HttpClient client, IHtmlDocument document, HtmlPageContext context)
+ public HtmlPage(HttpClient client, IHtmlDocument document, TApplicationContext context)
{
Client = client;
Document = document;
@@ -17,6 +17,6 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
public HttpClient Client { get; }
public IHtmlDocument Document { get; }
- public HtmlPageContext Context { get; }
+ public TApplicationContext Context { get; }
}
}
diff --git a/test/Identity.FunctionalTests/Infrastructure/HtmlPageContext.cs b/test/Identity.FunctionalTests/Infrastructure/HtmlPageContext.cs
index eb15cd8873..7884616c76 100644
--- a/test/Identity.FunctionalTests/Infrastructure/HtmlPageContext.cs
+++ b/test/Identity.FunctionalTests/Infrastructure/HtmlPageContext.cs
@@ -7,13 +7,26 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
{
public class HtmlPageContext
{
- private readonly IDictionary _properties =
- new Dictionary();
+ private readonly IDictionary _properties;
- public string this[string key]
+ protected HtmlPageContext()
+ : this(new Dictionary())
{
- get => _properties[key];
- set => _properties[key] = value;
}
+
+ protected HtmlPageContext(HtmlPageContext currentContext)
+ : this(new Dictionary(currentContext._properties))
+ {
+ }
+
+ private HtmlPageContext(IDictionary properties)
+ {
+ _properties = properties;
+ }
+
+ protected TValue GetValue(string key) =>
+ _properties.TryGetValue(key, out var rawValue) ? (TValue)rawValue : default;
+ protected void SetValue(string key, object value) =>
+ _properties[key] = value;
}
}
\ No newline at end of file
diff --git a/test/Identity.FunctionalTests/LoginTests.cs b/test/Identity.FunctionalTests/LoginTests.cs
index e923b57781..1b2a326657 100644
--- a/test/Identity.FunctionalTests/LoginTests.cs
+++ b/test/Identity.FunctionalTests/LoginTests.cs
@@ -5,7 +5,6 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using AngleSharp.Dom.Html;
-using Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
@@ -45,9 +44,9 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
var password = $"!Test.Password1$";
var loggedIn = await UserStories.RegisterNewUserAsync(client, userName, password);
- var showRecoveryCodes = await UserStories.EnableTwoFactorAuthentication(loggedIn, twoFactorEnabled: false);
+ var showRecoveryCodes = await UserStories.EnableTwoFactorAuthentication(loggedIn);
- var twoFactorKey = showRecoveryCodes.Context[EnableAuthenticator.AuthenticatorKey];
+ var twoFactorKey = showRecoveryCodes.Context.AuthenticatorKey;
// Act & Assert
// Use a new client to simulate a new browser session.
@@ -66,11 +65,9 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
var password = $"!Test.Password1$";
var loggedIn = await UserStories.RegisterNewUserAsync(client, userName, password);
- var showRecoveryCodes = await UserStories.EnableTwoFactorAuthentication(loggedIn, twoFactorEnabled: false);
+ var showRecoveryCodes = await UserStories.EnableTwoFactorAuthentication(loggedIn);
- var recoveryCode = showRecoveryCodes.Context[ShowRecoveryCodes.RecoveryCodes]
- .Split(' ')
- .First();
+ var recoveryCode = showRecoveryCodes.Context.RecoveryCodes.First();
// Act & Assert
// Use a new client to simulate a new browser session.
@@ -131,6 +128,24 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
await UserStories.LoginExistingUserAsync(newClient, userName, password);
}
+
+ [Fact]
+ public async Task CanLoginWithASocialLoginProvider()
+ {
+ // Arrange
+ var server = ServerFactory.CreateServer(builder =>
+ builder.ConfigureServices(services => services.SetupTestThirdPartyLogin()));
+ var client = ServerFactory.CreateDefaultClient(server);
+ var newClient = ServerFactory.CreateDefaultClient(server);
+
+ var guid = Guid.NewGuid();
+ var userName = $"{guid}";
+ var email = $"{guid}@example.com";
+
+ // Act & Assert
+ await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email);
+ await UserStories.LoginWithSocialLoginAsync(newClient, userName);
+ }
}
class TestEmailSender : IEmailSender
diff --git a/test/Identity.FunctionalTests/ManagementTests.cs b/test/Identity.FunctionalTests/ManagementTests.cs
index ac57738ca7..f871994787 100644
--- a/test/Identity.FunctionalTests/ManagementTests.cs
+++ b/test/Identity.FunctionalTests/ManagementTests.cs
@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
var index = await UserStories.RegisterNewUserAsync(client, userName, password);
// Act & Assert
- await UserStories.EnableTwoFactorAuthentication(index, twoFactorEnabled: false);
+ await UserStories.EnableTwoFactorAuthentication(index);
}
}
}
diff --git a/test/Identity.FunctionalTests/Pages/Account/ExternalLogin.cs b/test/Identity.FunctionalTests/Pages/Account/ExternalLogin.cs
new file mode 100644
index 0000000000..7a10597aa0
--- /dev/null
+++ b/test/Identity.FunctionalTests/Pages/Account/ExternalLogin.cs
@@ -0,0 +1,36 @@
+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 ExternalLogin : DefaultUIPage
+ {
+ private readonly IHtmlFormElement _emailForm;
+
+ public ExternalLogin(
+ HttpClient client,
+ IHtmlDocument externalLogin,
+ DefaultUIContext context)
+ : base(client, externalLogin, context)
+ {
+ _emailForm = HtmlAssert.HasForm(Document);
+ }
+
+ public async Task SendEmailAsync(string email)
+ {
+ var response = await Client.SendAsync(_emailForm, new Dictionary
+ {
+ ["Input_Email"] = email
+ });
+ var goToIndex = ResponseAssert.IsRedirect(response);
+ var indexResponse = await Client.GetAsync(goToIndex);
+ var index = await ResponseAssert.IsHtmlDocumentAsync(indexResponse);
+
+ return new Index(Client, index, Context.WithAuthenticatedUser());
+ }
+ }
+}
diff --git a/test/Identity.FunctionalTests/Pages/Account/Login.cs b/test/Identity.FunctionalTests/Pages/Account/Login.cs
index 1275ac599c..a5792a279b 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Login.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Login.cs
@@ -9,14 +9,35 @@ using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
{
- public class Login : HtmlPage
+ public class Login : DefaultUIPage
{
private readonly IHtmlFormElement _loginForm;
+ private readonly IHtmlFormElement _externalLoginForm;
+ private readonly IHtmlElement _contosoButton;
- public Login(HttpClient client, IHtmlDocument login, HtmlPageContext context)
+ public Login(
+ HttpClient client,
+ IHtmlDocument login,
+ DefaultUIContext context)
: base(client, login, context)
{
- _loginForm = HtmlAssert.HasForm(login);
+ _loginForm = HtmlAssert.HasForm("#account", login);
+ if (Context.ContosoLoginEnabled)
+ {
+ _externalLoginForm = HtmlAssert.HasForm("#external-account", login);
+ _contosoButton = HtmlAssert.HasElement("button[value=Contoso]", login);
+ }
+ }
+
+ public async Task ClickLoginWithContosoLinkAsync()
+ {
+ var externalFormResponse = await Client.SendAsync(_externalLoginForm, _contosoButton);
+ var goToContosoLogin = ResponseAssert.IsRedirect(externalFormResponse);
+ var contosoLoginResponse = await Client.GetAsync(goToContosoLogin);
+
+ var contosoLogin = await ResponseAssert.IsHtmlDocumentAsync(contosoLoginResponse);
+
+ return new Contoso.Login(Client, contosoLogin, Context);
}
public async Task LoginValidUserAsync(string userName, string password)
@@ -27,7 +48,10 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
Assert.Equal(Index.Path, loggedInLocation.ToString());
var indexResponse = await Client.GetAsync(loggedInLocation);
var index = await ResponseAssert.IsHtmlDocumentAsync(indexResponse);
- return new Index(Client, index, Context, authenticated: true);
+ return new Index(
+ Client,
+ index,
+ Context.WithAuthenticatedUser());
}
private async Task SendLoginForm(string userName, string password)
diff --git a/test/Identity.FunctionalTests/Pages/Account/LoginWith2fa.cs b/test/Identity.FunctionalTests/Pages/Account/LoginWith2fa.cs
index 8016352893..921d0da6d2 100644
--- a/test/Identity.FunctionalTests/Pages/Account/LoginWith2fa.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/LoginWith2fa.cs
@@ -10,14 +10,14 @@ using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
{
- public class LoginWith2fa : HtmlPage
+ public class LoginWith2fa : DefaultUIPage
{
public const string Path = "/Identity/Account/LoginWith2fa";
private readonly IHtmlFormElement _twoFactorForm;
private readonly IHtmlAnchorElement _loginWithRecoveryCodeLink;
- public LoginWith2fa(HttpClient client, IHtmlDocument loginWithTwoFactor, HtmlPageContext context)
+ public LoginWith2fa(HttpClient client, IHtmlDocument loginWithTwoFactor, DefaultUIContext context)
: base(client, loginWithTwoFactor, context)
{
_twoFactorForm = HtmlAssert.HasForm(loginWithTwoFactor);
@@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
var indexResponse = await Client.GetAsync(goToIndex);
var index = await ResponseAssert.IsHtmlDocumentAsync(indexResponse);
- return new Index(Client, index, Context, true);
+ return new Index(Client, index, Context.WithAuthenticatedUser());
}
internal async Task ClickRecoveryCodeLinkAsync()
diff --git a/test/Identity.FunctionalTests/Pages/Account/LoginWithRecoveryCode.cs b/test/Identity.FunctionalTests/Pages/Account/LoginWithRecoveryCode.cs
index 95b2c58b01..f5ee768809 100644
--- a/test/Identity.FunctionalTests/Pages/Account/LoginWithRecoveryCode.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/LoginWithRecoveryCode.cs
@@ -8,11 +8,11 @@ using AngleSharp.Dom.Html;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
{
- public class LoginWithRecoveryCode : HtmlPage
+ public class LoginWithRecoveryCode : DefaultUIPage
{
private readonly IHtmlFormElement _loginWithRecoveryCodeForm;
- public LoginWithRecoveryCode(HttpClient client, IHtmlDocument loginWithRecoveryCode, HtmlPageContext context)
+ public LoginWithRecoveryCode(HttpClient client, IHtmlDocument loginWithRecoveryCode, DefaultUIContext context)
: base(client, loginWithRecoveryCode, context)
{
_loginWithRecoveryCodeForm = HtmlAssert.HasForm(loginWithRecoveryCode);
@@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
var indexPage = await Client.GetAsync(goToIndex);
var index = await ResponseAssert.IsHtmlDocumentAsync(indexPage);
- return new Index(Client, index, Context, authenticated: true);
+ return new Index(Client, index, new DefaultUIContext(Context) { UserAuthenticated = true });
}
}
}
\ No newline at end of file
diff --git a/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs b/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
index bae3e319f3..53003d7c8e 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
@@ -7,19 +7,24 @@ using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using AngleSharp.Dom.Html;
+using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
{
- internal class EnableAuthenticator : HtmlPage
+ internal class EnableAuthenticator : DefaultUIPage
{
public const string AuthenticatorKey = nameof(EnableAuthenticator) + "." + nameof(AuthenticatorKey);
private readonly IHtmlElement _codeElement;
private readonly IHtmlFormElement _sendCodeForm;
- public EnableAuthenticator(HttpClient client, IHtmlDocument enableAuthenticator, HtmlPageContext context)
+ public EnableAuthenticator(
+ HttpClient client,
+ IHtmlDocument enableAuthenticator,
+ DefaultUIContext context)
: base(client, enableAuthenticator, context)
{
+ Assert.True(Context.UserAuthenticated);
_codeElement = HtmlAssert.HasElement("kbd", enableAuthenticator);
_sendCodeForm = HtmlAssert.HasForm("#send-code", enableAuthenticator);
}
@@ -27,7 +32,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
internal async Task SendValidCodeAsync()
{
var authenticatorKey = _codeElement.TextContent.Replace(" ", "");
- Context[AuthenticatorKey] = authenticatorKey;
+ Context.AuthenticatorKey = authenticatorKey;
var verificationCode = ComputeCode(authenticatorKey);
var sendCodeResponse = await Client.SendAsync(_sendCodeForm, new Dictionary
diff --git a/test/Identity.FunctionalTests/Pages/Account/Manage/Index.cs b/test/Identity.FunctionalTests/Pages/Account/Manage/Index.cs
index 270563de47..20e9b81356 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Manage/Index.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Manage/Index.cs
@@ -4,31 +4,34 @@
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Dom.Html;
+using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
{
- public class Index : HtmlPage
+ public class Index : DefaultUIPage
{
private readonly IHtmlAnchorElement _profileLink;
private readonly IHtmlAnchorElement _changePasswordLink;
private readonly IHtmlAnchorElement _twoFactorLink;
private readonly IHtmlAnchorElement _personalDataLink;
- public Index(HttpClient client, IHtmlDocument manage, HtmlPageContext context)
+ public Index(HttpClient client, IHtmlDocument manage, DefaultUIContext context)
: base(client, manage, context)
{
+ Assert.True(Context.UserAuthenticated);
+
_profileLink = HtmlAssert.HasLink("#profile", manage);
_changePasswordLink = HtmlAssert.HasLink("#change-password", manage);
_twoFactorLink = HtmlAssert.HasLink("#two-factor", manage);
_personalDataLink = HtmlAssert.HasLink("#personal-data", manage);
}
- public async Task ClickTwoFactorLinkAsync(bool twoFactorEnabled)
+ public async Task ClickTwoFactorLinkAsync()
{
var goToTwoFactor = await Client.GetAsync(_twoFactorLink.Href);
var twoFactor = await ResponseAssert.IsHtmlDocumentAsync(goToTwoFactor);
- return new TwoFactorAuthentication(Client, twoFactor, Context, twoFactorEnabled);
+ return new TwoFactorAuthentication(Client, twoFactor, Context);
}
}
}
diff --git a/test/Identity.FunctionalTests/Pages/Account/Manage/ShowRecoveryCodes.cs b/test/Identity.FunctionalTests/Pages/Account/Manage/ShowRecoveryCodes.cs
index f4601d6fb6..1fbc98fa04 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Manage/ShowRecoveryCodes.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Manage/ShowRecoveryCodes.cs
@@ -8,17 +8,15 @@ using AngleSharp.Dom.Html;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
{
- internal class ShowRecoveryCodes : HtmlPage
+ internal class ShowRecoveryCodes : DefaultUIPage
{
- public const string RecoveryCodes = nameof(ShowRecoveryCodes) + "." + nameof(RecoveryCodes);
-
private readonly IEnumerable _recoveryCodeElements;
- public ShowRecoveryCodes(HttpClient client, IHtmlDocument showRecoveryCodes, HtmlPageContext context)
+ public ShowRecoveryCodes(HttpClient client, IHtmlDocument showRecoveryCodes, DefaultUIContext context)
: base(client, showRecoveryCodes, context)
{
_recoveryCodeElements = HtmlAssert.HasElements(".recovery-code", showRecoveryCodes);
- Context[RecoveryCodes] = string.Join(" ", Codes);
+ Context.RecoveryCodes = Codes.ToArray();
}
public IEnumerable Codes => _recoveryCodeElements.Select(rc => rc.TextContent);
diff --git a/test/Identity.FunctionalTests/Pages/Account/Manage/TwoFactorAuthentication.cs b/test/Identity.FunctionalTests/Pages/Account/Manage/TwoFactorAuthentication.cs
index f280c28fc6..1faec56c1b 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Manage/TwoFactorAuthentication.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Manage/TwoFactorAuthentication.cs
@@ -8,16 +8,14 @@ using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
{
- public class TwoFactorAuthentication : HtmlPage
+ public class TwoFactorAuthentication : DefaultUIPage
{
- private readonly bool _twoFactorEnabled;
private readonly IHtmlAnchorElement _enableAuthenticatorLink;
- public TwoFactorAuthentication(HttpClient client, IHtmlDocument twoFactor, HtmlPageContext context, bool twoFactorEnabled)
+ public TwoFactorAuthentication(HttpClient client, IHtmlDocument twoFactor, DefaultUIContext context)
: base(client, twoFactor, context)
{
- _twoFactorEnabled = twoFactorEnabled;
- if (!_twoFactorEnabled)
+ if (!Context.TwoFactorEnabled)
{
_enableAuthenticatorLink = HtmlAssert.HasLink("#enable-authenticator", twoFactor);
}
@@ -25,7 +23,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage
internal async Task ClickEnableAuthenticatorLinkAsync()
{
- Assert.False(_twoFactorEnabled);
+ Assert.False(Context.TwoFactorEnabled);
var goToEnableAuthenticator = await Client.GetAsync(_enableAuthenticatorLink.Href);
var enableAuthenticator = await ResponseAssert.IsHtmlDocumentAsync(goToEnableAuthenticator);
diff --git a/test/Identity.FunctionalTests/Pages/Account/Register.cs b/test/Identity.FunctionalTests/Pages/Account/Register.cs
index 0c1ab8e931..41afb55fc6 100644
--- a/test/Identity.FunctionalTests/Pages/Account/Register.cs
+++ b/test/Identity.FunctionalTests/Pages/Account/Register.cs
@@ -9,11 +9,11 @@ using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
{
- public class Register : HtmlPage
+ public class Register : DefaultUIPage
{
private IHtmlFormElement _registerForm;
- public Register(HttpClient client, IHtmlDocument register, HtmlPageContext context)
+ public Register(HttpClient client, IHtmlDocument register, DefaultUIContext context)
: base(client, register, context)
{
_registerForm = HtmlAssert.HasForm(register);
@@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
var indexResponse = await Client.GetAsync(registeredLocation);
var index = await ResponseAssert.IsHtmlDocumentAsync(indexResponse);
- return new Index(Client, index, Context, authenticated: true);
+ return new Index(Client, index, Context.WithAuthenticatedUser());
}
}
}
diff --git a/test/Identity.FunctionalTests/Pages/Contoso/Login.cs b/test/Identity.FunctionalTests/Pages/Contoso/Login.cs
new file mode 100644
index 0000000000..c9e22a85f4
--- /dev/null
+++ b/test/Identity.FunctionalTests/Pages/Contoso/Login.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using AngleSharp.Dom.Html;
+using Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account;
+
+namespace Microsoft.AspNetCore.Identity.FunctionalTests.Contoso
+{
+ public class Login : DefaultUIPage
+ {
+ private readonly IHtmlFormElement _loginForm;
+
+ public Login(HttpClient client, IHtmlDocument login, DefaultUIContext context)
+ : base(client, login, context)
+ {
+ _loginForm = HtmlAssert.HasForm(login);
+ }
+
+ public async Task SendNewUserNameAsync(string userName)
+ {
+ var externalLogin = await SendLoginForm(userName);
+
+ return new ExternalLogin(Client, externalLogin, Context);
+ }
+
+ public async Task SendExistingUserNameAsync(string userName)
+ {
+ var externalLogin = await SendLoginForm(userName);
+
+ return new Index(Client, externalLogin, Context.WithAuthenticatedUser());
+ }
+
+ private async Task SendLoginForm(string userName)
+ {
+ var contosoResponse = await Client.SendAsync(_loginForm, new Dictionary
+ {
+ ["Input_Login"] = userName
+ });
+
+ var goToExternalLogin = ResponseAssert.IsRedirect(contosoResponse);
+ var externalLogInResponse = await Client.GetAsync(goToExternalLogin);
+ if (Context.ExistingUser)
+ {
+ var goToIndex = ResponseAssert.IsRedirect(externalLogInResponse);
+ var indexResponse = await Client.GetAsync(goToIndex);
+ return await ResponseAssert.IsHtmlDocumentAsync(indexResponse);
+ }
+ else
+ {
+ return await ResponseAssert.IsHtmlDocumentAsync(externalLogInResponse);
+ }
+ }
+ }
+}
diff --git a/test/Identity.FunctionalTests/Pages/Index.cs b/test/Identity.FunctionalTests/Pages/Index.cs
index 447754daac..71fdd9e7fa 100644
--- a/test/Identity.FunctionalTests/Pages/Index.cs
+++ b/test/Identity.FunctionalTests/Pages/Index.cs
@@ -9,19 +9,20 @@ using Xunit;
namespace Microsoft.AspNetCore.Identity.FunctionalTests
{
- public class Index : HtmlPage
+ public class Index : DefaultUIPage
{
- private readonly bool _authenticated;
private readonly IHtmlAnchorElement _registerLink;
private readonly IHtmlAnchorElement _loginLink;
private readonly IHtmlAnchorElement _manageLink;
public static readonly string Path = "/";
- public Index(HttpClient client, IHtmlDocument index, HtmlPageContext context, bool authenticated)
+ public Index(
+ HttpClient client,
+ IHtmlDocument index,
+ DefaultUIContext context)
: base(client, index, context)
{
- _authenticated = authenticated;
- if (!_authenticated)
+ if (!Context.UserAuthenticated)
{
_registerLink = HtmlAssert.HasLink("#register", Document);
_loginLink = HtmlAssert.HasLink("#login", Document);
@@ -32,17 +33,17 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
}
}
- public static async Task CreateAsync(HttpClient client, bool authenticated = false)
+ public static async Task CreateAsync(HttpClient client, DefaultUIContext context = null)
{
var goToIndex = await client.GetAsync("/");
var index = await ResponseAssert.IsHtmlDocumentAsync(goToIndex);
- return new Index(client, index, new HtmlPageContext(), authenticated);
+ return new Index(client, index, context ?? new DefaultUIContext());
}
public async Task ClickRegisterLinkAsync()
{
- Assert.False(_authenticated);
+ Assert.False(Context.UserAuthenticated);
var goToRegister = await Client.GetAsync(_registerLink.Href);
var register = await ResponseAssert.IsHtmlDocumentAsync(goToRegister);
@@ -52,7 +53,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
public async Task ClickLoginLinkAsync()
{
- Assert.False(_authenticated);
+ Assert.False(Context.UserAuthenticated);
var goToLogin = await Client.GetAsync(_loginLink.Href);
var login = await ResponseAssert.IsHtmlDocumentAsync(goToLogin);
@@ -62,7 +63,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
internal async Task ClickManageLinkAsync()
{
- Assert.True(_authenticated);
+ Assert.True(Context.UserAuthenticated);
var goToManage = await Client.GetAsync(_manageLink.Href);
var manage = await ResponseAssert.IsHtmlDocumentAsync(goToManage);
diff --git a/test/Identity.FunctionalTests/RegistrationTests.cs b/test/Identity.FunctionalTests/RegistrationTests.cs
index d6a4c7c450..f41f40ac98 100644
--- a/test/Identity.FunctionalTests/RegistrationTests.cs
+++ b/test/Identity.FunctionalTests/RegistrationTests.cs
@@ -21,5 +21,21 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
// Act & Assert
await UserStories.RegisterNewUserAsync(client, userName, password);
}
+
+ [Fact]
+ public async Task CanRegisterWithASocialLoginProvider()
+ {
+ // Arrange
+ var server = ServerFactory.CreateServer(builder =>
+ builder.ConfigureServices(services => services.SetupTestThirdPartyLogin()));
+ var client = ServerFactory.CreateDefaultClient(server);
+
+ var guid = Guid.NewGuid();
+ var userName = $"{guid}";
+ var email = $"{guid}@example.com";
+
+ // Act & Assert
+ await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email);
+ }
}
}
diff --git a/test/Identity.FunctionalTests/UserStories.cs b/test/Identity.FunctionalTests/UserStories.cs
index 63679b9b7c..cb81ac3a2b 100644
--- a/test/Identity.FunctionalTests/UserStories.cs
+++ b/test/Identity.FunctionalTests/UserStories.cs
@@ -30,6 +30,34 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
return await login.LoginValidUserAsync(userName, password);
}
+ internal static async Task RegisterNewUserWithSocialLoginAsync(HttpClient client, string userName, string email)
+ {
+ var index = await Index.CreateAsync(client,new DefaultUIContext().WithSocialLoginEnabled());
+
+ var login = await index.ClickLoginLinkAsync();
+
+ var contosoLogin = await login.ClickLoginWithContosoLinkAsync();
+
+ var externalLogin = await contosoLogin.SendNewUserNameAsync(userName);
+
+ return await externalLogin.SendEmailAsync(email);
+ }
+
+ internal static async Task LoginWithSocialLoginAsync(HttpClient client, string userName)
+ {
+ var index = await Index.CreateAsync(
+ client,
+ new DefaultUIContext()
+ .WithSocialLoginEnabled()
+ .WithExistingUser());
+
+ var login = await index.ClickLoginLinkAsync();
+
+ var contosoLogin = await login.ClickLoginWithContosoLinkAsync();
+
+ return await contosoLogin.SendExistingUserNameAsync(userName);
+ }
+
internal static async Task LoginExistingUser2FaAsync(HttpClient client, string userName, string password, string twoFactorKey)
{
var index = await Index.CreateAsync(client);
@@ -41,12 +69,10 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
return await login2Fa.Send2FACodeAsync(twoFactorKey);
}
- internal static async Task EnableTwoFactorAuthentication(
- Index index,
- bool twoFactorEnabled)
+ internal static async Task EnableTwoFactorAuthentication(Index index)
{
var manage = await index.ClickManageLinkAsync();
- var twoFactor = await manage.ClickTwoFactorLinkAsync(twoFactorEnabled);
+ var twoFactor = await manage.ClickTwoFactorLinkAsync();
var enableAuthenticator = await twoFactor.ClickEnableAuthenticatorLinkAsync();
return await enableAuthenticator.SendValidCodeAsync();
}
@@ -63,7 +89,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
var login2Fa = await loginWithPassword.PasswordLoginValidUserWith2FaAsync(userName, password);
- var loginRecoveryCode = await login2Fa.ClickRecoveryCodeLinkAsync();
+ var loginRecoveryCode = await login2Fa.ClickRecoveryCodeLinkAsync();
return await loginRecoveryCode.SendRecoveryCodeAsync(recoveryCode);
}
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.Designer.cs b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.Designer.cs
new file mode 100644
index 0000000000..3a5dc113b2
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.Designer.cs
@@ -0,0 +1,231 @@
+//
+using System;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+
+namespace Identity.DefaultUI.WebSite.Data.Migrations
+{
+ [DbContext(typeof(IdentityDbContext))]
+ [Migration("20180217170630_UpdateIdentitySchema")]
+ partial class UpdateIdentitySchema
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.1.0-preview2-30103")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Name")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("RoleId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AccessFailedCount");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Email")
+ .HasMaxLength(256);
+
+ b.Property("EmailConfirmed");
+
+ b.Property("LockoutEnabled");
+
+ b.Property("LockoutEnd");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256);
+
+ b.Property("PasswordHash");
+
+ b.Property("PhoneNumber");
+
+ b.Property("PhoneNumberConfirmed");
+
+ b.Property("SecurityStamp");
+
+ b.Property("TwoFactorEnabled");
+
+ b.Property("UserName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ClaimType");
+
+ b.Property("ClaimValue");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider");
+
+ b.Property("ProviderKey");
+
+ b.Property("ProviderDisplayName");
+
+ b.Property("UserId")
+ .IsRequired();
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("RoleId");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId");
+
+ b.Property("LoginProvider");
+
+ b.Property("Name");
+
+ b.Property("Value");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.cs b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.cs
new file mode 100644
index 0000000000..e99ded8a85
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/20180217170630_UpdateIdentitySchema.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Identity.DefaultUI.WebSite.Data.Migrations
+{
+ public partial class UpdateIdentitySchema : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropIndex(
+ name: "IX_AspNetUserRoles_UserId",
+ table: "AspNetUserRoles");
+
+ migrationBuilder.DropIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true,
+ filter: "[NormalizedUserName] IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true,
+ filter: "[NormalizedName] IS NOT NULL");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ table: "AspNetUserTokens",
+ column: "UserId",
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ table: "AspNetUserTokens");
+
+ migrationBuilder.DropIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_UserId",
+ table: "AspNetUserRoles",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName");
+ }
+ }
+}
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/IdentityDbContextModelSnapshot.cs b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/IdentityDbContextModelSnapshot.cs
index feafe9a295..78593d82d0 100644
--- a/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/IdentityDbContextModelSnapshot.cs
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Data/Migrations/IdentityDbContextModelSnapshot.cs
@@ -1,11 +1,13 @@
-// 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 Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
namespace Identity.DefaultUI.WebSite.Data.Migrations
{
@@ -14,32 +16,36 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
+#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "1.0.0-rc3")
+ .HasAnnotation("ProductVersion", "2.1.0-preview2-30103")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
- b.Property("Id");
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
b.Property("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property("Name")
- .HasAnnotation("MaxLength", 256);
+ .HasMaxLength(256);
b.Property("NormalizedName")
- .HasAnnotation("MaxLength", 256);
+ .HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
- .HasName("RoleNameIndex");
+ .IsUnique()
+ .HasName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd();
@@ -58,7 +64,58 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
b.ToTable("AspNetRoleClaims");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AccessFailedCount");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken();
+
+ b.Property("Email")
+ .HasMaxLength(256);
+
+ b.Property("EmailConfirmed");
+
+ b.Property("LockoutEnabled");
+
+ b.Property("LockoutEnd");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256);
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256);
+
+ b.Property("PasswordHash");
+
+ b.Property("PhoneNumber");
+
+ b.Property("PhoneNumberConfirmed");
+
+ b.Property("SecurityStamp");
+
+ b.Property("TwoFactorEnabled");
+
+ b.Property("UserName")
+ .HasMaxLength(256);
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd();
@@ -77,7 +134,7 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
b.ToTable("AspNetUserClaims");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
{
b.Property("LoginProvider");
@@ -95,7 +152,7 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
b.ToTable("AspNetUserLogins");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
{
b.Property("UserId");
@@ -105,12 +162,10 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
b.HasIndex("RoleId");
- b.HasIndex("UserId");
-
b.ToTable("AspNetUserRoles");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
{
b.Property("UserId");
@@ -125,91 +180,51 @@ namespace Identity.DefaultUI.WebSite.Data.Migrations
b.ToTable("AspNetUserTokens");
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
{
- b.Property("Id");
-
- b.Property("AccessFailedCount");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken();
-
- b.Property("Email")
- .HasAnnotation("MaxLength", 256);
-
- b.Property("EmailConfirmed");
-
- b.Property("LockoutEnabled");
-
- b.Property("LockoutEnd");
-
- b.Property("NormalizedEmail")
- .HasAnnotation("MaxLength", 256);
-
- b.Property("NormalizedUserName")
- .HasAnnotation("MaxLength", 256);
-
- b.Property("PasswordHash");
-
- b.Property("PhoneNumber");
-
- b.Property("PhoneNumberConfirmed");
-
- b.Property("SecurityStamp");
-
- b.Property("TwoFactorEnabled");
-
- b.Property("UserName")
- .HasAnnotation("MaxLength", 256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedEmail")
- .HasName("EmailIndex");
-
- b.HasIndex("NormalizedUserName")
- .IsUnique()
- .HasName("UserNameIndex");
-
- b.ToTable("AspNetUsers");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole")
- .WithMany("Claims")
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
- .WithMany("Claims")
+ .WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
- .WithMany("Logins")
+ .WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b =>
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
{
- b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole")
- .WithMany("Users")
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
+ .WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
- .WithMany("Roles")
+ .WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
}
}
}
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Identity.DefaultUI.WebSite.csproj b/test/WebSites/Identity.DefaultUI.WebSite/Identity.DefaultUI.WebSite.csproj
index db6eb71f42..9748c31546 100644
--- a/test/WebSites/Identity.DefaultUI.WebSite/Identity.DefaultUI.WebSite.csproj
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Identity.DefaultUI.WebSite.csproj
@@ -39,4 +39,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml b/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml
new file mode 100644
index 0000000000..cb6572a8c6
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml
@@ -0,0 +1,37 @@
+@page
+@model LoginModel
+@{
+ Layout = "_Layout";
+ ViewData["Title"] = "Contoso log-in";
+}
+
+@ViewData["Title"]
+
+
+@section Scripts {
+
+}
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml.cs b/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml.cs
new file mode 100644
index 0000000000..a99f1f7ff4
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml.cs
@@ -0,0 +1,72 @@
+// 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 Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Identity.DefaultUI.WebSite.Pages
+{
+ public class LoginModel : PageModel
+ {
+ public LoginModel(IOptionsMonitor options)
+ {
+ Options = options.CurrentValue;
+ }
+
+ public class InputModel
+ {
+ [Required]
+ public string Login { get; set; }
+ public bool RememberMe { get; set; }
+ }
+
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ [BindProperty(SupportsGet = true)]
+ public string ReturnUrl { get; set; }
+
+ [BindProperty]
+ public string State { get; set; }
+
+ public ContosoAuthenticationOptions Options { get; }
+
+ public IActionResult OnGet()
+ {
+ return Page();
+ }
+
+ public async Task OnPostAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+ else
+ {
+ var state = JsonConvert.DeserializeObject>(State);
+ var identity = new ClaimsIdentity(new Claim[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, Input.Login)
+ },
+ state["LoginProvider"],
+ ClaimTypes.NameIdentifier,
+ ClaimTypes.Role);
+ var principal = new ClaimsPrincipal(identity);
+ var properties = new AuthenticationProperties(state)
+ {
+ IsPersistent = Input.RememberMe
+ };
+ await HttpContext.SignInAsync(Options.SignInScheme, principal, properties);
+ return Redirect(ReturnUrl ?? "~/");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationBuilderExtensions.cs b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationBuilderExtensions.cs
new file mode 100644
index 0000000000..0e64aaf5a8
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationBuilderExtensions.cs
@@ -0,0 +1,19 @@
+// 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 Microsoft.AspNetCore.Authentication;
+
+namespace Identity.DefaultUI.WebSite
+{
+ public static class ContosoAuthenticationBuilderExtensions
+ {
+ public static AuthenticationBuilder AddContosoAuthentication(
+ this AuthenticationBuilder builder,
+ Action configure) =>
+ builder.AddScheme(
+ ContosoAuthenticationConstants.Scheme,
+ ContosoAuthenticationConstants.DisplayName,
+ configure);
+ }
+}
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationConstants.cs b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationConstants.cs
new file mode 100644
index 0000000000..229e80c592
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationConstants.cs
@@ -0,0 +1,11 @@
+// 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.
+
+namespace Identity.DefaultUI.WebSite
+{
+ public static class ContosoAuthenticationConstants
+ {
+ public const string Scheme = "Contoso";
+ public const string DisplayName = "Contoso";
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationHandler.cs b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationHandler.cs
new file mode 100644
index 0000000000..3ac1667244
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationHandler.cs
@@ -0,0 +1,42 @@
+// 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.Collections.Generic;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+
+namespace Identity.DefaultUI.WebSite
+{
+ public class ContosoAuthenticationHandler : AuthenticationHandler
+ {
+ public ContosoAuthenticationHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ ISystemClock clock)
+ : base(options, logger, encoder, clock)
+ {
+ }
+
+ protected override Task HandleAuthenticateAsync() =>
+ Task.FromResult(AuthenticateResult.NoResult());
+
+ protected override Task HandleChallengeAsync(AuthenticationProperties properties)
+ {
+ var uri = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Options.RemoteLoginPath}";
+ uri = QueryHelpers.AddQueryString(uri, new Dictionary()
+ {
+ ["State"] = JsonConvert.SerializeObject(properties.Items),
+ [Options.ReturnUrlQueryParameter] = properties.RedirectUri
+ });
+ Response.Redirect(uri);
+
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationOptions.cs b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationOptions.cs
new file mode 100644
index 0000000000..5c0de39d16
--- /dev/null
+++ b/test/WebSites/Identity.DefaultUI.WebSite/Services/ContosoAuthenticationOptions.cs
@@ -0,0 +1,19 @@
+// 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 Microsoft.AspNetCore.Authentication;
+
+namespace Identity.DefaultUI.WebSite
+{
+ public class ContosoAuthenticationOptions : AuthenticationSchemeOptions
+ {
+ public ContosoAuthenticationOptions()
+ {
+ Events = new object();
+ }
+
+ public string SignInScheme { get; set; }
+ public string ReturnUrlQueryParameter { get; set; } = "returnUrl";
+ public string RemoteLoginPath { get; set; } = "/Contoso/Login";
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/Identity.DefaultUI.WebSite/appsettings.json b/test/WebSites/Identity.DefaultUI.WebSite/appsettings.json
index 00e6b3cfce..acbebb34a4 100644
--- a/test/WebSites/Identity.DefaultUI.WebSite/appsettings.json
+++ b/test/WebSites/Identity.DefaultUI.WebSite/appsettings.json
@@ -1,6 +1,7 @@
{
"ConnectionStrings": {
- "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Identity.DefaultUI.WebSite-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
+ "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Identity.DefaultUI.WebSite-90455f3b-6c48-4aa0-a8d6-294d8e0b3d4d;Trusted_Connection=True;MultipleActiveResultSets=true"
+ //"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Identity.DefaultUI.WebSite-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {