1
1
using EAVFramework ;
2
2
using EAVFramework . Authentication ;
3
3
using EAVFramework . Endpoints ;
4
- using EAVFramework . Extensions ;
5
4
using EAVFW . Extensions . SecurityModel ;
6
- using IdentityModel ;
7
5
using IdentityModel . Client ;
8
6
using Microsoft . AspNetCore . Http ;
9
- using Microsoft . AspNetCore . Mvc ;
10
- using Microsoft . AspNetCore . WebUtilities ;
7
+ using Microsoft . EntityFrameworkCore ;
8
+ using Microsoft . Extensions . DependencyInjection ;
11
9
using Microsoft . Extensions . Options ;
12
- using Newtonsoft . Json ;
13
- using Newtonsoft . Json . Linq ;
14
10
using System ;
15
11
using System . Collections . Generic ;
16
12
using System . IdentityModel . Tokens . Jwt ;
17
13
using System . IO ;
18
14
using System . Linq ;
19
15
using System . Net . Http ;
20
- using System . Net . Sockets ;
21
16
using System . Security . Claims ;
22
- using System . Text ;
23
17
using System . Threading . Tasks ;
24
- using System . Web ;
25
18
using static IdentityModel . OidcConstants ;
26
- using static System . Net . WebRequestMethods ;
27
19
28
20
namespace EAVFW . Extensions . EasyAuth . MicrosoftEntraId
29
21
{
30
22
31
- public class MicrosoftEntraEasyAuthProvider < TSecurityGroup , TSecurityGroupMember > : IEasyAuthProvider
23
+ public class MicrosoftEntraEasyAuthProvider < TSecurityGroup , TSecurityGroupMember > : IEasyAuthProvider
32
24
where TSecurityGroup : DynamicEntity , IEntraIDSecurityGroup
33
- where TSecurityGroupMember : DynamicEntity , ISecurityGroupMember
25
+ where TSecurityGroupMember : DynamicEntity , ISecurityGroupMember , new ( )
34
26
{
35
27
private readonly IOptions < MicrosoftEntraIdEasyAuthOptions > _options ;
36
28
private readonly IHttpClientFactory _clientFactory ;
37
- private readonly EAVDBContext < DynamicContext > _db ;
38
29
39
30
public string AuthenticationName => "MicrosoftEntraId" ;
40
31
@@ -44,12 +35,12 @@ public class MicrosoftEntraEasyAuthProvider<TSecurityGroup,TSecurityGroupMember>
44
35
45
36
public MicrosoftEntraEasyAuthProvider ( ) { }
46
37
47
- public MicrosoftEntraEasyAuthProvider ( IOptions < MicrosoftEntraIdEasyAuthOptions > options ,
48
- IHttpClientFactory clientFactory , EAVDBContext < DynamicContext > db )
38
+ public MicrosoftEntraEasyAuthProvider (
39
+ IOptions < MicrosoftEntraIdEasyAuthOptions > options ,
40
+ IHttpClientFactory clientFactory )
49
41
{
50
42
_options = options ?? throw new System . ArgumentNullException ( nameof ( options ) ) ;
51
43
_clientFactory = clientFactory ?? throw new ArgumentNullException ( nameof ( clientFactory ) ) ;
52
- _db = db ;
53
44
}
54
45
55
46
public async Task OnAuthenticate ( HttpContext httpcontext , string handleId , string redirectUrl )
@@ -98,24 +89,71 @@ public async Task OnAuthenticate(HttpContext httpcontext, string handleId, strin
98
89
var handler = new JwtSecurityTokenHandler ( ) ;
99
90
var jwtSecurityToken = handler . ReadJwtToken ( response . IdentityToken ) ;
100
91
101
- var groupId = jwtSecurityToken . Claims . First ( claim => claim . Type == "groups" ) . Value ;
92
+ // Get string of group claims from the token
93
+ var groupClaims = jwtSecurityToken . Claims . Where ( c => c . Type == "groups" ) ;
94
+ if ( ! groupClaims . Any ( ) )
95
+ {
96
+ httpcontext . Response . Redirect ( $ "{ httpcontext . Request . Scheme } ://{ httpcontext . Request . Host } callback?error=access_denied&error_subcode=group_not_found") ;
97
+ //return;
98
+ }
99
+ // Get the group ids from the claims
100
+ var groupIds = groupClaims . Select ( c => new Guid ( c . Value ) ) . ToList ( ) ;
101
+ var db = httpcontext . RequestServices . GetRequiredService < EAVDBContext < DynamicContext > > ( ) ;
102
102
103
- var groupids = new string [ ] { "" , "" } ;
103
+ await SyncUserGroup ( identity , groupIds , db ) ;
104
104
105
- var sgs = _db . Set < TSecurityGroup > ( ) ;
106
- var assignments = _db . Set < TSecurityGroupMember > ( ) ;
105
+ return ( identity , redirectUri , handleId ) ;
106
+ }
107
107
108
+ private async Task SyncUserGroup ( ClaimsPrincipal identity , List < Guid > groupIds , EAVDBContext < DynamicContext > db )
109
+ {
110
+ var claimDict = identity . Claims . ToDictionary ( c => c . Type , c => c . Value ) ;
111
+ var userId = new Guid ( claimDict [ "sub" ] ) ;
108
112
109
- //Add securityMember for the group ids where found
113
+ // Fetch all security group members for user
114
+ var groupMembersQuery = db . Set < TSecurityGroupMember > ( )
115
+ . Where ( sgm => sgm . IdentityId == userId ) ;
116
+ // Fetch in memory
117
+ var groupMembersDict = await groupMembersQuery . ToDictionaryAsync ( sgm => sgm . Id ) ;
110
118
119
+ // Fetch all security groups
120
+ var groupsDict = await db . Set < TSecurityGroup > ( )
121
+ . Where ( sg => groupMembersQuery . Any ( sgm => sgm . SecurityGroupId == sg . Id ) ||
122
+ ( sg . EntraIdGroupId != null && groupIds . Contains ( sg . EntraIdGroupId . Value ) ) )
123
+ . ToDictionaryAsync ( sg => sg . Id ) ;
111
124
112
- //Remove securirymember from existing that was not given in groupids.
113
-
114
125
126
+ // Fetch specific security group and group members
127
+ var sgGroupSpecific = groupsDict . Values . Where ( sg => sg . EntraIdGroupId != null && groupIds . Contains ( sg . EntraIdGroupId . Value ) ) . ToDictionary ( sg => sg . Id ) ;
128
+ var sgmGroupSpecific = groupMembersDict . Values . Where ( sgm => sgm . SecurityGroupId != null && sgGroupSpecific . ContainsKey ( ( Guid ) sgm . SecurityGroupId ) ) ;
115
129
130
+ // Check if member group exists else add it
131
+ bool isDirty = false ;
132
+ foreach ( var sg in sgGroupSpecific . Values )
133
+ {
134
+ if ( ! sgmGroupSpecific . Any ( sgm => sgm . SecurityGroupId == sg . Id ) )
135
+ {
136
+ var sgm = new TSecurityGroupMember ( ) ;
137
+ sgm . IdentityId = userId ;
138
+ sgm . SecurityGroupId = sg . Id ;
139
+ db . Add ( sgm ) ;
140
+
141
+ isDirty = true ;
142
+ }
143
+ }
116
144
145
+ // Fecth expired group members by comparing the "historical" group members with that of the current based on the group ids
146
+ var expiredGroupMembers = groupMembersDict . Values . Where ( sgm =>
147
+ ! sgmGroupSpecific . Any ( x => x . Id == sgm . Id ) &&
148
+ sgm . SecurityGroupId != null &&
149
+ groupsDict [ ( Guid ) sgm . SecurityGroupId ] . EntraIdGroupId != null ) ; // Groups of higher aurthority has no EntraGroupId and should not be removed
150
+ foreach ( var sgm in expiredGroupMembers )
151
+ {
152
+ db . Remove ( sgm ) ;
153
+ isDirty = true ;
154
+ }
117
155
118
- return await Task . FromResult ( ( new ClaimsPrincipal ( identity ) , redirectUri , handleId ) ) ;
156
+ if ( isDirty ) await db . SaveChangesAsync ( identity ) ;
119
157
}
120
158
121
159
public RequestDelegate OnSignedOut ( )
0 commit comments