Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csdevkit",
"EditorConfig.EditorConfig",
"k--kato.docomment",
"dbaeumer.vscode-eslint"
],
"settings": {
"dotnet.defaultSolution": "AspNetCore.slnx",
// Loading projects on demand is better for larger codebases
"omnisharp.enableMsBuildLoadProjectsOnDemand": true,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.enableEditorConfigSupport": true,
"omnisharp.enableImportCompletion": true,
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"EditorConfig.EditorConfig",
"k--kato.docomment",
"dbaeumer.vscode-eslint"
],
"settings": {
"dotnet.defaultSolution": "AspNetCore.slnx",
// Loading projects on demand is better for larger codebases
"omnisharp.enableMsBuildLoadProjectsOnDemand": true,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.enableEditorConfigSupport": true,
"omnisharp.enableImportCompletion": true
}
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"onCreateCommand": "bash -i ${containerWorkspaceFolder}/.devcontainer/scripts/container-creation.sh",
Expand Down
19 changes: 17 additions & 2 deletions src/Identity/EntityFrameworkCore/src/IdentityUserContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ internal virtual void OnModelCreatingVersion3(ModelBuilder builder)
{
// Differences from Version 2:
// - Add a passkey entity
// - Changes added to IdentityUserToken and IdentityUserLogin to add a unique id field.

var storeOptions = GetStoreOptions();
var maxKeyLength = storeOptions?.MaxLengthForKeys ?? 0;
Expand Down Expand Up @@ -244,7 +245,8 @@ internal virtual void OnModelCreatingVersion3(ModelBuilder builder)

builder.Entity<TUserLogin>(b =>
{
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
b.HasKey(l => l.Id);
b.HasIndex(l => new { l.LoginProvider, l.ProviderKey }).IsUnique();

if (maxKeyLength > 0)
{
Expand All @@ -257,7 +259,8 @@ internal virtual void OnModelCreatingVersion3(ModelBuilder builder)

builder.Entity<TUserToken>(b =>
{
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
b.HasKey(t => t.Id);
b.HasIndex(t => new { t.UserId, t.LoginProvider, t.Name }).IsUnique();

if (maxKeyLength > 0)
{
Expand Down Expand Up @@ -356,6 +359,9 @@ internal virtual void OnModelCreatingVersion2(ModelBuilder builder)
{
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });

// V2 schema uses composite key, ignore the Id property added for V3
b.Ignore(l => l.Id);

if (maxKeyLength > 0)
{
b.Property(l => l.LoginProvider).HasMaxLength(maxKeyLength);
Expand All @@ -369,6 +375,9 @@ internal virtual void OnModelCreatingVersion2(ModelBuilder builder)
{
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });

// V2 schema uses composite key, ignore the Id property added for V3
b.Ignore(t => t.Id);

if (maxKeyLength > 0)
{
b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
Expand Down Expand Up @@ -451,6 +460,9 @@ internal virtual void OnModelCreatingVersion1(ModelBuilder builder)
{
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });

// V1 schema uses composite key, ignore the Id property added for V3
b.Ignore(l => l.Id);

if (maxKeyLength > 0)
{
b.Property(l => l.LoginProvider).HasMaxLength(maxKeyLength);
Expand All @@ -464,6 +476,9 @@ internal virtual void OnModelCreatingVersion1(ModelBuilder builder)
{
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });

// V1 schema uses composite key, ignore the Id property added for V3
b.Ignore(t => t.Id);

if (maxKeyLength > 0)
{
b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
Expand Down
2 changes: 2 additions & 0 deletions src/Identity/EntityFrameworkCore/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser,
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindByLoginAsync(string! loginProvider, string! providerKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<TUser?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindByNameAsync(string! normalizedUserName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<TUser?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindTokenAsync(TUser! user, string! loginProvider, string! name, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserToken?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindTokenByUniqueIndexAsync(TUser! user, string! loginProvider, string! name, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserToken?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindUserAsync(TKey userId, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUser?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindUserLoginAsync(string! loginProvider, string! providerKey, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserLogin?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken, TUserPasskey>.FindUserLoginAsync(TKey userId, string! loginProvider, string! providerKey, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserLogin?>!
Expand All @@ -56,6 +57,7 @@ override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRol
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindByNameAsync(string! normalizedUserName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<TUser?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindRoleAsync(string! normalizedRoleName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TRole?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindTokenAsync(TUser! user, string! loginProvider, string! name, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserToken?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindTokenByUniqueIndexAsync(TUser! user, string! loginProvider, string! name, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserToken?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindUserAsync(TKey userId, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUser?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindUserLoginAsync(string! loginProvider, string! providerKey, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserLogin?>!
override Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TUserPasskey>.FindUserLoginAsync(TKey userId, string! loginProvider, string! providerKey, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TUserLogin?>!
Expand Down
8 changes: 7 additions & 1 deletion src/Identity/EntityFrameworkCore/src/RoleStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,13 @@ protected void ThrowIfDisposed()
ArgumentNullException.ThrowIfNull(role);
ArgumentNullException.ThrowIfNull(claim);

RoleClaims.Add(CreateRoleClaim(role, claim));
var roleClaim = CreateRoleClaim(role, claim);
// For string keys, ensure Id is set before adding to EF
if (typeof(TKey) == typeof(string) && EqualityComparer<TKey>.Default.Equals(roleClaim.Id, default!))
{
roleClaim.Id = (TKey)(object)Guid.NewGuid().ToString();
}
RoleClaims.Add(roleClaim);
return Task.FromResult(false);
}

Expand Down
19 changes: 18 additions & 1 deletion src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ protected Task SaveChanges(CancellationToken cancellationToken)
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var id = ConvertIdFromString(userId);
return UsersSet.FindAsync(new object?[] { id }, cancellationToken).AsTask();
return UsersSet.FindAsync([id], cancellationToken).AsTask();
}

/// <summary>
Expand Down Expand Up @@ -529,16 +529,33 @@ join user in Users on userclaims.UserId equals user.Id
/// <param name="name">The name of the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The user token if it exists.</returns>
[Obsolete("This method uses composite primary keys from V1/V2 schema. Consider using FindTokenByUniqueIndexAsync for V3 schema with Id primary key and unique indexes.")]
protected override Task<TUserToken?> FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
=> UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken).AsTask();

/// <summary>
/// Find a user token if it exists using the unique index (for V3 schema with Id primary key).
/// </summary>
/// <param name="user">The token owner.</param>
/// <param name="loginProvider">The login provider for the token.</param>
/// <param name="name">The name of the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The user token if it exists.</returns>
protected override Task<TUserToken?> FindTokenByUniqueIndexAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
=> UserTokens.SingleOrDefaultAsync(ut => ut.UserId.Equals(user.Id) && ut.LoginProvider == loginProvider && ut.Name == name, cancellationToken);

/// <summary>
/// Add a new user token.
/// </summary>
/// <param name="token">The token to be added.</param>
/// <returns></returns>
protected override Task AddUserTokenAsync(TUserToken token)
{
// For string keys in V3 schema, ensure Id is set before adding to EF
if (typeof(TKey) == typeof(string) && EqualityComparer<TKey>.Default.Equals(token.Id, default!))
{
token.Id = (TKey)(object)Guid.NewGuid().ToString();
}
UserTokens.Add(token);
return Task.CompletedTask;
}
Expand Down
33 changes: 31 additions & 2 deletions src/Identity/EntityFrameworkCore/src/UserStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,13 @@ where userRole.UserId.Equals(userId)
ArgumentNullException.ThrowIfNull(claims);
foreach (var claim in claims)
{
UserClaims.Add(CreateUserClaim(user, claim));
var userClaim = CreateUserClaim(user, claim);
// For string keys, ensure Id is set before adding to EF
if (typeof(TKey) == typeof(string) && EqualityComparer<TKey>.Default.Equals(userClaim.Id, default!))
{
userClaim.Id = (TKey)(object)Guid.NewGuid().ToString();
}
UserClaims.Add(userClaim);
}
return Task.FromResult(false);
}
Expand Down Expand Up @@ -534,7 +540,13 @@ public override Task AddLoginAsync(TUser user, UserLoginInfo login,
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(login);
UserLogins.Add(CreateUserLogin(user, login));
var userLogin = CreateUserLogin(user, login);
// For string keys in V3 schema, ensure Id is set before adding to EF
if (typeof(TKey) == typeof(string) && EqualityComparer<TKey>.Default.Equals(userLogin.Id, default!))
{
userLogin.Id = (TKey)(object)Guid.NewGuid().ToString();
}
UserLogins.Add(userLogin);
return Task.FromResult(false);
}

Expand Down Expand Up @@ -674,16 +686,33 @@ where userrole.RoleId.Equals(role.Id)
/// <param name="name">The name of the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The user token if it exists.</returns>
[Obsolete("This method uses composite primary keys from V1/V2 schema. Consider using FindTokenByUniqueIndexAsync for V3 schema with Id primary key and unique indexes.")]
protected override Task<TUserToken?> FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
=> UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken).AsTask();

/// <summary>
/// Find a user token if it exists using the unique index (for V3 schema with Id primary key).
/// </summary>
/// <param name="user">The token owner.</param>
/// <param name="loginProvider">The login provider for the token.</param>
/// <param name="name">The name of the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The user token if it exists.</returns>
protected override Task<TUserToken?> FindTokenByUniqueIndexAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
=> UserTokens.SingleOrDefaultAsync(ut => ut.UserId.Equals(user.Id) && ut.LoginProvider == loginProvider && ut.Name == name, cancellationToken);

/// <summary>
/// Add a new user token.
/// </summary>
/// <param name="token">The token to be added.</param>
/// <returns></returns>
protected override Task AddUserTokenAsync(TUserToken token)
{
// For string keys in V3 schema, ensure Id is set before adding to EF
if (typeof(TKey) == typeof(string) && EqualityComparer<TKey>.Default.Equals(token.Id, default!))
{
token.Id = (TKey)(object)Guid.NewGuid().ToString();
}
UserTokens.Add(token);
return Task.CompletedTask;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void CanAddCustomColumn()
{
using var scope = _builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
var db = scope.ServiceProvider.GetRequiredService<CustomVersionDbContext>();
VersionTwoSchemaTest.VerifyVersion2Schema(db);
VersionThreeSchemaTest.VerifyVersion3Schema(db);
using var sqlConn = (SqliteConnection)db.Database.GetDbConnection();
sqlConn.Open();
Assert.True(DbUtil.VerifyColumns(sqlConn, "CustomColumns", "Id"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ internal static void VerifyDefaultSchema(TestDbContext dbContext)
Assert.True(DbUtil.VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"));
// V3 schema includes Id column for UserLogins and UserTokens
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserLogins", "Id", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserTokens", "Id", "UserId", "LoginProvider", "Name", "Value"));

Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUsers", 256, "UserName", "Email", "NormalizedUserName", "NormalizedEmail"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetRoles", 256, "Name", "NormalizedName"));
Expand Down
Loading
Loading