mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
fix(permissions): clean up overrides on user delete and converge concurrent override writes (SH-328)
DeleteUserWithCascadeAsync never removed UserPermissionOverrides rows, and the FK on UserId is Restrict. Once an override was saved for a member, the admin hard-delete (DeleteUserAsync -> DeleteUserWithCascadeAsync) failed the foreign key inside the transaction and the controller surfaced it as a 400, so the user was never deleted. Add an explicit ExecuteDelete on UserPermissionOverrides before the user is removed, matching how UserRoles is already cleared in the same method (no schema change). SetOverrideAsync was check-then-insert on the composite key with no DbUpdateException handling, so two concurrent PUTs for the same (UserId, PermissionKey) let the loser violate PK_UserPermissionOverrides and return 500. Catch the conflict, detach the pending insert, and converge by updating the persisted row to the caller's requested state.
This commit is contained in:
parent
441735d39d
commit
af441894d7
2 changed files with 34 additions and 13 deletions
|
|
@ -63,22 +63,39 @@ public sealed class TeamPermissionOverrideDataService : ITeamPermissionOverrideD
|
|||
&& permission.PermissionKey == permissionKey,
|
||||
cancellationToken);
|
||||
|
||||
if (existing is null)
|
||||
{
|
||||
await _context.UserPermissionOverrides.AddAsync(
|
||||
new UserPermissionOverride
|
||||
{
|
||||
UserId = userId,
|
||||
PermissionKey = permissionKey,
|
||||
State = state
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
else
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.State = state;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
var addition = new UserPermissionOverride
|
||||
{
|
||||
UserId = userId,
|
||||
PermissionKey = permissionKey,
|
||||
State = state
|
||||
};
|
||||
await _context.UserPermissionOverrides.AddAsync(addition, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
// A concurrent PUT inserted the same (UserId, PermissionKey) between our
|
||||
// existence check and this save, violating PK_UserPermissionOverrides.
|
||||
// Converge on the caller's intent by updating the persisted row.
|
||||
_context.Entry(addition).State = EntityState.Detached;
|
||||
|
||||
var winner = await _context.UserPermissionOverrides
|
||||
.SingleAsync(
|
||||
permission => permission.UserId == userId
|
||||
&& permission.PermissionKey == permissionKey,
|
||||
cancellationToken);
|
||||
winner.State = state;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,10 @@ namespace SeaHaven.DataServices.Implementation
|
|||
.Where(role => role.UserId == id)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
await _context.UserPermissionOverrides
|
||||
.Where(permissionOverride => permissionOverride.UserId == id)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
await _context.Users
|
||||
.Where(existingUser => existingUser.Id == id)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue