From 87d6d7e764b372d66cd5de6e2491b47492885f74 Mon Sep 17 00:00:00 2001 From: Tygozwolle Date: Fri, 24 Jul 2026 20:29:11 +0200 Subject: [PATCH] feat: implement automated account enforcement service with RADIUS disconnect capabilities and add RADIUS management service --- .../Services/AccountEnforcementService.cs | 25 +++++++++---------- radiuscontroller/Services/RadiusService.cs | 23 +++++++++-------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/radiuscontroller/Services/AccountEnforcementService.cs b/radiuscontroller/Services/AccountEnforcementService.cs index 50a33af..c6ea89f 100644 --- a/radiuscontroller/Services/AccountEnforcementService.cs +++ b/radiuscontroller/Services/AccountEnforcementService.cs @@ -87,20 +87,19 @@ public class AccountEnforcementService : BackgroundService // Disable the account profile.Status = "Expired"; - // Remove Cleartext-Password from radcheck so FreeRADIUS rejects future auth - var checks = await db.RadCheck - .Where(rc => rc.Username == profile.Username) - .ToListAsync(ct); - db.RadCheck.RemoveRange(checks); - - // Insert Auth-Type := Reject to explicitly deny - db.RadCheck.Add(new RadCheck + // Scramble the password so the device prompts for new credentials + var check = await db.RadCheck.FirstOrDefaultAsync(rc => rc.Username == profile.Username && rc.Attribute == "Cleartext-Password", ct); + if (check != null) { - Username = profile.Username, - Attribute = "Auth-Type", - Op = ":=", - Value = "Reject" - }); + check.Value = "REJECT_" + Guid.NewGuid().ToString("N").Substring(0, 8); + } + + // Cleanup any old Auth-Type Reject entries + var rejectCheck = await db.RadCheck.FirstOrDefaultAsync(rc => rc.Username == profile.Username && rc.Attribute == "Auth-Type", ct); + if (rejectCheck != null) + { + db.RadCheck.Remove(rejectCheck); + } _logger.LogInformation("Account '{Username}' disabled. Reason: {Reason}", profile.Username, reason); diff --git a/radiuscontroller/Services/RadiusService.cs b/radiuscontroller/Services/RadiusService.cs index ac1bca5..cf0be54 100644 --- a/radiuscontroller/Services/RadiusService.cs +++ b/radiuscontroller/Services/RadiusService.cs @@ -272,17 +272,18 @@ public class RadiusService : IRadiusService profile.Status = "Revoked"; - // Remove Cleartext-Password and insert Auth-Type := Reject - var checks = await _db.RadCheck.Where(rc => rc.Username == profile.Username).ToListAsync(); - _db.RadCheck.RemoveRange(checks); - - _db.RadCheck.Add(new RadCheck + // Scramble the password instead of Auth-Type := Reject so devices prompt for new credentials + var check = await _db.RadCheck.FirstOrDefaultAsync(rc => rc.Username == profile.Username && rc.Attribute == "Cleartext-Password"); + if (check != null) { - Username = profile.Username, - Attribute = "Auth-Type", - Op = ":=", - Value = "Reject" - }); + check.Value = "REJECT_" + Guid.NewGuid().ToString("N").Substring(0, 8); + } + + var rejectCheck = await _db.RadCheck.FirstOrDefaultAsync(rc => rc.Username == profile.Username && rc.Attribute == "Auth-Type"); + if (rejectCheck != null) + { + _db.RadCheck.Remove(rejectCheck); + } await _db.SaveChangesAsync(); @@ -331,7 +332,7 @@ public class RadiusService : IRadiusService }); } - if (profile.Status == "Revoked") + if (profile.Status == "Revoked" || profile.Status == "Expired") { profile.Status = "Active"; var rejectCheck = await _db.RadCheck.FirstOrDefaultAsync(rc => rc.Username == profile.Username && rc.Attribute == "Auth-Type");