Include social logins and authenticator key as part of personal download (#1745)
This commit is contained in:
parent
956e76f6cf
commit
5a2eb3becd
|
|
@ -119,6 +119,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
var storeOptions = GetStoreOptions();
|
||||
var maxKeyLength = storeOptions?.MaxLengthForKeys ?? 0;
|
||||
var encryptPersonalData = storeOptions?.ProtectPersonalData ?? false;
|
||||
PersonalDataConverter converter = null;
|
||||
|
||||
builder.Entity<TUser>(b =>
|
||||
{
|
||||
|
|
@ -135,7 +136,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
|
||||
if (encryptPersonalData)
|
||||
{
|
||||
var converter = new PersonalDataConverter(this.GetService<IPersonalDataProtector>());
|
||||
converter = new PersonalDataConverter(this.GetService<IPersonalDataProtector>());
|
||||
var personalDataProps = typeof(TUser).GetProperties().Where(
|
||||
prop => Attribute.IsDefined(prop, typeof(ProtectedPersonalDataAttribute)));
|
||||
foreach (var p in personalDataProps)
|
||||
|
|
@ -182,6 +183,20 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
b.Property(t => t.Name).HasMaxLength(maxKeyLength);
|
||||
}
|
||||
|
||||
if (encryptPersonalData)
|
||||
{
|
||||
var tokenProps = typeof(TUserToken).GetProperties().Where(
|
||||
prop => Attribute.IsDefined(prop, typeof(ProtectedPersonalDataAttribute)));
|
||||
foreach (var p in tokenProps)
|
||||
{
|
||||
if (p.PropertyType != typeof(string))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.CanOnlyProtectStrings);
|
||||
}
|
||||
b.Property(typeof(string), p.Name).HasConversion(converter);
|
||||
}
|
||||
}
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// <summary>
|
||||
/// Gets or sets the token value.
|
||||
/// </summary>
|
||||
[ProtectedPersonalData]
|
||||
public virtual string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,6 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
|
|||
|
||||
// Only include personal data for download
|
||||
var personalData = new Dictionary<string, string>();
|
||||
|
||||
var personalDataProps = typeof(TUser).GetProperties().Where(
|
||||
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
|
||||
foreach (var p in personalDataProps)
|
||||
|
|
@ -59,6 +58,14 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
|
|||
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
|
||||
}
|
||||
|
||||
var logins = await _userManager.GetLoginsAsync(user);
|
||||
foreach (var l in logins)
|
||||
{
|
||||
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
|
||||
}
|
||||
|
||||
personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user));
|
||||
|
||||
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
|
||||
return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,8 +87,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
var newName = Guid.NewGuid().ToString();
|
||||
Assert.Null(await manager.FindByNameAsync(newName));
|
||||
IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "123-456-7890"));
|
||||
var login = new UserLoginInfo("loginProvider", "<key>", "display");
|
||||
IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login));
|
||||
|
||||
Assert.Equal(user, await manager.FindByEmailAsync("hao@hao.com"));
|
||||
Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey));
|
||||
|
||||
IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newName));
|
||||
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user));
|
||||
|
|
@ -97,6 +100,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
DefaultKeyRing.Current = "NewPad";
|
||||
Assert.NotNull(await manager.FindByNameAsync(newName));
|
||||
Assert.Equal(user, await manager.FindByEmailAsync("hao@hao.com"));
|
||||
Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey));
|
||||
Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user));
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +144,26 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool FindAuthenticatorKeyInk(DbConnection conn, string id)
|
||||
=> FindTokenInk(conn, id, "[AspNetUserStore]", "AuthenticatorKey");
|
||||
|
||||
private bool FindTokenInk(DbConnection conn, string id, string loginProvider, string tokenName)
|
||||
{
|
||||
using (var command = conn.CreateCommand())
|
||||
{
|
||||
command.CommandText = $"SELECT u.Value FROM AspNetUserTokens u WHERE u.LoginProvider = '{loginProvider}' AND u.Name = '{tokenName}' AND u.UserId = '{id}'";
|
||||
command.CommandType = System.Data.CommandType.Text;
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
return reader.GetString(0) == "Default:ink";
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test.
|
||||
/// </summary>
|
||||
|
|
@ -188,6 +212,9 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
user.PhoneNumber = "12345678";
|
||||
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user));
|
||||
|
||||
IdentityResultAssert.IsSuccess(await manager.ResetAuthenticatorKeyAsync(user));
|
||||
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "loginProvider", "token", "value"));
|
||||
|
||||
var conn = dbContext.Database.GetDbConnection();
|
||||
conn.Open();
|
||||
if (protect)
|
||||
|
|
@ -197,6 +224,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
Assert.True(FindInk(conn, "UserName", guid));
|
||||
Assert.True(FindInk(conn, "PersonalData1", guid));
|
||||
Assert.True(FindInk(conn, "PersonalData2", guid));
|
||||
Assert.True(FindAuthenticatorKeyInk(conn, guid));
|
||||
Assert.True(FindTokenInk(conn, guid, "loginProvider", "token"));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -205,7 +234,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
Assert.False(FindInk(conn, "UserName", guid));
|
||||
Assert.False(FindInk(conn, "PersonalData1", guid));
|
||||
Assert.False(FindInk(conn, "PersonalData2", guid));
|
||||
|
||||
Assert.False(FindAuthenticatorKeyInk(conn, guid));
|
||||
Assert.False(FindTokenInk(conn, guid, "loginProvider", "token"));
|
||||
}
|
||||
Assert.False(FindInk(conn, "NonPersonalData1", guid));
|
||||
Assert.False(FindInk(conn, "NonPersonalData2", guid));
|
||||
|
|
|
|||
|
|
@ -196,18 +196,33 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanDownloadPersonalData()
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public async Task CanDownloadPersonalData(bool twoFactor, bool social)
|
||||
{
|
||||
using (StartLog(out var loggerFactory))
|
||||
{
|
||||
// Arrange
|
||||
var client = ServerFactory.CreateDefaultClient(loggerFactory);
|
||||
var server = ServerFactory.CreateServer(loggerFactory, builder =>
|
||||
builder.ConfigureTestServices(s => s.SetupTestThirdPartyLogin()));
|
||||
|
||||
var client = ServerFactory.CreateDefaultClient(server);
|
||||
|
||||
var userName = $"{Guid.NewGuid()}@example.com";
|
||||
var password = $"!Test.Password1$";
|
||||
var guid = Guid.NewGuid();
|
||||
var email = userName;
|
||||
|
||||
var index = await UserStories.RegisterNewUserAsync(client, userName, password);
|
||||
var index = social
|
||||
? await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email)
|
||||
: await UserStories.RegisterNewUserAsync(client, email, "!TestPassword1");
|
||||
|
||||
if (twoFactor)
|
||||
{
|
||||
await UserStories.EnableTwoFactorAuthentication(index);
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
var jsonData = await UserStories.DownloadPersonalData(index, userName);
|
||||
|
|
@ -217,7 +232,9 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
|
|||
Assert.Contains($"\"EmailConfirmed\":\"False\"", jsonData);
|
||||
Assert.Contains($"\"PhoneNumber\":\"null\"", jsonData);
|
||||
Assert.Contains($"\"PhoneNumberConfirmed\":\"False\"", jsonData);
|
||||
Assert.Contains($"\"TwoFactorEnabled\":\"False\"", jsonData);
|
||||
Assert.Contains($"\"TwoFactorEnabled\":\"{twoFactor}\"", jsonData);
|
||||
Assert.Equal(twoFactor, jsonData.Contains($"\"Authenticator Key\":\""));
|
||||
Assert.Equal(social, jsonData.Contains($"\"Contoso external login provider key\":\"{userName}\""));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue