Skip to content

Commit

Permalink
feat: added security groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon S. Pedersen committed May 26, 2024
1 parent 74217ea commit 498e230
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId
[EntityInterface(EntityKey = "Security Group")]
public interface IEntraIDSecurityGroup
{

public Guid Id { get; set; }
public Guid? EntraIdGroupId { get; set; }
}


}
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
using EAVFramework;
using EAVFramework.Authentication;
using EAVFramework.Endpoints;
using EAVFramework.Extensions;
using EAVFW.Extensions.SecurityModel;
using IdentityModel;
using IdentityModel.Client;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using static IdentityModel.OidcConstants;
using static System.Net.WebRequestMethods;

namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId
{

public class MicrosoftEntraEasyAuthProvider<TSecurityGroup,TSecurityGroupMember> : IEasyAuthProvider
public class MicrosoftEntraEasyAuthProvider<TSecurityGroup, TSecurityGroupMember> : IEasyAuthProvider
where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup
where TSecurityGroupMember : DynamicEntity, ISecurityGroupMember
where TSecurityGroupMember : DynamicEntity, ISecurityGroupMember, new()
{
private readonly IOptions<MicrosoftEntraIdEasyAuthOptions> _options;
private readonly IHttpClientFactory _clientFactory;
private readonly EAVDBContext<DynamicContext> _db;

public string AuthenticationName => "MicrosoftEntraId";

Expand All @@ -44,12 +35,12 @@ public class MicrosoftEntraEasyAuthProvider<TSecurityGroup,TSecurityGroupMember>

public MicrosoftEntraEasyAuthProvider() { }

public MicrosoftEntraEasyAuthProvider(IOptions<MicrosoftEntraIdEasyAuthOptions> options,
IHttpClientFactory clientFactory, EAVDBContext<DynamicContext> db)
public MicrosoftEntraEasyAuthProvider(
IOptions<MicrosoftEntraIdEasyAuthOptions> options,
IHttpClientFactory clientFactory)
{
_options = options ?? throw new System.ArgumentNullException(nameof(options));
_clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory));
_db = db;
}

public async Task OnAuthenticate(HttpContext httpcontext, string handleId, string redirectUrl)

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 46 in src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs

View workflow job for this annotation

GitHub Actions / Building

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
Expand Down Expand Up @@ -98,24 +89,71 @@ public async Task OnAuthenticate(HttpContext httpcontext, string handleId, strin
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(response.IdentityToken);

var groupId = jwtSecurityToken.Claims.First(claim => claim.Type == "groups").Value;
// Get string of group claims from the token
var groupClaims = jwtSecurityToken.Claims.Where(c => c.Type == "groups");
if (!groupClaims.Any())
{
httpcontext.Response.Redirect($"{httpcontext.Request.Scheme}://{httpcontext.Request.Host}callback?error=access_denied&error_subcode=group_not_found");
//return;
}
// Get the group ids from the claims
var groupIds = groupClaims.Select(c => new Guid(c.Value)).ToList();
var db = httpcontext.RequestServices.GetRequiredService<EAVDBContext<DynamicContext>>();

var groupids = new string[] { "", "" };
await SyncUserGroup(identity, groupIds, db);

var sgs = _db.Set<TSecurityGroup>();
var assignments = _db.Set<TSecurityGroupMember>();
return (identity, redirectUri, handleId);
}

private async Task SyncUserGroup(ClaimsPrincipal identity, List<Guid> groupIds, EAVDBContext<DynamicContext> db)
{
var claimDict = identity.Claims.ToDictionary(c => c.Type, c => c.Value);
var userId = new Guid(claimDict["sub"]);

//Add securityMember for the group ids where found
// Fetch all security group members for user
var groupMembersQuery = db.Set<TSecurityGroupMember>()
.Where(sgm => sgm.IdentityId == userId);
// Fetch in memory
var groupMembersDict = await groupMembersQuery.ToDictionaryAsync(sgm => sgm.Id);

// Fetch all security groups
var groupsDict = await db.Set<TSecurityGroup>()
.Where(sg => groupMembersQuery.Any(sgm => sgm.SecurityGroupId == sg.Id) ||
(sg.EntraIdGroupId != null && groupIds.Contains(sg.EntraIdGroupId.Value)))
.ToDictionaryAsync(sg => sg.Id);

//Remove securirymember from existing that was not given in groupids.


// 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));

// Check if member group exists else add it
bool isDirty = false;
foreach (var sg in sgGroupSpecific.Values)
{
if (!sgmGroupSpecific.Any(sgm => sgm.SecurityGroupId == sg.Id))
{
var sgm = new TSecurityGroupMember();
sgm.IdentityId = userId;
sgm.SecurityGroupId = sg.Id;
db.Add(sgm);

isDirty = true;
}
}

// 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) &&
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)
{
db.Remove(sgm);
isDirty = true;
}

return await Task.FromResult((new ClaimsPrincipal(identity), redirectUri, handleId));
if (isDirty) await db.SaveChangesAsync(identity);
}

public RequestDelegate OnSignedOut()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ public class GroupMatcherService<TSecurityGroup>
{

}

public static class MicrosoftEntraIdEasyAuthExtensions
{
public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth<TSecurityGroup,TSecurityGroupMemeber>(
public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth<TSecurityGroup, TSecurityGroupMemeber>(
this AuthenticatedEAVFrameworkBuilder builder,
Func<HttpContext, string, TokenResponse, Task<ClaimsPrincipal>> validateUserAsync,
Func<HttpContext, string> getMicrosoftAuthorizationUrl, Func<HttpContext, string> getMicrosoftTokenEndpoint)
where TSecurityGroup : DynamicEntity, IEntraIDSecurityGroup
where TSecurityGroupMemeber : DynamicEntity, ISecurityGroupMember
where TSecurityGroupMemeber : DynamicEntity, ISecurityGroupMember, new()
{
builder.AddAuthenticationProvider<MicrosoftEntraEasyAuthProvider<TSecurityGroup,TSecurityGroupMemeber>, MicrosoftEntraIdEasyAuthOptions,IConfiguration>((options, config) =>
{
Expand Down

0 comments on commit 498e230

Please sign in to comment.