diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/IEntraIDSecurityGroup.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/IEntraIDSecurityGroup.cs index adf033a..b2add4e 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/IEntraIDSecurityGroup.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/IEntraIDSecurityGroup.cs @@ -9,6 +9,7 @@ namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId public interface IEntraIDSecurityGroup { public Guid Id { get; set; } + public string Name { get; set; } public Guid? EntraIdGroupId { get; set; } } } \ No newline at end of file diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs index 19bcc74..b1469b5 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs @@ -17,6 +17,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Security.AccessControl; using System.Security.Claims; using System.Threading.Tasks; using static IdentityModel.OidcConstants; @@ -26,11 +27,12 @@ namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId public class MicrosoftEntraEasyAuthProvider : DefaultAuthProvider where TContext : DynamicContext - where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup + where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup,new() where TSecurityGroupMember : DynamicEntity, ISecurityGroupMember, new() where TIdentity : DynamicEntity, IIdentity { private readonly IOptions _options; + private readonly IOptions _frameworkOptions; private readonly IHttpClientFactory _clientFactory; @@ -38,9 +40,11 @@ public MicrosoftEntraEasyAuthProvider() :base("MicrosoftEntraId", HttpMethod.Pos public MicrosoftEntraEasyAuthProvider( IOptions options, + IOptions frameworkOptions, IHttpClientFactory clientFactory) : this() { _options = options ?? throw new System.ArgumentNullException(nameof(options)); + _frameworkOptions = frameworkOptions; _clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); } @@ -169,6 +173,9 @@ private async Task SyncUserGroup(ClaimsPrincipal identity, List groupIds, // Fetch in memory var groupMembersDict = await groupMembersQuery.ToDictionaryAsync(sgm => sgm.Id); + await EnsureAccessGroupsCreated(db); + + // Fetch all security groups var groupsDict = await db.Set() .Where(sg => groupMembersQuery.Any(sgm => sgm.SecurityGroupId == sg.Id) || @@ -176,6 +183,8 @@ private async Task SyncUserGroup(ClaimsPrincipal identity, List groupIds, .ToDictionaryAsync(sg => sg.Id); + + // Fetch specific security group and group members var sgGroupSpecific = groupsDict.Values.Where(sg => sg.EntraIdGroupId != null && groupIds.Contains(sg.EntraIdGroupId.Value)).ToDictionary(sg => sg.Id); var sgmGroupSpecific = groupMembersDict.Values.Where(sgm => sgm.SecurityGroupId != null && sgGroupSpecific.ContainsKey((Guid) sgm.SecurityGroupId)); @@ -197,7 +206,7 @@ private async Task SyncUserGroup(ClaimsPrincipal identity, List groupIds, // Fecth expired group members by comparing the "historical" group members with that of the current based on the group ids var expiredGroupMembers = groupMembersDict.Values.Where(sgm => - !sgmGroupSpecific.Any(x => x.Id == sgm.Id) && + !sgmGroupSpecific.Any(x => x.Id == sgm.Id) && sgm.SecurityGroupId != null && groupsDict[(Guid) sgm.SecurityGroupId].EntraIdGroupId != null); // Groups of higher aurthority has no EntraGroupId and should not be removed foreach (var sgm in expiredGroupMembers) @@ -209,6 +218,28 @@ private async Task SyncUserGroup(ClaimsPrincipal identity, List groupIds, if (isDirty) await db.SaveChangesAsync(identity); } + private async Task EnsureAccessGroupsCreated(EAVDBContext db) + { + var groups = _options.Value.AccessGroups.Where(kv => !string.IsNullOrEmpty(kv.Value)).Select(c => c.Key).ToArray(); + var existingGrouos = await db.Set().Where(g => groups.Contains(g.Name)).ToListAsync(); + var missingGroups = groups.Except(existingGrouos.Select(g => g.Name)).ToArray(); + foreach (var missingGroup in missingGroups) + { + var group = new TSecurityGroup(); + group.Name = missingGroup; + group.EntraIdGroupId = Guid.Parse(_options.Value.AccessGroups[missingGroup]); + db.Add(group); + + existingGrouos.Add(group); + } + foreach (var group in existingGrouos) + { + if (!group.EntraIdGroupId.HasValue) + group.EntraIdGroupId = Guid.Parse(_options.Value.AccessGroups[group.Name]); + } + await db.SaveChangesAsync(_frameworkOptions.Value.SystemAdministratorIdentity); + } + public RequestDelegate OnSignedOut() { throw new System.NotImplementedException(); diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs index 06417bd..395b4fc 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs @@ -31,7 +31,7 @@ public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth getMicrosoftAuthorizationUrl , Func getMicrosoftTokenEndpoint) where TContext : DynamicContext where TIdentity: DynamicEntity,IIdentity - where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup + where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup,new() where TSecurityGroupMemeber : DynamicEntity, ISecurityGroupMember, new() { builder.AddAuthenticationProvider, @@ -53,7 +53,7 @@ public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth, Task> findIdentityAsync) where TContext : DynamicContext where TIdentity : DynamicEntity, IIdentity - where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup + where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup,new() where TSecurityGroupMemeber : DynamicEntity, ISecurityGroupMember,new() { builder.AddAuthenticationProvider, MicrosoftEntraIdEasyAuthOptions, IConfiguration>((options, config) => diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs index 15b3f73..ca002eb 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs @@ -55,6 +55,7 @@ private static string DefaultGetMicrosoftTokenEndpoint(HttpContext context) return $"https://login.microsoftonline.com/{options.Value.TenantId}/oauth2/v2.0/token"; } - + public Dictionary AccessGroups { get; set; } = new Dictionary(); + } } \ No newline at end of file