Ability to restrict a user's ability to change passwords (#1018)

* Implemented a new role "Change Password". This role allows you to change your own password. By default, all users will have it. A user can have it removed arbitrarliy.

Removed components that are no longer going to be used.

* Cleaned up some code
This commit is contained in:
Joseph Milazzo 2022-02-01 07:40:41 -08:00 committed by GitHub
parent 9d20343f4e
commit 6ee8320c2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 48 additions and 174 deletions

View file

@ -20,8 +20,12 @@ namespace API.Constants
/// Used to give a user ability to download files from the server
/// </summary>
public const string DownloadRole = "Download";
/// <summary>
/// Used to give a user ability to change their own password
/// </summary>
public const string ChangePasswordRole = "Change Password";
public static readonly ImmutableArray<string> ValidRoles =
ImmutableArray.Create(AdminRole, PlebRole, DownloadRole);
ImmutableArray.Create(AdminRole, PlebRole, DownloadRole, ChangePasswordRole);
}
}

View file

@ -73,7 +73,7 @@ namespace API.Controllers
_logger.LogInformation("{UserName} is changing {ResetUser}'s password", User.GetUsername(), resetPasswordDto.UserName);
var user = await _userManager.Users.SingleAsync(x => x.UserName == resetPasswordDto.UserName);
if (resetPasswordDto.UserName != User.GetUsername() && !User.IsInRole(PolicyConstants.AdminRole))
if (resetPasswordDto.UserName != User.GetUsername() && !(User.IsInRole(PolicyConstants.AdminRole) || User.IsInRole(PolicyConstants.ChangePasswordRole)))
return Unauthorized("You are not permitted to this operation.");
var errors = await _accountService.ChangeUserPassword(user, resetPasswordDto.Password);
@ -245,45 +245,6 @@ namespace API.Controllers
f => (string) f.GetValue(null)).Values.ToList();
}
/// <summary>
/// Sets the given roles to the user.
/// </summary>
/// <param name="updateRbsDto"></param>
/// <returns></returns>
[HttpPost("update-rbs")]
public async Task<ActionResult> UpdateRoles(UpdateRbsDto updateRbsDto)
{
var user = await _userManager.Users
.Include(u => u.UserPreferences)
.SingleOrDefaultAsync(x => x.NormalizedUserName == updateRbsDto.Username.ToUpper());
if (updateRbsDto.Roles.Contains(PolicyConstants.AdminRole) ||
updateRbsDto.Roles.Contains(PolicyConstants.PlebRole))
{
return BadRequest("Invalid Roles");
}
var existingRoles = (await _userManager.GetRolesAsync(user))
.Where(s => s != PolicyConstants.AdminRole && s != PolicyConstants.PlebRole)
.ToList();
// Find what needs to be added and what needs to be removed
var rolesToRemove = existingRoles.Except(updateRbsDto.Roles);
var result = await _userManager.AddToRolesAsync(user, updateRbsDto.Roles);
if (!result.Succeeded)
{
await _unitOfWork.RollbackAsync();
return BadRequest("Something went wrong, unable to update user's roles");
}
if ((await _userManager.RemoveFromRolesAsync(user, rolesToRemove)).Succeeded)
{
return Ok();
}
await _unitOfWork.RollbackAsync();
return BadRequest("Something went wrong, unable to update user's roles");
}
/// <summary>
/// Resets the API Key assigned with a user

View file

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using API.Constants;
using API.Entities;
using Microsoft.AspNetCore.Identity;
namespace API.Data;
public static class MigrateChangePasswordRoles
{
public static async Task Migrate(IUnitOfWork unitOfWork, UserManager<AppUser> userManager)
{
foreach (var user in await unitOfWork.UserRepository.GetAllUsers())
{
await userManager.RemoveFromRoleAsync(user, "ChangePassword");
await userManager.AddToRoleAsync(user, PolicyConstants.ChangePasswordRole);
}
}
}

View file

@ -52,6 +52,7 @@ public interface IUserRepository
Task<AppUser> GetUserWithReadingListsByUsernameAsync(string username);
Task<IList<AppUserBookmark>> GetAllBookmarksByIds(IList<int> bookmarkIds);
Task<AppUser> GetUserByEmailAsync(string email);
Task<IEnumerable<AppUser>> GetAllUsers();
}
public class UserRepository : IUserRepository
@ -214,6 +215,11 @@ public class UserRepository : IUserRepository
return await _context.AppUser.SingleOrDefaultAsync(u => u.Email.ToLower().Equals(email.ToLower()));
}
public async Task<IEnumerable<AppUser>> GetAllUsers()
{
return await _context.AppUser.ToListAsync();
}
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
{
return await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);

View file

@ -67,6 +67,7 @@ namespace API.Extensions
{
opt.AddPolicy("RequireAdminRole", policy => policy.RequireRole(PolicyConstants.AdminRole));
opt.AddPolicy("RequireDownloadRole", policy => policy.RequireRole(PolicyConstants.DownloadRole, PolicyConstants.AdminRole));
opt.AddPolicy("RequireChangePasswordRole", policy => policy.RequireRole(PolicyConstants.ChangePasswordRole, PolicyConstants.AdminRole));
});
return services;

View file

@ -6,6 +6,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using API.Data;
using API.Entities;
using API.Extensions;
using API.Middleware;
using API.Services;
@ -20,6 +21,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
@ -140,27 +142,16 @@ namespace API
Task.Run(async () =>
{
// Apply all migrations on startup
// If we have pending migrations, make a backup first
//var isDocker = new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker;
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
var context = serviceProvider.GetRequiredService<DataContext>();
// var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
// if (pendingMigrations.Any())
// {
// logger.LogInformation("Performing backup as migrations are needed");
// await backupService.BackupDatabase();
// }
//
// await context.Database.MigrateAsync();
// var roleManager = serviceProvider.GetRequiredService<RoleManager<AppRole>>();
//
// await Seed.SeedRoles(roleManager);
// await Seed.SeedSettings(context, directoryService);
// await Seed.SeedUserApiKeys(context);
var userManager = serviceProvider.GetRequiredService<UserManager<AppUser>>();
await MigrateBookmarks.Migrate(directoryService, unitOfWork,
logger, cacheService);
await MigrateChangePasswordRoles.Migrate(unitOfWork, userManager);
var requiresCoverImageMigration = !Directory.Exists(directoryService.CoverImageDirectory);
try
{