1
1
package com .objectcomputing .checkins .security ;
2
2
3
+ import com .nimbusds .jwt .JWT ;
4
+ import com .nimbusds .jwt .JWTClaimsSet ;
5
+ import com .nimbusds .jwt .JWTParser ;
3
6
import com .objectcomputing .checkins .Environments ;
4
7
import com .objectcomputing .checkins .services .memberprofile .MemberProfile ;
5
8
import com .objectcomputing .checkins .services .memberprofile .MemberProfileServices ;
14
17
import io .micronaut .http .HttpResponse ;
15
18
import io .micronaut .http .MediaType ;
16
19
import io .micronaut .http .MutableHttpResponse ;
17
- import io .micronaut .http .annotation .Consumes ;
18
- import io .micronaut .http .annotation .Controller ;
19
- import io .micronaut .http .annotation .Get ;
20
- import io .micronaut .http .annotation .Post ;
21
- import io .micronaut .http .annotation .Produces ;
20
+ import io .micronaut .http .annotation .*;
22
21
import io .micronaut .http .cookie .Cookie ;
23
22
import io .micronaut .http .cookie .SameSite ;
24
23
import io .micronaut .http .netty .cookies .NettyCookie ;
29
28
import io .micronaut .security .event .LoginSuccessfulEvent ;
30
29
import io .micronaut .security .handlers .LoginHandler ;
31
30
import io .micronaut .security .rules .SecurityRule ;
31
+ import io .micronaut .security .token .jwt .generator .JwtTokenGenerator ;
32
32
import org .slf4j .Logger ;
33
33
import org .slf4j .LoggerFactory ;
34
34
35
35
import java .net .URI ;
36
- import java .util .HashMap ;
37
- import java .util .HashSet ;
38
- import java .util .Iterator ;
39
- import java .util .Locale ;
40
- import java .util .Map ;
41
- import java .util .Set ;
36
+ import java .text .ParseException ;
37
+ import java .util .*;
42
38
import java .util .stream .Collectors ;
43
39
44
40
@ Requires (env = {Environments .LOCAL , Environment .DEVELOPMENT })
@@ -54,67 +50,90 @@ public class ImpersonationController {
54
50
private final MemberProfileServices memberProfileServices ;
55
51
private final RoleServices roleServices ;
56
52
private final RolePermissionServices rolePermissionServices ;
53
+ private final JwtTokenGenerator generator ;
57
54
58
55
/**
59
- * @param loginHandler A collaborator which helps to build HTTP response depending on success or failure.
60
- * @param eventPublisher The application event publisher
61
- * @param roleServices Role services
62
- * @param rolePermissionServices Role permission services
63
- * @param memberProfileServices Member profile services
56
+ * @param loginHandler A collaborator which helps to build HTTP response depending on success or failure.
57
+ * @param eventPublisher The application event publisher
58
+ * @param roleServices Role services
59
+ * @param rolePermissionServices Role permission services
60
+ * @param memberProfileServices Member profile services
61
+ * @param generator Generator for creating and signing the new token
64
62
*/
65
63
public ImpersonationController (LoginHandler loginHandler ,
66
64
ApplicationEventPublisher eventPublisher ,
67
65
RoleServices roleServices ,
68
66
RolePermissionServices rolePermissionServices ,
69
- MemberProfileServices memberProfileServices ) {
67
+ MemberProfileServices memberProfileServices ,
68
+ JwtTokenGenerator generator ) {
70
69
this .loginHandler = loginHandler ;
71
70
this .eventPublisher = eventPublisher ;
72
71
this .roleServices = roleServices ;
73
72
this .rolePermissionServices = rolePermissionServices ;
74
73
this .memberProfileServices = memberProfileServices ;
74
+ this .generator = generator ;
75
75
}
76
76
77
77
@ Consumes ({MediaType .APPLICATION_FORM_URLENCODED , MediaType .APPLICATION_JSON })
78
78
@ Post ("/begin" )
79
79
@ RequiredPermission (Permission .CAN_IMPERSONATE_MEMBERS )
80
80
public HttpResponse <Void > auth (HttpRequest <?> request , String email ) {
81
- final Cookie jwt = request .getCookies ().get (JWT );
82
- if (jwt == null ) {
83
- // The user is required to be logged in. If this is null,
84
- // we are in an impossible state!
85
- LOG .error ("Unable to locate the JWT" );
81
+ final Cookie jwt = request .getCookies ().get (JWT );
82
+ if (jwt == null ) {
83
+ // The user is required to be logged in. If this is null,
84
+ // we are in an impossible state!
85
+ LOG .error ("Unable to locate the JWT" );
86
86
return HttpResponse .unauthorized ();
87
- } else {
88
- LOG .info ("Processing request to switch to user \ ' {}\ ' " , email );
87
+ } else {
88
+ LOG .info ("Processing request to switch to user '{}'" , email );
89
89
Set <MemberProfile > memberProfiles = memberProfileServices .findByValues (null , null , null , null , email , null , Boolean .FALSE );
90
90
Iterator <MemberProfile > iterator = memberProfiles .iterator ();
91
- if (!iterator .hasNext ()) return HttpResponse .badRequest ();
91
+ if (!iterator .hasNext ()) return HttpResponse .badRequest ();
92
92
93
93
MemberProfile memberProfile = iterator .next ();
94
- LOG .info ("Profile exists for \ ' {}\ ' " , email );
95
- String firstName = memberProfile .getFirstName () != null ? memberProfile .getFirstName () : "" ;
96
- String lastName = memberProfile .getLastName () != null ? memberProfile .getLastName () : "" ;
94
+ LOG .info ("Profile exists for '{}'" , email );
95
+ String firstName = memberProfile .getFirstName () != null ? memberProfile .getFirstName () : "" ;
96
+ String lastName = memberProfile .getLastName () != null ? memberProfile .getLastName () : "" ;
97
97
Set <String > roles = roleServices .findUserRoles (memberProfile .getId ()).stream ().map (role -> role .getRole ()).collect (Collectors .toSet ());
98
98
Set <String > permissions = rolePermissionServices .findUserPermissions (memberProfile .getId ()).stream ().map (permission -> permission .name ()).collect (Collectors .toSet ());
99
99
100
100
Map <String , Object > newAttributes = new HashMap <>();
101
- newAttributes .put ("email" , memberProfile .getWorkEmail ());
102
- newAttributes .put ("name" , firstName + ' ' + lastName );
103
- newAttributes .put ("picture" , "" );
101
+ newAttributes .put ("email" , memberProfile .getWorkEmail ());
102
+ newAttributes .put ("name" , firstName + ' ' + lastName );
103
+ newAttributes .put ("picture" , "" );
104
104
newAttributes .put ("roles" , roles );
105
105
newAttributes .put ("permissions" , permissions );
106
- newAttributes .put ("openIdToken" , "" );
106
+ JWTClaimsSet newSet = null ;
107
+ try {
108
+ JWT parse = JWTParser .parse (jwt .getValue ());
109
+ JWTClaimsSet jwtClaimsSet = parse .getJWTClaimsSet ();
110
+ Map <String , Object > claims = new HashMap <>();
111
+ claims .put ("email" , memberProfile .getWorkEmail ());
112
+ claims .put ("name" , firstName + ' ' + lastName );
113
+ claims .put ("picture" , "" );
114
+ claims .put ("exp" , ((Date ) jwtClaimsSet .getClaims ().get ("exp" )).getTime ());
115
+ claims .put ("iss" , jwtClaimsSet .getClaims ().get ("iss" ));
116
+ claims .put ("aud" , jwtClaimsSet .getClaims ().get ("aud" ));
117
+ claims .put ("sub" , jwtClaimsSet .getClaims ().get ("sub" ));
118
+ newSet = JWTClaimsSet .parse (claims );
119
+ Optional <String > signed = generator .generateToken (claims );
120
+
121
+ String token = signed .get ();
122
+ if (newSet != null ) newAttributes .put ("openIdToken" , token );
123
+ } catch (ParseException e ) {
124
+ throw new RuntimeException (e );
125
+ }
107
126
108
127
LOG .info ("Building authentication" );
109
128
Authentication updatedAuth = Authentication .build (email , roles , newAttributes );
110
129
LOG .info ("Publishing login" );
111
- eventPublisher .publishEvent (new LoginSuccessfulEvent (updatedAuth , null , Locale .getDefault ()));
112
- // Store the old JWT to allow the user to revert the impersonation.
130
+ eventPublisher .publishEvent (new LoginSuccessfulEvent (updatedAuth , null , Locale .getDefault ()));
131
+ // Store the old JWT to allow the user to revert the impersonation.
113
132
LOG .info ("Attempting to swap tokens" );
114
- return ((MutableHttpResponse )loginHandler .loginSuccess (updatedAuth , request )).cookie (
115
- new NettyCookie (originalJWT , jwt .getValue ()).path ("/" ).sameSite (SameSite .Strict )
116
- .maxAge (jwt .getMaxAge ()));
117
- }
133
+ return ((MutableHttpResponse ) loginHandler .loginSuccess (updatedAuth , request )).cookie (
134
+ new NettyCookie (originalJWT , jwt .getValue ()).path ("/" ).sameSite (SameSite .Strict )
135
+ .maxAge (jwt .getMaxAge ()));
136
+ }
118
137
}
119
138
120
139
@ Produces (MediaType .TEXT_HTML )
@@ -127,13 +146,13 @@ public HttpResponse<Object> revert(HttpRequest<?> request) {
127
146
// Swap the OJWT back to the JWT and remove the original JWT
128
147
Set <Cookie > cookies = new HashSet <Cookie >();
129
148
cookies .add (new NettyCookie (JWT , ojwt .getValue ()).path ("/" )
130
- .sameSite (SameSite .Strict )
131
- .maxAge (ojwt .getMaxAge ()).httpOnly ());
149
+ .sameSite (SameSite .Strict )
150
+ .maxAge (ojwt .getMaxAge ()).httpOnly ());
132
151
cookies .add (new NettyCookie (originalJWT , "" ).path ("/" ).maxAge (0 ));
133
152
134
153
// Redirect to "/" while setting the cookies.
135
154
return HttpResponse .temporaryRedirect (URI .create ("/" ))
136
- .cookies (cookies );
155
+ .cookies (cookies );
137
156
}
138
157
}
139
158
}
0 commit comments