From 0e1819bdd8a33be50ed715fb9ae05105b207ee81 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Jun 2026 10:53:20 +0800 Subject: [PATCH 1/2] Fix flaky username generation in GetUserNameFromEmailAsync --- .../Volo/Abp/Identity/IdentityUserManager.cs | 22 ++++----- .../Abp/Identity/IdentityUserManager_Tests.cs | 47 ++++++++++++++++++- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs index 46f7a635f06..73f997d9820 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -555,22 +555,18 @@ public virtual async Task GetUserNameFromEmailAsync(string email) } else if (Options.User.AllowedUserNameCharacters.Where(char.IsDigit).Distinct().Count() >= 4) { - // The AllowedUserNameCharacters includes 4 numbers. So, we are generating 4 random numbers and appending to the username. - var numbers = Options.User.AllowedUserNameCharacters.Where(char.IsDigit).OrderBy(x => Guid.NewGuid()).Take(4).ToArray(); - var minArray = numbers.OrderBy(x => x).ToArray(); - if (minArray[0] == '0') - { - var secondItem = minArray[1]; - minArray[0] = secondItem; - minArray[1] = '0'; - } - var min = int.Parse(new string(minArray)); - var max = int.Parse(new string(numbers.OrderByDescending(x => x).ToArray())); + // The AllowedUserNameCharacters includes at least 4 distinct digits. So, we are picking 4 random digits from them and appending to the username. + var allowedDigits = Options.User.AllowedUserNameCharacters.Where(char.IsDigit).Distinct().ToArray(); tryCount = 0; do { - var randomUserName = userName + RandomHelper.GetRandom(min, max); - if ( await ValidateUserNameAsync(randomUserName)) + var randomDigits = new char[4]; + for (var i = 0; i < randomDigits.Length; i++) + { + randomDigits[i] = allowedDigits[RandomHelper.GetRandom(0, allowedDigits.Length)]; + } + var randomUserName = userName + new string(randomDigits); + if (await ValidateUserNameAsync(randomUserName)) { return randomUserName; } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs index 82c133e6986..c65215cd625 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs @@ -441,7 +441,7 @@ public async Task GetUserNameFromEmailAsync() username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); username.Length.ShouldBe(9); //admin and random 4 numbers username.ShouldContain("admin"); - Regex.IsMatch(username, @"[0-4]{3}$").ShouldBeTrue(); + Regex.IsMatch(username, @"[0-4]{4}$").ShouldBeTrue(); _identityUserManager.Options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz"; username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); @@ -462,6 +462,51 @@ public async Task GetUserNameFromEmailAsync() Regex.IsMatch(username, @"[0-9]{4}$").ShouldBeTrue(); } + [Fact] + public async Task GetUserNameFromEmailAsync_Should_Only_Use_Allowed_Digits_When_AllowedUserNameCharacters_Has_Partial_Digits() + { + _identityUserManager.Options.User.AllowedUserNameCharacters = "admin01234"; + for (var i = 0; i < 200; i++) + { + var username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); + username.Length.ShouldBe(9); + username.ShouldStartWith("admin"); + username.All(c => "admin01234".Contains(c)).ShouldBeTrue($"username '{username}' contains chars outside AllowedUserNameCharacters"); + } + } + + [Fact] + public async Task GetUserNameFromEmailAsync_Should_Only_Use_Allowed_Digits_With_Exact_Four_Digits() + { + _identityUserManager.Options.User.AllowedUserNameCharacters = "admin1234"; + for (var i = 0; i < 200; i++) + { + var username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); + username.Length.ShouldBe(9); + username.ShouldStartWith("admin"); + username.Substring(5).All(c => "1234".Contains(c)).ShouldBeTrue($"suffix of '{username}' contains chars outside the allowed digits"); + } + } + + [Fact] + public async Task GetUserNameFromEmailAsync_Should_Allow_Leading_Zero_In_Random_Digits() + { + _identityUserManager.Options.User.AllowedUserNameCharacters = "admin0123"; + var sawLeadingZero = false; + for (var i = 0; i < 500; i++) + { + var username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); + username.Length.ShouldBe(9); + username.ShouldStartWith("admin"); + username.Substring(5).All(c => "0123".Contains(c)).ShouldBeTrue($"suffix of '{username}' contains chars outside the allowed digits"); + if (username[5] == '0') + { + sawLeadingZero = true; + } + } + sawLeadingZero.ShouldBeTrue("expected at least one username with leading-zero random suffix across 500 runs"); + } + private async Task CreateRandomDefaultRoleAsync() { await _identityRoleRepository.InsertAsync( From a4b170eaeddb0fed2f5c3fec5017a8e58cba9677 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Jun 2026 11:02:31 +0800 Subject: [PATCH 2/2] Reduce iteration count in leading-zero stress test --- .../Volo/Abp/Identity/IdentityUserManager_Tests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs index c65215cd625..f027444e55f 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs @@ -493,7 +493,7 @@ public async Task GetUserNameFromEmailAsync_Should_Allow_Leading_Zero_In_Random_ { _identityUserManager.Options.User.AllowedUserNameCharacters = "admin0123"; var sawLeadingZero = false; - for (var i = 0; i < 500; i++) + for (var i = 0; i < 100; i++) { var username = await _identityUserManager.GetUserNameFromEmailAsync("admin@abp.io"); username.Length.ShouldBe(9); @@ -504,7 +504,7 @@ public async Task GetUserNameFromEmailAsync_Should_Allow_Leading_Zero_In_Random_ sawLeadingZero = true; } } - sawLeadingZero.ShouldBeTrue("expected at least one username with leading-zero random suffix across 500 runs"); + sawLeadingZero.ShouldBeTrue("expected at least one username with leading-zero random suffix across 100 runs"); } private async Task CreateRandomDefaultRoleAsync()