Skip to content

Commit

Permalink
Version 1.1.1
Browse files Browse the repository at this point in the history
Bugfix in delete personal best record
Add change password function
  • Loading branch information
pbijdens committed Oct 16, 2024
1 parent d4e7dc6 commit 2b6cd5d
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 5 deletions.
17 changes: 17 additions & 0 deletions CentaurScores/Controllers/PersonalBestContoller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,23 @@ public async Task<ActionResult<PersonalBestListEntryModel>> GetPersonalBestListE
return await personalBestService.UpdatePersonalBestListEntry(memberListId, personalBestListId, model);
}

/// <summary>
/// Deleted a single personal best list entry by ID.
/// </summary>
/// <param name="memberListId">The parent participant list ID.</param>
/// <param name="personalBestListId">The ID of the list.</param>
/// <param name="memberId">The ID of the record that is to be updated.</param>
/// <param name="model">The new metadata model.</param>

Check warning on line 141 in CentaurScores/Controllers/PersonalBestContoller.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has a param tag for 'model', but there is no parameter by that name
/// <returns>The updated model.</returns>
[HttpDelete("{personalBestListId}/members/{memberId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<int>> DeletePersonalBestListEntry([FromRoute] int memberListId, [FromRoute] int personalBestListId, [FromRoute] int memberId)
{
return await personalBestService.DeletePersonalBestListEntry(memberListId, personalBestListId, memberId);
}

/// <summary>
/// Add a new personal best record to a list.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions CentaurScores/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ public async Task<ActionResult<UserModel>> UpdateUser(UserModel model)
return await authorizationService.UpdateUser(HttpContext.User, model);
}

/// <summary>
/// Updates a user's password.
/// </summary>
/// <returns></returns>
[HttpPut("password")]
[Authorize]
public async Task<ActionResult<UserModel>> UpdatePassword(UserModel model)
{
return await authorizationService.UpdatePassword(HttpContext.User, model);
}

/// <summary>
/// Updates an ACL.
/// </summary>
Expand Down
67 changes: 63 additions & 4 deletions CentaurScores/Services/AuthorizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CentaurScores.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
Expand All @@ -11,7 +12,7 @@ namespace CentaurScores.Services
/// <summary>
/// Authorization service
/// </summary>
public class AuthorizationService(IConfiguration configuration) : IAuthorizationService
public class AuthorizationService(IConfiguration configuration, IOptions<AppSettings> appSettings) : IAuthorizationService
{
/// <inheritdoc/>
public async Task<UserACLModel> CreateACL(ClaimsPrincipal loggedInUser, UserACLModel model)
Expand Down Expand Up @@ -56,6 +57,10 @@ public async Task<int> DeleteACL(ClaimsPrincipal loggedInUser, int aclId)
EnsureLoggedInUserIsAdmin(db, loggedInUser);
AclEntity aclEntity = db.ACLs.SingleOrDefault(a => a.Id == aclId) ?? throw new ArgumentException("Bad ID", nameof(aclId));
db.ACLs.Remove(aclEntity);

AccountEntity accountEntity = db.Accounts.SingleOrDefault(a => a.Id == GetUserIDForLoggedInUser(loggedInUser)) ?? throw new ArgumentException("Bad ID", nameof(loggedInUser));
EnsureUserIsStillAdministrator(loggedInUser, db, accountEntity); // throws on failure

await db.SaveChangesAsync();
return 1;
}
Expand All @@ -65,6 +70,12 @@ public async Task<int> DeleteUser(ClaimsPrincipal loggedInUser, int userId)
{
using var db = new CentaurScoresDbContext(configuration);
EnsureLoggedInUserIsAdmin(db, loggedInUser);

if (GetUserIDForLoggedInUser(loggedInUser) == userId)
{
throw new ArgumentException("Can't delete your own account", nameof(userId));
}

AccountEntity accountEntity = db.Accounts.SingleOrDefault(a => a.Id == userId) ?? throw new ArgumentException("Bad ID", nameof(userId));
db.Accounts.Remove(accountEntity);
await db.SaveChangesAsync();
Expand Down Expand Up @@ -103,7 +114,7 @@ public async Task<UserModel> UpdateUser(ClaimsPrincipal loggedInUser, UserModel
{
using var db = new CentaurScoresDbContext(configuration);
AccountEntity accountEntity = db.Accounts.Include(a => a.ACLs).SingleOrDefault(a => a.Id == model.Id) ?? throw new ArgumentException("Bad ID", nameof(model));
if (accountEntity.Username != model.Username)
if (!string.IsNullOrWhiteSpace(model.Username) && accountEntity.Username != model.Username)
{
EnsureLoggedInUserIsAdmin(db, loggedInUser);
if (db.Accounts.Where(n => n.Username == model.Username).Count() != 0)
Expand All @@ -115,8 +126,10 @@ public async Task<UserModel> UpdateUser(ClaimsPrincipal loggedInUser, UserModel
if (!string.IsNullOrWhiteSpace(model.Password))
{
EnsureLoggedInUserIsAdminOrSelf(db, loggedInUser, model.Id);
if (accountEntity.SaltedPasswordHash.Length > 4)
if (IsTargetUSerSameAsLoggedInUser(db, loggedInUser, model.Id) && accountEntity.SaltedPasswordHash.Length > 4)
{
// If the user wants to change their own password and they are not an admin
// they need to provide proof they know the current password
CheckCurrentPassword(model, accountEntity);
}
string salt = $"{Guid.NewGuid()}".Substring(0, 4);
Expand Down Expand Up @@ -145,15 +158,47 @@ public async Task<UserModel> UpdateUser(ClaimsPrincipal loggedInUser, UserModel
if (hasChange)
{
EnsureLoggedInUserIsAdmin(db, loggedInUser);
EnsureUserIsStillAdministrator(loggedInUser, db, accountEntity);
}
}
await db.SaveChangesAsync();
return db.Accounts.Include(a => a.ACLs).Single(a => a.Id == model.Id).ToModel();
}

/// <inheritdoc/>
public async Task<UserModel> UpdatePassword(ClaimsPrincipal loggedInUser, UserModel model)
{
using var db = new CentaurScoresDbContext(configuration);
AccountEntity accountEntity = db.Accounts.Include(a => a.ACLs).SingleOrDefault(a => a.Id == model.Id) ?? throw new ArgumentException("Bad ID", nameof(model));
if (!string.IsNullOrWhiteSpace(model.Password))
{
EnsureLoggedInUserIsAdminOrSelf(db, loggedInUser, model.Id);
if (IsTargetUSerSameAsLoggedInUser(db, loggedInUser, model.Id) && accountEntity.SaltedPasswordHash.Length > 4)
{
// If the user wants to change their own password and they are not an admin
// they need to provide proof they know the current password
CheckCurrentPassword(model, accountEntity);
}
string salt = $"{Guid.NewGuid()}".Substring(0, 4);
string newHashedPassword = salt + TokenService.CalculateSha256Hash(salt + model.Password);
accountEntity.SaltedPasswordHash = newHashedPassword;
}
await db.SaveChangesAsync();
return db.Accounts.Include(a => a.ACLs).Single(a => a.Id == model.Id).ToModel();
}

private void EnsureUserIsStillAdministrator(ClaimsPrincipal loggedInUser, CentaurScoresDbContext db, AccountEntity accountEntity)
{
if (IsTargetUSerSameAsLoggedInUser(db, loggedInUser, accountEntity.Id) &&
(!(accountEntity.ACLs ?? []).Any(acl => acl.Id == appSettings.Value.AdminACLId)))
{
throw new UnauthorizedAccessException("You can't remove your own admin rights");
}
}

private void EnsureLoggedInUserIsAdminOrSelf(CentaurScoresDbContext db, ClaimsPrincipal loggedInUser, int? id)
{
if (loggedInUser.Claims.FirstOrDefault(c => c.Type == TokenService.IdClaim)?.Value == $"{id}")
if (GetUserIDForLoggedInUser(loggedInUser) == id)
{
return;
}
Expand All @@ -163,6 +208,20 @@ private void EnsureLoggedInUserIsAdminOrSelf(CentaurScoresDbContext db, ClaimsPr
}
}

private static int GetUserIDForLoggedInUser(ClaimsPrincipal loggedInUser)
{
return int.Parse(loggedInUser.Claims.FirstOrDefault(c => c.Type == TokenService.IdClaim)?.Value ?? "-1");
}

private bool IsTargetUSerSameAsLoggedInUser(CentaurScoresDbContext db, ClaimsPrincipal loggedInUser, int? id)
{
if (loggedInUser.Claims.FirstOrDefault(c => c.Type == TokenService.IdClaim)?.Value == $"{id}")
{
return true;
}
return false;
}

private void EnsureLoggedInUserIsAdmin(CentaurScoresDbContext db, ClaimsPrincipal loggedInUser)
{
if (!loggedInUser.Claims.Any(c => c.Type == TokenService.IsAdministratorClaim))
Expand Down
1 change: 1 addition & 0 deletions CentaurScores/Services/IAuthorizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ public interface IAuthorizationService
/// <param name="model"></param>
/// <returns></returns>
Task<UserACLModel> UpdateACL(ClaimsPrincipal loggedInUser, UserACLModel model);
Task<UserModel> UpdatePassword(ClaimsPrincipal loggedInUser, UserModel model);

Check warning on line 64 in CentaurScores/Services/IAuthorizationService.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'IAuthorizationService.UpdatePassword(ClaimsPrincipal, UserModel)'
}
}
2 changes: 1 addition & 1 deletion CentaurScores/version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "1.1.0"
"version": "1.1.1"
}

0 comments on commit 2b6cd5d

Please sign in to comment.