From 1987b73835c097c20baac9ea0606b2c7d2437a0e Mon Sep 17 00:00:00 2001 From: Adam Barath Date: Sun, 26 Oct 2025 20:47:40 +0100 Subject: [PATCH 1/2] Updated to .Net 9.0 --- src/Host/Host.csproj | 6 +- src/Host/Startup.cs | 83 +- .../AspNetCoreIdentityManagerService.cs | 1260 ++++++++--------- .../IdentityManager2.AspNetIdentity.csproj | 8 +- 4 files changed, 682 insertions(+), 675 deletions(-) diff --git a/src/Host/Host.csproj b/src/Host/Host.csproj index e3f5aeb..cc27255 100644 --- a/src/Host/Host.csproj +++ b/src/Host/Host.csproj @@ -1,12 +1,12 @@  - netcoreapp3.1 + net9.0 - - + + diff --git a/src/Host/Startup.cs b/src/Host/Startup.cs index e558abe..0844c3b 100644 --- a/src/Host/Startup.cs +++ b/src/Host/Startup.cs @@ -1,38 +1,45 @@ -using IdentityManager2.AspNetIdentity; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; - -namespace Host -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(opt => opt.UseInMemoryDatabase("test")); - - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.AddIdentityManager() - .AddIdentityMangerService>(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseIdentityManager(); - - app.UseEndpoints(x => x.MapDefaultControllerRoute()); - } - } -} +using IdentityManager2.AspNetIdentity; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Host +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => opt.UseInMemoryDatabase("test")); + + services.AddIdentity(options => + { + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + options.Password.RequiredLength = 4; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddIdentityManager() + .AddIdentityMangerService>(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseIdentityManager(); + + app.UseEndpoints(x => x.MapDefaultControllerRoute()); + } + } +} diff --git a/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs b/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs index f64c086..bc050e7 100644 --- a/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs +++ b/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs @@ -8,634 +8,634 @@ using IdentityManager2.Extensions; using Microsoft.AspNetCore.Identity; -namespace IdentityManager2.AspNetIdentity -{ - public class AspNetCoreIdentityManagerService : IIdentityManagerService - where TUser : IdentityUser, new() - where TRole : IdentityRole, new() - where TUserKey : IEquatable - where TRoleKey : IEquatable - { - public string RoleClaimType { get; set; } - - protected readonly UserManager UserManager; - protected readonly RoleManager RoleManager; - protected readonly Func> MetadataFunc; - - internal AspNetCoreIdentityManagerService(UserManager userManager, RoleManager roleManager) - { - this.UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - this.RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - - if (!userManager.SupportsQueryableUsers) throw new InvalidOperationException("UserManager must support queryable users."); - - var email = userManager.Options.Tokens.EmailConfirmationTokenProvider; // TODO: and for rest... - if (!userManager.Options.Tokens.ProviderMap.ContainsKey(email)) { } - - RoleClaimType = IdentityManagerConstants.ClaimTypes.Role; - } - - public AspNetCoreIdentityManagerService( - UserManager userManager, - RoleManager roleManager, - bool includeAccountProperties = true) - : this(userManager, roleManager) - { - MetadataFunc = () => GetStandardMetadata(includeAccountProperties); - } - - public AspNetCoreIdentityManagerService( - UserManager userManager, - RoleManager roleManager, - IdentityManagerMetadata metadata) - : this(userManager, roleManager, () => Task.FromResult(metadata)) - { - } - - public AspNetCoreIdentityManagerService( - UserManager userManager, - RoleManager roleManager, - Func> metadataFunc) - : this(userManager, roleManager) - { - this.MetadataFunc = metadataFunc; - } - - public Task GetMetadataAsync() => MetadataFunc(); - - public async Task> CreateUserAsync(IEnumerable properties) - { - var usernameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Username); - var passwordClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Password); - - var username = usernameClaim.Value; - var password = passwordClaim.Value; - - var exclude = new[] { IdentityManagerConstants.ClaimTypes.Username, IdentityManagerConstants.ClaimTypes.Password }; - var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); - - var metadata = await GetMetadataAsync(); - var createProps = metadata.UserMetadata.GetCreateProperties(); - - var user = new TUser { UserName = username }; - foreach (var prop in otherProperties) - { - var propertyResult = await SetUserProperty(createProps, user, prop.Type, prop.Value); - if (!propertyResult.IsSuccess) - { - return new IdentityManagerResult(propertyResult.Errors.ToArray()); - } - } - - var result = await UserManager.CreateAsync(user, password); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - } - - return new IdentityManagerResult(new CreateResult { Subject = user.Id.ToString() }); - } - - public async Task DeleteUserAsync(string subject) - { - var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); - - var result = await UserManager.DeleteAsync(user); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - - return IdentityManagerResult.Success; - } - - public async Task>> QueryUsersAsync(string filter, int start, int count) - { - var query = - from user in UserManager.Users - orderby user.UserName - select user; - - if (!string.IsNullOrWhiteSpace(filter)) - { - query = - from user in query - where user.UserName.Contains(filter) - orderby user.UserName - select user; - } - - var total = query.Count(); - var users = query.Skip(start).Take(count).ToArray(); - - var items = new List(); - foreach (var user in users) - { - items.Add(new UserSummary - { - Subject = user.Id.ToString(), - Username = user.UserName, - Name = await DisplayNameFromUser(user) - }); - } - - var result = new QueryResult - { - Start = start, - Count = count, - Total = total, - Filter = filter, - Items = items - }; - - return new IdentityManagerResult>(result); - } - - public async Task> GetUserAsync(string subject) - { - var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult((UserDetail)null); - - var result = new UserDetail - { - Subject = subject, - Username = user.UserName, - Name = await DisplayNameFromUser(user), - }; - - var metadata = await GetMetadataAsync(); - - var props = new List(); - foreach (var prop in metadata.UserMetadata.UpdateProperties) - { - props.Add(new PropertyValue - { - Type = prop.Type, - Value = await GetUserProperty(prop, user) - }); - } - - result.Properties = props.ToArray(); - - if (UserManager.SupportsUserClaim) - { - var userClaims = await UserManager.GetClaimsAsync(user); - var claims = new List(); - if (userClaims != null) - { - claims.AddRange(userClaims.Select(x => new ClaimValue { Type = x.Type, Value = x.Value })); - } - result.Claims = claims.ToArray(); - } - - return new IdentityManagerResult(result); - } - - public async Task SetUserPropertyAsync(string subject, string type, string value) - { - var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); - - var errors = ValidateUserProperty(type, value).ToList(); - if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); - - var metadata = await GetMetadataAsync(); - var propResult = await SetUserProperty(metadata.UserMetadata.UpdateProperties, user, type, value); - if (!propResult.IsSuccess) return propResult; - - var result = await UserManager.UpdateAsync(user); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - - return IdentityManagerResult.Success; - } - - public async Task AddUserClaimAsync(string subject, string type, string value) - { - var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); - - var existingClaims = await UserManager.GetClaimsAsync(user); - if (!existingClaims.Any(x => x.Type == type && x.Value == value)) - { - var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - } - - return IdentityManagerResult.Success; - } - - public async Task RemoveUserClaimAsync(string subject, string type, string value) - { - var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); - - var result = await UserManager.RemoveClaimAsync(user, new Claim(type, value)); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - - return IdentityManagerResult.Success; - } - - public async Task> CreateRoleAsync(IEnumerable properties) - { - ValidateSupportsRoles(); - - var nameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Name); - var name = nameClaim.Value; - - var exclude = new[] { IdentityManagerConstants.ClaimTypes.Name }; - var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); - - var metadata = await GetMetadataAsync(); - var createProps = metadata.RoleMetadata.GetCreateProperties(); - - var role = new TRole { Name = name }; - foreach (var prop in otherProperties) - { - var roleResult = await SetRoleProperty(createProps, role, prop.Type, prop.Value); - if (!roleResult.IsSuccess) - { - return new IdentityManagerResult(roleResult.Errors.ToArray()); - } - } - - var result = await RoleManager.CreateAsync(role); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - - return new IdentityManagerResult(new CreateResult { Subject = role.Id.ToString() }); - } - - public async Task DeleteRoleAsync(string subject) - { - ValidateSupportsRoles(); - - var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult("Invalid subject"); - - var result = await RoleManager.DeleteAsync(role); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); - - return IdentityManagerResult.Success; - } - - public Task>> QueryRolesAsync(string filter, int start, int count) - { - ValidateSupportsRoles(); - - if (start < 0) start = 0; - if (count < 0) count = int.MaxValue; - - var query = - from role in RoleManager.Roles - orderby role.Name - select role; - - if (!string.IsNullOrWhiteSpace(filter)) - { - query = - from role in query - where role.Name.Contains(filter) - orderby role.Name - select role; - } - - var total = query.Count(); - var roles = query.Skip(start).Take(count).ToArray(); - - var result = new QueryResult - { - Start = start, - Count = count, - Total = total, - Filter = filter, - Items = roles.Select(x => - { - var user = new RoleSummary - { - Subject = x.Id.ToString(), - Name = x.Name, - // TODO: Role Description - }; - - return user; - }).ToArray() - }; - - return Task.FromResult(new IdentityManagerResult>(result)); - } - - public async Task> GetRoleAsync(string subject) - { - ValidateSupportsRoles(); - - var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult((RoleDetail)null); - - var result = new RoleDetail - { - Subject = subject, - Name = role.Name, - // TODO: Role Description - }; - - var metadata = await GetMetadataAsync(); - - var props = new List(); - foreach (var prop in metadata.RoleMetadata.UpdateProperties) - { - props.Add(new PropertyValue - { - Type = prop.Type, - Value = await GetRoleProperty(prop, role) - }); - } - - result.Properties = props.ToArray(); - - return new IdentityManagerResult(result); - } - - public async Task SetRolePropertyAsync(string subject, string type, string value) - { - ValidateSupportsRoles(); - - var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult("Invalid subject"); - - var errors = ValidateRoleProperty(type, value).ToList(); - if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); - - var metadata = await GetMetadataAsync(); - var result = await SetRoleProperty(metadata.RoleMetadata.UpdateProperties, role, type, value); - if (!result.IsSuccess) return result; - - var updateResult = await RoleManager.UpdateAsync(role); - if (!updateResult.Succeeded) return new IdentityManagerResult(result.Errors.ToArray()); - - return IdentityManagerResult.Success; - } - - public virtual Task GetStandardMetadata(bool includeAccountProperties = true) - { - var update = new List(); - if (UserManager.SupportsUserPassword) - { - update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Password, u => Task.FromResult(null), SetPassword, "Password", PropertyDataType.Password, true)); - } - if (UserManager.SupportsUserEmail) - { - update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Email, u => GetEmail(u), SetEmail, "Email", PropertyDataType.Email)); - } - if (UserManager.SupportsUserPhoneNumber) - { - update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Phone, u => GetPhone(u), SetPhone, "Phone", PropertyDataType.String)); - } - if (UserManager.SupportsUserTwoFactor) - { - update.Add(PropertyMetadata.FromFunctions("two_factor", u => GetTwoFactorEnabled(u), SetTwoFactorEnabled, "Two Factor Enabled", PropertyDataType.Boolean)); - } - if (UserManager.SupportsUserLockout) - { - update.Add(PropertyMetadata.FromFunctions("locked_enabled", GetLockoutEnabled, (user1, enabled) => SetLockoutEnabled(user1, enabled), "Lockout Enabled", PropertyDataType.Boolean)); - update.Add(PropertyMetadata.FromFunctions("locked", GetLockedOut, (user1, locked) => SetLockedOut(user1, locked), "Locked Out", PropertyDataType.Boolean)); - } - - if (includeAccountProperties) - { - update.AddRange(PropertyMetadata.FromType()); - } - - var create = new List(); - create.Add(PropertyMetadata.FromProperty(x => x.UserName, name: IdentityManagerConstants.ClaimTypes.Username, required: true)); - create.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Password, u => Task.FromResult(null), SetPassword, "Password", PropertyDataType.Password, true)); - - var user = new UserMetadata - { - SupportsCreate = true, - SupportsDelete = true, - SupportsClaims = UserManager.SupportsUserClaim, - CreateProperties = create, - UpdateProperties = update - }; - - var role = new RoleMetadata - { - RoleClaimType = RoleClaimType, - SupportsCreate = true, - SupportsDelete = true, - CreateProperties = new[] { +namespace IdentityManager2.AspNetIdentity +{ + public class AspNetCoreIdentityManagerService : IIdentityManagerService + where TUser : IdentityUser, new() + where TRole : IdentityRole, new() + where TUserKey : IEquatable + where TRoleKey : IEquatable + { + public string RoleClaimType { get; set; } + + protected readonly UserManager UserManager; + protected readonly RoleManager RoleManager; + protected readonly Func> MetadataFunc; + + internal AspNetCoreIdentityManagerService(UserManager userManager, RoleManager roleManager) + { + this.UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + this.RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + + if (!userManager.SupportsQueryableUsers) throw new InvalidOperationException("UserManager must support queryable users."); + + var email = userManager.Options.Tokens.EmailConfirmationTokenProvider; // TODO: and for rest... + if (!userManager.Options.Tokens.ProviderMap.ContainsKey(email)) { } + + RoleClaimType = IdentityManagerConstants.ClaimTypes.Role; + } + + public AspNetCoreIdentityManagerService( + UserManager userManager, + RoleManager roleManager, + bool includeAccountProperties = true) + : this(userManager, roleManager) + { + MetadataFunc = () => GetStandardMetadata(includeAccountProperties); + } + + public AspNetCoreIdentityManagerService( + UserManager userManager, + RoleManager roleManager, + IdentityManagerMetadata metadata) + : this(userManager, roleManager, () => Task.FromResult(metadata)) + { + } + + public AspNetCoreIdentityManagerService( + UserManager userManager, + RoleManager roleManager, + Func> metadataFunc) + : this(userManager, roleManager) + { + this.MetadataFunc = metadataFunc; + } + + public Task GetMetadataAsync() => MetadataFunc(); + + public async Task> CreateUserAsync(IEnumerable properties) + { + var usernameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Username); + var passwordClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Password); + + var username = usernameClaim.Value; + var password = passwordClaim.Value; + + var exclude = new[] { IdentityManagerConstants.ClaimTypes.Username, IdentityManagerConstants.ClaimTypes.Password }; + var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); + + var metadata = await GetMetadataAsync(); + var createProps = metadata.UserMetadata.GetCreateProperties(); + + var user = new TUser { UserName = username }; + foreach (var prop in otherProperties) + { + var propertyResult = await SetUserProperty(createProps, user, prop.Type, prop.Value); + if (!propertyResult.IsSuccess) + { + return new IdentityManagerResult(propertyResult.Errors.ToArray()); + } + } + + var result = await UserManager.CreateAsync(user, password); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + + return new IdentityManagerResult(new CreateResult { Subject = user.Id.ToString() }); + } + + public async Task DeleteUserAsync(string subject) + { + var user = await UserManager.FindByIdAsync(subject); + if (user == null) return new IdentityManagerResult("Invalid subject"); + + var result = await UserManager.DeleteAsync(user); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + + return IdentityManagerResult.Success; + } + + public async Task>> QueryUsersAsync(string filter, int start, int count) + { + var query = + from user in UserManager.Users + orderby user.UserName + select user; + + if (!string.IsNullOrWhiteSpace(filter)) + { + query = + from user in query + where user.UserName.Contains(filter) + orderby user.UserName + select user; + } + + var total = query.Count(); + var users = query.Skip(start).Take(count).ToArray(); + + var items = new List(); + foreach (var user in users) + { + items.Add(new UserSummary + { + Subject = user.Id.ToString(), + Username = user.UserName, + Name = await DisplayNameFromUser(user) + }); + } + + var result = new QueryResult + { + Start = start, + Count = count, + Total = total, + Filter = filter, + Items = items + }; + + return new IdentityManagerResult>(result); + } + + public async Task> GetUserAsync(string subject) + { + var user = await UserManager.FindByIdAsync(subject); + if (user == null) return new IdentityManagerResult((UserDetail)null); + + var result = new UserDetail + { + Subject = subject, + Username = user.UserName, + Name = await DisplayNameFromUser(user), + }; + + var metadata = await GetMetadataAsync(); + + var props = new List(); + foreach (var prop in metadata.UserMetadata.UpdateProperties) + { + props.Add(new PropertyValue + { + Type = prop.Type, + Value = await GetUserProperty(prop, user) + }); + } + + result.Properties = props.ToArray(); + + if (UserManager.SupportsUserClaim) + { + var userClaims = await UserManager.GetClaimsAsync(user); + var claims = new List(); + if (userClaims != null) + { + claims.AddRange(userClaims.Select(x => new ClaimValue { Type = x.Type, Value = x.Value })); + } + result.Claims = claims.ToArray(); + } + + return new IdentityManagerResult(result); + } + + public async Task SetUserPropertyAsync(string subject, string type, string value) + { + var user = await UserManager.FindByIdAsync(subject); + if (user == null) return new IdentityManagerResult("Invalid subject"); + + var errors = ValidateUserProperty(type, value).ToList(); + if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); + + var metadata = await GetMetadataAsync(); + var propResult = await SetUserProperty(metadata.UserMetadata.UpdateProperties, user, type, value); + if (!propResult.IsSuccess) return propResult; + + var result = await UserManager.UpdateAsync(user); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + + return IdentityManagerResult.Success; + } + + public async Task AddUserClaimAsync(string subject, string type, string value) + { + var user = await UserManager.FindByIdAsync(subject); + if (user == null) return new IdentityManagerResult("Invalid subject"); + + var existingClaims = await UserManager.GetClaimsAsync(user); + if (!existingClaims.Any(x => x.Type == type && x.Value == value)) + { + var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + + return IdentityManagerResult.Success; + } + + public async Task RemoveUserClaimAsync(string subject, string type, string value) + { + var user = await UserManager.FindByIdAsync(subject); + if (user == null) return new IdentityManagerResult("Invalid subject"); + + var result = await UserManager.RemoveClaimAsync(user, new Claim(type, value)); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + + return IdentityManagerResult.Success; + } + + public async Task> CreateRoleAsync(IEnumerable properties) + { + ValidateSupportsRoles(); + + var nameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Name); + var name = nameClaim.Value; + + var exclude = new[] { IdentityManagerConstants.ClaimTypes.Name }; + var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); + + var metadata = await GetMetadataAsync(); + var createProps = metadata.RoleMetadata.GetCreateProperties(); + + var role = new TRole { Name = name }; + foreach (var prop in otherProperties) + { + var roleResult = await SetRoleProperty(createProps, role, prop.Type, prop.Value); + if (!roleResult.IsSuccess) + { + return new IdentityManagerResult(roleResult.Errors.ToArray()); + } + } + + var result = await RoleManager.CreateAsync(role); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + + return new IdentityManagerResult(new CreateResult { Subject = role.Id.ToString() }); + } + + public async Task DeleteRoleAsync(string subject) + { + ValidateSupportsRoles(); + + var role = await RoleManager.FindByIdAsync(subject); + if (role == null) return new IdentityManagerResult("Invalid subject"); + + var result = await RoleManager.DeleteAsync(role); + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + + return IdentityManagerResult.Success; + } + + public Task>> QueryRolesAsync(string filter, int start, int count) + { + ValidateSupportsRoles(); + + if (start < 0) start = 0; + if (count < 0) count = int.MaxValue; + + var query = + from role in RoleManager.Roles + orderby role.Name + select role; + + if (!string.IsNullOrWhiteSpace(filter)) + { + query = + from role in query + where role.Name.Contains(filter) + orderby role.Name + select role; + } + + var total = query.Count(); + var roles = query.Skip(start).Take(count).ToArray(); + + var result = new QueryResult + { + Start = start, + Count = count, + Total = total, + Filter = filter, + Items = roles.Select(x => + { + var user = new RoleSummary + { + Subject = x.Id.ToString(), + Name = x.Name, + // TODO: Role Description + }; + + return user; + }).ToArray() + }; + + return Task.FromResult(new IdentityManagerResult>(result)); + } + + public async Task> GetRoleAsync(string subject) + { + ValidateSupportsRoles(); + + var role = await RoleManager.FindByIdAsync(subject); + if (role == null) return new IdentityManagerResult((RoleDetail)null); + + var result = new RoleDetail + { + Subject = subject, + Name = role.Name, + // TODO: Role Description + }; + + var metadata = await GetMetadataAsync(); + + var props = new List(); + foreach (var prop in metadata.RoleMetadata.UpdateProperties) + { + props.Add(new PropertyValue + { + Type = prop.Type, + Value = await GetRoleProperty(prop, role) + }); + } + + result.Properties = props.ToArray(); + + return new IdentityManagerResult(result); + } + + public async Task SetRolePropertyAsync(string subject, string type, string value) + { + ValidateSupportsRoles(); + + var role = await RoleManager.FindByIdAsync(subject); + if (role == null) return new IdentityManagerResult("Invalid subject"); + + var errors = ValidateRoleProperty(type, value).ToList(); + if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); + + var metadata = await GetMetadataAsync(); + var result = await SetRoleProperty(metadata.RoleMetadata.UpdateProperties, role, type, value); + if (!result.IsSuccess) return result; + + var updateResult = await RoleManager.UpdateAsync(role); + if (!updateResult.Succeeded) return new IdentityManagerResult(result.Errors.ToArray()); + + return IdentityManagerResult.Success; + } + + public virtual Task GetStandardMetadata(bool includeAccountProperties = true) + { + var update = new List(); + if (UserManager.SupportsUserPassword) + { + update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Password, u => Task.FromResult(null), SetPassword, "Password", PropertyDataType.Password, true)); + } + if (UserManager.SupportsUserEmail) + { + update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Email, u => GetEmail(u), SetEmail, "Email", PropertyDataType.Email)); + } + if (UserManager.SupportsUserPhoneNumber) + { + update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Phone, u => GetPhone(u), SetPhone, "Phone", PropertyDataType.String)); + } + if (UserManager.SupportsUserTwoFactor) + { + update.Add(PropertyMetadata.FromFunctions("two_factor", u => GetTwoFactorEnabled(u), SetTwoFactorEnabled, "Two Factor Enabled", PropertyDataType.Boolean)); + } + if (UserManager.SupportsUserLockout) + { + update.Add(PropertyMetadata.FromFunctions("locked_enabled", GetLockoutEnabled, (user1, enabled) => SetLockoutEnabled(user1, enabled), "Lockout Enabled", PropertyDataType.Boolean)); + update.Add(PropertyMetadata.FromFunctions("locked", GetLockedOut, (user1, locked) => SetLockedOut(user1, locked), "Locked Out", PropertyDataType.Boolean)); + } + + if (includeAccountProperties) + { + update.AddRange(PropertyMetadata.FromType()); + } + + var create = new List(); + create.Add(PropertyMetadata.FromProperty(x => x.UserName, name: IdentityManagerConstants.ClaimTypes.Username, required: true)); + create.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Password, u => Task.FromResult(null), SetPassword, "Password", PropertyDataType.Password, true)); + + var user = new UserMetadata + { + SupportsCreate = true, + SupportsDelete = true, + SupportsClaims = UserManager.SupportsUserClaim, + CreateProperties = create, + UpdateProperties = update + }; + + var role = new RoleMetadata + { + RoleClaimType = RoleClaimType, + SupportsCreate = true, + SupportsDelete = true, + CreateProperties = [ PropertyMetadata.FromProperty(x=>x.Name, name: IdentityManagerConstants.ClaimTypes.Name, required: true), - } - }; - - var meta = new IdentityManagerMetadata - { - UserMetadata = user, - RoleMetadata = role - }; - return Task.FromResult(meta); - } - - public virtual PropertyMetadata GetMetadataForClaim(string type, string name = null, PropertyDataType dataType = PropertyDataType.String, bool required = false) - { - return PropertyMetadata.FromFunctions(type, GetForClaim(type), SetForClaim(type), name, dataType, required); - } - public virtual Func> GetForClaim(string type) - { - return async user => (await UserManager.GetClaimsAsync(user)).Where(x => x.Type == type).Select(x => x.Value).FirstOrDefault(); - } - public virtual Func> SetForClaim(string type) - { - return async (user, value) => - { - var claims = await UserManager.GetClaimsAsync(user); - claims = claims.Where(x => x.Type == type).ToArray(); - - foreach (var claim in claims) - { - var result = await UserManager.RemoveClaimAsync(user, claim); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - } - if (!string.IsNullOrWhiteSpace(value)) - { - var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - } - return IdentityManagerResult.Success; - }; - } - - public virtual async Task SetPassword(TUser user, string password) - { - var token = await UserManager.GeneratePasswordResetTokenAsync(user); - var result = await UserManager.ResetPasswordAsync(user, token, password); - - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); - return IdentityManagerResult.Success; - } - - public virtual async Task SetUsername(TUser user, string username) - { - var result = await UserManager.SetUserNameAsync(user, username); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - return IdentityManagerResult.Success; - } - - public virtual Task GetEmail(TUser user) => UserManager.GetEmailAsync(user); - public virtual async Task SetEmail(TUser user, string email) - { - var result = await UserManager.SetEmailAsync(user, email); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - if (!string.IsNullOrWhiteSpace(email)) - { - var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); - result = await UserManager.ConfirmEmailAsync(user, token); // TODO: check internal usage of reset/confirmation tokens is still valid - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); - } - - return IdentityManagerResult.Success; - } - - public virtual Task GetPhone(TUser user) => UserManager.GetPhoneNumberAsync(user); - public virtual async Task SetPhone(TUser user, string phone) - { - var result = await UserManager.SetPhoneNumberAsync(user, phone); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - if (!string.IsNullOrWhiteSpace(phone)) - { - var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, phone); - result = await UserManager.ChangePhoneNumberAsync(user, phone, token); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - } - - return IdentityManagerResult.Success; - } - - public virtual Task GetTwoFactorEnabled(TUser user) => UserManager.GetTwoFactorEnabledAsync(user); - public virtual async Task SetTwoFactorEnabled(TUser user, bool enabled) - { - var result = await UserManager.SetTwoFactorEnabledAsync(user, enabled); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - return IdentityManagerResult.Success; - } - - public virtual Task GetLockoutEnabled(TUser user) => UserManager.GetLockoutEnabledAsync(user); - public virtual async Task SetLockoutEnabled(TUser user, bool enabled) - { - var result = await UserManager.SetLockoutEnabledAsync(user, enabled); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - return IdentityManagerResult.Success; - } - - public virtual Task GetLockedOut(TUser user) => UserManager.IsLockedOutAsync(user); - public virtual async Task SetLockedOut(TUser user, bool locked) - { - if (locked) - { - var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - } - else - { - var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MinValue); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - } - - return IdentityManagerResult.Success; - } - - public virtual async Task SetName(TRole user, string name) - { - var result = await RoleManager.SetRoleNameAsync(user, name); - if (!result.Succeeded) - { - return new IdentityManagerResult(result.Errors.First().Description); - } - - return IdentityManagerResult.Success; - } - - protected virtual Task GetUserProperty(PropertyMetadata propMetadata, TUser user) - { - if (propMetadata.TryGet(user, out var val)) return val; - throw new Exception("Invalid property type " + propMetadata.Type); - } - - protected virtual Task SetUserProperty(IEnumerable propsMeta, TUser user, string type, string value) - { - if (propsMeta.TrySet(user, type, value, out var result)) return result; - throw new Exception("Invalid property type " + type); - } - - protected virtual async Task DisplayNameFromUser(TUser user) - { - if (UserManager.SupportsUserClaim) - { - var claims = await UserManager.GetClaimsAsync(user); - var name = claims.Where(x => x.Type == IdentityManagerConstants.ClaimTypes.Name).Select(x => x.Value).FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(name)) return name; - } - - return null; - } - - protected virtual IEnumerable ValidateUserProperty(string type, string value) - { - return Enumerable.Empty(); - } - - protected virtual void ValidateSupportsRoles() - { - if (RoleManager == null) throw new InvalidOperationException("Roles Not Supported"); - } - - protected virtual Task GetRoleProperty(PropertyMetadata propMetadata, TRole role) - { - if (propMetadata.TryGet(role, out var val)) return val; - throw new Exception("Invalid property type " + propMetadata.Type); - } - - protected virtual IEnumerable ValidateRoleProperty(string type, string value) - { - return Enumerable.Empty(); - } - - protected virtual Task SetRoleProperty(IEnumerable propsMeta, TRole role, string type, string value) - { - if (propsMeta.TrySet(role, type, value, out var result)) return result; - throw new Exception("Invalid property type " + type); - } - } -} \ No newline at end of file + ] + }; + + var meta = new IdentityManagerMetadata + { + UserMetadata = user, + RoleMetadata = role + }; + return Task.FromResult(meta); + } + + public virtual PropertyMetadata GetMetadataForClaim(string type, string name = null, PropertyDataType dataType = PropertyDataType.String, bool required = false) + { + return PropertyMetadata.FromFunctions(type, GetForClaim(type), SetForClaim(type), name, dataType, required); + } + public virtual Func> GetForClaim(string type) + { + return async user => (await UserManager.GetClaimsAsync(user)).Where(x => x.Type == type).Select(x => x.Value).FirstOrDefault(); + } + public virtual Func> SetForClaim(string type) + { + return async (user, value) => + { + var claims = await UserManager.GetClaimsAsync(user); + claims = claims.Where(x => x.Type == type).ToArray(); + + foreach (var claim in claims) + { + var result = await UserManager.RemoveClaimAsync(user, claim); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + } + if (!string.IsNullOrWhiteSpace(value)) + { + var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + } + return IdentityManagerResult.Success; + }; + } + + public virtual async Task SetPassword(TUser user, string password) + { + var token = await UserManager.GeneratePasswordResetTokenAsync(user); + var result = await UserManager.ResetPasswordAsync(user, token, password); + + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); + return IdentityManagerResult.Success; + } + + public virtual async Task SetUsername(TUser user, string username) + { + var result = await UserManager.SetUserNameAsync(user, username); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + return IdentityManagerResult.Success; + } + + public virtual Task GetEmail(TUser user) => UserManager.GetEmailAsync(user); + public virtual async Task SetEmail(TUser user, string email) + { + var result = await UserManager.SetEmailAsync(user, email); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + if (!string.IsNullOrWhiteSpace(email)) + { + var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); + result = await UserManager.ConfirmEmailAsync(user, token); // TODO: check internal usage of reset/confirmation tokens is still valid + if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); + } + + return IdentityManagerResult.Success; + } + + public virtual Task GetPhone(TUser user) => UserManager.GetPhoneNumberAsync(user); + public virtual async Task SetPhone(TUser user, string phone) + { + var result = await UserManager.SetPhoneNumberAsync(user, phone); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + if (!string.IsNullOrWhiteSpace(phone)) + { + var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, phone); + result = await UserManager.ChangePhoneNumberAsync(user, phone, token); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + } + + return IdentityManagerResult.Success; + } + + public virtual Task GetTwoFactorEnabled(TUser user) => UserManager.GetTwoFactorEnabledAsync(user); + public virtual async Task SetTwoFactorEnabled(TUser user, bool enabled) + { + var result = await UserManager.SetTwoFactorEnabledAsync(user, enabled); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + return IdentityManagerResult.Success; + } + + public virtual Task GetLockoutEnabled(TUser user) => UserManager.GetLockoutEnabledAsync(user); + public virtual async Task SetLockoutEnabled(TUser user, bool enabled) + { + var result = await UserManager.SetLockoutEnabledAsync(user, enabled); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + return IdentityManagerResult.Success; + } + + public virtual Task GetLockedOut(TUser user) => UserManager.IsLockedOutAsync(user); + public virtual async Task SetLockedOut(TUser user, bool locked) + { + if (locked) + { + var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + } + else + { + var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MinValue); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + } + + return IdentityManagerResult.Success; + } + + public virtual async Task SetName(TRole user, string name) + { + var result = await RoleManager.SetRoleNameAsync(user, name); + if (!result.Succeeded) + { + return new IdentityManagerResult(result.Errors.First().Description); + } + + return IdentityManagerResult.Success; + } + + protected virtual Task GetUserProperty(PropertyMetadata propMetadata, TUser user) + { + if (propMetadata.TryGet(user, out var val)) return val; + throw new Exception("Invalid property type " + propMetadata.Type); + } + + protected virtual Task SetUserProperty(IEnumerable propsMeta, TUser user, string type, string value) + { + if (propsMeta.TrySet(user, type, value, out var result)) return result; + throw new Exception("Invalid property type " + type); + } + + protected virtual async Task DisplayNameFromUser(TUser user) + { + if (UserManager.SupportsUserClaim) + { + var claims = await UserManager.GetClaimsAsync(user); + var name = claims.Where(x => x.Type == IdentityManagerConstants.ClaimTypes.Name).Select(x => x.Value).FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(name)) return name; + } + + return null; + } + + protected virtual IEnumerable ValidateUserProperty(string type, string value) + { + return Enumerable.Empty(); + } + + protected virtual void ValidateSupportsRoles() + { + if (RoleManager == null) throw new InvalidOperationException("Roles Not Supported"); + } + + protected virtual Task GetRoleProperty(PropertyMetadata propMetadata, TRole role) + { + if (propMetadata.TryGet(role, out var val)) return val; + throw new Exception("Invalid property type " + propMetadata.Type); + } + + protected virtual IEnumerable ValidateRoleProperty(string type, string value) + { + return Enumerable.Empty(); + } + + protected virtual Task SetRoleProperty(IEnumerable propsMeta, TRole role, string type, string value) + { + if (propsMeta.TrySet(role, type, value, out var result)) return result; + throw new Exception("Invalid property type " + type); + } + } +} diff --git a/src/IdentityManager2.AspNetIdentity/IdentityManager2.AspNetIdentity.csproj b/src/IdentityManager2.AspNetIdentity/IdentityManager2.AspNetIdentity.csproj index ef2955b..b9ef26b 100644 --- a/src/IdentityManager2.AspNetIdentity/IdentityManager2.AspNetIdentity.csproj +++ b/src/IdentityManager2.AspNetIdentity/IdentityManager2.AspNetIdentity.csproj @@ -1,9 +1,9 @@  - netcoreapp3.1 + net8.0;net9.0 IdentityManager is an user management tool for ASP.NET Core Identity. Maintained by Rock Solid Knowledge. - 1.0.0 + 1.0.1 Scott Brady IdentityManager2.AspNetIdentity IdentityManager2.AspNetIdentity @@ -15,8 +15,8 @@ - - + + From 8201005f022acdb09cd32676da53ea2cd32d4a22 Mon Sep 17 00:00:00 2001 From: Adam Barath Date: Mon, 27 Oct 2025 07:48:39 +0100 Subject: [PATCH 2/2] introduce logger --- .../AspNetCoreIdentityManagerService.cs | 403 +++++++++++++++--- 1 file changed, 355 insertions(+), 48 deletions(-) diff --git a/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs b/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs index bc050e7..c6befda 100644 --- a/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs +++ b/src/IdentityManager2.AspNetIdentity/AspNetCoreIdentityManagerService.cs @@ -1,13 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using IdentityManager2.Core; -using IdentityManager2.Core.Metadata; -using IdentityManager2.Extensions; -using Microsoft.AspNetCore.Identity; - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityManager2.Core; +using IdentityManager2.Core.Metadata; +using IdentityManager2.Extensions; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; + namespace IdentityManager2.AspNetIdentity { public class AspNetCoreIdentityManagerService : IIdentityManagerService @@ -18,71 +19,116 @@ public class AspNetCoreIdentityManagerService { public string RoleClaimType { get; set; } + protected readonly ILogger logger; protected readonly UserManager UserManager; protected readonly RoleManager RoleManager; protected readonly Func> MetadataFunc; - internal AspNetCoreIdentityManagerService(UserManager userManager, RoleManager roleManager) + #region Constructors + + internal AspNetCoreIdentityManagerService( + UserManager userManager, + RoleManager roleManager, + ILogger> logger + ) { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); this.RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - if (!userManager.SupportsQueryableUsers) throw new InvalidOperationException("UserManager must support queryable users."); + logger.LogInformation("Initializing AspNetCoreIdentityManagerService with UserManager: {UserManagerType}, RoleManager: {RoleManagerType}", + typeof(TUser).Name, typeof(TRole).Name); + + if (!userManager.SupportsQueryableUsers) + { + logger.LogError("UserManager does not support queryable users"); + throw new InvalidOperationException("UserManager must support queryable users."); + } var email = userManager.Options.Tokens.EmailConfirmationTokenProvider; // TODO: and for rest... - if (!userManager.Options.Tokens.ProviderMap.ContainsKey(email)) { } + if (!userManager.Options.Tokens.ProviderMap.ContainsKey(email)) + { + logger.LogWarning("Email token provider '{EmailProvider}' not found in token provider map", email); + } RoleClaimType = IdentityManagerConstants.ClaimTypes.Role; + logger.LogDebug("RoleClaimType set to: {RoleClaimType}", RoleClaimType); } public AspNetCoreIdentityManagerService( UserManager userManager, RoleManager roleManager, - bool includeAccountProperties = true) - : this(userManager, roleManager) + ILogger> logger, + bool includeAccountProperties = true + ) + : this(userManager, roleManager, logger) { + logger.LogDebug("Using standard metadata with includeAccountProperties: {IncludeAccountProperties}", includeAccountProperties); MetadataFunc = () => GetStandardMetadata(includeAccountProperties); } public AspNetCoreIdentityManagerService( UserManager userManager, RoleManager roleManager, - IdentityManagerMetadata metadata) - : this(userManager, roleManager, () => Task.FromResult(metadata)) + IdentityManagerMetadata metadata, + ILogger> logger + ) + : this(userManager, roleManager, () => Task.FromResult(metadata), logger) { + logger.LogDebug("Using custom metadata"); } public AspNetCoreIdentityManagerService( UserManager userManager, RoleManager roleManager, - Func> metadataFunc) - : this(userManager, roleManager) + Func> metadataFunc, + ILogger> logger) + : this(userManager, roleManager, logger) { this.MetadataFunc = metadataFunc; + logger.LogDebug("Using custom metadata function"); } - public Task GetMetadataAsync() => MetadataFunc(); + #endregion + + public Task GetMetadataAsync() + { + logger.LogDebug("Getting metadata"); + return MetadataFunc(); + } public async Task> CreateUserAsync(IEnumerable properties) { + logger.LogInformation("Creating new user"); + var usernameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Username); var passwordClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Password); var username = usernameClaim.Value; var password = passwordClaim.Value; + logger.LogDebug("Creating user with username: {Username}", username); + var exclude = new[] { IdentityManagerConstants.ClaimTypes.Username, IdentityManagerConstants.ClaimTypes.Password }; var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); + if (otherProperties.Any()) + { + logger.LogDebug("Creating user with {PropertyCount} additional properties", otherProperties.Length); + } + var metadata = await GetMetadataAsync(); var createProps = metadata.UserMetadata.GetCreateProperties(); var user = new TUser { UserName = username }; foreach (var prop in otherProperties) { + logger.LogDebug("Setting user property: {PropertyType} for username: {Username}", prop.Type, username); var propertyResult = await SetUserProperty(createProps, user, prop.Type, prop.Value); if (!propertyResult.IsSuccess) { + logger.LogWarning("Failed to set user property {PropertyType} for username {Username}: {Errors}", + prop.Type, username, string.Join(", ", propertyResult.Errors)); return new IdentityManagerResult(propertyResult.Errors.ToArray()); } } @@ -90,25 +136,44 @@ public async Task> CreateUserAsync(IEnumerab var result = await UserManager.CreateAsync(user, password); if (!result.Succeeded) { + logger.LogWarning("Failed to create user {Username}: {Errors}", + username, string.Join(", ", result.Errors.Select(x => x.Description))); return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); } + logger.LogInformation("Successfully created user {Username} with ID: {UserId}", username, user.Id); return new IdentityManagerResult(new CreateResult { Subject = user.Id.ToString() }); } public async Task DeleteUserAsync(string subject) { + logger.LogInformation("Deleting user with subject: {Subject}", subject); + var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); + if (user == null) + { + logger.LogWarning("User not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Found user {Username} (ID: {UserId}) for deletion", user.UserName, user.Id); var result = await UserManager.DeleteAsync(user); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to delete user {Username} (ID: {UserId}): {Errors}", + user.UserName, user.Id, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully deleted user {Username} (ID: {UserId})", user.UserName, user.Id); return IdentityManagerResult.Success; } public async Task>> QueryUsersAsync(string filter, int start, int count) { + logger.LogInformation("Querying users with filter: '{Filter}', start: {Start}, count: {Count}", filter ?? "(none)", start, count); + var query = from user in UserManager.Users orderby user.UserName @@ -126,6 +191,8 @@ orderby user.UserName var total = query.Count(); var users = query.Skip(start).Take(count).ToArray(); + logger.LogDebug("Query returned {ResultCount} users out of {TotalCount} total", users.Length, total); + var items = new List(); foreach (var user in users) { @@ -146,13 +213,22 @@ orderby user.UserName Items = items }; + logger.LogInformation("Successfully queried users: returned {ResultCount} users", items.Count); return new IdentityManagerResult>(result); } public async Task> GetUserAsync(string subject) { + logger.LogInformation("Getting user details for subject: {Subject}", subject); + var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult((UserDetail)null); + if (user == null) + { + logger.LogWarning("User not found with subject: {Subject}", subject); + return new IdentityManagerResult((UserDetail)null); + } + + logger.LogDebug("Found user {Username} (ID: {UserId})", user.UserName, user.Id); var result = new UserDetail { @@ -174,6 +250,7 @@ public async Task> GetUserAsync(string subject } result.Properties = props.ToArray(); + logger.LogDebug("Retrieved {PropertyCount} properties for user {Username}", props.Count, user.UserName); if (UserManager.SupportsUserClaim) { @@ -184,39 +261,82 @@ public async Task> GetUserAsync(string subject claims.AddRange(userClaims.Select(x => new ClaimValue { Type = x.Type, Value = x.Value })); } result.Claims = claims.ToArray(); + logger.LogDebug("Retrieved {ClaimCount} claims for user {Username}", claims.Count, user.UserName); } + logger.LogInformation("Successfully retrieved details for user {Username}", user.UserName); return new IdentityManagerResult(result); } public async Task SetUserPropertyAsync(string subject, string type, string value) { + logger.LogInformation("Setting user property {PropertyType} for subject: {Subject}", type, subject); + var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); + if (user == null) + { + logger.LogWarning("User not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Setting property {PropertyType} to value '{Value}' for user {Username}", type, value, user.UserName); var errors = ValidateUserProperty(type, value).ToList(); - if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); + if (errors.Any()) + { + logger.LogWarning("Validation failed for user property {PropertyType}: {Errors}", type, string.Join(", ", errors)); + return new IdentityManagerResult(errors.ToArray()); + } var metadata = await GetMetadataAsync(); var propResult = await SetUserProperty(metadata.UserMetadata.UpdateProperties, user, type, value); - if (!propResult.IsSuccess) return propResult; + if (!propResult.IsSuccess) + { + logger.LogWarning("Failed to set user property {PropertyType} for user {Username}: {Errors}", + type, user.UserName, string.Join(", ", propResult.Errors)); + return propResult; + } var result = await UserManager.UpdateAsync(user); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to update user {Username} after setting property {PropertyType}: {Errors}", + user.UserName, type, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully set property {PropertyType} for user {Username}", type, user.UserName); return IdentityManagerResult.Success; } public async Task AddUserClaimAsync(string subject, string type, string value) { + logger.LogInformation("Adding claim {ClaimType} to user with subject: {Subject}", type, subject); + var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); + if (user == null) + { + logger.LogWarning("User not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Adding claim {ClaimType} with value '{ClaimValue}' to user {Username}", type, value, user.UserName); var existingClaims = await UserManager.GetClaimsAsync(user); if (!existingClaims.Any(x => x.Type == type && x.Value == value)) { var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to add claim {ClaimType} to user {Username}: {Errors}", + type, user.UserName, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully added claim {ClaimType} to user {Username}", type, user.UserName); + } + else + { + logger.LogDebug("Claim {ClaimType} with value '{ClaimValue}' already exists for user {Username}", type, value, user.UserName); } return IdentityManagerResult.Success; @@ -224,59 +344,104 @@ public async Task AddUserClaimAsync(string subject, strin public async Task RemoveUserClaimAsync(string subject, string type, string value) { + logger.LogInformation("Removing claim {ClaimType} from user with subject: {Subject}", type, subject); + var user = await UserManager.FindByIdAsync(subject); - if (user == null) return new IdentityManagerResult("Invalid subject"); + if (user == null) + { + logger.LogWarning("User not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Removing claim {ClaimType} with value '{ClaimValue}' from user {Username}", type, value, user.UserName); var result = await UserManager.RemoveClaimAsync(user, new Claim(type, value)); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to remove claim {ClaimType} from user {Username}: {Errors}", + type, user.UserName, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully removed claim {ClaimType} from user {Username}", type, user.UserName); return IdentityManagerResult.Success; } public async Task> CreateRoleAsync(IEnumerable properties) { + logger.LogInformation("Creating new role"); ValidateSupportsRoles(); var nameClaim = properties.Single(x => x.Type == IdentityManagerConstants.ClaimTypes.Name); var name = nameClaim.Value; + logger.LogDebug("Creating role with name: {RoleName}", name); + var exclude = new[] { IdentityManagerConstants.ClaimTypes.Name }; var otherProperties = properties.Where(x => !exclude.Contains(x.Type)).ToArray(); + if (otherProperties.Any()) + { + logger.LogDebug("Creating role with {PropertyCount} additional properties", otherProperties.Length); + } + var metadata = await GetMetadataAsync(); var createProps = metadata.RoleMetadata.GetCreateProperties(); var role = new TRole { Name = name }; foreach (var prop in otherProperties) { + logger.LogDebug("Setting role property: {PropertyType} for role: {RoleName}", prop.Type, name); var roleResult = await SetRoleProperty(createProps, role, prop.Type, prop.Value); if (!roleResult.IsSuccess) { + logger.LogWarning("Failed to set role property {PropertyType} for role {RoleName}: {Errors}", + prop.Type, name, string.Join(", ", roleResult.Errors)); return new IdentityManagerResult(roleResult.Errors.ToArray()); } } var result = await RoleManager.CreateAsync(role); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to create role {RoleName}: {Errors}", + name, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully created role {RoleName} with ID: {RoleId}", name, role.Id); return new IdentityManagerResult(new CreateResult { Subject = role.Id.ToString() }); } public async Task DeleteRoleAsync(string subject) { + logger.LogInformation("Deleting role with subject: {Subject}", subject); ValidateSupportsRoles(); var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult("Invalid subject"); + if (role == null) + { + logger.LogWarning("Role not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Found role {RoleName} (ID: {RoleId}) for deletion", role.Name, role.Id); var result = await RoleManager.DeleteAsync(role); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + if (!result.Succeeded) + { + logger.LogWarning("Failed to delete role {RoleName} (ID: {RoleId}): {Errors}", + role.Name, role.Id, string.Join(", ", result.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.Select(x => x.Description).ToArray()); + } + logger.LogInformation("Successfully deleted role {RoleName} (ID: {RoleId})", role.Name, role.Id); return IdentityManagerResult.Success; } public Task>> QueryRolesAsync(string filter, int start, int count) { + logger.LogInformation("Querying roles with filter: '{Filter}', start: {Start}, count: {Count}", filter ?? "(none)", start, count); ValidateSupportsRoles(); if (start < 0) start = 0; @@ -299,6 +464,8 @@ orderby role.Name var total = query.Count(); var roles = query.Skip(start).Take(count).ToArray(); + logger.LogDebug("Query returned {ResultCount} roles out of {TotalCount} total", roles.Length, total); + var result = new QueryResult { Start = start, @@ -318,15 +485,23 @@ orderby role.Name }).ToArray() }; + logger.LogInformation("Successfully queried roles: returned {ResultCount} roles", result.Items.Count); return Task.FromResult(new IdentityManagerResult>(result)); } public async Task> GetRoleAsync(string subject) { + logger.LogInformation("Getting role details for subject: {Subject}", subject); ValidateSupportsRoles(); var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult((RoleDetail)null); + if (role == null) + { + logger.LogWarning("Role not found with subject: {Subject}", subject); + return new IdentityManagerResult((RoleDetail)null); + } + + logger.LogDebug("Found role {RoleName} (ID: {RoleId})", role.Name, role.Id); var result = new RoleDetail { @@ -348,58 +523,90 @@ public async Task> GetRoleAsync(string subject } result.Properties = props.ToArray(); + logger.LogDebug("Retrieved {PropertyCount} properties for role {RoleName}", props.Count, role.Name); + logger.LogInformation("Successfully retrieved details for role {RoleName}", role.Name); return new IdentityManagerResult(result); } public async Task SetRolePropertyAsync(string subject, string type, string value) { + logger.LogInformation("Setting role property {PropertyType} for subject: {Subject}", type, subject); ValidateSupportsRoles(); var role = await RoleManager.FindByIdAsync(subject); - if (role == null) return new IdentityManagerResult("Invalid subject"); + if (role == null) + { + logger.LogWarning("Role not found with subject: {Subject}", subject); + return new IdentityManagerResult("Invalid subject"); + } + + logger.LogDebug("Setting property {PropertyType} to value '{Value}' for role {RoleName}", type, value, role.Name); var errors = ValidateRoleProperty(type, value).ToList(); - if (errors.Any()) return new IdentityManagerResult(errors.ToArray()); + if (errors.Any()) + { + logger.LogWarning("Validation failed for role property {PropertyType}: {Errors}", type, string.Join(", ", errors)); + return new IdentityManagerResult(errors.ToArray()); + } var metadata = await GetMetadataAsync(); var result = await SetRoleProperty(metadata.RoleMetadata.UpdateProperties, role, type, value); - if (!result.IsSuccess) return result; + if (!result.IsSuccess) + { + logger.LogWarning("Failed to set role property {PropertyType} for role {RoleName}: {Errors}", + type, role.Name, string.Join(", ", result.Errors)); + return result; + } var updateResult = await RoleManager.UpdateAsync(role); - if (!updateResult.Succeeded) return new IdentityManagerResult(result.Errors.ToArray()); + if (!updateResult.Succeeded) + { + logger.LogWarning("Failed to update role {RoleName} after setting property {PropertyType}: {Errors}", + role.Name, type, string.Join(", ", updateResult.Errors.Select(x => x.Description))); + return new IdentityManagerResult(result.Errors.ToArray()); + } + logger.LogInformation("Successfully set property {PropertyType} for role {RoleName}", type, role.Name); return IdentityManagerResult.Success; } public virtual Task GetStandardMetadata(bool includeAccountProperties = true) { + logger.LogDebug("Getting standard metadata with includeAccountProperties: {IncludeAccountProperties}", includeAccountProperties); + var update = new List(); if (UserManager.SupportsUserPassword) { update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Password, u => Task.FromResult(null), SetPassword, "Password", PropertyDataType.Password, true)); + logger.LogDebug("Added password metadata"); } if (UserManager.SupportsUserEmail) { update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Email, u => GetEmail(u), SetEmail, "Email", PropertyDataType.Email)); + logger.LogDebug("Added email metadata"); } if (UserManager.SupportsUserPhoneNumber) { update.Add(PropertyMetadata.FromFunctions(IdentityManagerConstants.ClaimTypes.Phone, u => GetPhone(u), SetPhone, "Phone", PropertyDataType.String)); + logger.LogDebug("Added phone metadata"); } if (UserManager.SupportsUserTwoFactor) { update.Add(PropertyMetadata.FromFunctions("two_factor", u => GetTwoFactorEnabled(u), SetTwoFactorEnabled, "Two Factor Enabled", PropertyDataType.Boolean)); + logger.LogDebug("Added two-factor metadata"); } if (UserManager.SupportsUserLockout) { update.Add(PropertyMetadata.FromFunctions("locked_enabled", GetLockoutEnabled, (user1, enabled) => SetLockoutEnabled(user1, enabled), "Lockout Enabled", PropertyDataType.Boolean)); update.Add(PropertyMetadata.FromFunctions("locked", GetLockedOut, (user1, locked) => SetLockedOut(user1, locked), "Locked Out", PropertyDataType.Boolean)); + logger.LogDebug("Added lockout metadata"); } if (includeAccountProperties) { update.AddRange(PropertyMetadata.FromType()); + logger.LogDebug("Added account properties from TUser type"); } var create = new List(); @@ -420,8 +627,8 @@ public virtual Task GetStandardMetadata(bool includeAcc RoleClaimType = RoleClaimType, SupportsCreate = true, SupportsDelete = true, - CreateProperties = [ - PropertyMetadata.FromProperty(x=>x.Name, name: IdentityManagerConstants.ClaimTypes.Name, required: true), + CreateProperties = [ + PropertyMetadata.FromProperty(x=>x.Name, name: IdentityManagerConstants.ClaimTypes.Name, required: true), ] }; @@ -430,21 +637,30 @@ public virtual Task GetStandardMetadata(bool includeAcc UserMetadata = user, RoleMetadata = role }; + + logger.LogInformation("Standard metadata created with {CreatePropertyCount} create properties and {UpdatePropertyCount} update properties", + create.Count, update.Count); + return Task.FromResult(meta); } public virtual PropertyMetadata GetMetadataForClaim(string type, string name = null, PropertyDataType dataType = PropertyDataType.String, bool required = false) { + logger.LogDebug("Getting metadata for claim type: {ClaimType}", type); return PropertyMetadata.FromFunctions(type, GetForClaim(type), SetForClaim(type), name, dataType, required); } + public virtual Func> GetForClaim(string type) { return async user => (await UserManager.GetClaimsAsync(user)).Where(x => x.Type == type).Select(x => x.Value).FirstOrDefault(); } + public virtual Func> SetForClaim(string type) { return async (user, value) => { + logger.LogDebug("Setting claim {ClaimType} for user {Username}", type, user.UserName); + var claims = await UserManager.GetClaimsAsync(user); claims = claims.Where(x => x.Type == type).ToArray(); @@ -453,149 +669,227 @@ public virtual Func> SetForClaim(stri var result = await UserManager.RemoveClaimAsync(user, claim); if (!result.Succeeded) { + logger.LogWarning("Failed to remove existing claim {ClaimType} for user {Username}: {Error}", + type, user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } } + if (!string.IsNullOrWhiteSpace(value)) { var result = await UserManager.AddClaimAsync(user, new Claim(type, value)); if (!result.Succeeded) { + logger.LogWarning("Failed to add claim {ClaimType} for user {Username}: {Error}", + type, user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogDebug("Successfully set claim {ClaimType} for user {Username}", type, user.UserName); + } + else + { + logger.LogDebug("Cleared claim {ClaimType} for user {Username}", type, user.UserName); } + return IdentityManagerResult.Success; }; } public virtual async Task SetPassword(TUser user, string password) { + logger.LogInformation("Setting password for user {Username}", user.UserName); + var token = await UserManager.GeneratePasswordResetTokenAsync(user); var result = await UserManager.ResetPasswordAsync(user, token, password); - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); + if (!result.Succeeded) + { + logger.LogWarning("Failed to set password for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); + return new IdentityManagerResult(result.Errors.First().Description); + } + + logger.LogInformation("Successfully set password for user {Username}", user.UserName); return IdentityManagerResult.Success; } public virtual async Task SetUsername(TUser user, string username) { + logger.LogInformation("Setting username for user {OldUsername} to {NewUsername}", user.UserName, username); + var result = await UserManager.SetUserNameAsync(user, username); if (!result.Succeeded) { + logger.LogWarning("Failed to set username for user {OldUsername}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully set username to {NewUsername}", username); return IdentityManagerResult.Success; } public virtual Task GetEmail(TUser user) => UserManager.GetEmailAsync(user); + public virtual async Task SetEmail(TUser user, string email) { + logger.LogInformation("Setting email for user {Username} to {Email}", user.UserName, email); + var result = await UserManager.SetEmailAsync(user, email); if (!result.Succeeded) { + logger.LogWarning("Failed to set email for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } if (!string.IsNullOrWhiteSpace(email)) { + logger.LogDebug("Confirming email for user {Username}", user.UserName); var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); - result = await UserManager.ConfirmEmailAsync(user, token); // TODO: check internal usage of reset/confirmation tokens is still valid - if (!result.Succeeded) return new IdentityManagerResult(result.Errors.First().Description); + result = await UserManager.ConfirmEmailAsync(user, token); // TODO: check internal usage of reset/confirmation tokens is still valid + if (!result.Succeeded) + { + logger.LogWarning("Failed to confirm email for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); + return new IdentityManagerResult(result.Errors.First().Description); + } + logger.LogDebug("Email confirmed for user {Username}", user.UserName); } + logger.LogInformation("Successfully set email for user {Username}", user.UserName); return IdentityManagerResult.Success; } public virtual Task GetPhone(TUser user) => UserManager.GetPhoneNumberAsync(user); + public virtual async Task SetPhone(TUser user, string phone) { + logger.LogInformation("Setting phone number for user {Username} to {Phone}", user.UserName, phone); + var result = await UserManager.SetPhoneNumberAsync(user, phone); if (!result.Succeeded) { + logger.LogWarning("Failed to set phone number for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } if (!string.IsNullOrWhiteSpace(phone)) { + logger.LogDebug("Confirming phone number for user {Username}", user.UserName); var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, phone); result = await UserManager.ChangePhoneNumberAsync(user, phone, token); if (!result.Succeeded) { + logger.LogWarning("Failed to confirm phone number for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogDebug("Phone number confirmed for user {Username}", user.UserName); } + logger.LogInformation("Successfully set phone number for user {Username}", user.UserName); return IdentityManagerResult.Success; } public virtual Task GetTwoFactorEnabled(TUser user) => UserManager.GetTwoFactorEnabledAsync(user); + public virtual async Task SetTwoFactorEnabled(TUser user, bool enabled) { + logger.LogInformation("Setting two-factor authentication for user {Username} to {Enabled}", user.UserName, enabled); + var result = await UserManager.SetTwoFactorEnabledAsync(user, enabled); if (!result.Succeeded) { + logger.LogWarning("Failed to set two-factor authentication for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully set two-factor authentication for user {Username} to {Enabled}", user.UserName, enabled); return IdentityManagerResult.Success; } public virtual Task GetLockoutEnabled(TUser user) => UserManager.GetLockoutEnabledAsync(user); + public virtual async Task SetLockoutEnabled(TUser user, bool enabled) { + logger.LogInformation("Setting lockout enabled for user {Username} to {Enabled}", user.UserName, enabled); + var result = await UserManager.SetLockoutEnabledAsync(user, enabled); if (!result.Succeeded) { + logger.LogWarning("Failed to set lockout enabled for user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully set lockout enabled for user {Username} to {Enabled}", user.UserName, enabled); return IdentityManagerResult.Success; } public virtual Task GetLockedOut(TUser user) => UserManager.IsLockedOutAsync(user); + public virtual async Task SetLockedOut(TUser user, bool locked) { + logger.LogInformation("Setting locked out status for user {Username} to {Locked}", user.UserName, locked); + if (locked) { var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); if (!result.Succeeded) { + logger.LogWarning("Failed to lock out user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully locked out user {Username}", user.UserName); } else { var result = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.MinValue); if (!result.Succeeded) { + logger.LogWarning("Failed to unlock user {Username}: {Error}", + user.UserName, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully unlocked user {Username}", user.UserName); } return IdentityManagerResult.Success; } - public virtual async Task SetName(TRole user, string name) + public virtual async Task SetName(TRole role, string name) { - var result = await RoleManager.SetRoleNameAsync(user, name); + logger.LogInformation("Setting role name from {OldName} to {NewName}", role.Name, name); + + var result = await RoleManager.SetRoleNameAsync(role, name); if (!result.Succeeded) { + logger.LogWarning("Failed to set role name from {OldName} to {NewName}: {Error}", + role.Name, name, result.Errors.First().Description); return new IdentityManagerResult(result.Errors.First().Description); } + logger.LogInformation("Successfully set role name to {NewName}", name); return IdentityManagerResult.Success; } protected virtual Task GetUserProperty(PropertyMetadata propMetadata, TUser user) { if (propMetadata.TryGet(user, out var val)) return val; + + logger.LogError("Invalid user property type: {PropertyType} for user {Username}", propMetadata.Type, user.UserName); throw new Exception("Invalid property type " + propMetadata.Type); } protected virtual Task SetUserProperty(IEnumerable propsMeta, TUser user, string type, string value) { if (propsMeta.TrySet(user, type, value, out var result)) return result; + + logger.LogError("Invalid user property type: {PropertyType} for user {Username}", type, user.UserName); throw new Exception("Invalid property type " + type); } @@ -605,9 +899,14 @@ protected virtual async Task DisplayNameFromUser(TUser user) { var claims = await UserManager.GetClaimsAsync(user); var name = claims.Where(x => x.Type == IdentityManagerConstants.ClaimTypes.Name).Select(x => x.Value).FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(name)) return name; + if (!string.IsNullOrWhiteSpace(name)) + { + logger.LogDebug("Display name for user {Username}: {DisplayName}", user.UserName, name); + return name; + } } + logger.LogDebug("No display name found for user {Username}", user.UserName); return null; } @@ -618,12 +917,18 @@ protected virtual IEnumerable ValidateUserProperty(string type, string v protected virtual void ValidateSupportsRoles() { - if (RoleManager == null) throw new InvalidOperationException("Roles Not Supported"); + if (RoleManager == null) + { + logger.LogError("Roles are not supported - RoleManager is null"); + throw new InvalidOperationException("Roles Not Supported"); + } } protected virtual Task GetRoleProperty(PropertyMetadata propMetadata, TRole role) { if (propMetadata.TryGet(role, out var val)) return val; + + logger.LogError("Invalid role property type: {PropertyType} for role {RoleName}", propMetadata.Type, role.Name); throw new Exception("Invalid property type " + propMetadata.Type); } @@ -635,6 +940,8 @@ protected virtual IEnumerable ValidateRoleProperty(string type, string v protected virtual Task SetRoleProperty(IEnumerable propsMeta, TRole role, string type, string value) { if (propsMeta.TrySet(role, type, value, out var result)) return result; + + logger.LogError("Invalid role property type: {PropertyType} for role {RoleName}", type, role.Name); throw new Exception("Invalid property type " + type); } }