Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrennan <brecon@microsoft.com>2022-10-24 21:03:37 +0300
committerGitHub <noreply@github.com>2022-10-24 21:03:37 +0300
commitc5ee07392f0b5bb2ccbfd2ee38d484724ad5bbf3 (patch)
tree6931f2c129a230e2ed917bf10432fdd28c2d62b3 /src
parent2f12b2a14ed5061a77daedc791ae921714759945 (diff)
Remove temporary allocations from Identity (#44557)
Diffstat (limited to 'src')
-rw-r--r--src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs4
-rw-r--r--src/Identity/Extensions.Core/src/Base32.cs38
-rw-r--r--src/Identity/Extensions.Core/src/IdentityResult.cs10
-rw-r--r--src/Identity/Extensions.Core/src/PasswordHasher.cs4
-rw-r--r--src/Identity/Extensions.Core/src/PasswordValidator.cs14
-rw-r--r--src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs53
-rw-r--r--src/Identity/Extensions.Core/src/RoleManager.cs12
-rw-r--r--src/Identity/Extensions.Core/src/RoleValidator.cs15
-rw-r--r--src/Identity/Extensions.Core/src/UserManager.cs37
-rw-r--r--src/Identity/Extensions.Core/src/UserValidator.cs25
-rw-r--r--src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs4
-rw-r--r--src/Identity/test/Identity.Test/Base32Test.cs7
12 files changed, 158 insertions, 65 deletions
diff --git a/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs b/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs
index 659fea8634..2d62fac472 100644
--- a/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs
+++ b/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs
@@ -68,9 +68,9 @@ public class AuthenticatorTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUs
for (int i = -2; i <= 2; i++)
{
#if NET6_0_OR_GREATER
- var expectedCode = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)(timestep + i), modifier: null);
+ var expectedCode = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)(timestep + i), modifierBytes: null);
#else
- var expectedCode = Rfc6238AuthenticationService.ComputeTotp(hash, (ulong)(timestep + i), modifier: null);
+ var expectedCode = Rfc6238AuthenticationService.ComputeTotp(hash, (ulong)(timestep + i), modifierBytes: null);
#endif
if (expectedCode == code)
{
diff --git a/src/Identity/Extensions.Core/src/Base32.cs b/src/Identity/Extensions.Core/src/Base32.cs
index 6a30a647fd..13effd61a0 100644
--- a/src/Identity/Extensions.Core/src/Base32.cs
+++ b/src/Identity/Extensions.Core/src/Base32.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Security.Cryptography;
using System.Text;
namespace Microsoft.AspNetCore.Identity;
@@ -11,6 +12,41 @@ internal static class Base32
{
private const string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+#if NET6_0_OR_GREATER
+ public static string GenerateBase32()
+ {
+ const int length = 20;
+ // base32 takes 5 bytes and converts them into 8 characters, which would be (byte length / 5) * 8
+ // except that it also pads ('=') for the last processed chunk if it's less than 5 bytes.
+ // So in order to handle the padding we add 1 less than the chunk size to our byte length
+ // which will either be removed due to integer division truncation if the length was already a multiple of 5
+ // or it will increase the divided length by 1 meaning that a 1-4 byte length chunk will be 1 instead of 0
+ // so the padding is now included in our string length calculation
+ return string.Create(((length + 4) / 5) * 8, 0, static (buffer, _) =>
+ {
+ Span<byte> bytes = stackalloc byte[length];
+ RandomNumberGenerator.Fill(bytes);
+
+ var index = 0;
+ for (int offset = 0; offset < bytes.Length;)
+ {
+ byte a, b, c, d, e, f, g, h;
+ int numCharsToOutput = GetNextGroup(bytes, ref offset, out a, out b, out c, out d, out e, out f, out g, out h);
+
+ buffer[index + 7] = ((numCharsToOutput >= 8) ? _base32Chars[h] : '=');
+ buffer[index + 6] = ((numCharsToOutput >= 7) ? _base32Chars[g] : '=');
+ buffer[index + 5] = ((numCharsToOutput >= 6) ? _base32Chars[f] : '=');
+ buffer[index + 4] = ((numCharsToOutput >= 5) ? _base32Chars[e] : '=');
+ buffer[index + 3] = ((numCharsToOutput >= 4) ? _base32Chars[d] : '=');
+ buffer[index + 2] = (numCharsToOutput >= 3) ? _base32Chars[c] : '=';
+ buffer[index + 1] = (numCharsToOutput >= 2) ? _base32Chars[b] : '=';
+ buffer[index] = (numCharsToOutput >= 1) ? _base32Chars[a] : '=';
+ index += 8;
+ }
+ });
+ }
+#endif
+
public static string ToBase32(byte[] input)
{
if (input == null)
@@ -84,7 +120,7 @@ internal static class Base32
}
// returns the number of bytes that were output
- private static int GetNextGroup(byte[] input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h)
+ private static int GetNextGroup(Span<byte> input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h)
{
uint b1, b2, b3, b4, b5;
diff --git a/src/Identity/Extensions.Core/src/IdentityResult.cs b/src/Identity/Extensions.Core/src/IdentityResult.cs
index 82010539d2..3ae3368372 100644
--- a/src/Identity/Extensions.Core/src/IdentityResult.cs
+++ b/src/Identity/Extensions.Core/src/IdentityResult.cs
@@ -49,6 +49,16 @@ public class IdentityResult
return result;
}
+ internal static IdentityResult Failed(List<IdentityError>? errors)
+ {
+ var result = new IdentityResult { Succeeded = false };
+ if (errors != null)
+ {
+ result._errors.AddRange(errors);
+ }
+ return result;
+ }
+
/// <summary>
/// Converts the value of the current <see cref="IdentityResult"/> object to its equivalent string representation.
/// </summary>
diff --git a/src/Identity/Extensions.Core/src/PasswordHasher.cs b/src/Identity/Extensions.Core/src/PasswordHasher.cs
index 4ac4eae30d..9164e1ef70 100644
--- a/src/Identity/Extensions.Core/src/PasswordHasher.cs
+++ b/src/Identity/Extensions.Core/src/PasswordHasher.cs
@@ -35,13 +35,15 @@ public class PasswordHasher<TUser> : IPasswordHasher<TUser> where TUser : class
private readonly int _iterCount;
private readonly RandomNumberGenerator _rng;
+ private static readonly PasswordHasherOptions DefaultOptions = new PasswordHasherOptions();
+
/// <summary>
/// Creates a new instance of <see cref="PasswordHasher{TUser}"/>.
/// </summary>
/// <param name="optionsAccessor">The options for this instance.</param>
public PasswordHasher(IOptions<PasswordHasherOptions>? optionsAccessor = null)
{
- var options = optionsAccessor?.Value ?? new PasswordHasherOptions();
+ var options = optionsAccessor?.Value ?? DefaultOptions;
_compatibilityMode = options.CompatibilityMode;
switch (_compatibilityMode)
diff --git a/src/Identity/Extensions.Core/src/PasswordValidator.cs b/src/Identity/Extensions.Core/src/PasswordValidator.cs
index 4180eda13d..3675bebb66 100644
--- a/src/Identity/Extensions.Core/src/PasswordValidator.cs
+++ b/src/Identity/Extensions.Core/src/PasswordValidator.cs
@@ -46,36 +46,42 @@ public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser :
{
throw new ArgumentNullException(nameof(manager));
}
- var errors = new List<IdentityError>();
+ List<IdentityError>? errors = null;
var options = manager.Options.Password;
if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength)
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordTooShort(options.RequiredLength));
}
if (options.RequireNonAlphanumeric && password.All(IsLetterOrDigit))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordRequiresNonAlphanumeric());
}
if (options.RequireDigit && !password.Any(IsDigit))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordRequiresDigit());
}
if (options.RequireLowercase && !password.Any(IsLower))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordRequiresLower());
}
if (options.RequireUppercase && !password.Any(IsUpper))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordRequiresUpper());
}
if (options.RequiredUniqueChars >= 1 && password.Distinct().Count() < options.RequiredUniqueChars)
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.PasswordRequiresUniqueChars(options.RequiredUniqueChars));
}
return
- Task.FromResult(errors.Count == 0
- ? IdentityResult.Success
- : IdentityResult.Failed(errors.ToArray()));
+ Task.FromResult(errors?.Count > 0
+ ? IdentityResult.Failed(errors)
+ : IdentityResult.Success);
}
/// <summary>
diff --git a/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs b/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
index 5f13a9be6d..aabd8b2d1e 100644
--- a/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
+++ b/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
@@ -17,21 +17,8 @@ internal static class Rfc6238AuthenticationService
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
#if NETSTANDARD2_0 || NETFRAMEWORK
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
- private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
#endif
- // Generates a new 80-bit security token
- public static byte[] GenerateRandomKey()
- {
- byte[] bytes = new byte[20];
-#if NETSTANDARD2_0 || NETFRAMEWORK
- _rng.GetBytes(bytes);
-#else
- RandomNumberGenerator.Fill(bytes);
-#endif
- return bytes;
- }
-
internal static int ComputeTotp(
#if NET6_0_OR_GREATER
byte[] key,
@@ -39,19 +26,33 @@ internal static class Rfc6238AuthenticationService
HashAlgorithm hashAlgorithm,
#endif
ulong timestepNumber,
- string? modifier)
+ byte[]? modifierBytes)
{
// # of 0's = length of pin
const int Mod = 1000000;
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
+#if NET6_0_OR_GREATER
+ Span<byte> timestepAsBytes = stackalloc byte[sizeof(long)];
+ var res = BitConverter.TryWriteBytes(timestepAsBytes, IPAddress.HostToNetworkOrder((long)timestepNumber));
+ Debug.Assert(res);
+#else
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
+#endif
#if NET6_0_OR_GREATER
- var hash = HMACSHA1.HashData(key, ApplyModifier(timestepAsBytes, modifier));
+ Span<byte> modifierCombinedBytes = timestepAsBytes;
+ if (modifierBytes is not null)
+ {
+ modifierCombinedBytes = ApplyModifier(timestepAsBytes, modifierBytes);
+ }
+ Span<byte> hash = stackalloc byte[HMACSHA1.HashSizeInBytes];
+ res = HMACSHA1.TryHashData(key, modifierCombinedBytes, hash, out var written);
+ Debug.Assert(res);
+ Debug.Assert(written == hash.Length);
#else
- var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier));
+ var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifierBytes));
#endif
// Generate DT string
@@ -65,16 +66,10 @@ internal static class Rfc6238AuthenticationService
return binaryCode % Mod;
}
- private static byte[] ApplyModifier(byte[] input, string? modifier)
+ private static byte[] ApplyModifier(Span<byte> input, byte[] modifierBytes)
{
- if (string.IsNullOrEmpty(modifier))
- {
- return input;
- }
-
- var modifierBytes = _encoding.GetBytes(modifier);
var combined = new byte[checked(input.Length + modifierBytes.Length)];
- Buffer.BlockCopy(input, 0, combined, 0, input.Length);
+ input.CopyTo(combined);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
@@ -100,12 +95,13 @@ internal static class Rfc6238AuthenticationService
// Allow a variance of no greater than 9 minutes in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
+ var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
#if NET6_0_OR_GREATER
- return ComputeTotp(securityToken, currentTimeStep, modifier);
+ return ComputeTotp(securityToken, currentTimeStep, modifierBytes);
#else
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
- return ComputeTotp(hashAlgorithm, currentTimeStep, modifier);
+ return ComputeTotp(hashAlgorithm, currentTimeStep, modifierBytes);
}
#endif
}
@@ -124,12 +120,13 @@ internal static class Rfc6238AuthenticationService
using (var hashAlgorithm = new HMACSHA1(securityToken))
#endif
{
+ var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
for (var i = -2; i <= 2; i++)
{
#if NET6_0_OR_GREATER
- var computedTotp = ComputeTotp(securityToken, (ulong)((long)currentTimeStep + i), modifier);
+ var computedTotp = ComputeTotp(securityToken, (ulong)((long)currentTimeStep + i), modifierBytes);
#else
- var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier);
+ var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifierBytes);
#endif
if (computedTotp == code)
{
diff --git a/src/Identity/Extensions.Core/src/RoleManager.cs b/src/Identity/Extensions.Core/src/RoleManager.cs
index 93f0014629..82dc0010ba 100644
--- a/src/Identity/Extensions.Core/src/RoleManager.cs
+++ b/src/Identity/Extensions.Core/src/RoleManager.cs
@@ -425,19 +425,23 @@ public class RoleManager<TRole> : IDisposable where TRole : class
/// <returns>A <see cref="IdentityResult"/> representing whether validation was successful.</returns>
protected virtual async Task<IdentityResult> ValidateRoleAsync(TRole role)
{
- var errors = new List<IdentityError>();
+ List<IdentityError>? errors = null;
foreach (var v in RoleValidators)
{
var result = await v.ValidateAsync(this, role).ConfigureAwait(false);
if (!result.Succeeded)
{
+ errors ??= new List<IdentityError>();
errors.AddRange(result.Errors);
}
}
- if (errors.Count > 0)
+ if (errors?.Count > 0)
{
- Logger.LogWarning(LoggerEventIds.RoleValidationFailed, "Role {roleId} validation failed: {errors}.", await GetRoleIdAsync(role).ConfigureAwait(false), string.Join(";", errors.Select(e => e.Code)));
- return IdentityResult.Failed(errors.ToArray());
+ if (Logger.IsEnabled(LogLevel.Warning))
+ {
+ Logger.LogWarning(LoggerEventIds.RoleValidationFailed, "Role {roleId} validation failed: {errors}.", await GetRoleIdAsync(role).ConfigureAwait(false), string.Join(";", errors.Select(e => e.Code)));
+ }
+ return IdentityResult.Failed(errors);
}
return IdentityResult.Success;
}
diff --git a/src/Identity/Extensions.Core/src/RoleValidator.cs b/src/Identity/Extensions.Core/src/RoleValidator.cs
index 3385018e34..26e44558b8 100644
--- a/src/Identity/Extensions.Core/src/RoleValidator.cs
+++ b/src/Identity/Extensions.Core/src/RoleValidator.cs
@@ -40,21 +40,21 @@ public class RoleValidator<TRole> : IRoleValidator<TRole> where TRole : class
{
throw new ArgumentNullException(nameof(role));
}
- var errors = new List<IdentityError>();
- await ValidateRoleName(manager, role, errors).ConfigureAwait(false);
- if (errors.Count > 0)
+ var errors = await ValidateRoleName(manager, role).ConfigureAwait(false);
+ if (errors?.Count > 0)
{
- return IdentityResult.Failed(errors.ToArray());
+ return IdentityResult.Failed(errors);
}
return IdentityResult.Success;
}
- private async Task ValidateRoleName(RoleManager<TRole> manager, TRole role,
- ICollection<IdentityError> errors)
+ private async Task<List<IdentityError>?> ValidateRoleName(RoleManager<TRole> manager, TRole role)
{
+ List<IdentityError>? errors = null;
var roleName = await manager.GetRoleNameAsync(role).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(roleName))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.InvalidRoleName(roleName));
}
else
@@ -63,8 +63,11 @@ public class RoleValidator<TRole> : IRoleValidator<TRole> where TRole : class
if (owner != null &&
!string.Equals(await manager.GetRoleIdAsync(owner).ConfigureAwait(false), await manager.GetRoleIdAsync(role).ConfigureAwait(false)))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.DuplicateRoleName(roleName));
}
}
+
+ return errors;
}
}
diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs
index 4b1708e71e..668dc024ac 100644
--- a/src/Identity/Extensions.Core/src/UserManager.cs
+++ b/src/Identity/Extensions.Core/src/UserManager.cs
@@ -2278,6 +2278,22 @@ public class UserManager<TUser> : IDisposable where TUser : class
/// <returns></returns>
protected virtual string CreateTwoFactorRecoveryCode()
{
+#if NET6_0_OR_GREATER
+ return string.Create(11, 0, static (buffer, _) =>
+ {
+ buffer[10] = GetRandomRecoveryCodeChar();
+ buffer[9] = GetRandomRecoveryCodeChar();
+ buffer[8] = GetRandomRecoveryCodeChar();
+ buffer[7] = GetRandomRecoveryCodeChar();
+ buffer[6] = GetRandomRecoveryCodeChar();
+ buffer[5] = '-';
+ buffer[4] = GetRandomRecoveryCodeChar();
+ buffer[3] = GetRandomRecoveryCodeChar();
+ buffer[2] = GetRandomRecoveryCodeChar();
+ buffer[1] = GetRandomRecoveryCodeChar();
+ buffer[0] = GetRandomRecoveryCodeChar();
+ });
+#else
var recoveryCode = new StringBuilder(11);
recoveryCode.Append(GetRandomRecoveryCodeChar());
recoveryCode.Append(GetRandomRecoveryCodeChar());
@@ -2291,6 +2307,7 @@ public class UserManager<TUser> : IDisposable where TUser : class
recoveryCode.Append(GetRandomRecoveryCodeChar());
recoveryCode.Append(GetRandomRecoveryCodeChar());
return recoveryCode.ToString();
+#endif
}
// We don't want to use any confusing characters like 0/O 1/I/L/l
@@ -2487,13 +2504,13 @@ public class UserManager<TUser> : IDisposable where TUser : class
private static string NewSecurityStamp()
{
- byte[] bytes = new byte[20];
#if NETSTANDARD2_0 || NETFRAMEWORK
+ byte[] bytes = new byte[20];
_rng.GetBytes(bytes);
+ return Base32.ToBase32(bytes);
#else
- RandomNumberGenerator.Fill(bytes);
+ return Base32.GenerateBase32();
#endif
- return Base32.ToBase32(bytes);
}
// IUserLoginStore methods
@@ -2550,19 +2567,20 @@ public class UserManager<TUser> : IDisposable where TUser : class
throw new InvalidOperationException(Resources.NullSecurityStamp);
}
}
- var errors = new List<IdentityError>();
+ List<IdentityError>? errors = null;
foreach (var v in UserValidators)
{
var result = await v.ValidateAsync(this, user).ConfigureAwait(false);
if (!result.Succeeded)
{
+ errors ??= new List<IdentityError>();
errors.AddRange(result.Errors);
}
}
- if (errors.Count > 0)
+ if (errors?.Count > 0)
{
Logger.LogDebug(LoggerEventIds.UserValidationFailed, "User validation failed: {errors}.", string.Join(";", errors.Select(e => e.Code)));
- return IdentityResult.Failed(errors.ToArray());
+ return IdentityResult.Failed(errors);
}
return IdentityResult.Success;
}
@@ -2576,7 +2594,7 @@ public class UserManager<TUser> : IDisposable where TUser : class
/// <returns>A <see cref="IdentityResult"/> representing whether validation was successful.</returns>
protected async Task<IdentityResult> ValidatePasswordAsync(TUser user, string? password)
{
- var errors = new List<IdentityError>();
+ List<IdentityError>? errors = null;
var isValid = true;
foreach (var v in PasswordValidators)
{
@@ -2585,6 +2603,7 @@ public class UserManager<TUser> : IDisposable where TUser : class
{
if (result.Errors.Any())
{
+ errors ??= new List<IdentityError>();
errors.AddRange(result.Errors);
}
@@ -2593,8 +2612,8 @@ public class UserManager<TUser> : IDisposable where TUser : class
}
if (!isValid)
{
- Logger.LogDebug(LoggerEventIds.PasswordValidationFailed, "User password validation failed: {errors}.", string.Join(";", errors.Select(e => e.Code)));
- return IdentityResult.Failed(errors.ToArray());
+ Logger.LogDebug(LoggerEventIds.PasswordValidationFailed, "User password validation failed: {errors}.", string.Join(";", errors?.Select(e => e.Code) ?? Array.Empty<string>()));
+ return IdentityResult.Failed(errors);
}
return IdentityResult.Success;
}
diff --git a/src/Identity/Extensions.Core/src/UserValidator.cs b/src/Identity/Extensions.Core/src/UserValidator.cs
index 81820d0142..1d67942d3c 100644
--- a/src/Identity/Extensions.Core/src/UserValidator.cs
+++ b/src/Identity/Extensions.Core/src/UserValidator.cs
@@ -46,25 +46,27 @@ public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class
{
throw new ArgumentNullException(nameof(user));
}
- var errors = new List<IdentityError>();
- await ValidateUserName(manager, user, errors).ConfigureAwait(false);
+ var errors = await ValidateUserName(manager, user).ConfigureAwait(false);
if (manager.Options.User.RequireUniqueEmail)
{
- await ValidateEmail(manager, user, errors).ConfigureAwait(false);
+ errors = await ValidateEmail(manager, user, errors).ConfigureAwait(false);
}
- return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
+ return errors?.Count > 0 ? IdentityResult.Failed(errors) : IdentityResult.Success;
}
- private async Task ValidateUserName(UserManager<TUser> manager, TUser user, ICollection<IdentityError> errors)
+ private async Task<List<IdentityError>?> ValidateUserName(UserManager<TUser> manager, TUser user)
{
+ List<IdentityError>? errors = null;
var userName = await manager.GetUserNameAsync(user).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(userName))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.InvalidUserName(userName));
}
else if (!string.IsNullOrEmpty(manager.Options.User.AllowedUserNameCharacters) &&
userName.Any(c => !manager.Options.User.AllowedUserNameCharacters.Contains(c)))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.InvalidUserName(userName));
}
else
@@ -73,30 +75,37 @@ public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class
if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner).ConfigureAwait(false), await manager.GetUserIdAsync(user).ConfigureAwait(false)))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.DuplicateUserName(userName));
}
}
+
+ return errors;
}
// make sure email is not empty, valid, and unique
- private async Task ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError> errors)
+ private async Task<List<IdentityError>?> ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError>? errors)
{
var email = await manager.GetEmailAsync(user).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(email))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.InvalidEmail(email));
- return;
+ return errors;
}
if (!new EmailAddressAttribute().IsValid(email))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.InvalidEmail(email));
- return;
+ return errors;
}
var owner = await manager.FindByEmailAsync(email).ConfigureAwait(false);
if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner).ConfigureAwait(false), await manager.GetUserIdAsync(user).ConfigureAwait(false)))
{
+ errors ??= new List<IdentityError>();
errors.Add(Describer.DuplicateEmail(email));
}
+ return errors;
}
}
diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
index 1e3a5ad435..e888c22d04 100644
--- a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
+++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
@@ -48,7 +48,7 @@ internal class EnableAuthenticator : DefaultUIPage
var keyBytes = Base32.FromBase32(key);
var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var timestep = Convert.ToInt64(unixTimestamp / 30);
- var topt = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)timestep, modifier: null);
+ var topt = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)timestep, modifierBytes: null);
return topt.ToString("D6", CultureInfo.InvariantCulture);
}
}
diff --git a/src/Identity/test/Identity.Test/Base32Test.cs b/src/Identity/test/Identity.Test/Base32Test.cs
index 8686193c2b..f188c23219 100644
--- a/src/Identity/test/Identity.Test/Base32Test.cs
+++ b/src/Identity/test/Identity.Test/Base32Test.cs
@@ -26,6 +26,13 @@ public class Base32Test
Assert.Equal<byte>(data, Base32.FromBase32(Base32.ToBase32(data)));
}
+ [Fact]
+ public void GenerateBase32IsValid()
+ {
+ var output = Base32.FromBase32(Base32.GenerateBase32());
+ Assert.Equal(20, output.Length);
+ }
+
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
private static byte[] GetRandomByteArray(int length)