Skip to content

Commit 36ef392

Browse files
Copilotascott18
andauthored
template: add safety checks to prevent removal of all user admins (#585)
--------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ascott18 <5017521+ascott18@users.noreply.github.com> Co-authored-by: Andrew Scott <andrew.scott@intellitect.com>
1 parent 23d0a40 commit 36ef392

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

  • templates/Coalesce.Vue.Template/content/Coalesce.Starter.Vue.Data/Models

templates/Coalesce.Vue.Template/content/Coalesce.Starter.Vue.Data/Models/Role.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,52 @@ public override ItemResult BeforeSave(SaveKind kind, Role? oldItem, Role item)
4646
}
4747
#endif
4848

49+
// Prevent removing UserAdmin permission if it would leave no user admins
50+
if (kind == SaveKind.Update && oldItem != null)
51+
{
52+
var oldHadUserAdmin = oldItem.Permissions?.Contains(Permission.UserAdmin) == true;
53+
var newHasUserAdmin = item.Permissions?.Contains(Permission.UserAdmin) == true;
54+
55+
if (oldHadUserAdmin && !newHasUserAdmin)
56+
{
57+
var result = CheckWouldLeaveNoUserAdmins(item.Id);
58+
if (!result.WasSuccessful) return result;
59+
}
60+
}
61+
4962
item.NormalizedName = roleManager.NormalizeKey(item.Name);
5063

5164
return base.BeforeSave(kind, oldItem, item);
5265
}
66+
67+
public override ItemResult BeforeDelete(Role item)
68+
{
69+
// Prevent deleting role with UserAdmin permission if it would leave no user admins
70+
if (item.Permissions?.Contains(Permission.UserAdmin) == true)
71+
{
72+
var result = CheckWouldLeaveNoUserAdmins(item.Id);
73+
if (!result.WasSuccessful) return result;
74+
}
75+
76+
return base.BeforeDelete(item);
77+
}
78+
79+
private ItemResult CheckWouldLeaveNoUserAdmins(string roleIdToExclude)
80+
{
81+
// Count users who have UserAdmin permission through roles other than the one being modified/deleted
82+
var adminUserCount = Db.Users
83+
.Where(u => u.UserRoles!.Any(ur =>
84+
ur.RoleId != roleIdToExclude &&
85+
ur.Role!.Permissions!.Contains(Permission.UserAdmin)))
86+
.Count();
87+
88+
if (adminUserCount == 0)
89+
{
90+
return "This action would leave the system with no user administrators. At least one user admin must remain.";
91+
}
92+
93+
return true;
94+
}
5395
}
5496
}
5597

@@ -74,4 +116,4 @@ public class RoleClaim : IdentityRoleClaim<string>
74116
[ForeignKey(nameof(RoleId))]
75117
public Role? Role { get; set; }
76118
}
77-
#endif
119+
#endif

templates/Coalesce.Vue.Template/content/Coalesce.Starter.Vue.Data/Models/UserRole.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,21 @@ public class Behaviors(
5757
SignInManager<User> signInManager
5858
) : AppBehaviors<UserRole>(context)
5959
{
60+
public override ItemResult BeforeDelete(UserRole item)
61+
{
62+
// Prevent removing the last user admin
63+
if (item.Role?.Permissions?.Contains(Permission.UserAdmin) == true)
64+
{
65+
var result = CheckWouldLeaveNoUserAdmins(item.UserId, item.RoleId);
66+
if (!result.WasSuccessful) return result;
67+
}
68+
69+
return base.BeforeDelete(item);
70+
}
71+
6072
public override async Task<ItemResult<UserRole>> AfterSaveAsync(SaveKind kind, UserRole? oldItem, UserRole item)
6173
{
62-
if (User.GetUserId() == item.Id)
74+
if (User.GetUserId() == item.UserId)
6375
{
6476
// If the user was editing their own roles, refresh their current sign-in immediately
6577
// so that it doesn't feel like nothing happened.
@@ -68,5 +80,22 @@ public override async Task<ItemResult<UserRole>> AfterSaveAsync(SaveKind kind, U
6880

6981
return true;
7082
}
83+
84+
private ItemResult CheckWouldLeaveNoUserAdmins(string userIdToExclude, string roleIdToExclude)
85+
{
86+
// Count users who have UserAdmin permission (excluding the user/role combination being removed)
87+
var adminUserCount = Db.Users
88+
.Where(u => u.UserRoles!.Any(ur =>
89+
!(ur.UserId == userIdToExclude && ur.RoleId == roleIdToExclude) &&
90+
ur.Role!.Permissions!.Contains(Permission.UserAdmin)))
91+
.Count();
92+
93+
if (adminUserCount == 0)
94+
{
95+
return "This action would leave the system with no user administrators. At least one user admin must remain.";
96+
}
97+
98+
return true;
99+
}
71100
}
72-
}
101+
}

0 commit comments

Comments
 (0)